commit 19dc5d82a073a8c575d8fd8298abb8607bd2bc51
Author: wehub-resource-sync
Date: Mon Jul 13 12:29:44 2026 +0800
chore: import upstream snapshot with attribution
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..bc52f17
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,34 @@
+# Build artifacts
+/target/
+
+# Git
+.git/
+.gitignore
+.dockerignore
+
+# IDE
+.vscode/
+.idea/
+*.swp
+*.swo
+
+# Logs
+logs/
+*.log
+
+# Documentation and non-essential files
+README.md
+CHANGELOG.md
+ROADMAP.md
+LICENSE
+RELEASE.md
+rustnet-*.tar.gz
+
+# Assets we don't need in container
+assets/rustnet.gif
+
+# Scripts (not needed in container)
+scripts/
+
+# CI/CD (already in repo context)
+.github/
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..79f08d9
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+# Ignore the vmlinux.h header for GitHub language stats
+vmlinux.h linguist-generated
diff --git a/.github/PPA_SETUP.md b/.github/PPA_SETUP.md
new file mode 100644
index 0000000..c230203
--- /dev/null
+++ b/.github/PPA_SETUP.md
@@ -0,0 +1,50 @@
+# GitHub Actions PPA Setup
+
+## Add GitHub Secrets
+
+Go to: **Settings** → **Secrets and variables** → **Actions** → **New repository secret**
+
+### 1. GPG_PRIVATE_KEY
+
+```bash
+# Display your CI private key
+cat ci-signing-key.asc
+```
+
+Copy the entire output (including `-----BEGIN PGP PRIVATE KEY BLOCK-----` and `-----END...`)
+
+- Name: `GPG_PRIVATE_KEY`
+- Value: [paste the entire key]
+
+### 2. GPG_KEY_ID
+
+```bash
+# Get your key ID
+gpg --list-keys cadetg@gmail.com
+```
+
+Copy the long hex string (e.g., `ABC123...`)
+
+- Name: `GPG_KEY_ID`
+- Value: [paste just the key ID]
+
+## Test the Workflow
+
+```bash
+# Create and push a test tag
+git tag v1.0.0-test
+git push origin v1.0.0-test
+```
+
+Check: **Actions** tab in GitHub → **Release to Ubuntu PPA**
+
+## Remove Test Tag (if needed)
+
+```bash
+git tag -d v1.0.0-test
+git push origin :refs/tags/v1.0.0-test
+```
+
+## Done!
+
+From now on, just push version tags and GitHub will handle the PPA release automatically! 🚀
diff --git a/.github/actions/build-rustnet/action.yml b/.github/actions/build-rustnet/action.yml
new file mode 100644
index 0000000..2b2ddad
--- /dev/null
+++ b/.github/actions/build-rustnet/action.yml
@@ -0,0 +1,38 @@
+name: 'Build RustNet'
+description: 'Build RustNet binary for a specific target'
+
+inputs:
+ target:
+ description: 'Rust target triple (e.g., x86_64-unknown-linux-gnu)'
+ required: true
+ cargo-command:
+ description: 'Cargo command to use (cargo or cross)'
+ required: false
+ default: 'cargo'
+ default-features:
+ description: 'Enable default features including eBPF (true/false)'
+ required: false
+ default: 'true'
+ strip-symbols:
+ description: 'Strip debug symbols from binary (true/false)'
+ required: false
+ default: 'true'
+
+runs:
+ using: 'composite'
+ steps:
+ - name: Build binary
+ shell: bash
+ env:
+ RUSTFLAGS: ${{ inputs.strip-symbols == 'true' && '-C strip=symbols' || '' }}
+ run: |
+ mkdir -p assets # Ensure asset dir exists for build.rs
+
+ BUILD_CMD="${{ inputs.cargo-command }} build --verbose --release --target ${{ inputs.target }}"
+
+ if [[ "${{ inputs.default-features }}" != "true" ]]; then
+ BUILD_CMD="$BUILD_CMD --no-default-features"
+ fi
+
+ echo "Running: $BUILD_CMD"
+ $BUILD_CMD
diff --git a/.github/actions/build-static/action.yml b/.github/actions/build-static/action.yml
new file mode 100644
index 0000000..ad9b54c
--- /dev/null
+++ b/.github/actions/build-static/action.yml
@@ -0,0 +1,30 @@
+name: 'Build Static Binary'
+description: 'Build a statically-linked Linux binary using musl'
+
+runs:
+ using: 'composite'
+ steps:
+ - name: Install dependencies
+ shell: sh
+ run: |
+ apk add --no-cache \
+ musl-dev libpcap-dev pkgconfig build-base perl \
+ elfutils-dev zlib-dev zlib-static zstd-dev zstd-static \
+ clang llvm linux-headers git
+ rustup component add rustfmt
+
+ - name: Build static binary
+ shell: sh
+ env:
+ # -C strip=symbols: Strip debug symbols for smaller binary
+ # -C link-arg=-l:libzstd.a: Fix elfutils 0.189+ zstd dependency (libbpf/bpftool#152)
+ RUSTFLAGS: "-C strip=symbols -C link-arg=-l:libzstd.a"
+ run: cargo build --release
+
+ - name: Verify static linking
+ shell: sh
+ run: |
+ file target/release/rustnet
+ # Use file command to verify (ldd behaves differently inside Alpine)
+ file target/release/rustnet | grep -q "static.* linked" || \
+ (echo "ERROR: Binary is not statically linked" && exit 1)
diff --git a/.github/actions/setup-linux-deps/action.yml b/.github/actions/setup-linux-deps/action.yml
new file mode 100644
index 0000000..b9543c8
--- /dev/null
+++ b/.github/actions/setup-linux-deps/action.yml
@@ -0,0 +1,11 @@
+name: 'Setup Linux Build Dependencies'
+description: 'Install dependencies required for building RustNet on Linux'
+
+runs:
+ using: 'composite'
+ steps:
+ - name: Install dependencies
+ shell: bash
+ run: |
+ sudo apt-get update -y
+ sudo apt-get install -y libpcap-dev libelf-dev zlib1g-dev clang llvm pkg-config
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..6ad2fb8
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,46 @@
+version: 2
+updates:
+ # Maintain Cargo dependencies
+ - package-ecosystem: "cargo"
+ directory: "/"
+ schedule:
+ interval: "daily"
+ cooldown:
+ default-days: 3
+ semver-major-days: 7
+ semver-minor-days: 4
+ semver-patch-days: 3
+ groups:
+ rust-dependencies:
+ patterns:
+ - "*"
+ update-types:
+ - "major"
+ - "minor"
+ - "patch"
+
+ # Maintain GitHub Actions
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "daily"
+ groups:
+ actions:
+ patterns:
+ - "*"
+ update-types:
+ - "major"
+ - "minor"
+ - "patch"
+
+ # Maintain Docker base images (Dockerfile FROM pins)
+ - package-ecosystem: "docker"
+ directory: "/"
+ schedule:
+ interval: "daily"
+ cooldown:
+ default-days: 7
+ groups:
+ docker:
+ patterns:
+ - "*"
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000..2b1409b
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,56 @@
+
+
+## Summary
+
+
+
+## Linked issue
+
+
+
+Closes #
+
+## Verification
+
+Per [CONTRIBUTING.md > Code Quality Requirements](https://github.com/domcyrus/rustnet/blob/main/CONTRIBUTING.md#code-quality-requirements),
+I ran the following locally and they all pass:
+
+- [ ] `cargo fmt --all -- --check`
+- [ ] `cargo clippy --all-targets --all-features -- -D warnings`
+- [ ] `cargo test --all-features`
+- [ ] `cargo build --release`
+
+
+
+## Scope
+
+- [ ] One feature or fix per PR (no unrelated cleanups bundled in)
+- [ ] No new dependencies, or rationale provided in the summary above
+- [ ] No `#[allow(clippy::...)]` suppressions, or rationale provided
+ (see [CONTRIBUTING.md](https://github.com/domcyrus/rustnet/blob/main/CONTRIBUTING.md#code-quality-requirements))
+
+## AI-assisted contributions
+
+If you used an AI assistant (Copilot, Claude, ChatGPT, Cursor, etc.) to
+write any of this code, that is fine, but please confirm:
+
+- [ ] I have read every line I am submitting
+- [ ] I ran the verification commands above myself
+- [ ] The PR description reflects what the code actually does
+
+## Notes for the reviewer
+
+
diff --git a/.github/workflows/aur-update.yml b/.github/workflows/aur-update.yml
new file mode 100644
index 0000000..6132752
--- /dev/null
+++ b/.github/workflows/aur-update.yml
@@ -0,0 +1,221 @@
+name: Update AUR Package
+
+on:
+ release:
+ types: [published]
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ update-aur:
+ name: update-aur
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+
+ - name: Extract version from tag
+ id: version
+ run: |
+ # Determine the tag based on trigger type
+ if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
+ # Get the latest tag when manually triggered
+ git fetch --tags
+ TAG=$(git tag --sort=-version:refname | head -n1)
+ else
+ # For release event, use the release tag
+ TAG="${{ github.event.release.tag_name }}"
+ fi
+
+ # Extract version by removing 'v' prefix
+ VERSION="${TAG#v}"
+
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
+ echo "tag=$TAG" >> $GITHUB_OUTPUT
+ echo "Extracted version: $VERSION"
+ echo "Tag: $TAG"
+
+ - name: Download release assets and calculate checksums
+ id: checksums
+ run: |
+ VERSION="${{ steps.version.outputs.version }}"
+ TAG="${{ steps.version.outputs.tag }}"
+
+ # Define asset names
+ ASSET_X64="rustnet-${TAG}-x86_64-unknown-linux-gnu.tar.gz"
+ ASSET_ARM64="rustnet-${TAG}-aarch64-unknown-linux-gnu.tar.gz"
+
+ echo "Downloading release assets..."
+
+ # Download assets
+ gh release download "$TAG" -p "$ASSET_X64" -p "$ASSET_ARM64"
+
+ # Calculate SHA256 checksums
+ SHA256_X64=$(sha256sum "$ASSET_X64" | awk '{print $1}')
+ SHA256_ARM64=$(sha256sum "$ASSET_ARM64" | awk '{print $1}')
+
+ echo "x64_checksum=$SHA256_X64" >> $GITHUB_OUTPUT
+ echo "arm64_checksum=$SHA256_ARM64" >> $GITHUB_OUTPUT
+
+ echo "Checksums calculated:"
+ echo " x86_64: $SHA256_X64"
+ echo " aarch64: $SHA256_ARM64"
+ env:
+ GH_TOKEN: ${{ github.token }}
+
+ - name: Verify checksums match release assets
+ run: |
+ TAG="${{ steps.version.outputs.tag }}"
+ EXPECTED_X64="${{ steps.checksums.outputs.x64_checksum }}"
+ EXPECTED_ARM64="${{ steps.checksums.outputs.arm64_checksum }}"
+ FAILED=0
+
+ echo "Re-downloading assets to verify checksums are stable..."
+
+ ASSET_X64="rustnet-${TAG}-x86_64-unknown-linux-gnu.tar.gz"
+ ASSET_ARM64="rustnet-${TAG}-aarch64-unknown-linux-gnu.tar.gz"
+
+ gh release download "$TAG" -p "$ASSET_X64" -p "$ASSET_ARM64" -D /tmp/verify
+
+ ACTUAL_X64=$(sha256sum "/tmp/verify/$ASSET_X64" | awk '{print $1}')
+ ACTUAL_ARM64=$(sha256sum "/tmp/verify/$ASSET_ARM64" | awk '{print $1}')
+ rm -rf /tmp/verify
+
+ if [ "$ACTUAL_X64" != "$EXPECTED_X64" ]; then
+ echo "::error::x86_64 checksum mismatch: expected $EXPECTED_X64, got $ACTUAL_X64"
+ FAILED=1
+ else
+ echo " ✓ x86_64 checksum verified"
+ fi
+
+ if [ "$ACTUAL_ARM64" != "$EXPECTED_ARM64" ]; then
+ echo "::error::aarch64 checksum mismatch: expected $EXPECTED_ARM64, got $ACTUAL_ARM64"
+ FAILED=1
+ else
+ echo " ✓ aarch64 checksum verified"
+ fi
+
+ if [ "$FAILED" -ne 0 ]; then
+ echo "::error::Checksum verification failed — aborting to prevent pushing stale checksums to AUR"
+ exit 1
+ fi
+ echo "All checksums verified successfully"
+ env:
+ GH_TOKEN: ${{ github.token }}
+
+ - name: Setup SSH for AUR
+ env:
+ AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
+ run: |
+ mkdir -p ~/.ssh
+ # Pass the key via env, not inline interpolation, so its bytes are never
+ # spliced into the rendered command line (matches the GPG handling in
+ # ppa-release.yml). printf avoids echo's escape interpretation.
+ printf '%s\n' "$AUR_SSH_PRIVATE_KEY" > ~/.ssh/aur
+ chmod 600 ~/.ssh/aur
+ ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts
+
+ # Configure SSH to use the AUR key
+ cat > ~/.ssh/config < .SRCINFO
+ ' || {
+ # If that fails (no write permissions), install tools as root then run as user
+ docker run --rm -v "$PWD/aur-rustnet-bin:/pkg" archlinux:latest bash -c "
+ pacman -Sy --noconfirm binutils fakeroot sudo &&
+ useradd -m -u $(id -u) builder &&
+ cd /pkg &&
+ su builder -c 'makepkg --printsrcinfo > .SRCINFO'
+ "
+ }
+
+ echo ".SRCINFO generated successfully"
+
+ - name: Commit and push to AUR
+ run: |
+ cd aur-rustnet-bin
+ VERSION="${{ steps.version.outputs.version }}"
+
+ # Check if there are changes to commit
+ if git diff --quiet PKGBUILD .SRCINFO; then
+ echo "No changes to PKGBUILD or .SRCINFO (already at version $VERSION)"
+ exit 0
+ fi
+
+ # Show changes
+ echo "Changes to be committed:"
+ git diff PKGBUILD .SRCINFO
+
+ # Commit and push
+ git add PKGBUILD .SRCINFO
+ git commit -m "Update to version $VERSION"
+ git push origin master
+
+ echo "Pushed changes to AUR repository"
+
+ - name: Summary
+ run: |
+ VERSION="${{ steps.version.outputs.version }}"
+ echo "## 🎉 AUR Package Updated" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "- **Package**: rustnet-bin" >> $GITHUB_STEP_SUMMARY
+ echo "- **Version**: $VERSION" >> $GITHUB_STEP_SUMMARY
+ echo "- **Architectures**: x86_64, aarch64" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "### Checksums" >> $GITHUB_STEP_SUMMARY
+ echo "- **x86_64**: \`${{ steps.checksums.outputs.x64_checksum }}\`" >> $GITHUB_STEP_SUMMARY
+ echo "- **aarch64**: \`${{ steps.checksums.outputs.arm64_checksum }}\`" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "[View AUR Package →](https://aur.archlinux.org/packages/rustnet-bin)" >> $GITHUB_STEP_SUMMARY
diff --git a/.github/workflows/build-android-static.yml b/.github/workflows/build-android-static.yml
new file mode 100644
index 0000000..b8ad815
--- /dev/null
+++ b/.github/workflows/build-android-static.yml
@@ -0,0 +1,222 @@
+name: Build Android Musl Static
+
+# One-off workflow to build and upload android musl static binaries
+# for multiple architectures to an existing release.
+#
+# Supported architectures:
+# aarch64 - native on ARM runner (existing)
+# x86_64 - native on x86_64 runner
+# armv7 - cross-compiled on x86_64 using musl.cc armv7l-linux-musleabihf toolchain
+# x86 - cross-compiled on x86_64 using musl.cc i686-linux-musl toolchain
+
+on:
+ workflow_dispatch:
+ inputs:
+ version:
+ description: 'Release tag (e.g., v1.0.0)'
+ required: true
+ ref:
+ description: 'Git ref to build from (default: tag)'
+ required: false
+ default: ''
+
+permissions:
+ contents: write
+
+jobs:
+ build-android:
+ name: build-${{ matrix.arch }}-android-static
+ runs-on: ${{ matrix.runner }}
+ container:
+ image: rust:alpine
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - arch: aarch64
+ runner: ubuntu-24.04-arm
+ artifact: aarch64-linux-android-musl
+ outline-atomics: true
+ - arch: x86_64
+ runner: ubuntu-latest
+ artifact: x86_64-linux-android-musl
+ outline-atomics: false
+ - arch: armv7
+ runner: ubuntu-latest
+ artifact: armv7-linux-android-musl
+ outline-atomics: true
+ - arch: x86
+ runner: ubuntu-latest
+ artifact: i686-linux-android-musl
+ outline-atomics: false
+
+ steps:
+ # aarch64: JS actions don't work in Alpine containers on ARM runners
+ - name: Checkout repository (aarch64 workaround)
+ if: matrix.arch == 'aarch64'
+ run: |
+ apk add --no-cache git
+ git config --global --add safe.directory /__w/rustnet/rustnet
+ git clone --depth 1 https://github.com/${{ github.repository }}.git .
+ git fetch --depth 1 origin tag "${{ inputs.version }}"
+ git checkout "${{ inputs.version }}"
+
+ - name: Checkout repository
+ if: matrix.arch != 'aarch64'
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ with:
+ ref: ${{ inputs.version }}
+
+ - name: Install common dependencies
+ run: |
+ apk add --no-cache \
+ musl-dev pkgconfig build-base perl \
+ zlib-dev zlib-static \
+ clang llvm linux-headers github-cli curl
+ rustup component add rustfmt
+
+ # aarch64/x86_64: libpcap-dev provides a native static libpcap.
+ # zstd-static is required because Alpine's libpcap-dev links against zstd.
+ - name: Install libpcap (native)
+ if: matrix.arch == 'aarch64' || matrix.arch == 'x86_64'
+ run: apk add --no-cache libpcap-dev elfutils-dev zstd-dev zstd-static
+
+ # armv7: use zig cc as the cross-compiler (no external toolchain download needed).
+ # zig bundles musl libc for all targets, so no -l:libzstd.a needed.
+ - name: Install cross-compiler and libpcap (armv7)
+ if: matrix.arch == 'armv7'
+ run: |
+ rustup target add armv7-unknown-linux-musleabihf
+ apk add zig flex bison bash
+
+ # zig cc wrapper: cc-rs detects it as clang and passes --target=armv7-*
+ # which zig rejects (zig uses 'arm' not 'armv7'). Filter those args out.
+ printf '%s\n' \
+ '#!/bin/bash' \
+ 'args=()' \
+ 'for arg in "$@"; do' \
+ ' case "$arg" in --target=*) ;; *) args+=("$arg") ;; esac' \
+ 'done' \
+ 'exec zig cc -target arm-linux-musleabihf "${args[@]}"' \
+ > /usr/local/bin/arm-linux-musleabihf-gcc
+ chmod +x /usr/local/bin/arm-linux-musleabihf-gcc
+
+ # libpcap is pinned by version + sha256 (Dependabot does not track
+ # tarballs fetched in run steps, so bump the URL and the hash together).
+ curl -fsSL --retry 3 --retry-delay 5 \
+ -o libpcap.tar.gz https://www.tcpdump.org/release/libpcap-1.10.5.tar.gz
+ echo "37ced90a19a302a7f32e458224a00c365c117905c2cd35ac544b6880a81488f0 libpcap.tar.gz" | sha256sum -c -
+ tar xzf libpcap.tar.gz
+ cd libpcap-1.10.5
+ CC=arm-linux-musleabihf-gcc \
+ ./configure --host=arm-linux-musleabihf \
+ --disable-shared --enable-static \
+ --disable-usb --disable-dbus --disable-bluetooth --disable-remote \
+ --prefix=/arm-sysroot
+ make -j$(nproc) && make install
+ cd ..
+
+ # x86: use zig cc as the cross-compiler (no external toolchain download needed).
+ # zig bundles musl libc for all targets, so no -l:libzstd.a needed.
+ - name: Install cross-compiler and libpcap (x86/i686)
+ if: matrix.arch == 'x86'
+ run: |
+ rustup target add i686-unknown-linux-musl
+ apk add zig flex bison bash
+
+ # zig cc wrapper: cc-rs passes --target=i686-* which zig doesn't accept;
+ # filter those args and use -target x86-linux-musl instead.
+ printf '%s\n' \
+ '#!/bin/bash' \
+ 'args=()' \
+ 'for arg in "$@"; do' \
+ ' case "$arg" in --target=*) ;; *) args+=("$arg") ;; esac' \
+ 'done' \
+ 'exec zig cc -target x86-linux-musl "${args[@]}"' \
+ > /usr/local/bin/i686-linux-musl-gcc
+ chmod +x /usr/local/bin/i686-linux-musl-gcc
+
+ # libpcap is pinned by version + sha256 (Dependabot does not track
+ # tarballs fetched in run steps, so bump the URL and the hash together).
+ curl -fsSL --retry 3 --retry-delay 5 \
+ -o libpcap.tar.gz https://www.tcpdump.org/release/libpcap-1.10.5.tar.gz
+ echo "37ced90a19a302a7f32e458224a00c365c117905c2cd35ac544b6880a81488f0 libpcap.tar.gz" | sha256sum -c -
+ tar xzf libpcap.tar.gz
+ cd libpcap-1.10.5
+ CC=i686-linux-musl-gcc \
+ ./configure --host=i686-linux-musl \
+ --disable-shared --enable-static \
+ --disable-usb --disable-dbus --disable-bluetooth --disable-remote \
+ --prefix=/x86-sysroot
+ make -j$(nproc) && make install
+ cd ..
+
+ - name: Build static binary (aarch64)
+ if: matrix.arch == 'aarch64'
+ env:
+ CFLAGS: "-mno-outline-atomics"
+ CXXFLAGS: "-mno-outline-atomics"
+ RUSTFLAGS: "-C strip=symbols -C link-arg=-l:libzstd.a"
+ run: cargo build --release --no-default-features
+
+ - name: Build static binary (x86_64)
+ if: matrix.arch == 'x86_64'
+ env:
+ RUSTFLAGS: "-C strip=symbols -C link-arg=-l:libzstd.a"
+ run: cargo build --release --no-default-features
+
+ - name: Build static binary (armv7)
+ if: matrix.arch == 'armv7'
+ env:
+ # link-self-contained=no: let zig provide musl CRT (avoid duplicate _start
+ # when both Rust's self-contained crt1.o and zig's crt1.o are linked)
+ RUSTFLAGS: "-C strip=symbols -C link-self-contained=no"
+ CARGO_TARGET_ARMV7_UNKNOWN_LINUX_MUSLEABIHF_LINKER: arm-linux-musleabihf-gcc
+ PKG_CONFIG_PATH: /arm-sysroot/lib/pkgconfig
+ PKG_CONFIG_SYSROOT_DIR: /arm-sysroot
+ PKG_CONFIG_ALLOW_CROSS: "1"
+ run: cargo build --release --target armv7-unknown-linux-musleabihf --no-default-features
+
+ - name: Build static binary (x86/i686)
+ if: matrix.arch == 'x86'
+ env:
+ RUSTFLAGS: "-C strip=symbols -C link-self-contained=no"
+ CARGO_TARGET_I686_UNKNOWN_LINUX_MUSL_LINKER: i686-linux-musl-gcc
+ PKG_CONFIG_PATH: /x86-sysroot/lib/pkgconfig
+ PKG_CONFIG_SYSROOT_DIR: /x86-sysroot
+ PKG_CONFIG_ALLOW_CROSS: "1"
+ run: cargo build --release --target i686-unknown-linux-musl --no-default-features
+
+ - name: Verify static linking
+ run: |
+ BIN="${{ (matrix.arch == 'armv7' && 'target/armv7-unknown-linux-musleabihf/release/rustnet') || (matrix.arch == 'x86' && 'target/i686-unknown-linux-musl/release/rustnet') || 'target/release/rustnet' }}"
+ file "$BIN"
+ file "$BIN" | grep -q "static.* linked" || \
+ (echo "ERROR: Binary is not statically linked" && exit 1)
+
+ - name: Create release archive
+ run: |
+ VERSION="${{ inputs.version }}"
+ ARTIFACT="${{ matrix.artifact }}"
+
+ case "${{ matrix.arch }}" in
+ armv7) BIN="target/armv7-unknown-linux-musleabihf/release/rustnet" ;;
+ x86) BIN="target/i686-unknown-linux-musl/release/rustnet" ;;
+ *) BIN="target/release/rustnet" ;;
+ esac
+
+ staging="rustnet-${VERSION}-${ARTIFACT}"
+ mkdir -p "$staging/assets"
+ cp "$BIN" "$staging/rustnet"
+ cp crates/rustnet-core/assets/services "$staging/assets/" 2>/dev/null || true
+ cp README.md "$staging/"
+ cp LICENSE "$staging/" 2>/dev/null || true
+ tar czf "$staging.tar.gz" "$staging"
+
+ - name: Upload to release
+ run: |
+ VERSION="${{ inputs.version }}"
+ ARCHIVE="rustnet-${VERSION}-${{ matrix.artifact }}.tar.gz"
+ gh release upload "$VERSION" "$ARCHIVE" --repo "${{ github.repository }}" --clobber
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/build-musl-arm64.yml b/.github/workflows/build-musl-arm64.yml
new file mode 100644
index 0000000..d4ad93e
--- /dev/null
+++ b/.github/workflows/build-musl-arm64.yml
@@ -0,0 +1,73 @@
+name: Build ARM64 Musl Static
+
+# One-off workflow to build and upload aarch64 musl static binary
+# to an existing release. Used when the regular release pipeline
+# fails to upload this artifact.
+
+on:
+ workflow_dispatch:
+ inputs:
+ version:
+ description: 'Release tag (e.g., v1.0.0)'
+ required: true
+ ref:
+ description: 'Git ref to build from (default: tag)'
+ required: false
+ default: ''
+
+permissions:
+ contents: write
+
+jobs:
+ build-musl-arm64:
+ name: build-aarch64-musl-static
+ runs-on: ubuntu-24.04-arm
+ container:
+ image: rust:alpine
+ steps:
+ - name: Checkout repository
+ run: |
+ apk add --no-cache git
+ git config --global --add safe.directory /__w/rustnet/rustnet
+ REF="${{ inputs.ref || inputs.version }}"
+ git clone --depth 1 https://github.com/${{ github.repository }}.git .
+ git fetch --depth 1 origin tag "${{ inputs.version }}"
+ git checkout "${{ inputs.version }}"
+
+ - name: Install dependencies
+ run: |
+ apk add --no-cache \
+ musl-dev libpcap-dev pkgconfig build-base perl \
+ elfutils-dev zlib-dev zlib-static zstd-dev zstd-static \
+ clang llvm linux-headers github-cli
+ rustup component add rustfmt
+
+ - name: Build static binary
+ env:
+ CFLAGS: "-mno-outline-atomics"
+ CXXFLAGS: "-mno-outline-atomics"
+ RUSTFLAGS: "-C strip=symbols -C link-arg=-l:libzstd.a"
+ run: |
+ cargo build --release
+ file target/release/rustnet
+ file target/release/rustnet | grep -q "static.* linked" || \
+ (echo "ERROR: Binary is not statically linked" && exit 1)
+
+ - name: Create release archive
+ run: |
+ VERSION="${{ inputs.version }}"
+ staging="rustnet-${VERSION}-aarch64-unknown-linux-musl"
+ mkdir -p "$staging/assets"
+ cp target/release/rustnet "$staging/"
+ cp crates/rustnet-core/assets/services "$staging/assets/" 2>/dev/null || true
+ cp README.md "$staging/"
+ cp LICENSE "$staging/" 2>/dev/null || true
+ tar czf "$staging.tar.gz" "$staging"
+
+ - name: Upload to release
+ run: |
+ VERSION="${{ inputs.version }}"
+ ARCHIVE="rustnet-${VERSION}-aarch64-unknown-linux-musl.tar.gz"
+ gh release upload "$VERSION" "$ARCHIVE" --clobber
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/build-platforms.yml b/.github/workflows/build-platforms.yml
new file mode 100644
index 0000000..46b4fe5
--- /dev/null
+++ b/.github/workflows/build-platforms.yml
@@ -0,0 +1,353 @@
+name: Build Platforms
+
+# Reusable workflow for building RustNet on all platforms.
+# Called by both test-platform-builds.yml and release.yml
+
+on:
+ workflow_call:
+ inputs:
+ create-archives:
+ description: 'Create release archives with README/LICENSE (true for release, false for test)'
+ type: boolean
+ default: false
+ strip-symbols:
+ description: 'Strip debug symbols from binaries'
+ type: boolean
+ default: false
+ version:
+ description: 'Version string for archive naming (e.g., v0.3.0)'
+ type: string
+ default: ''
+
+env:
+ RUST_BACKTRACE: 1
+ CARGO_TERM_COLOR: always
+
+jobs:
+ build:
+ permissions:
+ contents: read
+ name: build-${{ matrix.build }}
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ build:
+ - linux-x64-gnu
+ - linux-aarch64-gnu
+ - linux-armv7-gnueabihf
+ - macos-aarch64
+ - macos-x64
+ - windows-x64-msvc
+ - windows-x86-msvc
+ include:
+ - os: ubuntu-22.04
+ - cargo: cargo
+ - build: linux-x64-gnu
+ target: x86_64-unknown-linux-gnu
+ run-tests: true
+ - build: linux-aarch64-gnu
+ target: aarch64-unknown-linux-gnu
+ cargo: cross
+ - build: linux-armv7-gnueabihf
+ target: armv7-unknown-linux-gnueabihf
+ cargo: cross
+ - build: macos-aarch64
+ os: macos-14
+ target: aarch64-apple-darwin
+ run-tests: true
+ - build: macos-x64
+ os: macos-14
+ target: x86_64-apple-darwin
+ - build: windows-x64-msvc
+ os: windows-latest
+ target: x86_64-pc-windows-msvc
+ - build: windows-x86-msvc
+ os: windows-latest
+ target: i686-pc-windows-msvc
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+
+ - name: Setup Linux dependencies
+ if: matrix.os == 'ubuntu-22.04'
+ uses: ./.github/actions/setup-linux-deps
+
+ - name: Install Rust
+ uses: dtolnay/rust-toolchain@stable # unpinned: maintained branch by Rust team member
+ with:
+ targets: ${{ matrix.target }}
+ components: rustfmt
+
+ - name: Install cross
+ if: matrix.cargo == 'cross'
+ run: cargo install cross@0.2.5
+
+ - name: Build
+ uses: ./.github/actions/build-rustnet
+ with:
+ target: ${{ matrix.target }}
+ cargo-command: ${{ matrix.cargo }}
+ default-features: ${{ contains(matrix.target, 'linux') && 'true' || 'false' }}
+ strip-symbols: ${{ inputs.strip-symbols && 'true' || 'false' }}
+
+ - name: Run tests
+ if: matrix.run-tests
+ run: cargo test --verbose
+
+ # For test builds: upload raw binary
+ - name: Upload binary artifact
+ if: ${{ !inputs.create-archives }}
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
+ with:
+ name: rustnet-${{ matrix.build }}
+ path: target/${{ matrix.target }}/release/rustnet${{ contains(matrix.os, 'windows') && '.exe' || '' }}
+ if-no-files-found: error
+
+ # For release builds: create archive with README/LICENSE
+ - name: Create release archive
+ if: ${{ inputs.create-archives }}
+ shell: bash
+ env:
+ RUSTNET_BIN: ${{ contains(matrix.os, 'windows') && 'rustnet.exe' || 'rustnet' }}
+ run: |
+ staging="rustnet-${{ inputs.version }}-${{ matrix.target }}"
+ mkdir -p "$staging"
+
+ cp "target/${{ matrix.target }}/release/$RUSTNET_BIN" "$staging/"
+
+ if [ -d "crates/rustnet-core/assets" ] && [ -f "crates/rustnet-core/assets/services" ]; then
+ mkdir -p "$staging/assets"
+ cp "crates/rustnet-core/assets/services" "$staging/assets/"
+ fi
+
+ cp README.md "$staging/"
+ cp LICENSE "$staging/" 2>/dev/null || true
+
+ if [[ "${{ matrix.os }}" == "windows-latest" ]]; then
+ 7z a "$staging.zip" "$staging"
+ echo "ASSET=$staging.zip" >> $GITHUB_ENV
+ else
+ tar czf "$staging.tar.gz" "$staging"
+ echo "ASSET=$staging.tar.gz" >> $GITHUB_ENV
+ fi
+
+ - name: Upload release archive
+ if: ${{ inputs.create-archives }}
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
+ with:
+ name: build-${{ matrix.target }}
+ path: ${{ env.ASSET }}
+ if-no-files-found: error
+
+ build-static:
+ permissions:
+ contents: read
+ name: build-linux-static-${{ matrix.arch }}
+ runs-on: ${{ matrix.runner }}
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - arch: x86_64
+ runner: ubuntu-latest
+ target: x86_64-unknown-linux-musl
+ - arch: aarch64
+ runner: ubuntu-24.04-arm
+ target: aarch64-unknown-linux-musl
+ features: '--features default'
+ - arch: aarch64-android
+ runner: ubuntu-24.04-arm
+ target: aarch64-linux-android-musl # Note: it's not a real rust target but we rename the artifact
+ features: '--no-default-features'
+ - arch: x86_64-android
+ runner: ubuntu-latest
+ target: x86_64-linux-android-musl
+ features: '--no-default-features'
+ - arch: armv7-android
+ runner: ubuntu-latest
+ target: armv7-linux-android-musl
+ features: '--no-default-features'
+ - arch: x86-android
+ runner: ubuntu-latest
+ target: i686-linux-android-musl
+ features: '--no-default-features'
+ container:
+ image: rust:alpine
+ steps:
+ # x86_64: use standard checkout action
+ - name: Checkout repository
+ if: matrix.arch == 'x86_64' || matrix.arch == 'x86_64-android' || matrix.arch == 'armv7-android' || matrix.arch == 'x86-android'
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+
+ # aarch64: JS actions don't work in Alpine containers on ARM runners
+ - name: Checkout repository (ARM workaround)
+ if: contains(matrix.arch, 'aarch64')
+ run: |
+ apk add --no-cache git
+ git config --global --add safe.directory /__w/rustnet/rustnet
+ git clone --depth 1 https://github.com/${{ github.repository }}.git .
+ if [[ "${{ github.ref }}" == refs/tags/* ]]; then
+ git fetch --depth 1 origin tag "${{ github.ref_name }}"
+ git checkout "${{ github.ref_name }}"
+ fi
+
+ # x86_64: use composite action
+ - name: Build static binary
+ if: matrix.arch == 'x86_64'
+ uses: ./.github/actions/build-static
+
+ # aarch64 & android: inline build (composite actions don't work with ARM workaround/zig wrapper easily here)
+ - name: Install dependencies (ARM & Android)
+ if: matrix.arch != 'x86_64'
+ run: |
+ apk add --no-cache \
+ musl-dev libpcap-dev pkgconfig build-base perl \
+ elfutils-dev zlib-dev zlib-static zstd-dev zstd-static \
+ clang llvm linux-headers curl github-cli
+ rustup component add rustfmt
+
+ - name: Install cross-compiler and libpcap (armv7)
+ if: matrix.arch == 'armv7-android'
+ run: |
+ rustup target add armv7-unknown-linux-musleabihf
+ apk add zig flex bison bash
+
+ printf '%s\n' \
+ '#!/bin/bash' \
+ 'args=()' \
+ 'for arg in "$@"; do' \
+ ' case "$arg" in --target=*) ;; *) args+=("$arg") ;; esac' \
+ 'done' \
+ 'exec zig cc -target arm-linux-musleabihf "${args[@]}"' \
+ > /usr/local/bin/arm-linux-musleabihf-gcc
+ chmod +x /usr/local/bin/arm-linux-musleabihf-gcc
+
+ # libpcap is pinned by version + sha256 (Dependabot does not track
+ # tarballs fetched in run steps, so bump the URL and the hash together).
+ curl -fsSL --retry 3 --retry-delay 5 \
+ -o libpcap.tar.gz https://www.tcpdump.org/release/libpcap-1.10.5.tar.gz
+ echo "37ced90a19a302a7f32e458224a00c365c117905c2cd35ac544b6880a81488f0 libpcap.tar.gz" | sha256sum -c -
+ tar xzf libpcap.tar.gz
+ cd libpcap-1.10.5
+ CC=arm-linux-musleabihf-gcc \
+ ./configure --host=arm-linux-musleabihf \
+ --disable-shared --enable-static \
+ --disable-usb --disable-dbus --disable-bluetooth --disable-remote \
+ --prefix=/arm-sysroot
+ make -j$(nproc) && make install
+ cd ..
+
+ - name: Install cross-compiler and libpcap (x86/i686)
+ if: matrix.arch == 'x86-android'
+ run: |
+ rustup target add i686-unknown-linux-musl
+ apk add zig flex bison bash
+
+ printf '%s\n' \
+ '#!/bin/bash' \
+ 'args=()' \
+ 'for arg in "$@"; do' \
+ ' case "$arg" in --target=*) ;; -Wl,-melf_i386) ;; *) args+=("$arg") ;; esac' \
+ 'done' \
+ 'exec zig cc -target x86-linux-musl "${args[@]}"' \
+ > /usr/local/bin/i686-linux-musl-gcc
+ chmod +x /usr/local/bin/i686-linux-musl-gcc
+
+ # libpcap is pinned by version + sha256 (Dependabot does not track
+ # tarballs fetched in run steps, so bump the URL and the hash together).
+ curl -fsSL --retry 3 --retry-delay 5 \
+ -o libpcap.tar.gz https://www.tcpdump.org/release/libpcap-1.10.5.tar.gz
+ echo "37ced90a19a302a7f32e458224a00c365c117905c2cd35ac544b6880a81488f0 libpcap.tar.gz" | sha256sum -c -
+ tar xzf libpcap.tar.gz
+ cd libpcap-1.10.5
+ CC=i686-linux-musl-gcc \
+ ./configure --host=i686-linux-musl \
+ --disable-shared --enable-static \
+ --disable-usb --disable-dbus --disable-bluetooth --disable-remote \
+ --prefix=/x86-sysroot
+ make -j$(nproc) && make install
+ cd ..
+
+ - name: Build static binary (AArch64 / AArch64 Android)
+ if: matrix.arch == 'aarch64' || matrix.arch == 'aarch64-android'
+ env:
+ # -mno-outline-atomics: Prevent GCC from generating calls to __aarch64_ldadd4_sync etc.
+ # which aren't in Alpine's libatomic. Uses inline atomics instead.
+ CFLAGS: "-mno-outline-atomics"
+ CXXFLAGS: "-mno-outline-atomics"
+ RUSTFLAGS: "-C strip=symbols -C link-arg=-l:libzstd.a"
+ run: cargo build --release ${{ matrix.features }}
+
+ - name: Build static binary (x86_64 Android)
+ if: matrix.arch == 'x86_64-android'
+ env:
+ RUSTFLAGS: "-C strip=symbols -C link-arg=-l:libzstd.a"
+ run: cargo build --release ${{ matrix.features }}
+
+ - name: Build static binary (armv7-android)
+ if: matrix.arch == 'armv7-android'
+ env:
+ RUSTFLAGS: "-C strip=symbols -C link-self-contained=no"
+ CARGO_TARGET_ARMV7_UNKNOWN_LINUX_MUSLEABIHF_LINKER: arm-linux-musleabihf-gcc
+ PKG_CONFIG_PATH: /arm-sysroot/lib/pkgconfig
+ PKG_CONFIG_SYSROOT_DIR: /arm-sysroot
+ PKG_CONFIG_ALLOW_CROSS: "1"
+ run: cargo build --release --target armv7-unknown-linux-musleabihf ${{ matrix.features }}
+
+ - name: Build static binary (x86-android)
+ if: matrix.arch == 'x86-android'
+ env:
+ RUSTFLAGS: "-C strip=symbols -C link-self-contained=no"
+ CARGO_TARGET_I686_UNKNOWN_LINUX_MUSL_LINKER: i686-linux-musl-gcc
+ PKG_CONFIG_PATH: /x86-sysroot/lib/pkgconfig
+ PKG_CONFIG_SYSROOT_DIR: /x86-sysroot
+ PKG_CONFIG_ALLOW_CROSS: "1"
+ run: cargo build --release --target i686-unknown-linux-musl ${{ matrix.features }}
+
+ - name: Verify static linking (ARM & Android)
+ if: matrix.arch != 'x86_64'
+ run: |
+ BIN="${{ (matrix.arch == 'armv7-android' && 'target/armv7-unknown-linux-musleabihf/release/rustnet') || (matrix.arch == 'x86-android' && 'target/i686-unknown-linux-musl/release/rustnet') || 'target/release/rustnet' }}"
+ file "$BIN"
+ file "$BIN" | grep -q "static.* linked" || \
+ (echo "ERROR: Binary is not statically linked" && exit 1)
+
+ # For test builds: upload raw binary
+ - name: Upload binary artifact
+ if: ${{ !inputs.create-archives }}
+ continue-on-error: ${{ matrix.arch != 'x86_64' }}
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
+ with:
+ name: rustnet-linux-static-${{ matrix.arch }}
+ path: ${{ (matrix.arch == 'armv7-android' && 'target/armv7-unknown-linux-musleabihf/release/rustnet') || (matrix.arch == 'x86-android' && 'target/i686-unknown-linux-musl/release/rustnet') || 'target/release/rustnet' }}
+ if-no-files-found: error
+
+ # For release builds: create archive
+ - name: Create release archive
+ if: ${{ inputs.create-archives }}
+ run: |
+ BIN="${{ (matrix.arch == 'armv7-android' && 'target/armv7-unknown-linux-musleabihf/release/rustnet') || (matrix.arch == 'x86-android' && 'target/i686-unknown-linux-musl/release/rustnet') || 'target/release/rustnet' }}"
+
+ staging="rustnet-${{ inputs.version }}-${{ matrix.target }}"
+ mkdir -p "$staging/assets"
+
+ cp "$BIN" "$staging/rustnet"
+ cp crates/rustnet-core/assets/services "$staging/assets/" 2>/dev/null || true
+ cp README.md "$staging/"
+ cp LICENSE "$staging/" 2>/dev/null || true
+
+ tar czf "$staging.tar.gz" "$staging"
+
+ - name: Upload release archive
+ if: ${{ inputs.create-archives }}
+ # JS actions don't work in Alpine containers on ARM runners;
+ # release.yml has dedicated upload-arm-static/upload-android-static jobs as fallback
+ continue-on-error: ${{ contains(matrix.arch, 'aarch64') }}
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
+ with:
+ name: build-${{ matrix.target }}
+ path: rustnet-${{ inputs.version }}-${{ matrix.target }}.tar.gz
+ if-no-files-found: error
+
diff --git a/.github/workflows/copr-update-spec.yml b/.github/workflows/copr-update-spec.yml
new file mode 100644
index 0000000..d5006f5
--- /dev/null
+++ b/.github/workflows/copr-update-spec.yml
@@ -0,0 +1,72 @@
+name: Update COPR Spec Version
+
+on:
+ workflow_call:
+ workflow_dispatch:
+
+permissions:
+ contents: write
+
+jobs:
+ update-spec:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ with:
+ fetch-depth: 0
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Extract version from tag
+ id: version
+ run: |
+ # Extract version by removing 'v' prefix
+ VERSION=${GITHUB_REF#refs/tags/v}
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
+ echo "Extracted version: $VERSION"
+
+ - name: Update RPM spec file
+ run: |
+ VERSION="${{ steps.version.outputs.version }}"
+ SPEC_FILE="rpm/rustnet.spec"
+
+ # Update the Version: line in the spec file
+ sed -i "s/^Version:.*/Version: $VERSION/" "$SPEC_FILE"
+
+ echo "Updated $SPEC_FILE to version $VERSION"
+ echo ""
+ echo "Version line:"
+ grep "^Version:" "$SPEC_FILE"
+
+ - name: Commit and push changes
+ run: |
+ VERSION="${{ steps.version.outputs.version }}"
+
+ # Configure git
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+
+ # Check if there are changes to commit
+ if git diff --quiet rpm/rustnet.spec; then
+ echo "No changes to rpm/rustnet.spec (already at version $VERSION)"
+ exit 0
+ fi
+
+ # Commit and push
+ git add rpm/rustnet.spec
+ git commit -m "chore: update RPM spec to version $VERSION"
+ git push origin HEAD:main
+
+ echo "Pushed changes to main branch"
+
+ - name: Summary
+ run: |
+ echo "## 🎉 RPM Spec Updated" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "- **Version**: ${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
+ echo "- **File**: rpm/rustnet.spec" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "COPR will automatically rebuild the package via webhook." >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "[View COPR Builds →](https://copr.fedorainfracloud.org/coprs/domcyrus/rustnet/builds/)" >> $GITHUB_STEP_SUMMARY
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
new file mode 100644
index 0000000..99ee5a7
--- /dev/null
+++ b/.github/workflows/docker.yml
@@ -0,0 +1,72 @@
+name: Docker Build and Publish
+on:
+ workflow_call:
+ workflow_dispatch:
+
+env:
+ REGISTRY: ghcr.io
+ IMAGE_NAME: ${{ github.repository }}
+
+jobs:
+ build-and-push:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ packages: write
+ attestations: write
+ id-token: write
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
+
+ - name: Log in to Container Registry
+ if: github.event_name != 'pull_request'
+ uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4
+ with:
+ registry: ${{ env.REGISTRY }}
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Extract metadata
+ id: meta
+ uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
+ with:
+ images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
+ tags: |
+ type=ref,event=branch
+ type=ref,event=pr
+ type=semver,pattern={{version}}
+ type=semver,pattern={{major}}.{{minor}}
+ type=semver,pattern={{major}}
+ type=raw,value=latest,enable={{is_default_branch}}
+
+ - name: Build and push Docker image
+ id: build-and-push
+ uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
+ with:
+ context: .
+ platforms: linux/amd64,linux/arm64
+ push: ${{ github.event_name != 'pull_request' }}
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
+ # Enable Kubernetes pod/container attribution in the container image.
+ # It is dependency-free and runtime-gated (`--kubernetes auto`, the
+ # default, is inert outside a pod), so a single image serves both
+ # general container use and kubectl-rustnet. Native installs
+ # (cargo/brew/deb/rpm) leave the feature off and stay lean.
+ build-args: |
+ CARGO_FEATURES=kubernetes
+ cache-from: type=gha
+ cache-to: type=gha,mode=max
+
+ - name: Generate artifact attestation
+ if: github.event_name != 'pull_request'
+ uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4
+ with:
+ subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}
+ subject-digest: ${{ steps.build-and-push.outputs.digest }}
+ push-to-registry: true
\ No newline at end of file
diff --git a/.github/workflows/obs-release.yml b/.github/workflows/obs-release.yml
new file mode 100644
index 0000000..7aeedf5
--- /dev/null
+++ b/.github/workflows/obs-release.yml
@@ -0,0 +1,146 @@
+name: Release to OBS
+
+on:
+ workflow_call:
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ release-obs:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ with:
+ fetch-depth: 0
+
+ - name: Get version
+ id: version
+ run: |
+ # Extract version by removing 'v' prefix if it exists, fallback to Cargo.toml
+ if [[ $GITHUB_REF == refs/tags/* ]]; then
+ VERSION=${GITHUB_REF#refs/tags/v}
+ else
+ # Read the binary's [package] version, not [workspace.package] (the
+ # latter is the 0.x library version and comes first in Cargo.toml).
+ VERSION=$(awk -F'"' '/^\[package\]/{p=1} p && /^version = /{print $2; exit}' Cargo.toml)
+ fi
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
+ echo "Version: $VERSION"
+
+ - name: Setup Linux dependencies
+ uses: ./.github/actions/setup-linux-deps
+
+ - name: Install Rust
+ uses: dtolnay/rust-toolchain@stable # unpinned: maintained branch by Rust team member
+ with:
+ toolchain: stable
+
+ - name: Install OBS dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y osc obs-build build-essential
+
+ - name: Configure OBS credentials
+ run: |
+ cat < ~/.oscrc
+ [general]
+ apiurl = https://api.opensuse.org
+
+ [https://api.opensuse.org]
+ user = ${{ secrets.OBS_USER }}
+ pass = ${{ secrets.OBS_PASS }}
+ EOF
+ chmod 600 ~/.oscrc
+
+ - name: Download source tarball
+ run: |
+ VERSION="${{ steps.version.outputs.version }}"
+ PACKAGE_NAME="rustnet"
+
+ # Output filename must match Source0's basename (v%{version}.tar.gz)
+ # so the offline OBS build can resolve it; the internal dir prefix
+ # stays rustnet-%{version}/ to match %autosetup -n.
+ RELEASE_TAG="v${VERSION}"
+ if git rev-parse "$RELEASE_TAG" >/dev/null 2>&1; then
+ echo "✓ Found release tag: $RELEASE_TAG"
+ git archive --format=tar --prefix="${PACKAGE_NAME}-${VERSION}/" "$RELEASE_TAG" | gzip > "v${VERSION}.tar.gz"
+ else
+ echo "⚠ Release tag $RELEASE_TAG not found, using HEAD"
+ git archive --format=tar --prefix="${PACKAGE_NAME}-${VERSION}/" HEAD | gzip > "v${VERSION}.tar.gz"
+ fi
+
+ - name: Vendor dependencies
+ run: |
+ VERSION="${{ steps.version.outputs.version }}"
+ PACKAGE_NAME="rustnet"
+ RELEASE_TAG="v${VERSION}"
+
+ mkdir -p build-vendor
+ if git rev-parse "$RELEASE_TAG" >/dev/null 2>&1; then
+ git archive --format=tar "$RELEASE_TAG" | tar -x -C build-vendor
+ else
+ git archive --format=tar HEAD | tar -x -C build-vendor
+ fi
+
+ cd build-vendor
+ # Capture the source-replacement config that cargo prints. The OBS
+ # build has no network, so it needs this at .cargo/config.toml to
+ # resolve crates from the vendored directory instead of crates.io.
+ mkdir -p .cargo
+ cargo vendor vendor > .cargo/config.toml
+
+ # Clean up static libs to reduce size
+ find vendor -name "*.a" -delete
+ find vendor -name "*.lib" -delete
+
+ # Bundle .cargo/config.toml alongside vendor/ so `%autosetup -a 1`
+ # drops both into the source tree for the offline build.
+ tar --use-compress-program="zstd -T0 -10" -cf ../vendor.tar.zst .cargo vendor
+ cd ..
+ rm -rf build-vendor
+
+ - name: Prepare and push to OBS
+ run: |
+ VERSION="${{ steps.version.outputs.version }}"
+ OBS_PROJECT="${{ secrets.OBS_PROJECT }}"
+ OBS_PACKAGE="rustnet"
+
+ if [ -z "$OBS_PROJECT" ]; then
+ echo "::error::OBS_PROJECT secret is not set!"
+ exit 1
+ fi
+
+ # Checkout OBS project
+ osc checkout "$OBS_PROJECT" "$OBS_PACKAGE"
+ cd "$OBS_PROJECT/$OBS_PACKAGE"
+
+ # Clean old files
+ rm -f *.tar.gz *.tar.zst *.spec
+
+ # Copy new files
+ cp ../../v${VERSION}.tar.gz .
+ cp ../../vendor.tar.zst .
+ cp ../../rpm/rustnet.spec .
+
+ # Update version in spec file
+ sed -i "s/^Version:.*/Version: $VERSION/" rustnet.spec
+
+ # Prepend a changelog entry. OBS has no rpmautospec, so the spec's
+ # %changelog is empty on SUSE and the changelog lives here instead.
+ STAMP="$(LC_ALL=C date -u '+%a %b %e %T %Z %Y')"
+ {
+ printf -- '-------------------------------------------------------------------\n'
+ printf '%s - %s@opensuse.org\n\n' "$STAMP" "${{ secrets.OBS_USER }}"
+ printf -- '- Update to version %s\n\n' "$VERSION"
+ [ -f rustnet.changes ] && cat rustnet.changes
+ } > rustnet.changes.new
+ mv rustnet.changes.new rustnet.changes
+
+ # Add new files, remove deleted ones
+ osc addremove
+
+ # Commit to OBS
+ osc commit -m "Update to version $VERSION"
diff --git a/.github/workflows/ppa-release.yml b/.github/workflows/ppa-release.yml
new file mode 100644
index 0000000..e4e37c6
--- /dev/null
+++ b/.github/workflows/ppa-release.yml
@@ -0,0 +1,254 @@
+name: Release to Ubuntu PPA
+
+on:
+ workflow_call:
+ inputs:
+ tarball_suffix:
+ description: 'Tarball suffix (e.g., ds1, ds2)'
+ required: false
+ type: string
+ default: ''
+ workflow_dispatch:
+ inputs:
+ ubuntu_release:
+ description: 'Ubuntu release codename'
+ required: true
+ default: 'resolute'
+ type: choice
+ options:
+ - questing # 25.10
+ - resolute # 26.04 LTS
+ tarball_suffix:
+ description: 'Tarball suffix (e.g., ds1, ds2) - leave empty for new releases'
+ required: false
+ default: ''
+ type: string
+
+permissions:
+ contents: read
+
+env:
+ DEBEMAIL: cadetg@gmail.com
+ DEBFULLNAME: Marco Cadetg
+ PPA: ppa:domcyrus/rustnet
+
+jobs:
+ set-matrix:
+ runs-on: ubuntu-24.04
+ outputs:
+ releases: ${{ steps.set.outputs.releases }}
+ steps:
+ - id: set
+ run: |
+ if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
+ echo 'releases=["${{ inputs.ubuntu_release }}"]' >> $GITHUB_OUTPUT
+ echo "Dispatch: building only ${{ inputs.ubuntu_release }}"
+ else
+ echo 'releases=["questing","resolute"]' >> $GITHUB_OUTPUT
+ echo "Auto: building questing and resolute"
+ fi
+
+ build-and-upload:
+ needs: set-matrix
+ runs-on: ubuntu-24.04
+ strategy:
+ fail-fast: false
+ matrix:
+ ubuntu_release: ${{ fromJSON(needs.set-matrix.outputs.releases) }}
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ with:
+ fetch-depth: 0
+
+ - name: Install dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y \
+ debhelper \
+ devscripts \
+ dput \
+ gnupg \
+ libpcap-dev \
+ libelf-dev \
+ elfutils \
+ zlib1g-dev \
+ clang \
+ llvm \
+ pkg-config
+
+ - name: Install Rust
+ uses: dtolnay/rust-toolchain@stable # unpinned: maintained branch by Rust team member
+ with:
+ toolchain: stable
+
+ - name: Import GPG key
+ env:
+ GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
+ run: |
+ echo "$GPG_PRIVATE_KEY" | gpg --batch --import
+ gpg --list-secret-keys
+
+ - name: Get version
+ id: version
+ run: |
+ # Read the binary's [package] version, not [workspace.package]. Since
+ # the workspace split, Cargo.toml has two `version =` lines and the
+ # workspace one (0.x libs) comes first, so `head -1` read the wrong
+ # value. Anchor on the [package] section.
+ VERSION=$(awk -F'"' '/^\[package\]/{p=1} p && /^version = /{print $2; exit}' Cargo.toml)
+
+ # Add tarball suffix if provided (e.g., +ds1, +ds2)
+ TARBALL_SUFFIX="${{ inputs.tarball_suffix }}"
+ if [ -n "$TARBALL_SUFFIX" ]; then
+ TARBALL_VERSION="${VERSION}+${TARBALL_SUFFIX}"
+ echo "version=$TARBALL_VERSION" >> $GITHUB_OUTPUT
+ echo "Using tarball version: $TARBALL_VERSION"
+ else
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
+ echo "Using version: $VERSION"
+ fi
+
+ - name: Update changelog
+ run: |
+ VERSION="${{ steps.version.outputs.version }}"
+ # Per-series version suffix so each Ubuntu release has an independent
+ # version line on Launchpad. The `+series1` suffix sorts strictly
+ # higher than a plain `-1ubuntu1`, so future uploads always supersede
+ # any previously published package.
+ NEW_VERSION="${VERSION}-1ubuntu1+${{ matrix.ubuntu_release }}1"
+
+ DEBFULLNAME="${{ env.DEBFULLNAME }}" DEBEMAIL="${{ env.DEBEMAIL }}" \
+ dch --newversion "$NEW_VERSION" \
+ --distribution "${{ matrix.ubuntu_release }}" \
+ "Build for ${{ matrix.ubuntu_release }} - upstream release $VERSION"
+
+ echo "✓ Changelog set to $NEW_VERSION (${{ matrix.ubuntu_release }})"
+
+ - name: Pin Rust toolchain version per release
+ run: |
+ # Each Ubuntu release ships a different set of versioned rustc/cargo
+ # packages. We pin to the lowest version available on the target
+ # release that still satisfies our >= 1.88 floor (let-chains).
+ case "${{ matrix.ubuntu_release }}" in
+ questing) RUST_VERSION=1.88 ;;
+ resolute) RUST_VERSION=1.91 ;;
+ *) echo "::error::Unknown release ${{ matrix.ubuntu_release }} - add it to the case statement"; exit 1 ;;
+ esac
+ echo "Pinning to rustc-$RUST_VERSION / cargo-$RUST_VERSION for ${{ matrix.ubuntu_release }}"
+
+ sed -i -E "s/(cargo|rustc|rustdoc)-1\.[0-9]+/\1-${RUST_VERSION}/g" debian/control debian/rules
+
+ echo "--- debian/control build-depends ---"
+ grep -E "(cargo|rustc)-1\." debian/control || true
+ echo "--- debian/rules toolchain exports ---"
+ grep -E "(CARGO|RUSTC|RUSTDOC) *=" debian/rules || true
+
+ - name: Build source package
+ run: |
+ VERSION="${{ steps.version.outputs.version }}"
+ # Binary [package] version (drives the release tag to check out below).
+ BASE_VERSION=$(awk -F'"' '/^\[package\]/{p=1} p && /^version = /{print $2; exit}' Cargo.toml)
+ PACKAGE_NAME="rustnet-monitor"
+
+ # Create build directory
+ mkdir -p build-ppa
+
+ # Extract source from release tag
+ RELEASE_TAG="v${BASE_VERSION}"
+ if git rev-parse "$RELEASE_TAG" >/dev/null 2>&1; then
+ echo "✓ Found release tag: $RELEASE_TAG"
+ git archive --format=tar --prefix="${PACKAGE_NAME}-${VERSION}/" "$RELEASE_TAG" | tar -x -C build-ppa
+ else
+ echo "⚠ Release tag $RELEASE_TAG not found, using HEAD"
+ git archive --format=tar --prefix="${PACKAGE_NAME}-${VERSION}/" HEAD | tar -x -C build-ppa
+ fi
+
+ # Vendor dependencies separately from orig tarball
+ echo "Vendoring Rust dependencies..."
+ cd build-ppa/${PACKAGE_NAME}-${VERSION}
+
+ cargo vendor vendor
+
+ # Remove prebuilt static libraries (keep .dll for tests)
+ echo "Cleaning vendor directory..."
+ find vendor -name "*.a" -delete
+ find vendor -name "*.lib" -delete
+
+ # Pack vendor directory as separate tarball in debian/
+ echo "Creating vendor tarball..."
+ tar -cJf ../vendor.tar.xz vendor
+ rm -rf vendor
+
+ # Create orig tarball (without vendor directory)
+ echo "Creating orig tarball..."
+ cd ..
+ ORIG_TARBALL="${PACKAGE_NAME}_${VERSION}.orig.tar.gz"
+ tar -czf "${ORIG_TARBALL}" "${PACKAGE_NAME}-${VERSION}"
+
+ # Add debian directory and vendor tarball
+ cp -r "$GITHUB_WORKSPACE/debian" "${PACKAGE_NAME}-${VERSION}/"
+ mv vendor.tar.xz "${PACKAGE_NAME}-${VERSION}/debian/"
+
+ # Build source package
+ cd "${PACKAGE_NAME}-${VERSION}"
+
+ # Always use -sa to include orig tarball
+ # Launchpad will reuse existing file if hash matches
+ debuild -S -sa -d -us -uc
+
+ - name: Sign and upload
+ env:
+ GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }}
+ run: |
+ cd build-ppa
+ # Locate the source.changes file produced by debuild. The version
+ # suffix (+seriesN) is dynamic, so glob rather than reconstruct.
+ CHANGES_FILE=$(ls rustnet-monitor_*_source.changes | head -1)
+ if [ -z "$CHANGES_FILE" ]; then
+ echo "::error::No source.changes file found in build-ppa"
+ exit 1
+ fi
+ echo "Signing and uploading $CHANGES_FILE"
+
+ # Sign
+ debsign -k${GPG_KEY_ID} ${CHANGES_FILE}
+
+ # Verify
+ gpg --verify ${CHANGES_FILE}
+
+ # Upload to PPA
+ dput ${{ env.PPA }} ${CHANGES_FILE}
+
+ - name: Upload artifacts
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
+ with:
+ name: ppa-source-${{ matrix.ubuntu_release }}
+ path: |
+ build-ppa/*.dsc
+ build-ppa/*.tar.gz
+ build-ppa/*.tar.xz
+ build-ppa/*.changes
+ build-ppa/*.buildinfo
+ retention-days: 30
+
+ - name: Summary
+ run: |
+ cd build-ppa
+ CHANGES_FILE=$(ls rustnet-monitor_*_source.changes | head -1)
+ FULL_VERSION=$(echo "$CHANGES_FILE" | sed 's/rustnet-monitor_\(.*\)_source\.changes/\1/')
+
+ echo "## 🎉 PPA Upload Complete" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "- **Package**: rustnet-monitor" >> $GITHUB_STEP_SUMMARY
+ echo "- **Version**: $FULL_VERSION" >> $GITHUB_STEP_SUMMARY
+ echo "- **Ubuntu**: ${{ matrix.ubuntu_release }}" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "### Installation" >> $GITHUB_STEP_SUMMARY
+ echo '```bash' >> $GITHUB_STEP_SUMMARY
+ echo "sudo add-apt-repository ppa:domcyrus/rustnet" >> $GITHUB_STEP_SUMMARY
+ echo "sudo apt update && sudo apt install rustnet" >> $GITHUB_STEP_SUMMARY
+ echo '```' >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "[View PPA](https://launchpad.net/~domcyrus/+archive/ubuntu/rustnet/+packages)" >> $GITHUB_STEP_SUMMARY
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 0000000..47f3bfa
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,64 @@
+name: Publish to crates.io
+
+on:
+ workflow_call:
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+
+ - name: Install dependencies
+ run: |
+ sudo apt-get update -y
+ sudo apt-get install -y libpcap-dev libelf-dev zlib1g-dev clang llvm pkg-config
+
+ - name: Set up Rust
+ uses: dtolnay/rust-toolchain@stable # unpinned: maintained branch by Rust team member
+
+ - name: Publish to crates.io
+ env:
+ CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
+ run: |
+ # True if $crate@$VERSION is already on crates.io. `cargo search` does
+ # fuzzy matching and prints `name = "version" # description`, so we
+ # anchor on the exact `name = "version"` prefix. A bare `grep VERSION`
+ # would false-match the version string appearing in a description, and
+ # `--limit 1` alone isn't guaranteed to be the exact crate.
+ is_published() {
+ cargo search "$1" --limit 20 \
+ | grep -qF "$1 = \"$2\""
+ }
+
+ # The workspace must be published in dependency order: rustnet-core
+ # first, then the crates that depend on it, then the binary last.
+ # Each crate's path deps also carry a version, so dependents resolve
+ # against crates.io once their dependencies are indexed.
+ for crate in rustnet-core rustnet-capture rustnet-host rustnet-monitor; do
+ VERSION=$(cargo metadata --no-deps --format-version 1 \
+ | jq -r --arg n "$crate" '.packages[] | select(.name == $n) | .version')
+ if is_published "$crate" "$VERSION"; then
+ echo "⚠️ $crate@$VERSION already published to crates.io, skipping"
+ continue
+ fi
+ echo "📦 Publishing $crate@$VERSION"
+ cargo publish -p "$crate"
+ # Wait for the new version to appear in the index before publishing
+ # a dependent that requires it.
+ if [ "$crate" != "rustnet-monitor" ]; then
+ for _ in $(seq 1 30); do
+ if is_published "$crate" "$VERSION"; then
+ break
+ fi
+ echo "⏳ waiting for $crate@$VERSION to appear on crates.io..."
+ sleep 10
+ done
+ fi
+ done
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..8b4bdec
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,817 @@
+name: Release
+
+# This workflow builds and packages Rustnet for multiple platforms.
+#
+# For macOS code signing and notarization, configure these GitHub repository secrets:
+# - APPLE_DEVELOPER_CERTIFICATE_P12: Base64-encoded .p12 certificate file
+# (Export from Keychain: Developer ID Application certificate → Export as .p12 → base64 encode)
+# - APPLE_DEVELOPER_CERTIFICATE_PASSWORD: Password for the .p12 file
+# - APPLE_ID_ISSUER_ID: App Store Connect API issuer ID (from App Store Connect → Users and Access → Keys)
+# - APPLE_ID_KEY_ID: App Store Connect API key ID
+# - APPLE_ID_KEY: App Store Connect API key content (.p8 file contents)
+#
+# Without these secrets, the DMG will be unsigned and users must use "Open Anyway" in
+# System Settings → Privacy & Security to bypass Gatekeeper protection.
+#
+# To obtain Apple Developer credentials:
+# 1. Join the Apple Developer Program ($99/year): https://developer.apple.com/programs/
+# 2. Create a Developer ID Application certificate in your Apple Developer account
+# 3. Create an App Store Connect API key with "Developer" role
+# 4. Export certificate as .p12, encode with: base64 -i certificate.p12 | pbcopy
+
+on:
+ push:
+ tags:
+ - "v[0-9]+.[0-9]+.[0-9]+"
+ workflow_dispatch:
+
+permissions:
+ contents: write
+ packages: write
+ attestations: write
+ id-token: write
+
+env:
+ RUST_BACKTRACE: 1
+ RUSTNET_ASSET_DIR: assets
+
+jobs:
+ # Build all platforms using shared workflow
+ build:
+ uses: ./.github/workflows/build-platforms.yml
+ with:
+ create-archives: true
+ strip-symbols: true
+ version: ${{ github.ref_name }}
+
+ create-release:
+ name: create-release
+ runs-on: ubuntu-latest
+ needs: build
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+
+ - name: Download all artifacts
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
+ with:
+ path: artifacts
+
+ - name: Extract changelog for release
+ run: |
+ # Extract the version-specific section from CHANGELOG.md
+ VERSION="${{ github.ref_name }}"
+
+ # Remove 'v' prefix if present for matching changelog headers
+ VERSION_NUMBER="${VERSION#v}"
+
+ # Extract content between version headers
+ awk -v version="$VERSION_NUMBER" '
+ /^## \[/ {
+ if (found) exit
+ if ($0 ~ "\\[" version "\\]") {
+ found=1
+ next
+ }
+ }
+ found { print }
+ ' CHANGELOG.md > release_notes.md
+
+ # Check if we extracted any content
+ if [ ! -s release_notes.md ]; then
+ echo "⚠️ No changelog entry found for $VERSION, will use auto-generated notes"
+ echo "USE_GENERATED_NOTES=true" >> "$GITHUB_ENV"
+ else
+ echo "✅ Extracted changelog content for $VERSION"
+ cat release_notes.md
+ fi
+
+ - name: Create Release
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ # Create the release if it doesn't exist
+ if ! gh release view ${{ github.ref_name }} 2>/dev/null; then
+ if [ "$USE_GENERATED_NOTES" = "true" ]; then
+ # Fallback to auto-generated notes if changelog extraction failed
+ gh release create ${{ github.ref_name }} \
+ --title "Release ${{ github.ref_name }}" \
+ --draft \
+ --generate-notes
+ else
+ # Use extracted changelog content
+ gh release create ${{ github.ref_name }} \
+ --title "Release ${{ github.ref_name }}" \
+ --draft \
+ --notes-file release_notes.md
+ fi
+ fi
+
+ # Upload all build artifacts
+ for artifact in artifacts/build-*/rustnet-*; do
+ gh release upload ${{ github.ref_name }} "$artifact" --clobber
+ done
+
+ # aarch64 static: JS actions don't work in Alpine containers on ARM runners,
+ # so the reusable workflow can't upload artifacts. Build and upload here instead,
+ # where we have contents: write from the workflow-level permissions.
+ upload-arm-static:
+ name: upload-arm-static
+ runs-on: ubuntu-24.04-arm
+ needs: create-release
+ container:
+ image: rust:alpine
+ steps:
+ - name: Checkout repository (ARM workaround)
+ run: |
+ apk add --no-cache git
+ git config --global --add safe.directory /__w/rustnet/rustnet
+ git clone --depth 1 "https://github.com/${{ github.repository }}.git" .
+ # shellcheck disable=SC2193
+ if [[ "${{ github.ref }}" == refs/tags/* ]]; then
+ git fetch --depth 1 origin tag "${{ github.ref_name }}"
+ git checkout "${{ github.ref_name }}"
+ fi
+
+ - name: Install dependencies
+ run: |
+ apk add --no-cache \
+ musl-dev libpcap-dev pkgconfig build-base perl \
+ elfutils-dev zlib-dev zlib-static zstd-dev zstd-static \
+ clang llvm linux-headers
+ rustup component add rustfmt
+
+ - name: Build static binary
+ env:
+ CFLAGS: "-mno-outline-atomics"
+ CXXFLAGS: "-mno-outline-atomics"
+ RUSTFLAGS: "-C strip=symbols -C link-arg=-l:libzstd.a"
+ run: cargo build --release
+
+ - name: Verify static linking
+ run: |
+ file target/release/rustnet
+ file target/release/rustnet | grep -q "static.* linked" || \
+ (echo "ERROR: Binary is not statically linked" && exit 1)
+
+ - name: Create and upload release archive
+ run: |
+ staging="rustnet-${{ github.ref_name }}-aarch64-unknown-linux-musl"
+ mkdir -p "$staging/assets"
+
+ cp target/release/rustnet "$staging/"
+ cp crates/rustnet-core/assets/services "$staging/assets/" 2>/dev/null || true
+ cp README.md "$staging/"
+ cp LICENSE "$staging/" 2>/dev/null || true
+
+ tar czf "$staging.tar.gz" "$staging"
+
+ apk add --no-cache github-cli
+ gh release upload "${{ github.ref_name }}" "$staging.tar.gz" --clobber
+ env:
+ GH_TOKEN: ${{ github.token }}
+
+ upload-android-static:
+ name: upload-${{ matrix.arch }}-android-static
+ runs-on: ${{ matrix.runner }}
+ needs: create-release
+ container:
+ image: rust:alpine
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - arch: aarch64
+ runner: ubuntu-24.04-arm
+ artifact: aarch64-linux-android-musl
+ - arch: x86_64
+ runner: ubuntu-latest
+ artifact: x86_64-linux-android-musl
+ - arch: armv7
+ runner: ubuntu-latest
+ artifact: armv7-linux-android-musl
+ - arch: x86
+ runner: ubuntu-latest
+ artifact: i686-linux-android-musl
+
+ steps:
+ # aarch64: JS actions don't work in Alpine containers on ARM runners
+ - name: Checkout repository (aarch64 workaround)
+ if: matrix.arch == 'aarch64'
+ run: |
+ apk add --no-cache git
+ git config --global --add safe.directory /__w/rustnet/rustnet
+ git clone --depth 1 "https://github.com/${{ github.repository }}.git" .
+ # shellcheck disable=SC2193
+ if [[ "${{ github.ref }}" == refs/tags/* ]]; then
+ git fetch --depth 1 origin tag "${{ github.ref_name }}"
+ git checkout "${{ github.ref_name }}"
+ fi
+
+ - name: Checkout repository
+ if: matrix.arch != 'aarch64'
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+
+ - name: Install common dependencies
+ run: |
+ apk add --no-cache \
+ musl-dev pkgconfig build-base perl \
+ zlib-dev zlib-static \
+ clang llvm linux-headers github-cli curl
+ rustup component add rustfmt
+
+ # aarch64/x86_64: libpcap-dev provides a native static libpcap.
+ # zstd-static is required because Alpine's libpcap-dev links against zstd.
+ - name: Install libpcap (native)
+ if: matrix.arch == 'aarch64' || matrix.arch == 'x86_64'
+ run: apk add --no-cache libpcap-dev elfutils-dev zstd-dev zstd-static
+
+ # armv7: use zig cc as the cross-compiler (no external toolchain download needed).
+ # zig bundles musl libc for all targets, so no -l:libzstd.a needed.
+ - name: Install cross-compiler and libpcap (armv7)
+ if: matrix.arch == 'armv7'
+ run: |
+ rustup target add armv7-unknown-linux-musleabihf
+ apk add zig flex bison bash
+
+ # zig cc wrapper: cc-rs detects it as clang and passes --target=armv7-*
+ # which zig rejects (zig uses 'arm' not 'armv7'). Filter those args out.
+ # shellcheck disable=SC2016
+ printf '%s\n' \
+ '#!/bin/bash' \
+ 'args=()' \
+ 'for arg in "$@"; do' \
+ ' case "$arg" in --target=*) ;; *) args+=("$arg") ;; esac' \
+ 'done' \
+ 'exec zig cc -target arm-linux-musleabihf "${args[@]}"' \
+ > /usr/local/bin/arm-linux-musleabihf-gcc
+ chmod +x /usr/local/bin/arm-linux-musleabihf-gcc
+
+ # libpcap is pinned by version + sha256 (Dependabot does not track
+ # tarballs fetched in run steps, so bump the URL and the hash together).
+ curl -fsSL --retry 3 --retry-delay 5 \
+ -o libpcap.tar.gz https://www.tcpdump.org/release/libpcap-1.10.5.tar.gz
+ echo "37ced90a19a302a7f32e458224a00c365c117905c2cd35ac544b6880a81488f0 libpcap.tar.gz" | sha256sum -c -
+ tar xzf libpcap.tar.gz
+ cd libpcap-1.10.5
+ CC=arm-linux-musleabihf-gcc \
+ ./configure --host=arm-linux-musleabihf \
+ --disable-shared --enable-static \
+ --disable-usb --disable-dbus --disable-bluetooth --disable-remote \
+ --prefix=/arm-sysroot
+ make -j"$(nproc)" && make install
+ cd ..
+
+ # x86: use zig cc as the cross-compiler (no external toolchain download needed).
+ # zig bundles musl libc for all targets, so no -l:libzstd.a needed.
+ - name: Install cross-compiler and libpcap (x86/i686)
+ if: matrix.arch == 'x86'
+ run: |
+ rustup target add i686-unknown-linux-musl
+ apk add zig flex bison bash
+
+ # zig cc wrapper: cc-rs passes --target=i686-* which zig doesn't accept;
+ # filter those args and use -target x86-linux-musl instead.
+ # shellcheck disable=SC2016
+ printf '%s\n' \
+ '#!/bin/bash' \
+ 'args=()' \
+ 'for arg in "$@"; do' \
+ ' case "$arg" in --target=*) ;; -Wl,-melf_i386) ;; *) args+=("$arg") ;; esac' \
+ 'done' \
+ 'exec zig cc -target x86-linux-musl "${args[@]}"' \
+ > /usr/local/bin/i686-linux-musl-gcc
+ chmod +x /usr/local/bin/i686-linux-musl-gcc
+
+ # libpcap is pinned by version + sha256 (Dependabot does not track
+ # tarballs fetched in run steps, so bump the URL and the hash together).
+ curl -fsSL --retry 3 --retry-delay 5 \
+ -o libpcap.tar.gz https://www.tcpdump.org/release/libpcap-1.10.5.tar.gz
+ echo "37ced90a19a302a7f32e458224a00c365c117905c2cd35ac544b6880a81488f0 libpcap.tar.gz" | sha256sum -c -
+ tar xzf libpcap.tar.gz
+ cd libpcap-1.10.5
+ CC=i686-linux-musl-gcc \
+ ./configure --host=i686-linux-musl \
+ --disable-shared --enable-static \
+ --disable-usb --disable-dbus --disable-bluetooth --disable-remote \
+ --prefix=/x86-sysroot
+ make -j"$(nproc)" && make install
+ cd ..
+
+ - name: Build static binary (aarch64)
+ if: matrix.arch == 'aarch64'
+ env:
+ CFLAGS: "-mno-outline-atomics"
+ CXXFLAGS: "-mno-outline-atomics"
+ RUSTFLAGS: "-C strip=symbols -C link-arg=-l:libzstd.a"
+ run: cargo build --release --no-default-features
+
+ - name: Build static binary (x86_64)
+ if: matrix.arch == 'x86_64'
+ env:
+ RUSTFLAGS: "-C strip=symbols -C link-arg=-l:libzstd.a"
+ run: cargo build --release --no-default-features
+
+ - name: Build static binary (armv7)
+ if: matrix.arch == 'armv7'
+ env:
+ # link-self-contained=no: let zig provide musl CRT (avoid duplicate _start
+ # when both Rust's self-contained crt1.o and zig's crt1.o are linked)
+ RUSTFLAGS: "-C strip=symbols -C link-self-contained=no"
+ CARGO_TARGET_ARMV7_UNKNOWN_LINUX_MUSLEABIHF_LINKER: arm-linux-musleabihf-gcc
+ PKG_CONFIG_PATH: /arm-sysroot/lib/pkgconfig
+ PKG_CONFIG_SYSROOT_DIR: /arm-sysroot
+ PKG_CONFIG_ALLOW_CROSS: "1"
+ run: cargo build --release --target armv7-unknown-linux-musleabihf --no-default-features
+
+ - name: Build static binary (x86/i686)
+ if: matrix.arch == 'x86'
+ env:
+ RUSTFLAGS: "-C strip=symbols -C link-self-contained=no"
+ CARGO_TARGET_I686_UNKNOWN_LINUX_MUSL_LINKER: i686-linux-musl-gcc
+ PKG_CONFIG_PATH: /x86-sysroot/lib/pkgconfig
+ PKG_CONFIG_SYSROOT_DIR: /x86-sysroot
+ PKG_CONFIG_ALLOW_CROSS: "1"
+ run: cargo build --release --target i686-unknown-linux-musl --no-default-features
+
+ - name: Verify static linking
+ run: |
+ BIN="${{ (matrix.arch == 'armv7' && 'target/armv7-unknown-linux-musleabihf/release/rustnet') || (matrix.arch == 'x86' && 'target/i686-unknown-linux-musl/release/rustnet') || 'target/release/rustnet' }}"
+ file "$BIN"
+ file "$BIN" | grep -q "static.* linked" || \
+ (echo "ERROR: Binary is not statically linked" && exit 1)
+
+ - name: Create release archive
+ run: |
+ VERSION="${{ github.ref_name }}"
+ ARTIFACT="${{ matrix.artifact }}"
+
+ case "${{ matrix.arch }}" in
+ armv7) BIN="target/armv7-unknown-linux-musleabihf/release/rustnet" ;;
+ x86) BIN="target/i686-unknown-linux-musl/release/rustnet" ;;
+ *) BIN="target/release/rustnet" ;;
+ esac
+
+ staging="rustnet-${VERSION}-${ARTIFACT}"
+ mkdir -p "$staging/assets"
+ cp "$BIN" "$staging/rustnet"
+ cp crates/rustnet-core/assets/services "$staging/assets/" 2>/dev/null || true
+ cp README.md "$staging/"
+ cp LICENSE "$staging/" 2>/dev/null || true
+ tar czf "$staging.tar.gz" "$staging"
+
+ - name: Upload to release
+ run: |
+ VERSION="${{ github.ref_name }}"
+ ARCHIVE="rustnet-${VERSION}-${{ matrix.artifact }}.tar.gz"
+ gh release upload "$VERSION" "$ARCHIVE" --repo "${{ github.repository }}" --clobber
+ env:
+ GH_TOKEN: ${{ github.token }}
+
+ # Publish the release (un-draft) after all assets are uploaded.
+ # This ensures downstream jobs (Homebrew, Chocolatey) can download assets.
+ publish-release:
+ name: publish-release
+ runs-on: ubuntu-latest
+ needs:
+ - create-release
+ - upload-arm-static
+ - upload-android-static
+ - package-installers
+ - package-macos
+ - package-windows
+ if: always() && needs.create-release.result == 'success'
+ steps:
+ - name: Publish release
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: gh release edit ${{ github.ref_name }} --draft=false --repo ${{ github.repository }}
+
+ # Gate job that waits for all publish steps to finish before firing
+ # downstream package-manager triggers. Without this gate, triggers that
+ # run as soon as publish-release completes race against publish-crates,
+ # publish-docker, update-copr, and release-ppa — and the downstream
+ # Homebrew/Chocolatey workflows bail because their "is release.yml still
+ # running?" safety check sees the parent run as in-progress.
+ all-published:
+ name: all-published
+ runs-on: ubuntu-latest
+ needs:
+ - publish-release
+ - publish-crates
+ - publish-docker
+ - update-copr
+ - release-ppa
+ - release-obs
+ steps:
+ - name: All publish steps complete
+ run: echo "Release pipeline fully published; safe to fire downstream triggers."
+
+ trigger-bsd-build:
+ name: trigger-bsd-build
+ runs-on: ubuntu-latest
+ needs: all-published
+ steps:
+ - name: Trigger FreeBSD build
+ run: |
+ gh api repos/domcyrus/rustnet-bsd/dispatches \
+ -f event_type=freebsd-build \
+ -f "client_payload[tag]=${{ github.ref_name }}" \
+ -f "client_payload[rustnet_ref]=${{ github.sha }}"
+ env:
+ GH_TOKEN: ${{ secrets.BSD_DISPATCH_TOKEN }}
+
+ trigger-homebrew-update:
+ name: trigger-homebrew-update
+ runs-on: ubuntu-latest
+ needs: all-published
+ steps:
+ - name: Trigger Homebrew formula update
+ run: |
+ gh workflow run update-formula.yml \
+ --repo domcyrus/homebrew-rustnet \
+ -f version=${{ github.ref_name }}
+ env:
+ GH_TOKEN: ${{ secrets.HOMEBREW_PAT }}
+
+ trigger-chocolatey-update:
+ name: trigger-chocolatey-update
+ runs-on: ubuntu-latest
+ needs: all-published
+ steps:
+ - name: Trigger Chocolatey package update
+ run: |
+ gh workflow run update-package.yml \
+ --repo domcyrus/rustnet-chocolatey \
+ -f version=${{ github.ref_name }}
+ env:
+ GH_TOKEN: ${{ secrets.CHOCOLATEY_PAT }}
+
+ # Dispatching a same-repo workflow via gh workflow run requires
+ # actions: write on the token. The top-level permissions block grants
+ # contents/packages/attestations/id-token only, so explicitly opt in here.
+ trigger-aur-update:
+ name: trigger-aur-update
+ runs-on: ubuntu-latest
+ needs: all-published
+ permissions:
+ actions: write
+ contents: read
+ steps:
+ - name: Trigger AUR package update
+ run: |
+ gh workflow run aur-update.yml \
+ --repo domcyrus/rustnet
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ package-installers:
+ name: package-installers
+ runs-on: ubuntu-22.04
+ needs: create-release
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+
+ - name: Download build artifacts
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
+ with:
+ path: artifacts
+
+ - name: Setup Linux dependencies
+ uses: ./.github/actions/setup-linux-deps
+
+ - name: Install Rust and tools
+ uses: dtolnay/rust-toolchain@stable # unpinned: maintained branch by Rust team member
+ with:
+ targets: x86_64-unknown-linux-gnu,aarch64-unknown-linux-gnu,armv7-unknown-linux-gnueabihf
+ components: rustfmt
+
+ - name: Install packaging tools
+ run: |
+ cargo install cargo-deb cargo-generate-rpm
+
+ - name: Package Debian packages
+ run: |
+ mkdir -p installers
+ for arch in amd64 arm64 armhf; do
+ case "$arch" in
+ amd64) target="x86_64-unknown-linux-gnu" ;;
+ arm64) target="aarch64-unknown-linux-gnu" ;;
+ armhf) target="armv7-unknown-linux-gnueabihf" ;;
+ esac
+
+ # Extract binary and assets from artifact
+ tar -xzf "artifacts/build-$target/rustnet-${{ github.ref_name }}-$target.tar.gz"
+ mkdir -p "target/$target/release"
+ cp "rustnet-${{ github.ref_name }}-$target/rustnet" "target/$target/release/"
+
+ # Ensure services file exists for packaging
+ mkdir -p crates/rustnet-core/assets
+ if [ -f "rustnet-${{ github.ref_name }}-$target/assets/services" ]; then
+ cp "rustnet-${{ github.ref_name }}-$target/assets/services" crates/rustnet-core/assets/
+ fi
+
+ # Create deb package
+ cargo deb --no-build --no-strip --target "$target"
+ mv "target/$target/debian"/*.deb "installers/Rustnet_LinuxDEB_$arch.deb"
+
+ # Clean up for next iteration
+ rm -rf "rustnet-${{ github.ref_name }}-$target" "target/$target"
+ done
+
+ - name: Package RPM packages
+ run: |
+ for arch in x86_64 aarch64; do
+ target="$arch-unknown-linux-gnu"
+
+ # Extract binary and assets from artifact
+ tar -xzf "artifacts/build-$target/rustnet-${{ github.ref_name }}-$target.tar.gz"
+ mkdir -p "target/$target/release"
+ cp "rustnet-${{ github.ref_name }}-$target/rustnet" "target/$target/release/"
+
+ # Ensure services file exists for packaging
+ mkdir -p crates/rustnet-core/assets
+ if [ -f "rustnet-${{ github.ref_name }}-$target/assets/services" ]; then
+ cp "rustnet-${{ github.ref_name }}-$target/assets/services" crates/rustnet-core/assets/
+ fi
+
+ # Fix library linking if needed
+ patchelf --replace-needed libpcap.so.0.8 libpcap.so.1 "target/$target/release/rustnet" 2>/dev/null || true
+
+ # Create rpm package
+ cargo generate-rpm --target "$target"
+ mv "target/$target/generate-rpm"/*.rpm "installers/Rustnet_LinuxRPM_$arch.rpm"
+
+ # Clean up for next iteration
+ rm -rf "rustnet-${{ github.ref_name }}-$target" "target/$target"
+ done
+
+ - name: Upload installer packages
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ # Upload all installer packages
+ for installer in installers/*; do
+ gh release upload ${{ github.ref_name }} "$installer" --clobber
+ done
+
+ package-macos:
+ name: package-macos
+ runs-on: macos-latest
+ needs: create-release
+ strategy:
+ matrix:
+ include:
+ - arch: Intel
+ target: x86_64-apple-darwin
+ - arch: AppleSilicon
+ target: aarch64-apple-darwin
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+
+ - name: Install packaging tools
+ run: |
+ cargo install toml-cli
+ cargo install apple-codesign
+ brew install create-dmg
+
+ - name: Download build artifact
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
+ with:
+ name: build-${{ matrix.target }}
+ path: artifacts
+
+ - name: Package for macOS
+ run: |
+ # Extract binary
+ tar -xzf "artifacts/rustnet-${{ github.ref_name }}-${{ matrix.target }}.tar.gz"
+
+ # Get version and update plist
+ VERSION=$(toml get Cargo.toml package.version --raw)
+ sed -i'.bak' -e "s/0\.0\.0/${VERSION}/g" -e "s/fffffff/${GITHUB_SHA:0:7}/g" resources/packaging/macos/Info.plist
+
+ # Create app bundle
+ mkdir -p "Rustnet.app/Contents/"{MacOS,Resources/assets}
+ cp resources/packaging/macos/Info.plist "Rustnet.app/Contents/"
+ cp resources/packaging/macos/graphics/rustnet.icns "Rustnet.app/Contents/Resources/"
+ cp "rustnet-${{ github.ref_name }}-${{ matrix.target }}/rustnet" "Rustnet.app/Contents/MacOS/"
+ cp resources/packaging/macos/wrapper.sh "Rustnet.app/Contents/MacOS/"
+ cp "rustnet-${{ github.ref_name }}-${{ matrix.target }}/assets/services" "Rustnet.app/Contents/Resources/assets/"
+ chmod +x "Rustnet.app/Contents/MacOS/"{rustnet,wrapper.sh}
+
+ - name: Code sign and notarize app bundle
+ env:
+ APPLE_CERTIFICATE_P12: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12 }}
+ APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_PASSWORD }}
+ APPLE_API_ISSUER: ${{ secrets.APPLE_ID_ISSUER_ID }}
+ APPLE_API_KEY_ID: ${{ secrets.APPLE_ID_KEY_ID }}
+ APPLE_API_KEY: ${{ secrets.APPLE_ID_KEY }}
+ run: |
+ # Skip signing if secrets are not configured
+ if [ -z "$APPLE_CERTIFICATE_P12" ]; then
+ echo "⚠️ Code signing secrets not configured. DMG will be unsigned."
+ echo "⚠️ Users will need to use 'Open Anyway' in System Settings > Privacy & Security"
+ echo "SKIP_SIGNING=true" >> "$GITHUB_ENV"
+ exit 0
+ fi
+
+ # Decode certificate and save to file
+ echo "$APPLE_CERTIFICATE_P12" | base64 --decode > certificate.p12
+
+ # Create temporary JSON for API credentials
+ cat > api-key.json < oui-clean.txt
+ gzip -9 -c oui-clean.txt > crates/rustnet-core/assets/oui.gz
+ rm oui-raw.txt oui-clean.txt
+
+ - name: Create pull request if changed
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ if git diff --quiet crates/rustnet-core/assets/oui.gz; then
+ echo "OUI database is already up to date."
+ exit 0
+ fi
+
+ BRANCH="update-oui-db"
+ git config user.name "github-actions[bot]"
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
+ git checkout -B "$BRANCH"
+ git add crates/rustnet-core/assets/oui.gz
+ git commit -m "Update OUI vendor database"
+ git push -f origin "$BRANCH"
+
+ # Only create PR if one doesn't already exist for this branch
+ if ! gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number' | grep -q .; then
+ gh pr create \
+ --title "Update OUI vendor database" \
+ --body "Automated monthly update of the IEEE OUI (MAC vendor) database.
+
+ Source: https://standards-oui.ieee.org/oui/oui.txt" \
+ --label dependencies
+ fi
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c93f878
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,24 @@
+# Generated by Cargo
+# will have compiled files and executables
+debug/
+target/
+
+# These are backup files generated by rustfmt
+**/*.rs.bk
+
+# MSVC Windows builds of rustc generate these, which store debugging information
+*.pdb
+
+# RustRover
+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
+
+# Added by cargo
+
+/target
+.aider*
+/logs
+.venv
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
new file mode 100644
index 0000000..f6b3925
--- /dev/null
+++ b/ARCHITECTURE.md
@@ -0,0 +1,490 @@
+ English | 简体中文
+
+# Architecture
+
+This document describes the technical architecture and implementation details of RustNet.
+
+## Table of Contents
+
+- [Crate Structure](#crate-structure)
+- [Multi-threaded Architecture](#multi-threaded-architecture)
+- [Key Components](#key-components)
+- [Platform-Specific Implementations](#platform-specific-implementations)
+- [Performance Considerations](#performance-considerations)
+- [Dependencies](#dependencies)
+- [Security](#security)
+
+## Crate Structure
+
+RustNet is a Cargo workspace of four crates. The analysis logic, capture backend, and process attribution each live in their own reusable library crate; the binary composes them into the TUI application.
+
+| Crate | Type | Responsibility |
+| --- | --- | --- |
+| [`rustnet-core`](crates/rustnet-core) | library | Platform- and capture-independent analysis core: packet parsing, protocol/connection types, deep packet inspection, link-layer parsers, connection merging, DNS/GeoIP/OUI lookups, and a reusable `ConnectionTracker` (live table + RTT + QUIC coalescing + lifecycle) for headless tools. Operates only on byte slices and parsed structures -- no libpcap, raw sockets, or OS process tables. |
+| [`rustnet-capture`](crates/rustnet-capture) | library | libpcap/Npcap packet-capture backend: device selection, BPF filters, macOS PKTAP, TUN/TAP, and a raw-frame `PacketReader`. |
+| [`rustnet-host`](crates/rustnet-host) | library | Per-connection process attribution behind one `ProcessLookup` trait: eBPF/procfs on Linux, PKTAP/lsof on macOS, the IP Helper API on Windows, and `sockstat` on FreeBSD. Owns the eBPF build tooling and bundled `vmlinux.h`. |
+| `rustnet-monitor` (binary `rustnet`) | binary | The user-facing application: CLI, TUI, app event loop, sandboxing (Landlock/Seatbelt), and interface statistics. Dogfoods `ConnectionTracker` as the single source of truth. |
+
+The package is named `rustnet-monitor` because the `rustnet` crate name is taken on crates.io; the installed binary is `rustnet`.
+
+### Dependency Graph
+
+```mermaid
+flowchart TD
+ BIN[rustnet-monitor bin: rustnet]
+ CAP[rustnet-capture]
+ HOST[rustnet-host]
+ CORE[rustnet-core]
+
+ BIN --> CAP
+ BIN --> HOST
+ BIN --> CORE
+ CAP --> CORE
+ HOST --> CORE
+```
+
+The graph is acyclic: `rustnet-core` has no workspace dependencies, and both `rustnet-capture` and `rustnet-host` depend only on it. Keeping `rustnet-core` a leaf lets it be published and reused independently -- a headless front-end (e.g. a Prometheus exporter) can pair `rustnet-capture` + `rustnet-core` without the TUI.
+
+### Re-export Facade
+
+To keep the split internal to the binary, `src/network/mod.rs` re-exports `rustnet_core::network::*` and `rustnet_capture` (as `capture`), so existing `crate::network::*` paths, integration tests, and benches compile unchanged. The `src/network/platform` module still hosts the OS sandboxing (Landlock/Seatbelt) and interface-stats collectors, and wires in `rustnet-host`'s process lookup.
+
+## Multi-threaded Architecture
+
+RustNet uses a multi-threaded architecture for efficient packet processing:
+
+```mermaid
+flowchart LR
+ PC[Packet Capture libpcap]
+ CH([Crossbeam Channel])
+ PP[Packet Processors Thread 0..N]
+ PE[Process Enrichment Platform API]
+ DM[(DashMap)]
+ SP[Snapshot Provider]
+ UI[/RwLock<Vec<Connection>> for UI/]
+ CT[Cleanup Thread]
+
+ PC -- packets --> CH --> PP --> DM
+ PE --> DM
+ DM --> SP --> UI
+ DM --> CT
+```
+
+## Key Components
+
+### 1. Packet Capture Thread
+
+Uses libpcap to capture raw packets from the network interface. This thread runs independently and feeds packets into a Crossbeam channel for processing.
+
+**Responsibilities:**
+- Open network interface for packet capture (non-promiscuous, read-only mode)
+- Apply BPF filters if needed
+- Capture raw packets
+- Stream packets to PCAP file if `--pcap-export` is enabled (direct disk write, no memory buffering)
+- Feed parsed packets to the annotated PCAPNG writer if `--pcapng-export` is enabled (bounded best-effort queue)
+- Send packets to processing queue
+
+### 2. Packet Processors
+
+Multiple worker threads (up to 4 by default, based on CPU cores) that parse packets and perform Deep Packet Inspection (DPI) analysis.
+
+**Responsibilities:**
+- Parse Ethernet, IP, TCP, UDP, ICMP, ARP headers
+- Extract connection 5-tuple (protocol, src IP, src port, dst IP, dst port)
+- Perform DPI to detect application protocols:
+ - HTTP with host information
+ - HTTPS/TLS with SNI (Server Name Indication)
+ - DNS queries and responses
+ - SSH connections with version detection
+ - FTP control channel with commands, response codes, username, server software, and system type
+ - QUIC protocol with CONNECTION_CLOSE frame detection
+ - MQTT with packet types, version, and client identifier
+ - BitTorrent handshakes and DHT messages
+ - STUN for WebRTC and NAT traversal
+ - NTP with version, mode, and stratum
+ - mDNS and LLMNR for local name resolution
+ - DHCP with message types and hostnames
+ - SNMP (v1, v2c, v3) with PDU types
+ - SSDP for UPnP device discovery
+ - NetBIOS Name Service and Datagram Service
+- Track connection states and lifecycle
+- Update connection metadata in DashMap
+- Calculate bandwidth metrics
+
+### 3. Process Enrichment
+
+Platform-specific APIs to associate network connections with running processes. This component runs periodically to enrich connection data with process information.
+
+**Responsibilities:**
+- Map socket inodes to process IDs
+- Resolve process names and command lines
+- Update connection records with process information
+- Handle permission-related fallbacks
+
+See [Platform-Specific Implementations](#platform-specific-implementations) for details on each platform.
+
+### 4. Snapshot Provider
+
+Creates consistent snapshots of connection data for the UI at regular intervals (default: 1 second). This ensures the UI has a stable view of connections without race conditions.
+
+**Responsibilities:**
+- Read from DashMap at configured intervals
+- Apply filtering based on user criteria (localhost, etc.)
+- Sort connections based on user-selected column
+- Create immutable snapshot for UI rendering
+- Provide RwLock-protected Vec for UI thread
+
+### 5. Cleanup Thread
+
+Removes inactive connections using smart, protocol-aware timeouts. This prevents memory leaks and keeps the connection list relevant. When `--pcap-export` is enabled, also streams connection metadata (PID, process name, timestamps) to a JSONL sidecar file as connections close.
+
+**Timeout Strategy:**
+
+#### TCP Connections
+- **HTTP/HTTPS** (detected via DPI): **10 minutes** - supports HTTP keep-alive
+- **SSH** (detected via DPI): **30 minutes** - accommodates long interactive sessions
+- **Active established** (< 1 min idle): **10 minutes**
+- **Idle established** (> 1 min idle): **5 minutes**
+- **TIME_WAIT**: 30 seconds - standard TCP timeout
+- **CLOSED**: 5 seconds - rapid cleanup
+- **SYN_SENT, FIN_WAIT, etc.**: 30-60 seconds
+
+#### UDP Connections
+- **HTTP/3 (QUIC with HTTP)**: **10 minutes** - connection reuse
+- **HTTPS/3 (QUIC with HTTPS)**: **10 minutes** - connection reuse
+- **SSH over UDP**: **30 minutes** - long-lived sessions
+- **DNS**: **30 seconds** - short-lived queries
+- **Regular UDP**: **60 seconds** - standard timeout
+
+#### QUIC Connections (Detected State)
+- **Connected**: 3 minutes default, or the peer's `max_idle_timeout` transport parameter when present
+- **With CONNECTION_CLOSE frame**: 1-10 seconds (based on close type)
+- **Initial/Handshaking**: 60 seconds - allow connection establishment
+- **Draining**: 10 seconds - RFC 9000 draining period
+- **Closed**: 1 second - immediate cleanup
+
+**Visual Staleness Indicators:**
+
+Connections change color based on proximity to timeout:
+- **White** (default): < 75% of timeout
+- **Yellow**: 75-90% of timeout (warning)
+- **Red**: > 90% of timeout (critical)
+
+### 6. Rate Refresh Thread
+
+Updates bandwidth calculations every second with gentle decay. This provides smooth bandwidth visualization without abrupt changes.
+
+**Responsibilities:**
+- Calculate bytes/second for download and upload
+- Apply exponential decay to older measurements
+- Update visual bandwidth indicators
+- Maintain rolling window of packet rates
+
+### 7. DashMap
+
+Concurrent hashmap (`DashMap`) for storing connection state. This lock-free data structure enables efficient concurrent access from multiple threads.
+
+**Key Features:**
+- Fine-grained locking (per-shard)
+- No global lock contention
+- Safe concurrent reads and writes
+- High performance under concurrent load
+
+## Platform-Specific Implementations
+
+### Process Lookup
+
+RustNet uses platform-specific APIs to associate network connections with processes:
+
+#### Linux
+
+**Standard Mode (procfs):**
+- Parses `/proc/net/tcp` and `/proc/net/udp` to get socket inodes
+- Iterates through `/proc//fd/` to find socket file descriptors
+- Maps inodes to process IDs and resolves process names from `/proc//cmdline`
+
+**eBPF Mode (Default on Linux):**
+- Uses kernel eBPF programs attached to socket syscalls
+- Captures socket creation events with process context
+- Provides lower overhead than procfs scanning
+- **Limitations:**
+ - Process names limited to 16 characters (kernel `comm` field)
+ - May show thread names instead of full executable names
+ - Multi-threaded applications show internal thread names
+- **Capability requirements:**
+ - Modern Linux (5.8+): `CAP_NET_RAW` (packet capture), `CAP_BPF`, `CAP_PERFMON` (eBPF)
+ - Legacy Linux (pre-5.8): eBPF requires broad `CAP_SYS_ADMIN`; RustNet packages do not grant it automatically and fall back to procfs instead
+ - Note: CAP_NET_ADMIN is NOT required (uses read-only, non-promiscuous packet capture)
+
+**Fallback Behavior:**
+- If eBPF fails to load (permissions, kernel compatibility), automatically falls back to procfs mode
+- TUI Statistics panel shows active detection method
+
+#### macOS
+
+**PKTAP Mode (with sudo):**
+- Uses PKTAP (Packet Tap) kernel interface
+- Extracts process information directly from packet metadata
+- Requires root privileges (privileged kernel interface)
+- Faster and more accurate than lsof
+
+**lsof Mode (without sudo or fallback):**
+- Uses `lsof -i -n -P` to list network connections
+- Parses output to associate sockets with processes
+- Higher CPU overhead but works without root
+- Used automatically when PKTAP is unavailable
+
+**Detection:**
+- TUI Statistics panel shows "pktap" or "lsof" based on active method
+- Automatically selects best available method
+
+#### Windows
+
+**IP Helper API:**
+- Uses `GetExtendedTcpTable` and `GetExtendedUdpTable` from Windows IP Helper API
+- Retrieves connection tables with process IDs
+- Supports both IPv4 and IPv6 connections
+- Resolves process names using `OpenProcess` and `QueryFullProcessImageNameW`
+
+**Requirements:**
+- May require Administrator privileges depending on system configuration
+- Requires Npcap or WinPcap for packet capture
+
+### Network Interfaces
+
+The tool automatically detects and lists available network interfaces using platform-specific methods:
+
+- **Linux**: Uses `netlink` or falls back to `/sys/class/net/`
+- **macOS**: Uses `getifaddrs()` system call
+- **Windows**: Uses `GetAdaptersInfo()` from IP Helper API
+- **All platforms**: Falls back to pcap's `pcap_findalldevs()` when native methods fail
+
+## Performance Considerations
+
+### Multi-threaded Processing
+
+Packet processing is distributed across multiple threads (up to 4 by default, based on CPU cores). This enables:
+- Parallel packet parsing and DPI analysis
+- Better utilization of multi-core systems
+- Reduced latency for high packet rates
+
+### Concurrent Data Structures
+
+**DashMap** provides lock-free concurrent access with:
+- Per-shard locking (16 shards by default)
+- No global lock contention
+- Read-heavy workload optimization
+- Safe concurrent modifications
+
+### Batch Processing
+
+Packets are processed in batches to improve cache efficiency:
+- Multiple packets processed before context switching
+- Reduced system call overhead
+- Better CPU cache utilization
+
+### Selective DPI
+
+Deep packet inspection can be disabled with `--no-dpi` for lower overhead:
+- Reduces CPU usage by 20-40% on high-traffic networks
+- Still tracks basic connection information
+- Useful for performance-constrained environments
+
+### Configurable Intervals
+
+Adjust refresh rates based on your needs:
+- **UI refresh**: Default 1000ms (adjustable with `--refresh-interval`)
+- **Process enrichment**: Every 2 seconds
+- **Cleanup check**: Every 5 seconds
+- **Rate calculation**: Every 1 second
+
+### Memory Management
+
+**Connection cleanup** prevents unbounded memory growth:
+- Protocol-aware timeouts remove stale connections
+- Visual staleness warnings before removal
+- Configurable timeout thresholds
+
+**Snapshot isolation** prevents UI blocking:
+- UI reads from immutable snapshots
+- Background threads update DashMap concurrently
+- No lock contention between UI and packet processing
+
+## Dependencies
+
+RustNet is built with the following key dependencies:
+
+### Core Dependencies
+
+- **ratatui** - Terminal user interface framework with full widget support
+- **crossterm** - Cross-platform terminal manipulation
+- **pcap** - Packet capture library bindings for libpcap/Npcap
+- **pnet_datalink** - Network interface enumeration and low-level networking
+
+### Concurrency & Threading
+
+- **dashmap** - Concurrent hashmap with fine-grained locking
+- **crossbeam** - Multi-threading utilities and lock-free channels
+
+### Networking & Protocols
+
+- **dns-lookup** - DNS resolution capabilities
+- **maxminddb** - GeoIP database lookups (GeoLite2)
+
+### Serialization
+
+- **serde** / **serde_json** - JSON serialization for event logging and PCAP sidecar
+
+### Command-line & Logging
+
+- **clap** - Command-line argument parsing with derive features
+- **simplelog** - Flexible logging framework
+- **log** - Logging facade
+- **anyhow** - Error handling and context
+
+### Platform-Specific
+
+- **procfs** (Linux) - Process information from /proc filesystem (runtime fallback)
+- **libbpf-rs** (Linux) - eBPF program loading and management
+- **landlock** (Linux) - Filesystem and network sandboxing
+- **caps** (Linux) - Linux capability management
+- **windows** (Windows) - Windows API bindings for IP Helper API
+
+### Utilities
+
+- **arboard** - Clipboard access for copying addresses
+- **chrono** - Date and time handling
+- **ring** - Cryptographic operations (for TLS/SNI parsing)
+- **aes** - AES encryption support (for protocol detection)
+- **flate2** - Gzip decompression (for compressed embedded data)
+- **libc** - Low-level C bindings
+
+## Embedded Data Files
+
+RustNet embeds static lookup databases at compile time, avoiding runtime file dependencies. Both follow the same pattern: embed the file, parse into a `HashMap` at startup, expose a `lookup()` method.
+
+### Service Lookup (`crates/rustnet-core/assets/services`)
+
+Port-to-service-name mappings (e.g., 80/tcp -> http). Loaded by `ServiceLookup` in `crates/rustnet-core/src/network/services.rs` using `include_str!`.
+
+### OUI Vendor Database (`crates/rustnet-core/assets/oui.gz`)
+
+IEEE MA-L OUI prefix-to-vendor mappings for MAC address vendor resolution (e.g., `00:1B:63` -> Apple). Gzip-compressed to reduce binary size (~400KB compressed vs ~1.2MB raw). Decompressed at startup by `OuiLookup` in `crates/rustnet-core/src/network/oui.rs` using `include_bytes!` + `flate2`. Currently used for ARP connections only.
+
+A GitHub Action (`.github/workflows/update-oui.yml`) updates this file monthly from the [IEEE public database](https://standards-oui.ieee.org/oui/oui.txt) and opens a PR if there are changes.
+
+## Security
+
+For security documentation including Landlock sandboxing, privilege requirements, and threat model, see [SECURITY.md](SECURITY.md).
+
+## Comparison with Similar Tools
+
+Network monitoring tools exist on a spectrum from simple connection listing to full packet forensics:
+
+```
+Simple ←─────────────────────────────────────────────────────→ Complex
+
+netstat iftop bandwhich RustNet tcpdump Wireshark
+ │ │ │ │ │ │
+ └── Socket ┴── Bandwidth ──────────┴── Live DPI ┴── Capture ──┴── Forensics
+ state monitoring + Process & CLI & Deep
+ tracking Analysis
+```
+
+**RustNet's position**: Real-time connection monitoring with DPI and process identification - more capable than bandwidth monitors, more focused than forensic capture tools.
+
+### Feature Comparison
+
+| Feature | RustNet | bandwhich | sniffnet | iftop | netstat | ss | tcpdump/wireshark |
+|---------|---------|-----------|----------|-------|---------|-----|-------------------|
+| **Language** | Rust | Rust | Rust | C | C | C | C |
+| **Interface** | TUI | TUI | GUI | TUI | CLI | CLI | CLI/GUI |
+| **Real-time monitoring** | Yes | Yes | Yes | Yes | Snapshot | Snapshot | Yes |
+| **Process identification** | Yes | Yes | No | No | Yes | Yes | No |
+| **Deep Packet Inspection** | Yes | No | No | No | No | No | Yes |
+| **SNI/Host extraction** | Yes | No | No | No | No | No | Yes |
+| **Protocol state tracking** | Yes | No | Partial | No | Yes | Yes | Yes |
+| **Bandwidth per connection** | Yes | Yes | Yes | Yes | No | No | No |
+| **Connection filtering** | Yes | No | Yes | Yes | No | Yes | Yes (BPF) |
+| **DNS reverse lookup** | Yes | Yes | Yes | Yes | No | No | Yes |
+| **GeoIP lookup** | Yes | No | Yes | No | No | No | Yes |
+| **Notifications** | No | No | Yes | No | No | No | No |
+| **i18n (translations)** | No | No | Yes | No | No | No | No |
+| **Cross-platform** | Linux, macOS, Windows, FreeBSD | Linux, macOS | Linux, macOS, Windows | Linux, macOS, BSD | All | Linux | All |
+| **eBPF support** | Yes (Linux) | No | No | No | No | Yes | No |
+| **Landlock sandboxing** | Yes (Linux) | No | No | No | No | No | No |
+| **JSON event logging** | Yes | No | No | No | No | No | Yes |
+| **PCAP export** | Yes (+ process sidecar / annotated PCAPNG) | No | Yes | No | No | No | Yes |
+| **Packet capture** | libpcap | Raw sockets | libpcap | libpcap | Kernel | Kernel | libpcap |
+
+### Tool Focus Areas
+
+- **RustNet**: Real-time connection monitoring with DPI, protocol state tracking, and process identification in a TUI
+- **bandwhich**: Bandwidth utilization by process/connection with minimal overhead
+- **sniffnet**: Network traffic analysis with a graphical interface and notifications
+- **iftop**: Interface bandwidth monitoring with per-host traffic display
+- **netstat/ss**: System socket and connection state inspection (ss is the modern replacement for netstat on Linux)
+- **tcpdump/wireshark/tshark**: Full packet capture and protocol analysis for deep debugging
+
+### Choosing the Right Tool
+
+| Your Goal | Best Tool |
+|-----------|-----------|
+| See which process is making a connection | RustNet |
+| Decode packets byte-by-byte | Wireshark |
+| Monitor connection states (SYN_SENT, ESTABLISHED, etc.) | RustNet |
+| Extract files or credentials from traffic | Wireshark |
+| Attribute network activity to specific applications | RustNet |
+| Deep protocol dissection (3000+ protocols) | Wireshark |
+| Quick terminal-based network overview | RustNet |
+| Save captures with process attribution | RustNet (`--pcap-export` or `--pcapng-export`) |
+| Save captures for deep analysis | Wireshark/tcpdump |
+
+### RustNet and Wireshark: Different Strengths
+
+The key difference: **RustNet knows which process owns each connection. Wireshark cannot.**
+
+Wireshark operates at the packet capture layer (libpcap) - it sees raw network traffic but has no visibility into which application created it. RustNet combines packet capture with OS-level socket introspection (via eBPF on Linux, /proc, or platform APIs) to attribute every connection to its owning process.
+
+| Capability | RustNet | Wireshark |
+|------------|---------|-----------|
+| Process identification | Yes (eBPF, procfs, platform APIs) | No |
+| Connection state tracking | Native (TCP FSM, QUIC states) | Via dissectors |
+| Protocol dissectors | ~15 common protocols | 3000+ protocols |
+| Packet-level inspection | Metadata only | Full payload |
+| Interface | TUI (terminal) | GUI |
+| Capture to file | Yes (`--pcap-export`) | Yes (native) |
+
+Both tools can run in real-time. Choose based on what you need to see:
+- **"What is making this connection?"** → RustNet
+- **"What's inside this packet?"** → Wireshark
+
+### Bridging the Gap: PCAP Export with Process Attribution
+
+RustNet can now export packet captures while preserving process attribution - something neither tcpdump nor Wireshark can do alone:
+
+```bash
+# Capture packets with RustNet (includes process tracking)
+sudo rustnet -i eth0 --pcap-export capture.pcap
+
+# Creates:
+# capture.pcap - Standard PCAP file
+# capture.pcap.connections.jsonl - Process attribution (PID, name, timestamps)
+
+# Or write an annotated PCAPNG directly during live capture
+sudo rustnet -i eth0 --pcapng-export annotated.pcapng
+
+# Or enrich a classic PCAP after capture
+python scripts/pcap_enrich.py capture.pcap -o enriched.pcapng
+
+# Open in Wireshark - packets now show process info in comments
+wireshark annotated.pcapng
+```
+
+This workflow gives you the best of both worlds:
+- **RustNet's process attribution**: Know which application generated each packet
+- **Wireshark's deep analysis**: Full protocol dissection with 3000+ analyzers
+
+Native PCAPNG export embeds live best-effort packet comments directly. The enrichment script remains useful when cleanup-time sidecar metadata completeness is more important than producing a single file during capture.
+
+See [USAGE.md - PCAP Export](USAGE.md#pcap-export) for detailed documentation.
diff --git a/ARCHITECTURE.zh-CN.md b/ARCHITECTURE.zh-CN.md
new file mode 100644
index 0000000..ca4685e
--- /dev/null
+++ b/ARCHITECTURE.zh-CN.md
@@ -0,0 +1,490 @@
+English | 简体中文
+
+# 架构
+
+本文档描述 RustNet 的技术架构和实现细节。
+
+## 目录
+
+- [Crate 结构](#crate-structure)
+- [多线程架构](#multi-threaded-architecture)
+- [核心组件](#key-components)
+- [平台特定实现](#platform-specific-implementations)
+- [性能考量](#performance-considerations)
+- [依赖项](#dependencies)
+- [安全](#security)
+
+## Crate 结构
+
+RustNet 是一个由四个 crate 组成的 Cargo 工作区。分析逻辑、捕获后端和进程归属各自位于独立的可复用库 crate 中;二进制 crate 将它们组合成 TUI 应用。
+
+| Crate | 类型 | 职责 |
+| --- | --- | --- |
+| [`rustnet-core`](crates/rustnet-core) | 库 | 与平台和捕获无关的分析核心:数据包解析、协议/连接类型、深度包检测、链路层解析器、连接合并、DNS/GeoIP/OUI 查找,以及供无界面工具使用的可复用 `ConnectionTracker`(实时连接表 + RTT + QUIC 合并 + 生命周期)。仅操作字节切片和已解析的结构 —— 不依赖 libpcap、原始套接字或操作系统进程表。 |
+| [`rustnet-capture`](crates/rustnet-capture) | 库 | 基于 libpcap/Npcap 的数据包捕获后端:设备选择、BPF 过滤器、macOS PKTAP、TUN/TAP,以及原始帧 `PacketReader`。 |
+| [`rustnet-host`](crates/rustnet-host) | 库 | 单一 `ProcessLookup` trait 背后的按连接进程归属:Linux 上的 eBPF/procfs、macOS 上的 PKTAP/lsof、Windows 上的 IP Helper API,以及 FreeBSD 上的 `sockstat`。负责 eBPF 构建工具链及内置的 `vmlinux.h`。 |
+| `rustnet-monitor`(二进制 `rustnet`) | 二进制 | 面向用户的应用:CLI、TUI、应用事件循环、沙箱(Landlock/Seatbelt)和接口统计。以 `ConnectionTracker` 作为唯一数据来源(dogfooding)。 |
+
+包名为 `rustnet-monitor`,因为 `rustnet` 这个 crate 名称在 crates.io 上已被占用;安装后的二进制文件名为 `rustnet`。
+
+### 依赖关系图
+
+```mermaid
+flowchart TD
+ BIN[rustnet-monitor 二进制: rustnet]
+ CAP[rustnet-capture]
+ HOST[rustnet-host]
+ CORE[rustnet-core]
+
+ BIN --> CAP
+ BIN --> HOST
+ BIN --> CORE
+ CAP --> CORE
+ HOST --> CORE
+```
+
+依赖关系是无环的:`rustnet-core` 没有任何工作区内依赖,`rustnet-capture` 和 `rustnet-host` 都仅依赖它。让 `rustnet-core` 保持为叶子节点,使其可以独立发布和复用 —— 无界面的前端(例如 Prometheus 导出器)可以仅组合 `rustnet-capture` + `rustnet-core` 而无需 TUI。
+
+### 重导出门面
+
+为了将拆分对二进制内部保持透明,`src/network/mod.rs` 重导出 `rustnet_core::network::*` 和 `rustnet_capture`(作为 `capture`),因此现有的 `crate::network::*` 路径、集成测试和基准测试无需改动即可编译。`src/network/platform` 模块仍承载操作系统沙箱(Landlock/Seatbelt)和接口统计采集器,并接入 `rustnet-host` 的进程查找。
+
+## 多线程架构
+
+RustNet 采用多线程架构以实现高效的数据包处理:
+
+```mermaid
+flowchart LR
+ PC[数据包捕获 libpcap]
+ CH([Crossbeam Channel])
+ PP[数据包处理器 线程 0..N]
+ PE[进程信息补全 平台 API]
+ DM[(DashMap)]
+ SP[快照提供器]
+ UI[/RwLock<Vec<Connection>> 供 UI 使用/]
+ CT[清理线程]
+
+ PC -- 数据包 --> CH --> PP --> DM
+ PE --> DM
+ DM --> SP --> UI
+ DM --> CT
+```
+
+## 核心组件
+
+### 1. 数据包捕获线程
+
+使用 libpcap 从网络接口捕获原始数据包。该线程独立运行,将数据包送入 Crossbeam channel 进行处理。
+
+**职责:**
+- 打开网络接口进行数据包捕获(非混杂、只读模式)
+- 按需应用 BPF 过滤器
+- 捕获原始数据包
+- 如果启用了 `--pcap-export`,将数据包流式写入 PCAP 文件(直接磁盘写入,无内存缓冲)
+- 如果启用了 `--pcapng-export`,将解析后的数据包送入带注释 PCAPNG writer(有界 best-effort 队列)
+- 将数据包发送到处理队列
+
+### 2. 数据包处理器
+
+多个工作线程(默认最多 4 个,基于 CPU 核心数)解析数据包并执行深度包检测(DPI)分析。
+
+**职责:**
+- 解析 Ethernet、IP、TCP、UDP、ICMP、ARP 头部
+- 提取连接五元组(协议、源 IP、源端口、目的 IP、目的端口)
+- 执行 DPI 检测应用协议:
+ - 带主机信息的 HTTP
+ - 带 SNI(Server Name Indication)的 HTTPS/TLS
+ - DNS 查询和响应
+ - 带版本检测的 SSH 连接
+ - 带命令、响应代码、用户名、服务器软件和系统类型的 FTP 控制通道
+ - 带 CONNECTION_CLOSE 帧检测的 QUIC 协议
+ - 带报文类型、版本和客户端标识符的 MQTT
+ - BitTorrent 握手和 DHT 消息
+ - 用于 WebRTC 和 NAT 穿越的 STUN
+ - 带版本、模式和 stratum 的 NTP
+ - 用于本地名称解析的 mDNS 和 LLMNR
+ - 带消息类型和主机名的 DHCP
+ - 带 PDU 类型的 SNMP(v1、v2c、v3)
+ - 用于 UPnP 设备发现的 SSDP
+ - NetBIOS 名称服务和数据报服务
+- 追踪连接状态和生命周期
+- 在 DashMap 中更新连接元数据
+- 计算带宽指标
+
+### 3. 进程信息补全
+
+使用平台特定的 API 将网络连接与运行中的进程关联。该组件定期运行,为连接数据补充进程信息。
+
+**职责:**
+- 将 socket inode 映射到进程 ID
+- 解析进程名和命令行
+- 用进程信息更新连接记录
+- 处理与权限相关的回退
+
+各平台的详情见[平台特定实现](#platform-specific-implementations)。
+
+### 4. 快照提供器
+
+按固定间隔(默认 1 秒)创建连接数据的一致快照供 UI 使用。这确保 UI 拥有稳定的连接视图,避免竞争条件。
+
+**职责:**
+- 按配置间隔从 DashMap 读取
+- 根据用户条件应用过滤(localhost 等)
+- 按用户选择的列排序连接
+- 为 UI 渲染创建不可变快照
+- 为 UI 线程提供 RwLock 保护的 Vec
+
+### 5. 清理线程
+
+使用智能的、协议感知的超时机制移除不活跃的连接。这防止内存泄漏并保持连接列表的相关性。当启用 `--pcap-export` 时,连接关闭时还会将连接元数据(PID、进程名、时间戳)流式写入 JSONL sidecar 文件。
+
+**超时策略:**
+
+#### TCP 连接
+- **HTTP/HTTPS**(通过 DPI 检测):**10 分钟** —— 支持 HTTP keep-alive
+- **SSH**(通过 DPI 检测):**30 分钟** —— 适应长交互会话
+- **活跃已建立**(< 1 分钟空闲):**10 分钟**
+- **空闲已建立**(> 1 分钟空闲):**5 分钟**
+- **TIME_WAIT**:30 秒 —— 标准 TCP 超时
+- **CLOSED**:5 秒 —— 快速清理
+- **SYN_SENT、FIN_WAIT 等**:30-60 秒
+
+#### UDP 连接
+- **HTTP/3(带 HTTP 的 QUIC)**:**10 分钟** —— 连接复用
+- **HTTPS/3(带 HTTPS 的 QUIC)**:**10 分钟** —— 连接复用
+- **基于 UDP 的 SSH**:**30 分钟** —— 长会话
+- **DNS**:**30 秒** —— 短查询
+- **普通 UDP**:**60 秒** —— 标准超时
+
+#### QUIC 连接(检测到的状态)
+- **已连接**:默认 3 分钟,或当存在对端的 `max_idle_timeout` 传输参数时使用该值
+- **带 CONNECTION_CLOSE 帧**:1-10 秒(基于关闭类型)
+- **Initial/Handshaking**:60 秒 —— 允许连接建立
+- **Draining**:10 秒 —— RFC 9000 draining 周期
+- **Closed**:1 秒 —— 立即清理
+
+**视觉陈旧度指示器:**
+
+连接根据距离超时的远近改变颜色:
+- **白色**(默认):< 75% 的超时时间
+- **黄色**:75-90% 的超时时间(警告)
+- **红色**:> 90% 的超时时间(严重)
+
+### 6. 速率刷新线程
+
+每秒更新带宽计算,采用温和衰减。这提供平滑的带宽可视化,避免突变。
+
+**职责:**
+- 计算下载和上传的字节/秒
+- 对旧测量值应用指数衰减
+- 更新可视化带宽指示器
+- 维护包速率的滚动窗口
+
+### 7. DashMap
+
+并发哈希表(`DashMap`)用于存储连接状态。这种无锁数据结构支持来自多个线程的高效并发访问。
+
+**关键特性:**
+- 细粒度锁定(per-shard)
+- 无全局锁竞争
+- 安全的并发读写
+- 高并发负载下的高性能
+
+## 平台特定实现
+
+### 进程查找
+
+RustNet 使用平台特定的 API 将网络连接与进程关联:
+
+#### Linux
+
+**标准模式(procfs):**
+- 解析 `/proc/net/tcp` 和 `/proc/net/udp` 获取 socket inode
+- 遍历 `/proc//fd/` 查找 socket 文件描述符
+- 将 inode 映射到进程 ID,并从 `/proc//cmdline` 解析进程名
+
+**eBPF 模式(Linux 默认):**
+- 使用附加到 socket 系统调用的内核 eBPF 程序
+- 捕获带进程上下文的 socket 创建事件
+- 比 procfs 扫描开销更低
+- **局限性:**
+ - 进程名限制为 16 个字符(内核 `comm` 字段)
+ - 可能显示线程名而非完整可执行文件名
+ - 多线程应用显示内部线程名
+- **Linux capabilities 需求:**
+ - 现代 Linux(5.8+):`CAP_NET_RAW`(包捕获)、`CAP_BPF`、`CAP_PERFMON`(eBPF)
+ - 旧版 Linux(pre-5.8):eBPF 需要宽泛的 `CAP_SYS_ADMIN`;RustNet 安装包不会自动授予它,并会回退到 procfs
+ - 注意:不需要 CAP_NET_ADMIN(使用只读、非混杂包捕获)
+
+**回退行为:**
+- 如果 eBPF 加载失败(权限、内核兼容性),自动回退到 procfs 模式
+- TUI 统计面板显示当前使用的检测方法
+
+#### macOS
+
+**PKTAP 模式(使用 sudo 时):**
+- 使用 PKTAP(Packet Tap)内核接口
+- 直接从包元数据提取进程信息
+- 需要 root 特权(特权内核接口)
+- 比 lsof 更快更准确
+
+**lsof 模式(无 sudo 或回退时):**
+- 使用 `lsof -i -n -P` 列出网络连接
+- 解析输出以将 socket 与进程关联
+- CPU 开销更高,但无需 root
+- 当 PKTAP 不可用时自动使用
+
+**检测:**
+- TUI 统计面板根据当前方法显示 "pktap" 或 "lsof"
+- 自动选择最佳可用方法
+
+#### Windows
+
+**IP Helper API:**
+- 使用 Windows IP Helper API 的 `GetExtendedTcpTable` 和 `GetExtendedUdpTable`
+- 检索带进程 ID 的连接表
+- 支持 IPv4 和 IPv6 连接
+- 使用 `OpenProcess` 和 `QueryFullProcessImageNameW` 解析进程名
+
+**需求:**
+- 根据系统配置可能需要 Administrator 特权
+- 包捕获需要 Npcap 或 WinPcap
+
+### 网络接口
+
+该工具使用平台特定的方法自动检测和列出可用网络接口:
+
+- **Linux**:使用 `netlink` 或回退到 `/sys/class/net/`
+- **macOS**:使用 `getifaddrs()` 系统调用
+- **Windows**:使用 IP Helper API 的 `GetAdaptersInfo()`
+- **所有平台**:当原生方法失败时回退到 pcap 的 `pcap_findalldevs()`
+
+## 性能考量
+
+### 多线程处理
+
+数据包处理分布在多个线程上(默认最多 4 个,基于 CPU 核心数)。这实现了:
+- 并行数据包解析和 DPI 分析
+- 更好地利用多核系统
+- 降低高包速率下的延迟
+
+### 并发数据结构
+
+**DashMap** 提供无锁并发访问,具备:
+- Per-shard 锁定(默认 16 个 shard)
+- 无全局锁竞争
+- 读密集型工作负载优化
+- 安全的并发修改
+
+### 批处理
+
+数据包以批次处理以提高缓存效率:
+- 上下文切换前处理多个数据包
+- 减少系统调用开销
+- 更好的 CPU 缓存利用率
+
+### 选择性 DPI
+
+可以使用 `--no-dpi` 禁用深度包检测以降低开销:
+- 在高流量网络中降低 20-40% 的 CPU 使用率
+- 仍然追踪基本连接信息
+- 适用于性能受限的环境
+
+### 可配置间隔
+
+根据需求调整刷新率:
+- **UI 刷新**:默认 1000ms(可通过 `--refresh-interval` 调整)
+- **进程补全**:每 2 秒
+- **清理检查**:每 5 秒
+- **速率计算**:每 1 秒
+
+### 内存管理
+
+**连接清理**防止内存无界增长:
+- 协议感知的超时移除陈旧连接
+- 移除前的视觉陈旧度警告
+- 可配置的超时阈值
+
+**快照隔离**防止 UI 阻塞:
+- UI 从不可变快照读取
+- 后台线程并发更新 DashMap
+- UI 和数据包处理之间无锁竞争
+
+## 依赖项
+
+RustNet 使用以下关键依赖构建:
+
+### 核心依赖
+
+- **ratatui** —— 终端用户界面框架,带完整控件支持
+- **crossterm** —— 跨平台终端操作
+- **pcap** —— libpcap/Npcap 的包捕获库绑定
+- **pnet_datalink** —— 网络接口枚举和低层网络
+
+### 并发与线程
+
+- **dashmap** —— 细粒度锁定的并发哈希表
+- **crossbeam** —— 多线程工具和无锁 channel
+
+### 网络与协议
+
+- **dns-lookup** —— DNS 解析能力
+- **maxminddb** —— GeoIP 数据库查询(GeoLite2)
+
+### 序列化
+
+- **serde** / **serde_json** —— 事件日志和 PCAP sidecar 的 JSON 序列化
+
+### 命令行与日志
+
+- **clap** —— 带 derive 特性的命令行参数解析
+- **simplelog** —— 灵活的日志框架
+- **log** —— 日志门面
+- **anyhow** —— 错误处理和上下文
+
+### 平台特定
+
+- **procfs**(Linux)— 从 /proc 文件系统获取进程信息(运行时回退)
+- **libbpf-rs**(Linux)— eBPF 程序加载和管理
+- **landlock**(Linux)— 文件系统和网络沙箱
+- **caps**(Linux)— Linux capabilities 管理
+- **windows**(Windows)— IP Helper API 的 Windows API 绑定
+
+### 工具库
+
+- **arboard** —— 复制地址的剪贴板访问
+- **chrono** —— 日期和时间处理
+- **ring** —— 加密操作(用于 TLS/SNI 解析)
+- **aes** —— AES 加密支持(用于协议检测)
+- **flate2** —— Gzip 解压(用于压缩的嵌入式数据)
+- **libc** —— 低层 C 绑定
+
+## 嵌入式数据文件
+
+RustNet 在编译时嵌入静态查找数据库,避免运行时文件依赖。两者遵循相同的模式:嵌入文件,启动时解析为 `HashMap`,暴露 `lookup()` 方法。
+
+### 服务查找(`crates/rustnet-core/assets/services`)
+
+端口到服务名的映射(例如 80/tcp -> http)。由 `crates/rustnet-core/src/network/services.rs` 中的 `ServiceLookup` 使用 `include_str!` 加载。
+
+### OUI 厂商数据库(`crates/rustnet-core/assets/oui.gz`)
+
+IEEE MA-L OUI 前缀到厂商的映射,用于 MAC 地址厂商解析(例如 `00:1B:63` -> Apple)。Gzip 压缩以减小二进制体积(压缩后约 400KB,原始约 1.2MB)。由 `crates/rustnet-core/src/network/oui.rs` 中的 `OuiLookup` 使用 `include_bytes!` + `flate2` 在启动时解压。
+
+GitHub Action(`.github/workflows/update-oui.yml`)每月从 [IEEE 公开数据库](https://standards-oui.ieee.org/oui/oui.txt) 更新此文件,如有变更则自动打开 PR。
+
+## 安全
+
+关于 Landlock 沙箱、权限需求和威胁模型的安全文档,参见 [SECURITY.zh-CN.md](SECURITY.zh-CN.md)。
+
+## 与同类工具的对比
+
+网络监控工具存在于从简单连接列表到完整数据包取证的光谱上:
+
+```
+简单 ←─────────────────────────────────────────────────────→ 复杂
+
+netstat iftop bandwhich RustNet tcpdump Wireshark
+ │ │ │ │ │ │
+ └── Socket ┴── Bandwidth ──────────┴── Live DPI ┴── Capture ──┴── Forensics
+ state monitoring + Process & CLI & Deep
+ tracking Analysis
+```
+
+**RustNet 的定位**:实时连接监控,带 DPI 和进程识别——比带宽监控器功能更强,比取证捕获工具更聚焦。
+
+### 功能对比
+
+| 功能 | RustNet | bandwhich | sniffnet | iftop | netstat | ss | tcpdump/wireshark |
+|---------|---------|-----------|----------|-------|---------|-----|-------------------|
+| **语言** | Rust | Rust | Rust | C | C | C | C |
+| **界面** | TUI | TUI | GUI | TUI | CLI | CLI | CLI/GUI |
+| **实时监控** | 是 | 是 | 是 | 是 | 快照 | 快照 | 是 |
+| **进程识别** | 是 | 是 | 否 | 否 | 是 | 是 | 否 |
+| **深度包检测** | 是 | 否 | 否 | 否 | 否 | 否 | 是 |
+| **SNI/主机提取** | 是 | 否 | 否 | 否 | 否 | 否 | 是 |
+| **协议状态追踪** | 是 | 否 | 部分 | 否 | 是 | 是 | 是 |
+| **逐连接带宽** | 是 | 是 | 是 | 是 | 否 | 否 | 否 |
+| **连接过滤** | 是 | 否 | 是 | 是 | 否 | 是 | 是(BPF) |
+| **DNS 反向解析** | 是 | 是 | 是 | 是 | 否 | 否 | 是 |
+| **GeoIP 查询** | 是 | 否 | 是 | 否 | 否 | 否 | 是 |
+| **通知** | 否 | 否 | 是 | 否 | 否 | 否 | 否 |
+| **i18n(翻译)** | 否 | 否 | 是 | 否 | 否 | 否 | 否 |
+| **跨平台** | Linux、macOS、Windows、FreeBSD | Linux、macOS | Linux、macOS、Windows | Linux、macOS、BSD | 全部 | Linux | 全部 |
+| **eBPF 支持** | 是(Linux) | 否 | 否 | 否 | 否 | 是 | 否 |
+| **Landlock 沙箱** | 是(Linux) | 否 | 否 | 否 | 否 | 否 | 否 |
+| **JSON 事件日志** | 是 | 否 | 否 | 否 | 否 | 否 | 是 |
+| **PCAP 导出** | 是(PCAP + sidecar,或带注释 PCAPNG) | 否 | 是 | 否 | 否 | 否 | 是 |
+| **数据包捕获** | libpcap | Raw sockets | libpcap | libpcap | Kernel | Kernel | libpcap |
+
+### 工具聚焦领域
+
+- **RustNet**:TUI 中的实时连接监控,带 DPI、协议状态追踪和进程识别
+- **bandwhich**:按进程/连接的带宽利用率,开销最小
+- **sniffnet**:带图形界面和通知的网络流量分析
+- **iftop**:带逐主机流量显示的接口带宽监控
+- **netstat/ss**:系统 socket 和连接状态检查(ss 是 Linux 上 netstat 的现代替代品)
+- **tcpdump/wireshark/tshark**:用于深度调试的完整数据包捕获和协议分析
+
+### 如何选择合适的工具
+
+| 你的目标 | 最佳工具 |
+|-----------|-----------|
+| 查看哪个进程正在建立连接 | RustNet |
+| 逐字节解码数据包 | Wireshark |
+| 监控连接状态(SYN_SENT、ESTABLISHED 等) | RustNet |
+| 从流量中提取文件或凭据 | Wireshark |
+| 将网络活动归因于特定应用 | RustNet |
+| 深度协议解析(3000+ 协议) | Wireshark |
+| 快速终端网络概览 | RustNet |
+| 保存带进程归因的捕获 | RustNet(`--pcap-export` 或 `--pcapng-export`) |
+| 保存捕获用于深度分析 | Wireshark/tcpdump |
+
+### RustNet 与 Wireshark:不同的强项
+
+关键区别:**RustNet 知道每个连接属于哪个进程。Wireshark 不知道。**
+
+Wireshark 在数据包捕获层(libpcap)运行——它看到原始网络流量,但不知道哪个应用创建了它。RustNet 将数据包捕获与 OS 级 socket 内省(通过 Linux eBPF、/proc 或平台 API)相结合,将每个连接归因于其所属进程。
+
+| 能力 | RustNet | Wireshark |
+|------------|---------|-----------|
+| 进程识别 | 是(eBPF、procfs、平台 API) | 否 |
+| 连接状态追踪 | 原生(TCP FSM、QUIC 状态) | 通过解析器 |
+| 协议解析器 | ~15 个常见协议 | 3000+ 协议 |
+| 数据包级检查 | 仅元数据 | 完整 payload |
+| 界面 | TUI(终端) | GUI |
+| 捕获到文件 | 是(`--pcap-export`、`--pcapng-export`) | 是(原生) |
+
+两种工具都可以实时运行。根据你想看什么来选择:
+- **"是什么在发起这个连接?"** → RustNet
+- **"这个数据包里有什么?"** → Wireshark
+
+### 弥合差距:带进程归因的 PCAP 导出
+
+RustNet 现在可以在保留进程归因的同时导出数据包捕获——这是 tcpdump 和 Wireshark 单独都无法做到的:
+
+```bash
+# 使用 RustNet 捕获数据包(包含进程追踪)
+sudo rustnet -i eth0 --pcap-export capture.pcap
+
+# 创建:
+# capture.pcap - 标准 PCAP 文件
+# capture.pcap.connections.jsonl - 进程归因(PID、名称、时间戳)
+
+# 用进程信息富化 PCAP 并创建注释过的 PCAPNG
+python scripts/pcap_enrich.py capture.pcap -o annotated.pcapng
+
+# 或者直接写出带 RustNet 数据包注释的 PCAPNG
+sudo rustnet -i eth0 --pcapng-export annotated.pcapng
+
+# 在 Wireshark 中打开 —— 数据包现在显示进程信息注释
+wireshark annotated.pcapng
+```
+
+这个工作流让你兼得两者之长:
+- **RustNet 的进程归因**:知道哪个应用生成了每个数据包
+- **Wireshark 的深度分析**:3000+ 解析器的完整协议解析
+
+原生 PCAPNG 导出会直接嵌入实时 best-effort 数据包注释。富化脚本在清理阶段 sidecar 元数据完整性比捕获时生成单个文件更重要时仍然有用。
+
+详见 [USAGE.zh-CN.md - PCAP 导出](USAGE.zh-CN.md#pcap-export)。
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..d39f0c2
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,601 @@
+# Changelog
+
+All notable changes to RustNet will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+## [1.4.0] - 2026-06-16
+
+This release redesigns the TUI around a calmer visual hierarchy and, under the hood,
+splits RustNet into a Cargo workspace of reusable library crates. Many of the TUI ideas
+came from a detailed UI review by [@joshka](https://github.com/joshka) (Ratatui
+maintainer) on our showcase submission
+([ratatui/ratatui-website#1118](https://github.com/ratatui/ratatui-website/pull/1118)) —
+thanks for the thoughtful feedback!
+
+### Added
+- **Theme Presets**: New `--theme` flag. The default `muted` preset keeps a single
+ cyan accent and reserves color for signals (state changes, staleness, live
+ bandwidth) and addresses; `--theme classic` restores the previous full-color palette (#377)
+- **System Sidebar Toggle**: The System panel now has a fixed width and can be
+ hidden with the `i` key (auto-hidden on narrow terminals) (#377)
+- **Details Continuity Strip**: The Details tab opens with a mini connection table
+ of the selected row and its neighbors; `j`/`k` flips through them without leaving
+ the tab, following the grouped order when process grouping is enabled (#377)
+- **Direct-Jump Tab Shortcuts**: Jump straight to a tab with keys `1`-`5`, with
+ bracket cycle aliases (#318, thanks @obchain)
+- **Connection List Scrollbar**: A scrollbar appears on the connection list when it
+ overflows the viewport (#365)
+- **FTP Deep Packet Inspection**: Detect the FTP control channel and extract command
+ and response metadata (#266, thanks @0xghost42)
+- **DNS / mDNS / LLMNR Response IPs**: Populate `response_ips` from A/AAAA answer
+ records and extend the extraction to mDNS and LLMNR responses (#319, #333, #341, thanks @0xghost42)
+- **Log Identity Banner**: Emit a program identity banner and the module target on
+ every log line for easier diagnostics (#320, thanks @0xghost42)
+- **Landlock v6 IPC Scoping** (Linux): Best-effort Landlock that scopes abstract-socket
+ and signal IPC on kernels that support it, falling back gracefully on older ABIs (#363)
+- **`no_new_privs` Always Set + cargo-deny**: Always set `no_new_privs` at startup, and
+ adopt `cargo-deny` for supply-chain and license auditing in CI (#382)
+- **openSUSE OBS Release Pipeline**: Automated openSUSE Build Service releases (#356)
+
+### Changed
+- **Stable Column Layout**: Column widths depend only on the terminal width — they
+ no longer shift while scrolling. Narrow terminals hide low-priority columns
+ instead of truncating cells; wide terminals distribute the spare width so the
+ table spans the full screen with the bandwidth column flush right (#377)
+- **Merged Proto/App Column**: The Protocol column is merged into Application
+ ("TCP·HTTPS"), and the status-dot column is gone — staleness now lives entirely
+ in the row styling (#377)
+- **Custom Tab Bar and Borderless Sections**: Numbered tab bar with an accent
+ underline, a single-line filter prompt, and section headers in place of the
+ border-box-around-everything look (#377)
+- **Dependencies**: Routine dependency and GitHub Actions updates across the cycle
+ (Dependabot, ~18 PRs)
+
+### Fixed
+- **Process attribution for short-lived and multithreaded processes** (Linux):
+ eBPF socket tracking now records the process name (thread-group leader)
+ instead of the calling thread's name, so connections from e.g. firefox or dig
+ no longer show up as "Socket Thread" or "isc-net-0000"; PID-to-name
+ resolution reads `/proc//comm` on demand instead of waiting for the
+ periodic scan; and new connections are enriched on a fast 250ms tick, so
+ process names appear almost immediately instead of after up to 2 seconds (#376)
+- **DLT_NULL Link Layer**: Strip the 4-byte address-family header before parsing
+ DLT_NULL/loopback captures (#394, thanks @0xghost42)
+- **Terminal Restore on Panic**: Restore the terminal via a chained panic hook so a
+ panic no longer leaves the terminal in raw mode (#364)
+- **Scrollbar Thumb**: The scrollbar thumb now reaches the bottom at max scroll (#366)
+- **Landlock `/sys` Access**: Allow read access to `/sys` so interface statistics work
+ under the Landlock sandbox (#370)
+- **Filter Mode Backspace**: Handle raw backspace characters in filter mode (#335, thanks @iccccccccccccc)
+- **eBPF Error Surfacing**: Classify libbpf errors and surface them in the TUI (#255, #258)
+- **Native Builds**: Skip cross-compile library paths on native builds (#259)
+- **RPM Packaging**: Own the directories and hicolor icon dirs the package creates, and
+ require `libcap-progs` on openSUSE so the `%post` `setcap` runs (#357, #358, #359, #360)
+
+### Performance
+- **Per-Packet Allocations**: Cut per-packet allocations and snapshot copy-on-write
+ copies on the hot path (#380)
+- **Core Types**: Add `Protocol::as_str()` and drop per-row/per-filter `to_string`
+ allocations (#392, thanks @obchain)
+- **Connection Table**: Borrow the process name in `process_text` instead of cloning (#390, thanks @obchain)
+- **Sparklines / Parsers**: Single-allocation sparkline getters, fewer redundant
+ collects in the HTTP and SSH parsers, and removed redundant clones in the render path
+ and sandbox init (#339, #345, #355, thanks @obchain)
+
+### Internal
+- **Cargo Workspace Split**: RustNet is now a four-crate workspace — `rustnet-core`
+ (packet parsing, protocol/DPI types, link-layer, connection merging, DNS/GeoIP/OUI
+ lookups), `rustnet-capture` (libpcap/Npcap capture backend), `rustnet-host`
+ (per-connection process attribution), and the `rustnet-monitor` binary. The three
+ libraries are now published to crates.io alongside the binary (#367)
+
+### Documentation
+- **Simplified Chinese**: Added a Simplified Chinese README translation and translated
+ the rest of the docs, plus zh-CN openSUSE Tumbleweed install instructions
+ (#263 thanks @whtis, #277 thanks @luojiyin1987, #361)
+- **Install / Packaging Docs**: Nix and NixOS instructions and nixpkgs/NixOS-module
+ notes, Homebrew core formula pointer, Repology packaging overview, a Mermaid
+ architecture diagram, and a PR template with tightened contributor guidelines
+ (#264, #270, #281, #285, #286, #311, #332, #369)
+- **Ubuntu 26.04 (Resolute) PPA**: Added the Resolute PPA build (#254, #256)
+
+### Contributors
+
+Special thanks to the contributors in this release:
+- [@0xghost42](https://github.com/0xghost42) — FTP DPI, DNS/mDNS/LLMNR response-IP
+ extraction, the log identity banner, the DLT_NULL fix, and many DPI/eBPF refactors
+ (#266, #278, #279, #289, #290, #307, #309, #319, #320, #333, #341, #394)
+- [@obchain](https://github.com/obchain) — performance and allocation cleanups across
+ the DPI parsers, render path, and core types, plus direct-jump tab shortcuts
+ (#292, #294, #296, #301, #303, #317, #318, #327, #339, #345, #355, #390, #392)
+- [@iccccccccccccc](https://github.com/iccccccccccccc) — raw backspace handling in filter mode (#335)
+- [@whtis](https://github.com/whtis) (HaiTao Wu) — Simplified Chinese README translation (#263)
+- [@luojiyin1987](https://github.com/luojiyin1987) (luo jiyin) — Simplified Chinese documentation translation (#277)
+
+## [1.3.0] - 2026-05-05
+
+The headline of this release is a major TUI refresh. The tabs, stats panel, and details view have all been redesigned, with new per-field colors, a status dot, and address scope labels making it easier to read connections at a glance.
+
+### Added
+- **TUI Revamp**: Redesigned tabs, stats panel, and details view (#239)
+- **Per-field Colors and Status Dot**: New per-field colors, status dot, and magenta panel borders for at-a-glance readability (#241)
+- **Address Scope Labels**: Remote addresses are tagged PUBLIC, PRIVATE, etc. in the connection list (#251)
+- **Reverse DNS Resolution by Default**: Reverse DNS resolution is now enabled by default. Use the new `--no-resolve-dns` flag to opt out (#245)
+
+### Fixed
+- **Sandbox Info on Overview**: Show the full sandbox details on the overview tab (#250)
+- **Search Scope and Status Bars**: Scope the `/` search to Overview and tidy the status bars (#229, #230)
+- **QUIC Initial Packet Parser**: Bounds-check `token_len` in the Initial packet parser (#244)
+- **QUIC Varint Parser**: Bounds-check varint lengths and isolate parser panics (#232)
+- **Release Pipeline**: Fix the downstream trigger race and AUR token permissions (#223)
+
+### Changed
+- **Demo Recording Automation**: Automate VHS recording for the demo GIF and README screenshots (#247)
+- **OUI Vendor Database**: Refreshed IEEE OUI vendor database (#242)
+- **Dependencies**: Bumped `rand` (0.8.5 to 0.8.6), `openssl` (0.10.75 to 0.10.78), `zip`, `libbpf-cargo`, and other rust-dependencies and actions group updates (#224, #225, #226, #227, #231, #233, #234, #238, #240, #243)
+
+### Documentation
+- **Windows Sandbox Terminology**: Accurate Windows sandbox terminology and roadmap entry (#237)
+- **README Polish**: README hero polish, metadata tune-up, and accuracy fixes (#236)
+- **Crate and Module Docs**: Expanded crate and module docs and tuned metadata for discoverability (#235)
+
+## [1.2.0] - 2026-04-09
+
+### Added
+- **Windows Restricted Token Sandbox**: Drop privileges at startup on Windows using a restricted process token (#206)
+- **macOS Seatbelt Sandboxing**: Apply a Seatbelt sandbox profile at startup on macOS, later tightened to restrict filesystem and IPC access (#196, #203)
+- **Linux Sandbox Hardening**: Drop Linux capabilities and clear the ambient capability set after startup (#208)
+- **Process Privilege in UI**: Show whether a process is privileged in the security section of the TUI (#197)
+- **Filter: Exact Port Matching and Regex Support**: Filter syntax supports exact port matches and regex patterns (#195)
+- **VLAN Support in PKTAP and SLL/SLL2**: Parse VLAN tags in PKTAP and SLL/SLL2 capture formats (#202)
+- **VLAN Header in Layer 3 Extraction**: Account for VLAN headers when extracting layer 3 data (#199, thanks @deepakpjose)
+- **IGMP Protocol Parsing**: Recognize and parse IGMP traffic (#209, thanks @deepakpjose)
+- **Process Name for Wildcard /proc/net/ Entries**: Resolve process names for wildcard (`0.0.0.0`/`::`) entries in `/proc/net/` (#218, thanks @deepakpjose)
+- **CI Supply-Chain Hardening**: Pin GitHub Actions to commit SHAs and verify Npcap installer checksums (#210)
+- **Architecture Roadmap**: Added workspace split and macOS privilege separation roadmap docs (#211)
+
+### Fixed
+- **Default Interface Selection**: Use the active routing table to pick the default interface (#194, thanks @l1a)
+- **Root Detection on Unix**: Use `geteuid()` instead of `getuid()` to detect root (#192, thanks @DeepChirp)
+- **Release Pipeline Reliability**: Improved release workflow reliability, gated downstream jobs on `publish-release`, added checksum verification to AUR updates, and documented the no-retag policy (2a38f2d, 795f7a1, 002eb55, 8403a0f)
+- **FreeBSD CI Dispatch**: Restrict FreeBSD dispatch to manual triggers only (#201)
+
+### Changed
+- **CPU Efficiency Improvements**: Substantial reductions in CPU usage across hot paths — rate calculation moved from per-update to per-refresh (#220), timeouts avoided to improve CPU performance (#213), threads given meaningful names to aid profiling (#212), and allocations reduced in sorting and snapshot paths (#222). Big thanks to [@deepakpjose](https://github.com/deepakpjose) for driving the CPU-efficiency work (#213, #220, #212) — these changes make RustNet noticeably lighter on the CPU.
+- **FreeBSD Platform Cleanup**: Refactored FreeBSD platform support code (#205)
+- **Dependencies**: Bumped `zip` (8.2.0 → 8.3.0 → 8.5.0), `clap_mangen`, `docker/login-action`, and other rust-dependencies group updates (#198, #200, #214, #216, #219, #221)
+- **OUI Vendor Database**: Refreshed IEEE OUI vendor database (#215)
+
+### Contributors
+
+Special thanks to the external contributors in this release:
+- [@deepakpjose](https://github.com/deepakpjose) — CPU-efficiency improvements and additional features (#199, #209, #212, #213, #218, #220)
+- [@l1a](https://github.com/l1a) — default interface selection via active routing table (#194)
+- [@DeepChirp](https://github.com/DeepChirp) — Unix root detection via `geteuid()` (#192)
+
+## [1.1.0] - 2026-03-16
+
+### Added
+- **OUI Vendor Lookup for ARP**: Display MAC vendor names for ARP connections using IEEE OUI database (#183)
+- **Historic Connections Toggle**: Toggle to show/hide historic (closed) connections (#184)
+- **Mouse Support**: Mouse interaction support for TUI navigation (#170)
+- **Security Hardening & Packet Stats**: Enhanced security hardening and packet statistics display in TUI (#169)
+- **GeoIP City Lookup**: Show city-level geolocation for remote IPs using GeoLite2 City database (#168)
+- **Android Build Support**: Native Android builds with static musl linking (#167)
+- **Multi-Arch Android Builds**: Added armv7, x86_64, and x86 Android static build targets
+- **MQTT Protocol Detection**: Deep packet inspection for MQTT protocol traffic (#161)
+- **STUN Traffic Detection**: Detect STUN protocol traffic per RFC 5389/8489 (#160)
+- **BitTorrent Traffic Detection**: Detect BitTorrent protocol traffic (#159)
+- **ARP Performance Benchmarks**: Added criterion benchmarks for ARP-related operations (#188)
+
+### Fixed
+- **Undefined Behavior Fix**: Fix UB issues, remove clippy suppressions, add safety documentation (#187)
+- **Light Terminal Readability**: Fix selection highlight unreadable on light terminal themes (#182)
+- **Clipboard Warning**: Fix unused variable warning in copy_to_clipboard across platforms (#178)
+- **Android Cross-Compilation**: Fix cross-compilation and release upload issues for Android targets (#174)
+- **MQTT Detection Accuracy**: Restrict MQTT signature detection to CONNECT packets only (#164)
+
+### Changed
+- **Documentation**: Synced docs with implementation, added missing keyboard shortcuts (#190, #157)
+- **CI/CD**: Staged release pipeline so downstream jobs wait for builds (#154), added FreeBSD coverage to PR builds (#158)
+- **Dependencies**: Bumped chrono, http_req, zip, and various rust-dependencies groups
+
+## [1.0.0] - 2026-02-09
+
+### Added
+- **GeoIP Location Support**: Show country codes for remote IPs using GeoLite2 databases with auto-discovery (#151)
+- **PCAP Export with Process Attribution**: Export captured packets to PCAP files with a process attribution JSONL sidecar for Wireshark enrichment (#137)
+- **eBPF-based ICMP PID Tracking**: Track process IDs for ICMP connections using eBPF on Linux (#136)
+- **Process Detection Degradation Warnings**: Show warnings in the UI when process detection falls back to a less accurate method (#128)
+- **ARM64 Musl Static Builds**: CI now produces arm64 musl static Linux builds with eBPF support
+
+### Fixed
+- **Service Name Precedence**: Corrected ordering when multiple service name sources conflict (#150)
+- **Pointer Dereference Safety**: Use `as_ref()` for safer pointer dereference in macOS/FreeBSD interface stats (#147)
+- **Clippy Warnings**: Resolve `unnecessary_unwrap` errors flagged by clippy (#144)
+- **ICMP Dead Code**: Remove dead code warning in ICMP handling (#138)
+- **GitHub Actions Permissions**: Add explicit permissions to all GitHub Actions workflows (#131)
+- **Logging Initialization**: Set up logging level before privileges check for earlier diagnostic output (#143)
+
+### Changed
+- **SSH Heuristic Tightened**: Tighten SSH packet structure heuristic to reduce false positives (#135)
+- **CI Reusable Workflows**: Share build logic via reusable workflow, remove redundant test-static-builds workflow
+- **Chocolatey Automation**: Trigger Chocolatey package publish on release automatically
+- **Code Alignment**: Refactoring and code alignment improvements (#149)
+- **Dependencies**: Updated libbpf-rs to 0.26, bumped clap, time, zip, lru, and libc
+- **Documentation**: Clarified RustNet vs Wireshark positioning, added PowerShell font troubleshooting, added JSON logging to feature comparison, added bandwhich to acknowledgments (#129, #130, #132, #133)
+
+## [0.18.0] - 2026-01-07
+
+### Added
+- **Process Grouping**: Expandable tree view to group connections by process (`a` to toggle grouping, `Space` to expand/collapse)
+- **Traffic Visualization Graph Tab**: New Graph tab with real-time network traffic graphs and bandwidth visualization (press `Tab` to cycle through tabs)
+- **Network Health Visualization**: Health indicators in Graph tab showing connection quality metrics
+- **Reverse DNS Hostnames**: Display reverse DNS names in Details tab and filter PTR traffic (`--resolve-dns` to enable, `d` to toggle display)
+- **BPF Filter Support**: New `--bpf-filter` option for custom packet capture filtering (e.g., `--bpf-filter "port 443"`)
+- **Clear All Connections**: New hotkey (`x`) to clear all tracked connections
+- **Enhanced JSON Logging**: Added pid, process_name, service_name fields to JSON log output
+- **New DPI Protocols**: NTP, mDNS, LLMNR, DHCP, SNMP, SSDP, NetBIOS protocol detection with enhanced ARP display
+- **Static Musl Builds**: Linux static binary builds using musl for better portability
+- **Platform-Specific Help**: CLI help now shows platform-specific options
+
+### Fixed
+- **macOS BPF Filter**: Skip PKTAP when BPF filter is specified to avoid conflicts
+- **Linux Clipboard**: Handle clipboard access blocked by Landlock sandbox gracefully
+- **Interface Stats**: Use safer pointer dereference in interface statistics
+
+### Changed
+- **FreeBSD Builds**: Moved to separate rustnet-bsd repository for native builds
+- **CI Improvements**: Homebrew formula auto-update on release, AUR workflow on publish
+- **Dependencies**: Updated ratatui to 0.30.0, various dependency updates
+- **Documentation**: Added contribution guidelines, Chocolatey and Arch Linux installation instructions
+
+## [0.17.0] - 2025-12-07
+
+### Added
+- **Landlock Sandbox for Linux**: Filesystem and network sandboxing for enhanced security
+ - Restricts filesystem access to `/proc` only after initialization
+ - Network sandbox blocks TCP bind/connect on kernel 6.4+
+ - Drops `CAP_NET_RAW` capability after pcap handle is opened
+ - New CLI options: `--no-sandbox` and `--sandbox-strict`
+ - Comprehensive security documentation in SECURITY.md
+- **eBPF Thread Name Resolution**: Resolve eBPF thread names (e.g., 'Socket Thread') to main process names (e.g., 'firefox')
+ - Uses periodic procfs PID cache for resolution
+ - Falls back to eBPF name for short-lived processes
+- **AUR Package Automation**: Automated Arch Linux AUR package publishing workflow
+
+### Changed
+- **Platform Code Reorganization**: Restructured platform-specific code into cleaner module hierarchy
+ - `src/network/platform/linux/` - Linux-specific code with eBPF and sandbox subdirectories
+ - `src/network/platform/macos/` - macOS-specific code
+ - `src/network/platform/freebsd/` - FreeBSD-specific code
+ - `src/network/platform/windows/` - Windows-specific code
+- **QUIC DPI Simplification**: Unified SNI extraction helpers and simplified QUIC protocol handling
+
+### Fixed
+- **Test Determinism**: Made RateTracker tests deterministic with injectable timestamps
+
+## [0.16.1] - 2025-11-22
+
+### Fixed
+- **Cross-Compilation**: Fixed eBPF build issues when cross-compiling to non-Linux platforms
+ - Made `libbpf-cargo` an optional build dependency
+ - Fixed `build.rs` to check TARGET environment variable instead of host platform
+ - Prevents Linux-specific dependencies from being built for FreeBSD, macOS, and Windows
+- **FreeBSD Build**: Switched from cross-compilation to native FreeBSD VM builds
+ - Uses `vmactions/freebsd-vm` for native FreeBSD compilation
+ - Eliminates cross-compilation sysroot and library linking issues
+ - Ensures FreeBSD builds work reliably with native package manager
+
+## [0.16.0] - 2025-11-22
+
+### Added
+- **Network Interface Statistics**: Real-time monitoring of network interface statistics across all platforms
+ - Cross-platform support for Linux, macOS, Windows, and FreeBSD
+ - Display of interface-level metrics including packets sent/received, bytes transferred, and errors
+ - Platform-specific implementations optimized for each operating system
+ - New interface statistics module with dedicated platform handlers
+
+### Changed
+- **Link Layer Parsing**: Refactored link layer parsing into modular components
+ - Separated link layer types (Ethernet, Linux SLL, Raw IP, TUN/TAP, PKTAP)
+ - Improved packet parsing architecture for better maintainability
+ - Enhanced support for various network interface types
+
+### Fixed
+- **Windows Interface Stats**: Fixed interface statistics collection on Windows platforms
+ - Improved reliability of Windows network adapter statistics
+ - Better handling of Windows-specific network interfaces
+- **macOS Interface Stats**: Platform-specific improvements for macOS interface statistics
+ - Enhanced accuracy of macOS network interface metrics
+ - Better integration with macOS network stack
+
+## [0.15.0] - 2025-10-25
+
+### Added
+- **Ubuntu PPA Packaging**: Official Ubuntu PPA repository for easy installation on Ubuntu/Debian-based distributions
+ - Automated GitHub Actions workflow for PPA releases
+ - Support for multiple Ubuntu versions
+
+### Changed
+- **Bandwidth Sorting**: Changed bandwidth sorting to use combined up+down total instead of separate up/down sorting
+ - Simpler sorting behavior: press `s` once to sort by total bandwidth
+ - Display still shows "Down/Up" with individual values
+ - Arrow indicator shows when sorting by combined bandwidth total
+- **Packet Capture Permissions**: Removed CAP_NET_ADMIN and CAP_SYS_ADMIN requirements
+ - Uses read-only packet capture (non-promiscuous mode)
+ - Reduced security footprint with minimal required capabilities
+
+### Fixed
+- **Bandwidth Rate Tracking**: Improved accuracy and stability of bandwidth rate calculations
+ - More consistent rate measurements
+ - Better handling of network traffic bursts
+
+## [0.14.0] - 2025-10-12
+
+### Added
+- **eBPF Enabled by Default on Linux**: eBPF support is now enabled by default on Linux builds for enhanced performance
+ - Provides faster socket tracking with reduced overhead
+ - Includes CO-RE (Compile Once - Run Everywhere) support
+ - Graceful fallback to procfs when eBPF is unavailable
+- **JSON Logging for SIEM Integration**: New JSON-structured logging output for security information and event management systems
+ - Enables integration with enterprise monitoring and security platforms
+ - Structured log format for easier parsing and analysis
+- **TUN/TAP Interface Support**: Added support for TUN/TAP virtual network interfaces
+ - Enables monitoring of VPN connections and virtual network devices
+ - Expands interface compatibility for complex network setups
+- **Fedora COPR RPM Packaging**: Official Fedora COPR repository for easy installation on Fedora/RHEL-based distributions
+
+### Fixed
+- **High CPU Usage on Linux**: Eliminated excessive procfs scanning causing high CPU utilization
+ - Optimized process lookup frequency and caching strategy
+ - Significantly reduced system resource consumption during monitoring
+
+### Changed
+- **Build Dependencies**: Bundled vmlinux.h files to eliminate network dependency during builds
+ - Improves build reliability and offline build capability
+ - Reduces external dependencies for compilation
+- **Documentation**: Restructured documentation into focused files with improved musl static build documentation
+
+## [0.13.0] - 2025-10-04
+
+### Added
+- **Windows Process Identification**: Implemented full process lookup using Windows IP Helper API
+ - Uses GetExtendedTcpTable and GetExtendedUdpTable for connection-to-process mapping
+ - Resolves process names via OpenProcess and QueryFullProcessImageNameW
+ - Supports both TCP/UDP and IPv4/IPv6 connections
+ - Implements time-based caching with 2-second TTL for performance
+ - Migrated from winapi to windows crate (v0.59) for better maintainability
+- **Privilege Detection**: Pre-flight privilege checking before network interface access
+ - Detects insufficient privileges on Linux, macOS, and Windows
+ - Provides platform-specific instructions (sudo, setcap, Docker flags)
+ - Shows errors before TUI initialization for better visibility
+ - Detects container environments with Docker-specific guidance
+
+### Fixed
+- **Packet Length Calculation**: Use actual packet length from IP headers instead of captured length
+ - Extracts Total Length field from IP headers for accurate byte counting
+ - Fixes severe undercounting for large packets (NFS, jumbo frames)
+ - Resolves issues with snaplen-limited capture buffers
+
+### Changed
+- **Documentation**: Updated ROADMAP.md and README.md with Windows process identification status and Arch Linux installation instructions
+
+## [0.12.1] - 2025-10-02
+
+### Changed
+- **Build Configuration**: Improved crate metadata for crates.io publishing
+ - No functional changes to the binary or runtime behavior
+ - Enhanced package configuration for better crate ecosystem integration
+
+## [0.12.0] - 2025-10-01
+
+### Added
+- **Vim-style Navigation**: Jump to beginning of connection list with `g` and end with `G` (Shift+g)
+- **Table Sorting**: Comprehensive sorting functionality for all connection table columns
+ - Press `s` to cycle through sortable columns (Protocol, Local Address, Remote Address, State, Service, Application, Bandwidth ↓, Bandwidth ↑, Process)
+ - Press `S` (Shift+s) to toggle sort direction (ascending/descending)
+ - Visual indicators with arrows and cyan highlighting on active sort column
+ - Sort by download/upload bandwidth to find bandwidth hogs
+ - Alphabetical sorting for text columns
+- **Port Display Toggle**: Press `p` to switch between service names and port numbers display
+- **Connection Navigation Improvements**: Enhanced navigation with better visual cleanup indication
+- **Localhost Filtering Control**: New `--show-localhost` command-line flag to override default localhost filtering
+
+### Fixed
+- **Windows Double Key Issue**: Fixed duplicate key event handling on Windows platforms
+- **Windows MSI Runtime Dependencies**: Added startup check for missing Npcap/WinPcap DLLs
+ - Displays helpful error message with installation instructions when DLLs are missing
+ - Added winapi dependency for Windows DLL detection
+ - Updated README with runtime dependency information
+- **Linux Interface Selection**: Fixed "any" interface selection on Linux
+ - Improved interface detection and validation
+ - Better error handling for interface configuration
+- **Package Dependencies**: Removed unnecessary runtime dependencies (clang, llvm) from RPM and DEB packages
+ - Reduces installation footprint and dependency conflicts
+- **Docker Build**: Removed armv7 architecture from Docker builds for improved stability
+
+### Changed
+- **Documentation**: Updated roadmap and README with new features and keyboard shortcuts
+
+## [0.11.0] - 2025-09-30
+
+### Added
+- **Docker Support with eBPF**: Docker images now include eBPF support for enhanced performance
+ - Multi-architecture Docker builds (amd64, arm64)
+ - eBPF-enabled images for advanced socket tracking on Linux
+ - Optimized container builds with proper dependency management
+- **Cross-Platform Packaging and Release Automation**: Comprehensive automated release workflow
+ - Automated DEB, RPM, DMG, and MSI package generation
+ - Cross-platform CI/CD improvements
+
+### Fixed
+- **RPM Package Dependencies**: Corrected libelf dependency specification in RPM packages
+- **Windows MSI Packaging**: Fixed MSI installer generation issues
+- **Release Workflow**: Resolved various release automation issues
+
+## [0.10.0] - 2025-09-28
+
+### Added
+- **Rust Version Requirements**: Added minimum Rust version requirement (1.88.0+) for let-chains support
+
+### Changed
+- **Build Requirements**: Now requires Rust 1.88.0 or later for advanced language features
+
+## [0.9.0] - 2025-09-18
+
+### Added
+- **Experimental eBPF Support for Linux**: Enhanced socket tracking with optional eBPF backend
+ - eBPF-based socket tracker with CO-RE (Compile Once - Run Everywhere) support
+ - Minimal vmlinux header (5.5KB instead of full 3.4MB file)
+ - Graceful fallback mechanism to procfs when eBPF unavailable
+ - Support for both IPv4 and IPv6 socket tracking
+ - Optional feature disabled by default (enable with `--features=ebpf`)
+ - Comprehensive capability checking for required permissions
+- **Windows Platform Support**: Network monitoring capability on Windows (without process identification)
+
+## [0.8.0] - 2025-09-11
+
+### Added
+- **SSH Deep Packet Inspection (DPI)**: Comprehensive SSH protocol analysis including:
+ - SSH version detection (SSH-1.x, SSH-2.0)
+ - Client and server software identification (OpenSSH, PuTTY, libssh, etc.)
+ - Connection state tracking: Banner, KeyExchange, Authentication, Established
+ - Algorithm detection and negotiation monitoring
+ - SSH-specific filtering with `ssh:` prefix in connection filters
+- **Enhanced Filtering**: SSH connections now support detailed filtering by software name and connection state
+
+### Improved
+- **CI/CD**: Enhanced GitHub Actions with path-based triggers for more efficient workflows
+- **Documentation**: Updated README with SSH DPI examples and state descriptions
+
+## [0.7.0] - 2025-09-11
+
+### Fixed
+- SecureCRT backspace handling issue
+
+## [0.6.0] - 2025-09-10
+
+### Added
+- Connection state filtering (ESTABLISHED, TIME_WAIT, etc.)
+
+## [0.5.0] - 2025-01-09
+
+### Added
+- **Connection Filtering System**: New comprehensive filtering capability allowing users to filter connections by:
+ - Protocol type (TCP, UDP, ICMP)
+ - Local and remote IP addresses
+ - Local and remote ports
+ - Process names
+ - Service names
+ - Customizable filter expressions with intuitive UI
+- **Enhanced Documentation**: Added asciinema demo recording for better user onboarding
+- **Visual Demonstrations**: Added animated GIF showcasing RustNet functionality
+
+### Fixed
+- **README Improvements**: Fixed image syntax and formatting issues for better GitHub display
+
+### Changed
+- **User Interface**: Enhanced TUI to support dynamic filtering with keyboard shortcuts
+- **Documentation**: Improved project presentation with visual aids and demonstrations
+
+## [0.4.0] - 2025-01-29
+
+### Improved
+- Enhanced traffic monitoring with better rate tracking and byte counters
+- Fixed Linux platform build warnings for improved compilation stability
+- Corrected version display to use dynamic version from Cargo.toml instead of hardcoded value
+
+## [0.3.0] - 2024-12-28
+
+### Added
+- Created `RELEASE.md` and `ROADMAP.md` for better project organization
+- Enhanced memory efficiency through enum variant boxing
+
+### Fixed
+- Major clippy warning cleanup (97% reduction from 38 to 1 warnings)
+- Refactored functions using `TransportParams` struct to reduce complexity
+- Fixed collapsible if patterns and improved code readability
+- Eliminated needless borrows and manual implementations
+
+### Changed
+- Moved release documentation to dedicated files
+- Streamlined README to focus on user information
+- Improved code organization and Rust best practices
+
+## [0.2.0] - 2024-12-19
+
+### Added
+- **Enhanced PKTAP Support on macOS**: Comprehensive process identification using macOS PKTAP (Packet Tap) headers
+ - Direct extraction of process names and PIDs from kernel packet metadata
+ - Robust handling of 20-byte PKTAP process name fields with proper normalization
+ - Support for both `pth_comm` and `pth_e_comm` (effective command name) fields
+ - Fallback to `lsof` system commands when PKTAP data is unavailable
+- **Process Data Immutability System**: Once process information is set from any source, it becomes immutable to prevent display inconsistencies
+- **Advanced Process Name Normalization**: Handles all types of whitespace, control characters, and padding in process names
+- **Comprehensive Debug Logging**: Extensive logging for PKTAP header processing, process name extraction, and data flow tracking
+
+### Fixed
+- **Process Display Stability on macOS**: Fixed issue where process names would change format during UI scrolling (e.g., "firefox (123)" → "firefox (123)")
+- **PKTAP Header Processing**: Improved parsing of raw PKTAP packet headers with better error handling and validation
+- **Process Name Consistency**: Eliminated race conditions and data inconsistencies in process name display
+- **Whitespace Normalization**: Fixed handling of tabs, multiple spaces, unicode whitespace, and control characters in process names
+
+### Changed
+- **Process Enrichment Logic**: Modified to respect existing PKTAP data and only fill in missing information from `lsof`
+- **UI Rendering Optimization**: Simplified process name rendering to use pre-normalized data from sources
+- **Error Handling**: Enhanced error reporting for PKTAP processing and process lookup failures
+
+### Technical Details
+- Implemented `extract_process_name_from_bytes()` function for robust PKTAP process name extraction
+- Added immutability enforcement in connection merge logic with violation detection
+- Enhanced macOS process lookup with `normalize_process_name_robust()` function
+- Improved byte-level debugging and logging for process identification troubleshooting
+
+### Platform-Specific Improvements
+- **macOS**: PKTAP now provides primary process identification with significant performance and accuracy improvements over `lsof`-only approach
+- **Linux**: Process enrichment logic updated to work consistently with new immutability system
+
+## [0.1.0] - 2024-XX-XX
+
+### Added
+- Initial release of RustNet
+- Real-time network connection monitoring
+- Deep packet inspection (DPI) for HTTP, HTTPS, DNS, SSH, and QUIC
+- Cross-platform support (Linux, macOS, Windows)
+- Terminal user interface with ratatui
+- Multi-threaded packet processing
+- Process identification using platform-specific APIs
+- Service name resolution
+- Configurable refresh intervals and filtering options
+- Optional logging with multiple log levels
+
+[Unreleased]: https://github.com/domcyrus/rustnet/compare/v1.4.0...HEAD
+[1.4.0]: https://github.com/domcyrus/rustnet/compare/v1.3.0...v1.4.0
+[1.3.0]: https://github.com/domcyrus/rustnet/compare/v1.2.0...v1.3.0
+[1.2.0]: https://github.com/domcyrus/rustnet/compare/v1.1.0...v1.2.0
+[1.1.0]: https://github.com/domcyrus/rustnet/compare/v1.0.0...v1.1.0
+[1.0.0]: https://github.com/domcyrus/rustnet/compare/v0.18.0...v1.0.0
+[0.18.0]: https://github.com/domcyrus/rustnet/compare/v0.17.0...v0.18.0
+[0.17.0]: https://github.com/domcyrus/rustnet/compare/v0.16.1...v0.17.0
+[0.16.1]: https://github.com/domcyrus/rustnet/compare/v0.15.0...v0.16.1
+[0.15.0]: https://github.com/domcyrus/rustnet/compare/v0.14.0...v0.15.0
+[0.14.0]: https://github.com/domcyrus/rustnet/compare/v0.13.0...v0.14.0
+[0.13.0]: https://github.com/domcyrus/rustnet/compare/v0.12.1...v0.13.0
+[0.12.1]: https://github.com/domcyrus/rustnet/compare/v0.12.0...v0.12.1
+[0.12.0]: https://github.com/domcyrus/rustnet/compare/v0.11.0...v0.12.0
+[0.11.0]: https://github.com/domcyrus/rustnet/compare/v0.10.0...v0.11.0
+[0.10.0]: https://github.com/domcyrus/rustnet/compare/v0.9.0...v0.10.0
+[0.9.0]: https://github.com/domcyrus/rustnet/compare/v0.8.0...v0.9.0
+[0.8.0]: https://github.com/domcyrus/rustnet/compare/v0.7.0...v0.8.0
+[0.7.0]: https://github.com/domcyrus/rustnet/compare/v0.6.0...v0.7.0
+[0.6.0]: https://github.com/domcyrus/rustnet/compare/v0.5.0...v0.6.0
+[0.5.0]: https://github.com/domcyrus/rustnet/compare/v0.4.0...v0.5.0
+[0.4.0]: https://github.com/domcyrus/rustnet/compare/v0.3.0...v0.4.0
+[0.3.0]: https://github.com/domcyrus/rustnet/compare/v0.2.0...v0.3.0
+[0.2.0]: https://github.com/domcyrus/rustnet/compare/v0.1.0...v0.2.0
+[0.1.0]: https://github.com/domcyrus/rustnet/releases/tag/v0.1.0
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..2d070d0
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,120 @@
+ English | 简体中文
+
+# Contributing to RustNet
+
+Pull requests are very welcome! Whether you're fixing bugs, adding features, improving documentation, or providing feedback, all contributions help make RustNet better.
+
+## Project Scope
+
+RustNet aims to stay small and fast. Not every protocol or feature belongs in the core tool, even when a contribution is well-written.
+
+For Deep Packet Inspection in particular, we lean toward protocols that:
+
+- A meaningful share of RustNet users will actually encounter on their networks.
+- Produce visible, useful information (the plaintext window is wide enough to extract real metadata, not just to confirm the protocol exists before everything goes TLS).
+- Fit the existing architecture without disproportionate maintenance cost.
+
+If a protocol is rarely seen in modern traffic, almost always TLS-wrapped, or niche to a single user base, we may still close the PR with thanks even if the code is correct. Please open an issue first for any new protocol so we can sanity-check fit before you invest implementation time.
+
+## Performance & Optimizations
+
+If a PR is motivated by performance, it must show that the code is on a real hot path. A microbenchmark alone is **not** enough: it only proves a function got faster in isolation, not that the function matters to overall runtime.
+
+- **Profile first.** Capture a flamegraph under real traffic (see [PROFILING.md](PROFILING.md)) and confirm the code shows up as meaningful CPU time.
+- **A bench is supporting evidence, not proof.** A `cargo bench` / criterion comparison showing your change is faster is welcome, but it does not establish that the code is hot.
+- **Cold-path micro-optimizations may be closed with thanks.** Optimizing code that runs rarely (connection setup, one-time parsing, error handling) adds review and maintenance cost without a meaningful gain.
+
+## Development Workflow
+
+We use the standard open-source fork and feature branch approach:
+
+1. **Open an issue first** to discuss your proposed changes. For features and non-trivial refactors, please wait for a maintainer response before writing code. Typo fixes, small bug fixes, and documentation corrections do not need a prior issue.
+2. Fork the repository
+3. Clone your fork locally
+4. Create a feature branch from `main` (`git checkout -b feature/your-feature`)
+5. Make your changes
+6. Push to your fork (`git push origin feature/your-feature`)
+7. Open a Pull Request against `main` and reference the related issue
+
+## Code Quality Requirements
+
+Before submitting a PR, please ensure:
+
+- **Unit tests**: Add tests for complex or critical code paths
+- **No dead code**: Remove unused code, imports, and dependencies
+- **Code style**: Follow the existing code style and patterns in the codebase
+- **Clippy**: Fix all clippy warnings
+ ```bash
+ cargo clippy --all-targets --all-features -- -D warnings
+ ```
+- **No clippy suppression**: Do not use `#[allow(clippy::...)]` to suppress warnings. Fix the underlying issue instead (e.g., reduce arguments, refactor code). If a suppression is truly unavoidable, discuss it in the PR.
+- **Formatting**: Run the formatter
+ ```bash
+ cargo fmt
+ ```
+- **Security audit**: Check for known vulnerabilities and policy violations in dependencies
+ ```bash
+ cargo deny check
+ ```
+
+## CI Checks
+
+When you open a PR, our CI pipeline will automatically run checks including:
+
+- Clippy lints
+- Code formatting verification
+- Build on multiple platforms
+- Test suite
+
+Please ensure all CI checks pass before requesting a review.
+
+## Dependency Policy
+
+Please be conservative with dependencies:
+
+- Don't add dependencies unless there's a good reason
+- Prefer standard library solutions when possible
+- If adding a dependency, explain the rationale in your PR description
+- Consider the dependency's maintenance status and security track record
+
+## Security
+
+Security is important for a network monitoring tool:
+
+- Keep security in mind when writing code
+- Avoid introducing common vulnerabilities (injection, buffer issues, etc.)
+- Be careful with user input and network data parsing
+- Report security issues responsibly (see [SECURITY.md](SECURITY.md))
+
+## PR Guidelines
+
+- Write a clear description of what your changes do and why
+- Link any related issues
+- Keep PRs focused - one feature or fix per PR
+- Be responsive to review feedback
+- Verify locally before opening the PR. The PR template lists the exact commands.
+- Add user-visible changes to the `## [Unreleased]` section of `CHANGELOG.md` in the same PR
+
+## Duplicate Pull Requests
+
+If two or more PRs address the same issue, the maintainers will evaluate them on their merits (code quality, test coverage, architectural fit) rather than submission order. The PR that best fits the project will be merged; others will be closed with thanks. If your PR is closed in favor of another, useful pieces from your work (documentation, tests, edge cases) may be ported over and credited.
+
+To avoid duplicate work, please comment on the linked issue stating that you intend to work on it before you start writing code.
+
+## AI-Assisted Contributions
+
+AI-assisted contributions are welcome, provided you treat the output as your own work and take responsibility for it.
+
+If you use an AI assistant (Copilot, Claude, ChatGPT, Cursor, or similar) to help write code or documentation:
+
+- **Read every line** you are submitting. You are accountable for it.
+- **Run the verification commands** listed in the PR template yourself. Do not submit code with a note that tests "could not be run locally" and ask the reviewer to verify.
+- **Make sure the PR description matches the code.** If the model wrote the PR body, confirm it describes what the diff actually does.
+- **Do not split a single change into many cosmetic per-file commits** to make the work look incremental. One logical change, one commit, with a real commit message.
+- **Do not open a PR seconds after filing the issue that motivates it.** Allow time for the maintainers and other contributors to weigh in on the proposed approach.
+
+PRs that show signs of unreviewed AI output (failing tests, fabricated APIs, unrelated bundled changes, mismatched descriptions) will be closed.
+
+## Questions?
+
+Feel free to open an issue if you have questions or want to discuss a potential contribution before starting work.
diff --git a/CONTRIBUTING.zh-CN.md b/CONTRIBUTING.zh-CN.md
new file mode 100644
index 0000000..0fb2565
--- /dev/null
+++ b/CONTRIBUTING.zh-CN.md
@@ -0,0 +1,119 @@
+English | 简体中文
+
+# 参与贡献
+
+欢迎提交 Pull Request!无论你是修复 bug、添加功能、改进文档,还是提供反馈,所有贡献都能让 RustNet 变得更好。
+
+## 项目范围
+
+RustNet 追求小而快。并不是每个协议或功能都适合放在核心工具里,即使贡献的代码写得很好。
+
+对于深度包检测(DPI),我们倾向于满足以下条件的协议:
+
+- RustNet 用户中有相当一部分会在自己的网络中实际遇到。
+- 能提取出可见且有用的信息(明文窗口足够宽,能提取出真正的元数据,而不仅仅是确认协议存在就进入 TLS)。
+- 能以合理的维护成本融入现有架构。
+
+如果某个协议在现代流量中很少见、几乎总是包裹在 TLS 里,或者仅服务于单一小众用户群,即使代码正确,我们也可能婉拒该 PR。请在实现任何新协议前先开一个 issue,以便我们在你投入实现时间之前确认其是否合适。
+
+## 性能与优化
+
+如果一个 PR 的出发点是性能,它必须证明相关代码确实位于真正的热点路径上。仅有微基准测试是**不够**的:它只能证明某个函数在孤立测试中变快了,并不能说明该函数对整体运行时间有影响。
+
+- **先做性能分析。** 在真实流量下采集火焰图(参见 [PROFILING.zh-CN.md](PROFILING.zh-CN.md)),确认相关代码确实占用了可观的 CPU 时间。
+- **基准测试只是佐证,而非证明。** 欢迎附上 `cargo bench` / criterion 的对比结果来说明你的改动更快,但这并不能证明该代码是热点。
+- **冷路径上的微优化可能会被婉拒并致谢。** 优化很少执行的代码(连接建立、一次性解析、错误处理)会增加 review 和维护成本,却没有有意义的收益。
+
+## 开发流程
+
+我们采用标准的开源 fork 和功能分支方式:
+
+1. **先开 issue** 讨论你打算做的改动。对于功能性和非平凡的代码重构,请在写代码之前等待维护者的回复。拼写错误、小型 bug 修复和文档勘误不需要先开 issue。
+2. Fork 本仓库
+3. 将 fork 克隆到本地
+4. 从 `main` 创建功能分支(`git checkout -b feature/your-feature`)
+5. 进行你的修改
+6. 推送到你的 fork(`git push origin feature/your-feature`)
+7. 向 `main` 提交 Pull Request,并引用相关 issue
+
+## 代码质量要求
+
+在提交 PR 之前,请确保:
+
+- **单元测试**:为复杂或关键的代码路径添加测试
+- **无死代码**:删除未使用的代码、导入和依赖
+- **代码风格**:遵循代码库中已有的代码风格和模式
+- **Clippy**:修复所有 clippy 警告
+ ```bash
+ cargo clippy --all-targets --all-features -- -D warnings
+ ```
+- **禁止 clippy 抑制**:不要使用 `#[allow(clippy::...)]` 来抑制警告。请修复根本问题(例如减少参数、重构代码)。如果确实无法避免,请在 PR 中说明。
+- **格式化**:运行格式化工具
+ ```bash
+ cargo fmt
+ ```
+- **安全审计**:检查依赖中的已知漏洞和策略违规
+ ```bash
+ cargo deny check
+ ```
+
+## CI 检查
+
+当你提交 PR 时,我们的 CI 流水线会自动运行以下检查:
+
+- Clippy lint
+- 代码格式化验证
+- 多平台构建
+- 测试套件
+
+请在请求 review 之前确保所有 CI 检查通过。
+
+## 依赖策略
+
+请谨慎添加依赖:
+
+- 除非有充分的理由,否则不要添加依赖
+- 尽可能优先使用标准库方案
+- 如果必须添加依赖,请在 PR 描述中说明理由
+- 考虑依赖的维护状态和安全记录
+
+## 安全
+
+对于网络监控工具,安全至关重要:
+
+- 编写代码时牢记安全
+- 避免引入常见漏洞(注入、缓冲区问题等)
+- 谨慎处理用户输入和网络数据解析
+- 请负责任地报告安全问题(参见 [SECURITY.zh-CN.md](SECURITY.zh-CN.md))
+
+## PR 指南
+
+- 清晰描述你的改动做了什么以及为什么
+- 链接任何相关的 issue
+- 保持 PR 聚焦——每个 PR 只做一件事
+- 及时响应 review 反馈
+- 在提交 PR 之前在本地验证。PR 模板中列出了需要执行的精确命令。
+
+## 重复 Pull Request
+
+如果两个或多个 PR 解决了同一个 issue,维护者将根据代码质量、测试覆盖率和架构契合度来评估,而不是提交顺序。最符合项目需求的 PR 将被合并;其他 PR 将被关闭并致谢。如果你的 PR 被另一个替代,你工作中的有用部分(文档、测试、边界情况)可能会被移植并给予署名。
+
+为避免重复工作,请在开始编码之前,在相关 issue 下评论说明你有意处理它。
+
+## AI 辅助贡献
+
+欢迎使用 AI 辅助贡献,但前提是你将其输出视为自己的作品并对其负责。
+
+如果你使用 AI 助手(Copilot、Claude、ChatGPT、Cursor 或类似工具)来辅助编写代码或文档:
+
+- **阅读你提交的每一行**。你要对它负责。
+- **自己运行 PR 模板中列出的验证命令**。不要提交代码后附带一句“无法在本地运行测试”并请 reviewer 验证。
+- **确保 PR 描述与代码一致。** 如果模型写了 PR 正文,请确认它确实描述了 diff 实际做的事情。
+- **不要把一个改动拆成多个仅涉及样式的逐文件提交** 来让工作看起来是增量完成的。一个逻辑改动,一个提交,附带有意义的提交信息。
+- **不要在提交 issue 的下一秒就开 PR。** 给维护者和其他贡献者留出时间对你提出的方案发表意见。
+
+那些显示出未经 review 的 AI 输出痕迹的 PR(测试失败、虚构 API、无关的捆绑改动、描述不匹配)将被关闭。
+
+## 有问题?
+
+如果你有任何问题,或者想在开始工作之前讨论潜在的贡献,欢迎随时开一个 issue。
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
new file mode 100644
index 0000000..2d6b282
--- /dev/null
+++ b/CONTRIBUTORS.md
@@ -0,0 +1,21 @@
+# Contributors
+
+We would like to thank the following people for their contributions to RustNet:
+
+## Maintainer
+
+- **Marco Cadetg** ([@domcyrus](https://github.com/domcyrus)) - Creator and maintainer
+
+## Contributors
+
+We would like to thank these people for their valuable contributions:
+
+- **DeepChirp** ([@DeepChirp](https://github.com/DeepChirp)) - Code contributions
+- **Conor O'Callaghan** ([@Conor0Callaghan](https://github.com/Conor0Callaghan)) - JSON/SIEM logging research and design input
+- **Ken Tobias** ([@l1a](https://github.com/l1a)) - Code contributions
+
+## Contributing
+
+We welcome and appreciate all contributions! Whether you're fixing bugs, adding features, improving documentation, or helping with ideas and feedback, your contributions make RustNet better.
+
+See our [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on how to contribute.
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..96000ea
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,4142 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "adler2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
+[[package]]
+name = "aes"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138"
+dependencies = [
+ "cipher",
+ "cpubits",
+ "cpufeatures 0.3.0",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "alloca"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "allocator-api2"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anes"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
+
+[[package]]
+name = "anstream"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
+
+[[package]]
+name = "anstyle-parse"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
+dependencies = [
+ "anstyle",
+ "once_cell_polyfill",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.103"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
+
+[[package]]
+name = "approx"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "arboard"
+version = "3.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf"
+dependencies = [
+ "clipboard-win",
+ "image",
+ "log",
+ "objc2",
+ "objc2-app-kit",
+ "objc2-core-foundation",
+ "objc2-core-graphics",
+ "objc2-foundation",
+ "parking_lot",
+ "percent-encoding",
+ "windows-sys 0.60.2",
+ "wl-clipboard-rs",
+ "x11rb",
+]
+
+[[package]]
+name = "atomic"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340"
+dependencies = [
+ "bytemuck",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "bit-set"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
+dependencies = [
+ "bit-vec",
+]
+
+[[package]]
+name = "bit-vec"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be"
+dependencies = [
+ "hybrid-array",
+ "zeroize",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.19.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
+
+[[package]]
+name = "by_address"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06"
+
+[[package]]
+name = "bytemuck"
+version = "1.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
+
+[[package]]
+name = "byteorder-lite"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
+
+[[package]]
+name = "bzip2"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c"
+dependencies = [
+ "libbz2-rs-sys",
+]
+
+[[package]]
+name = "camino"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "caps"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd1ddba47aba30b6a889298ad0109c3b8dcb0e8fc993b459daa7067d46f865e0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "cargo-platform"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cargo_metadata"
+version = "0.19.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba"
+dependencies = [
+ "camino",
+ "cargo-platform",
+ "semver",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.18",
+]
+
+[[package]]
+name = "cast"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
+
+[[package]]
+name = "castaway"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
+dependencies = [
+ "rustversion",
+]
+
+[[package]]
+name = "cc"
+version = "1.2.55"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
+dependencies = [
+ "find-msvc-tools",
+ "jobserver",
+ "libc",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+
+[[package]]
+name = "chrono"
+version = "0.4.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
+dependencies = [
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "wasm-bindgen",
+ "windows-link",
+]
+
+[[package]]
+name = "ciborium"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
+dependencies = [
+ "ciborium-io",
+ "ciborium-ll",
+ "serde",
+]
+
+[[package]]
+name = "ciborium-io"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
+
+[[package]]
+name = "ciborium-ll"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
+dependencies = [
+ "ciborium-io",
+ "half",
+]
+
+[[package]]
+name = "cipher"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e34d8227fe1ba289043aeb13792056ff80fd6de1a9f49137a5f499de8e8c78ea"
+dependencies = [
+ "crypto-common 0.2.1",
+ "inout",
+]
+
+[[package]]
+name = "clap"
+version = "4.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_complete"
+version = "4.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db8b397918185f0161ff3d6fcaa9e4bfc09b8367caf6e1d4a2848e5477ed027b"
+dependencies = [
+ "clap",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "clap_lex"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
+
+[[package]]
+name = "clap_mangen"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d82842b45bf9f6a3be090dd860095ac30728042c08e0d6261ca7259b5d850f07"
+dependencies = [
+ "clap",
+ "roff",
+]
+
+[[package]]
+name = "clipboard-win"
+version = "5.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4"
+dependencies = [
+ "error-code",
+]
+
+[[package]]
+name = "cmov"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
+
+[[package]]
+name = "compact_str"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a"
+dependencies = [
+ "castaway",
+ "cfg-if",
+ "itoa",
+ "rustversion",
+ "ryu",
+ "static_assertions",
+]
+
+[[package]]
+name = "console"
+version = "0.16.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87"
+dependencies = [
+ "encode_unicode",
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "const-oid"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
+
+[[package]]
+name = "constant_time_eq"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
+
+[[package]]
+name = "convert_case"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "cpubits"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ef0c543070d296ea414df2dd7625d1b24866ce206709d8a4a424f28377f5861"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "cpufeatures"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "criterion"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3"
+dependencies = [
+ "alloca",
+ "anes",
+ "cast",
+ "ciborium",
+ "clap",
+ "criterion-plot",
+ "itertools 0.13.0",
+ "num-traits",
+ "oorandom",
+ "page_size",
+ "plotters",
+ "rayon",
+ "regex",
+ "serde",
+ "serde_json",
+ "tinytemplate",
+ "walkdir",
+]
+
+[[package]]
+name = "criterion-plot"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea"
+dependencies = [
+ "cast",
+ "itertools 0.13.0",
+]
+
+[[package]]
+name = "critical-section"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
+
+[[package]]
+name = "crossbeam"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8"
+dependencies = [
+ "crossbeam-channel",
+ "crossbeam-deque",
+ "crossbeam-epoch",
+ "crossbeam-queue",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-queue"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
+name = "crossterm"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
+dependencies = [
+ "bitflags 2.13.0",
+ "crossterm_winapi",
+ "derive_more",
+ "document-features",
+ "mio",
+ "parking_lot",
+ "rustix",
+ "signal-hook",
+ "signal-hook-mio",
+ "winapi",
+]
+
+[[package]]
+name = "crossterm_winapi"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "crunchy"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
+
+[[package]]
+name = "crypto-common"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710"
+dependencies = [
+ "hybrid-array",
+]
+
+[[package]]
+name = "csscolorparser"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf"
+dependencies = [
+ "lab",
+ "phf",
+]
+
+[[package]]
+name = "ctutils"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"
+dependencies = [
+ "cmov",
+]
+
+[[package]]
+name = "darling"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
+dependencies = [
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "dashmap"
+version = "6.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+ "hashbrown 0.14.5",
+ "lock_api",
+ "once_cell",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "deflate64"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26bf8fc351c5ed29b5c2f0cbbac1b209b74f60ecd62e675a998df72c49af5204"
+
+[[package]]
+name = "deltae"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4"
+
+[[package]]
+name = "deranged"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "derive_more"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
+dependencies = [
+ "derive_more-impl",
+]
+
+[[package]]
+name = "derive_more-impl"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
+dependencies = [
+ "convert_case",
+ "proc-macro2",
+ "quote",
+ "rustc_version",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer 0.10.4",
+ "crypto-common 0.1.7",
+]
+
+[[package]]
+name = "digest"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c"
+dependencies = [
+ "block-buffer 0.12.0",
+ "const-oid",
+ "crypto-common 0.2.1",
+ "ctutils",
+ "zeroize",
+]
+
+[[package]]
+name = "dispatch2"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
+dependencies = [
+ "bitflags 2.13.0",
+ "objc2",
+]
+
+[[package]]
+name = "dns-lookup"
+version = "3.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e39034cee21a2f5bbb66ba0e3689819c4bb5d00382a282006e802a7ffa6c41d"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "socket2",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "document-features"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
+dependencies = [
+ "litrs",
+]
+
+[[package]]
+name = "downcast-rs"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
+
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
+name = "encode_unicode"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
+
+[[package]]
+name = "enumflags2"
+version = "0.7.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef"
+dependencies = [
+ "enumflags2_derive",
+]
+
+[[package]]
+name = "enumflags2_derive"
+version = "0.7.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "errno"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
+dependencies = [
+ "errno-dragonfly",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "errno-dragonfly"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "error-code"
+version = "3.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59"
+
+[[package]]
+name = "euclid"
+version = "0.22.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df61bf483e837f88d5c2291dcf55c67be7e676b3a51acc48db3a7b163b91ed63"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "fancy-regex"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2"
+dependencies = [
+ "bit-set",
+ "regex",
+]
+
+[[package]]
+name = "fast-srgb8"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1"
+
+[[package]]
+name = "fastrand"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
+
+[[package]]
+name = "fax"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab"
+dependencies = [
+ "fax_derive",
+]
+
+[[package]]
+name = "fax_derive"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "fdeflate"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
+dependencies = [
+ "simd-adler32",
+]
+
+[[package]]
+name = "filedescriptor"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d"
+dependencies = [
+ "libc",
+ "thiserror 1.0.69",
+ "winapi",
+]
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
+
+[[package]]
+name = "finl_unicode"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5"
+
+[[package]]
+name = "fixedbitset"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
+
+[[package]]
+name = "fixedbitset"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
+
+[[package]]
+name = "flate2"
+version = "1.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+ "zlib-rs",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foldhash"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+
+[[package]]
+name = "foldhash"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
+
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "gethostname"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
+dependencies = [
+ "rustix",
+ "windows-link",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasip2",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "r-efi",
+ "wasip2",
+ "wasip3",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "half"
+version = "2.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
+dependencies = [
+ "cfg-if",
+ "crunchy",
+ "zerocopy",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.14.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
+
+[[package]]
+name = "hashbrown"
+version = "0.15.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
+dependencies = [
+ "foldhash 0.1.5",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
+dependencies = [
+ "allocator-api2",
+ "equivalent",
+ "foldhash 0.2.0",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
+dependencies = [
+ "allocator-api2",
+ "equivalent",
+ "foldhash 0.2.0",
+]
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "hmac"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f"
+dependencies = [
+ "digest 0.11.2",
+]
+
+[[package]]
+name = "http_req"
+version = "0.14.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a74cb441cd28aa6f00dfb4d17216c134cfa17d252b2c65367e30fcbb824923f8"
+dependencies = [
+ "base64",
+ "native-tls",
+ "unicase",
+ "zeroize",
+]
+
+[[package]]
+name = "hybrid-array"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8655f91cd07f2b9d0c24137bd650fe69617773435ee5ec83022377777ce65ef1"
+dependencies = [
+ "typenum",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "log",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "id-arena"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
+
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
+name = "image"
+version = "0.25.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a"
+dependencies = [
+ "bytemuck",
+ "byteorder-lite",
+ "moxcms",
+ "num-traits",
+ "png",
+ "tiff",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.16.1",
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "indoc"
+version = "2.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
+dependencies = [
+ "rustversion",
+]
+
+[[package]]
+name = "inout"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7"
+dependencies = [
+ "hybrid-array",
+]
+
+[[package]]
+name = "insta"
+version = "1.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86f0f8fee8c926415c58d6ae43a08523a26faccb2323f5e6b644fe7dd4ef6b82"
+dependencies = [
+ "console",
+ "once_cell",
+ "regex",
+ "similar",
+ "strip-ansi-escapes",
+ "tempfile",
+]
+
+[[package]]
+name = "instability"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "357b7205c6cd18dd2c86ed312d1e70add149aea98e7ef72b9fdf0270e555c11d"
+dependencies = [
+ "darling",
+ "indoc",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "ipnetwork"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "ipnetwork"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf370abdafd54d13e54a620e8c3e1145f28e46cc9d704bc6d94414559df41763"
+
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
+
+[[package]]
+name = "itertools"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itertools"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
+
+[[package]]
+name = "jobserver"
+version = "0.1.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
+dependencies = [
+ "getrandom 0.3.4",
+ "libc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.85"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "kasuari"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fe90c1150662e858c7d5f945089b7517b0a80d8bf7ba4b1b5ffc984e7230a5b"
+dependencies = [
+ "hashbrown 0.16.1",
+ "portable-atomic",
+ "thiserror 2.0.18",
+]
+
+[[package]]
+name = "lab"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f"
+
+[[package]]
+name = "landlock"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "635839550ae8b90d9fd2571460a6645dc0aec070225956ca7a2831ed31d2795d"
+dependencies = [
+ "enumflags2",
+ "libc",
+ "thiserror 2.0.18",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "leb128fmt"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
+
+[[package]]
+name = "libbpf-cargo"
+version = "0.26.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67e63283df9689af55b85f91e99dc8b213ae89fc3e1d9bb29ab7f75eb8a8a783"
+dependencies = [
+ "anyhow",
+ "cargo_metadata",
+ "clap",
+ "libbpf-rs",
+ "memmap2",
+ "serde",
+ "serde_json",
+ "tempfile",
+ "tracing",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "libbpf-rs"
+version = "0.26.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd8e8db46a2d5967b5b3dba7aba32a875dc38f222395040607045635ec1f9b63"
+dependencies = [
+ "bitflags 2.13.0",
+ "libbpf-sys",
+ "libc",
+ "vsprintf",
+]
+
+[[package]]
+name = "libbpf-sys"
+version = "1.7.0+v1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a109478760b2900aa2a6f2087e9d0de1d9c535b1758602af2845d5d2ccfaed7c"
+dependencies = [
+ "cc",
+ "nix 0.31.2",
+ "pkg-config",
+]
+
+[[package]]
+name = "libbz2-rs-sys"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7"
+
+[[package]]
+name = "libc"
+version = "0.2.186"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
+
+[[package]]
+name = "libloading"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
+dependencies = [
+ "cfg-if",
+ "windows-link",
+]
+
+[[package]]
+name = "libm"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
+
+[[package]]
+name = "line-clipping"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f4de44e98ddbf09375cbf4d17714d18f39195f4f4894e8524501726fd9a8a4a"
+dependencies = [
+ "bitflags 2.13.0",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
+
+[[package]]
+name = "litrs"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
+
+[[package]]
+name = "lock_api"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
+
+[[package]]
+name = "lru"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a860605968fce16869fd239cf4237a82f3ac470723415db603b0e8b6c8d4fb9"
+dependencies = [
+ "hashbrown 0.17.1",
+]
+
+[[package]]
+name = "lzma-rust2"
+version = "0.16.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47bb1e988e6fb779cf720ad431242d3f03167c1b3f2b1aae7f1a94b2495b36ae"
+dependencies = [
+ "sha2 0.10.9",
+]
+
+[[package]]
+name = "mac_address"
+version = "1.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303"
+dependencies = [
+ "nix 0.29.0",
+ "winapi",
+]
+
+[[package]]
+name = "maxminddb"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65e84ef32bcbf18a95548989e880db4af6fafd563463753afb4b9a149fb2782c"
+dependencies = [
+ "ipnetwork 0.21.1",
+ "log",
+ "memchr",
+ "serde",
+ "thiserror 2.0.18",
+]
+
+[[package]]
+name = "memchr"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
+
+[[package]]
+name = "memmap2"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "memmem"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15"
+
+[[package]]
+name = "memoffset"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
+dependencies = [
+ "adler2",
+ "simd-adler32",
+]
+
+[[package]]
+name = "mio"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
+dependencies = [
+ "libc",
+ "log",
+ "wasi",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "moxcms"
+version = "0.7.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97"
+dependencies = [
+ "num-traits",
+ "pxfm",
+]
+
+[[package]]
+name = "native-tls"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
+dependencies = [
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
+]
+
+[[package]]
+name = "nix"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
+dependencies = [
+ "bitflags 2.13.0",
+ "cfg-if",
+ "cfg_aliases",
+ "libc",
+ "memoffset",
+]
+
+[[package]]
+name = "nix"
+version = "0.31.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3"
+dependencies = [
+ "bitflags 2.13.0",
+ "cfg-if",
+ "cfg_aliases",
+ "libc",
+]
+
+[[package]]
+name = "no-std-net"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65"
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "nom"
+version = "8.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "nu-ansi-term"
+version = "0.50.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "num-conv"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
+
+[[package]]
+name = "num-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_threads"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "objc2"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05"
+dependencies = [
+ "objc2-encode",
+]
+
+[[package]]
+name = "objc2-app-kit"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
+dependencies = [
+ "bitflags 2.13.0",
+ "objc2",
+ "objc2-core-graphics",
+ "objc2-foundation",
+]
+
+[[package]]
+name = "objc2-core-foundation"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
+dependencies = [
+ "bitflags 2.13.0",
+ "dispatch2",
+ "objc2",
+]
+
+[[package]]
+name = "objc2-core-graphics"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
+dependencies = [
+ "bitflags 2.13.0",
+ "dispatch2",
+ "objc2",
+ "objc2-core-foundation",
+ "objc2-io-surface",
+]
+
+[[package]]
+name = "objc2-encode"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
+
+[[package]]
+name = "objc2-foundation"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
+dependencies = [
+ "bitflags 2.13.0",
+ "objc2",
+ "objc2-core-foundation",
+]
+
+[[package]]
+name = "objc2-io-surface"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
+dependencies = [
+ "bitflags 2.13.0",
+ "objc2",
+ "objc2-core-foundation",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+
+[[package]]
+name = "once_cell_polyfill"
+version = "1.70.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
+
+[[package]]
+name = "oorandom"
+version = "11.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
+
+[[package]]
+name = "openssl"
+version = "0.10.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967"
+dependencies = [
+ "bitflags 2.13.0",
+ "cfg-if",
+ "foreign-types",
+ "libc",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.116"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "ordered-float"
+version = "4.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "os_pipe"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "page_size"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "palette"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6"
+dependencies = [
+ "approx",
+ "fast-srgb8",
+ "libm",
+ "palette_derive",
+]
+
+[[package]]
+name = "palette_derive"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30"
+dependencies = [
+ "by_address",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-link",
+]
+
+[[package]]
+name = "pbkdf2"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629"
+dependencies = [
+ "digest 0.11.2",
+ "hmac",
+]
+
+[[package]]
+name = "pcap"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2eecc2ddc671ec563b5b39f846556aade68a65d1afb14d8fe6b30b0457d75"
+dependencies = [
+ "bitflags 1.3.2",
+ "errno 0.2.8",
+ "libc",
+ "libloading",
+ "pkg-config",
+ "regex",
+ "windows-sys 0.36.1",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
+
+[[package]]
+name = "pest"
+version = "2.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662"
+dependencies = [
+ "memchr",
+ "ucd-trie",
+]
+
+[[package]]
+name = "pest_derive"
+version = "2.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77"
+dependencies = [
+ "pest",
+ "pest_generator",
+]
+
+[[package]]
+name = "pest_generator"
+version = "2.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f"
+dependencies = [
+ "pest",
+ "pest_meta",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "pest_meta"
+version = "2.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220"
+dependencies = [
+ "pest",
+ "sha2 0.10.9",
+]
+
+[[package]]
+name = "petgraph"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455"
+dependencies = [
+ "fixedbitset 0.5.7",
+ "hashbrown 0.15.5",
+ "indexmap",
+]
+
+[[package]]
+name = "phf"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
+dependencies = [
+ "phf_macros",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
+dependencies = [
+ "phf_shared",
+ "rand",
+]
+
+[[package]]
+name = "phf_macros"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+
+[[package]]
+name = "plotters"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
+dependencies = [
+ "num-traits",
+ "plotters-backend",
+ "plotters-svg",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "plotters-backend"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
+
+[[package]]
+name = "plotters-svg"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
+dependencies = [
+ "plotters-backend",
+]
+
+[[package]]
+name = "pnet_base"
+version = "0.35.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffc190d4067df16af3aba49b3b74c469e611cad6314676eaf1157f31aa0fb2f7"
+dependencies = [
+ "no-std-net",
+]
+
+[[package]]
+name = "pnet_datalink"
+version = "0.35.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e79e70ec0be163102a332e1d2d5586d362ad76b01cec86f830241f2b6452a7b7"
+dependencies = [
+ "ipnetwork 0.20.0",
+ "libc",
+ "pnet_base",
+ "pnet_sys",
+ "winapi",
+]
+
+[[package]]
+name = "pnet_sys"
+version = "0.35.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d4643d3d4db6b08741050c2f3afa9a892c4244c085a72fcda93c9c2c9a00f4b"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "png"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0"
+dependencies = [
+ "bitflags 2.13.0",
+ "crc32fast",
+ "fdeflate",
+ "flate2",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "portable-atomic"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "ppmd-rust"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24"
+
+[[package]]
+name = "prettyplease"
+version = "0.2.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
+dependencies = [
+ "proc-macro2",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "procfs"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25485360a54d6861439d60facef26de713b1e126bf015ec8f98239467a2b82f7"
+dependencies = [
+ "bitflags 2.13.0",
+ "chrono",
+ "flate2",
+ "procfs-core",
+ "rustix",
+]
+
+[[package]]
+name = "procfs-core"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6401bf7b6af22f78b563665d15a22e9aef27775b79b149a66ca022468a4e405"
+dependencies = [
+ "bitflags 2.13.0",
+ "chrono",
+ "hex",
+]
+
+[[package]]
+name = "pxfm"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "quick-error"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
+
+[[package]]
+name = "quick-xml"
+version = "0.38.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "rand"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+
+[[package]]
+name = "ratatui"
+version = "0.30.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3274ba0a2c5e1bcad2a2005d20f4dc59dad26b2eb0940fb094500dba4099d57d"
+dependencies = [
+ "instability",
+ "ratatui-core",
+ "ratatui-crossterm",
+ "ratatui-macros",
+ "ratatui-termina",
+ "ratatui-termwiz",
+ "ratatui-widgets",
+ "serde",
+]
+
+[[package]]
+name = "ratatui-core"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbb175c433c8e28a809d1f5773a2ae96e68c0ce40db865cbab1020bf33ae479c"
+dependencies = [
+ "bitflags 2.13.0",
+ "compact_str",
+ "critical-section",
+ "hashbrown 0.17.1",
+ "itertools 0.14.0",
+ "kasuari",
+ "lru",
+ "palette",
+ "serde",
+ "strum",
+ "thiserror 2.0.18",
+ "unicode-segmentation",
+ "unicode-truncate",
+ "unicode-width",
+]
+
+[[package]]
+name = "ratatui-crossterm"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567584a3b0e6a8203c23de40b4861497266725eb5363dbfd18a1edd603cca9f0"
+dependencies = [
+ "cfg-if",
+ "crossterm",
+ "instability",
+ "ratatui-core",
+]
+
+[[package]]
+name = "ratatui-macros"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed7dc68daa7498a43e4d68e0eb078427e10c38fbcfbb1e42d955f1fa2140d814"
+dependencies = [
+ "ratatui-core",
+ "ratatui-widgets",
+]
+
+[[package]]
+name = "ratatui-termina"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0bf912d9e66f057a759d92e386a280ea886b352ab757d6ac4d653c7ed2c43c2"
+dependencies = [
+ "instability",
+ "ratatui-core",
+ "termina",
+]
+
+[[package]]
+name = "ratatui-termwiz"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "faf03e0380b7744054d6cb74224fe3adf062a029754933f575ca1e3b4c2ce977"
+dependencies = [
+ "ratatui-core",
+ "termwiz",
+]
+
+[[package]]
+name = "ratatui-widgets"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66e3d19bcc9130ca376277d93b60767ff121ace3be06f5f95f81dd68956407d1"
+dependencies = [
+ "bitflags 2.13.0",
+ "hashbrown 0.17.1",
+ "indoc",
+ "instability",
+ "itertools 0.14.0",
+ "line-clipping",
+ "ratatui-core",
+ "serde",
+ "strum",
+ "time",
+ "unicode-segmentation",
+ "unicode-width",
+]
+
+[[package]]
+name = "rayon"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
+dependencies = [
+ "bitflags 2.13.0",
+]
+
+[[package]]
+name = "regex"
+version = "1.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-lite"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973"
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c"
+
+[[package]]
+name = "ring"
+version = "0.17.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom 0.2.17",
+ "libc",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "roff"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbf2048e0e979efb2ca7b91c4f1a8d77c91853e9b987c94c555668a8994915ad"
+
+[[package]]
+name = "rustc-hash"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustix"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
+dependencies = [
+ "bitflags 2.13.0",
+ "errno 0.3.14",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "rustnet-capture"
+version = "0.3.0"
+dependencies = [
+ "anyhow",
+ "log",
+ "pcap",
+]
+
+[[package]]
+name = "rustnet-core"
+version = "0.3.0"
+dependencies = [
+ "aes",
+ "anyhow",
+ "crossbeam",
+ "dashmap",
+ "dns-lookup",
+ "flate2",
+ "log",
+ "maxminddb",
+ "pnet_datalink",
+ "ring",
+ "rustc-hash",
+]
+
+[[package]]
+name = "rustnet-host"
+version = "0.3.0"
+dependencies = [
+ "anyhow",
+ "libbpf-cargo",
+ "libbpf-rs",
+ "libc",
+ "log",
+ "procfs",
+ "rustnet-core",
+ "windows",
+]
+
+[[package]]
+name = "rustnet-monitor"
+version = "1.4.0"
+dependencies = [
+ "anyhow",
+ "arboard",
+ "caps",
+ "chrono",
+ "clap",
+ "clap_complete",
+ "clap_mangen",
+ "criterion",
+ "crossbeam",
+ "crossterm",
+ "dashmap",
+ "http_req",
+ "insta",
+ "landlock",
+ "libc",
+ "log",
+ "pcap",
+ "ratatui",
+ "regex-lite",
+ "rustc-hash",
+ "rustnet-capture",
+ "rustnet-core",
+ "rustnet-host",
+ "serde",
+ "serde_json",
+ "sha2 0.11.0",
+ "simplelog",
+ "windows",
+ "zip",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+
+[[package]]
+name = "ryu"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "schannel"
+version = "0.1.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "security-framework"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
+dependencies = [
+ "bitflags 2.13.0",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
+dependencies = [
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.150"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
+dependencies = [
+ "itoa",
+ "memchr",
+ "serde",
+ "serde_core",
+ "zmij",
+]
+
+[[package]]
+name = "sha1"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.0",
+ "digest 0.11.2",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.2.17",
+ "digest 0.10.7",
+]
+
+[[package]]
+name = "sha2"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.0",
+ "digest 0.11.2",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "signal-hook"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
+dependencies = [
+ "libc",
+ "signal-hook-registry",
+]
+
+[[package]]
+name = "signal-hook-mio"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc"
+dependencies = [
+ "libc",
+ "mio",
+ "signal-hook",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
+dependencies = [
+ "errno 0.3.14",
+ "libc",
+]
+
+[[package]]
+name = "simd-adler32"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
+
+[[package]]
+name = "similar"
+version = "2.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
+
+[[package]]
+name = "simplelog"
+version = "0.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16257adbfaef1ee58b1363bdc0664c9b8e1e30aed86049635fb5f147d065a9c0"
+dependencies = [
+ "log",
+ "termcolor",
+ "time",
+]
+
+[[package]]
+name = "siphasher"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
+
+[[package]]
+name = "smallvec"
+version = "1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+
+[[package]]
+name = "socket2"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0"
+dependencies = [
+ "libc",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "strip-ansi-escapes"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025"
+dependencies = [
+ "vte",
+]
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "strum"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd"
+dependencies = [
+ "strum_macros",
+]
+
+[[package]]
+name = "strum_macros"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
+dependencies = [
+ "fastrand",
+ "getrandom 0.3.4",
+ "once_cell",
+ "rustix",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "termina"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9048a889effe34a5cddee0af7f53285198b16dca3be510858d38dfdb3e62a04e"
+dependencies = [
+ "bitflags 2.13.0",
+ "parking_lot",
+ "rustix",
+ "signal-hook",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "terminfo"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662"
+dependencies = [
+ "fnv",
+ "nom 7.1.3",
+ "phf",
+ "phf_codegen",
+]
+
+[[package]]
+name = "termios"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "termwiz"
+version = "0.23.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7"
+dependencies = [
+ "anyhow",
+ "base64",
+ "bitflags 2.13.0",
+ "fancy-regex",
+ "filedescriptor",
+ "finl_unicode",
+ "fixedbitset 0.4.2",
+ "hex",
+ "lazy_static",
+ "libc",
+ "log",
+ "memmem",
+ "nix 0.29.0",
+ "num-derive",
+ "num-traits",
+ "ordered-float",
+ "pest",
+ "pest_derive",
+ "phf",
+ "sha2 0.10.9",
+ "signal-hook",
+ "siphasher",
+ "terminfo",
+ "termios",
+ "thiserror 1.0.69",
+ "ucd-trie",
+ "unicode-segmentation",
+ "vtparse",
+ "wezterm-bidi",
+ "wezterm-blob-leases",
+ "wezterm-color-types",
+ "wezterm-dynamic",
+ "wezterm-input-types",
+ "winapi",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
+dependencies = [
+ "thiserror-impl 2.0.18",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "tiff"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f"
+dependencies = [
+ "fax",
+ "flate2",
+ "half",
+ "quick-error",
+ "weezl",
+ "zune-jpeg",
+]
+
+[[package]]
+name = "time"
+version = "0.3.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
+dependencies = [
+ "deranged",
+ "itoa",
+ "js-sys",
+ "libc",
+ "num-conv",
+ "num_threads",
+ "powerfmt",
+ "serde_core",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
+
+[[package]]
+name = "time-macros"
+version = "0.2.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
+[[package]]
+name = "tinytemplate"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "tracing"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
+dependencies = [
+ "pin-project-lite",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
+dependencies = [
+ "chrono",
+ "nu-ansi-term",
+ "sharded-slab",
+ "thread_local",
+ "tracing-core",
+]
+
+[[package]]
+name = "tree_magic_mini"
+version = "3.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8765b90061cba6c22b5831f675da109ae5561588290f9fa2317adab2714d5a6"
+dependencies = [
+ "memchr",
+ "nom 8.0.0",
+ "petgraph",
+]
+
+[[package]]
+name = "typed-path"
+version = "0.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3015e6ce46d5ad8751e4a772543a30c7511468070e98e64e20165f8f81155b64"
+
+[[package]]
+name = "typenum"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
+
+[[package]]
+name = "ucd-trie"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
+
+[[package]]
+name = "unicase"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
+
+[[package]]
+name = "unicode-truncate"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5"
+dependencies = [
+ "itertools 0.14.0",
+ "unicode-segmentation",
+ "unicode-width",
+]
+
+[[package]]
+name = "unicode-width"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
+
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
+[[package]]
+name = "uuid"
+version = "1.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f"
+dependencies = [
+ "atomic",
+ "getrandom 0.3.4",
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "vsprintf"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aec2f81b75ca063294776b4f7e8da71d1d5ae81c2b1b149c8d89969230265d63"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "vte"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "vtparse"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d9b2acfb050df409c972a37d3b8e08cdea3bddb0c09db9d53137e504cfabed0"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasip2"
+version = "1.0.2+wasi-0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
+name = "wasip3"
+version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
+dependencies = [
+ "bumpalo",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "wasm-encoder"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
+dependencies = [
+ "leb128fmt",
+ "wasmparser",
+]
+
+[[package]]
+name = "wasm-metadata"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
+dependencies = [
+ "anyhow",
+ "indexmap",
+ "wasm-encoder",
+ "wasmparser",
+]
+
+[[package]]
+name = "wasmparser"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
+dependencies = [
+ "bitflags 2.13.0",
+ "hashbrown 0.15.5",
+ "indexmap",
+ "semver",
+]
+
+[[package]]
+name = "wayland-backend"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fee64194ccd96bf648f42a65a7e589547096dfa702f7cadef84347b66ad164f9"
+dependencies = [
+ "cc",
+ "downcast-rs",
+ "rustix",
+ "smallvec",
+ "wayland-sys",
+]
+
+[[package]]
+name = "wayland-client"
+version = "0.31.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8e6faa537fbb6c186cb9f1d41f2f811a4120d1b57ec61f50da451a0c5122bec"
+dependencies = [
+ "bitflags 2.13.0",
+ "rustix",
+ "wayland-backend",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-protocols"
+version = "0.32.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baeda9ffbcfc8cd6ddaade385eaf2393bd2115a69523c735f12242353c3df4f3"
+dependencies = [
+ "bitflags 2.13.0",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-protocols-wlr"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e9597cdf02cf0c34cd5823786dce6b5ae8598f05c2daf5621b6e178d4f7345f3"
+dependencies = [
+ "bitflags 2.13.0",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-scanner"
+version = "0.31.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5423e94b6a63e68e439803a3e153a9252d5ead12fd853334e2ad33997e3889e3"
+dependencies = [
+ "proc-macro2",
+ "quick-xml",
+ "quote",
+]
+
+[[package]]
+name = "wayland-sys"
+version = "0.31.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e6dbfc3ac5ef974c92a2235805cc0114033018ae1290a72e474aa8b28cbbdfd"
+dependencies = [
+ "pkg-config",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.85"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "weezl"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
+
+[[package]]
+name = "wezterm-bidi"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c0a6e355560527dd2d1cf7890652f4f09bb3433b6aadade4c9b5ed76de5f3ec"
+dependencies = [
+ "log",
+ "wezterm-dynamic",
+]
+
+[[package]]
+name = "wezterm-blob-leases"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "692daff6d93d94e29e4114544ef6d5c942a7ed998b37abdc19b17136ea428eb7"
+dependencies = [
+ "getrandom 0.3.4",
+ "mac_address",
+ "sha2 0.10.9",
+ "thiserror 1.0.69",
+ "uuid",
+]
+
+[[package]]
+name = "wezterm-color-types"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7de81ef35c9010270d63772bebef2f2d6d1f2d20a983d27505ac850b8c4b4296"
+dependencies = [
+ "csscolorparser",
+ "deltae",
+ "lazy_static",
+ "wezterm-dynamic",
+]
+
+[[package]]
+name = "wezterm-dynamic"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f2ab60e120fd6eaa68d9567f3226e876684639d22a4219b313ff69ec0ccd5ac"
+dependencies = [
+ "log",
+ "ordered-float",
+ "strsim",
+ "thiserror 1.0.69",
+ "wezterm-dynamic-derive",
+]
+
+[[package]]
+name = "wezterm-dynamic-derive"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46c0cf2d539c645b448eaffec9ec494b8b19bd5077d9e58cb1ae7efece8d575b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "wezterm-input-types"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7012add459f951456ec9d6c7e6fc340b1ce15d6fc9629f8c42853412c029e57e"
+dependencies = [
+ "bitflags 1.3.2",
+ "euclid",
+ "lazy_static",
+ "serde",
+ "wezterm-dynamic",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows"
+version = "0.62.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
+dependencies = [
+ "windows-collections",
+ "windows-core",
+ "windows-future",
+ "windows-numerics",
+]
+
+[[package]]
+name = "windows-collections"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
+dependencies = [
+ "windows-core",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.62.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-future"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
+dependencies = [
+ "windows-core",
+ "windows-link",
+ "windows-threading",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-numerics"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
+dependencies = [
+ "windows-core",
+ "windows-link",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
+dependencies = [
+ "windows_aarch64_msvc 0.36.1",
+ "windows_i686_gnu 0.36.1",
+ "windows_i686_msvc 0.36.1",
+ "windows_x86_64_gnu 0.36.1",
+ "windows_x86_64_msvc 0.36.1",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
+dependencies = [
+ "windows-targets 0.53.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm 0.52.6",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.53.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
+dependencies = [
+ "windows-link",
+ "windows_aarch64_gnullvm 0.53.1",
+ "windows_aarch64_msvc 0.53.1",
+ "windows_i686_gnu 0.53.1",
+ "windows_i686_gnullvm 0.53.1",
+ "windows_i686_msvc 0.53.1",
+ "windows_x86_64_gnu 0.53.1",
+ "windows_x86_64_gnullvm 0.53.1",
+ "windows_x86_64_msvc 0.53.1",
+]
+
+[[package]]
+name = "windows-threading"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
+
+[[package]]
+name = "wit-bindgen"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
+dependencies = [
+ "wit-bindgen-rust-macro",
+]
+
+[[package]]
+name = "wit-bindgen-core"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
+dependencies = [
+ "anyhow",
+ "heck",
+ "wit-parser",
+]
+
+[[package]]
+name = "wit-bindgen-rust"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
+dependencies = [
+ "anyhow",
+ "heck",
+ "indexmap",
+ "prettyplease",
+ "syn 2.0.117",
+ "wasm-metadata",
+ "wit-bindgen-core",
+ "wit-component",
+]
+
+[[package]]
+name = "wit-bindgen-rust-macro"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
+dependencies = [
+ "anyhow",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+ "wit-bindgen-core",
+ "wit-bindgen-rust",
+]
+
+[[package]]
+name = "wit-component"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
+dependencies = [
+ "anyhow",
+ "bitflags 2.13.0",
+ "indexmap",
+ "log",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "wasm-encoder",
+ "wasm-metadata",
+ "wasmparser",
+ "wit-parser",
+]
+
+[[package]]
+name = "wit-parser"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
+dependencies = [
+ "anyhow",
+ "id-arena",
+ "indexmap",
+ "log",
+ "semver",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "unicode-xid",
+ "wasmparser",
+]
+
+[[package]]
+name = "wl-clipboard-rs"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e9651471a32e87d96ef3a127715382b2d11cc7c8bb9822ded8a7cc94072eb0a3"
+dependencies = [
+ "libc",
+ "log",
+ "os_pipe",
+ "rustix",
+ "thiserror 2.0.18",
+ "tree_magic_mini",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-protocols-wlr",
+]
+
+[[package]]
+name = "x11rb"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414"
+dependencies = [
+ "gethostname",
+ "rustix",
+ "x11rb-protocol",
+]
+
+[[package]]
+name = "x11rb-protocol"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
+
+[[package]]
+name = "zerocopy"
+version = "0.8.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
+dependencies = [
+ "zeroize_derive",
+]
+
+[[package]]
+name = "zeroize_derive"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "zip"
+version = "8.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b"
+dependencies = [
+ "aes",
+ "bzip2",
+ "constant_time_eq",
+ "crc32fast",
+ "deflate64",
+ "flate2",
+ "getrandom 0.4.1",
+ "hmac",
+ "indexmap",
+ "lzma-rust2",
+ "memchr",
+ "pbkdf2",
+ "ppmd-rust",
+ "sha1",
+ "time",
+ "typed-path",
+ "zeroize",
+ "zopfli",
+ "zstd",
+]
+
+[[package]]
+name = "zlib-rs"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7948af682ccbc3342b6e9420e8c51c1fe5d7bf7756002b4a3c6cabfe96a7e3c"
+
+[[package]]
+name = "zmij"
+version = "1.0.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445"
+
+[[package]]
+name = "zopfli"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249"
+dependencies = [
+ "bumpalo",
+ "crc32fast",
+ "log",
+ "simd-adler32",
+]
+
+[[package]]
+name = "zstd"
+version = "0.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
+dependencies = [
+ "zstd-safe",
+]
+
+[[package]]
+name = "zstd-safe"
+version = "7.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
+dependencies = [
+ "zstd-sys",
+]
+
+[[package]]
+name = "zstd-sys"
+version = "2.0.16+zstd.1.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
+
+[[package]]
+name = "zune-core"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
+
+[[package]]
+name = "zune-jpeg"
+version = "0.4.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713"
+dependencies = [
+ "zune-core",
+]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..ad8fbc4
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,245 @@
+[workspace]
+members = [".", "crates/rustnet-core", "crates/rustnet-capture", "crates/rustnet-host"]
+resolver = "3"
+
+# Shared metadata for every crate in the workspace. Members opt in with
+# `.workspace = true`. The binary keeps its own `version` (it tracks the
+# user-facing 1.x release line, independent of the 0.x library crates).
+[workspace.package]
+version = "0.3.0"
+authors = ["domcyrus"]
+edition = "2024"
+rust-version = "1.88.0" # Let-chains require Rust 1.88.0+
+repository = "https://github.com/domcyrus/rustnet"
+homepage = "https://github.com/domcyrus/rustnet"
+license = "Apache-2.0"
+
+# Single source of truth for the internal crate versions (and the few deps
+# shared across crates). Bumping a library version is now a one-line edit here
+# instead of one edit per member manifest. Members reference these with
+# `.workspace = true`.
+[workspace.dependencies]
+rustnet-core = { version = "0.3.0", path = "crates/rustnet-core" }
+rustnet-capture = { version = "0.3.0", path = "crates/rustnet-capture" }
+rustnet-host = { version = "0.3.0", path = "crates/rustnet-host" }
+anyhow = "1.0"
+log = "0.4"
+
+[package]
+name = "rustnet-monitor"
+version = "1.4.0"
+authors.workspace = true
+edition.workspace = true
+rust-version.workspace = true
+description = "A cross-platform network monitoring terminal UI tool built with Rust"
+repository.workspace = true
+homepage.workspace = true
+documentation = "https://docs.rs/rustnet-monitor"
+readme = "README.md"
+license.workspace = true
+keywords = ["network", "monitoring", "tui", "ebpf", "packet-capture"]
+categories = ["command-line-utilities", "network-programming", "visualization"]
+exclude = [".github/", "scripts/", "tests/", "*.log", "target/", ".gitignore"]
+
+[lib]
+name = "rustnet_monitor"
+path = "src/lib.rs"
+
+[[bin]]
+name = "rustnet"
+path = "src/main.rs"
+# The bin re-compiles every src/* module that lib.rs also re-exports.
+# Running `cargo test` on the bin therefore duplicates every unit test
+# under a different crate prefix, which breaks snapshot-test file paths
+# (`rustnet__...` vs `rustnet_monitor__...`). Tests live in the lib and
+# in `tests/`; the bin is just the entry point.
+test = false
+
+[dependencies]
+rustnet-core.workspace = true
+rustnet-capture.workspace = true
+rustnet-host.workspace = true
+anyhow.workspace = true
+libc = "0.2"
+arboard = { version = "3.6", features = ["wayland-data-control"] }
+crossterm = "0.29"
+crossbeam = "0.8"
+dashmap = "6.2"
+log.workspace = true
+pcap = "2.4.0"
+clap = { version = "4.6", features = ["derive"] }
+simplelog = "0.12"
+chrono = "0.4"
+ratatui = { version = "0.30", features = ["all-widgets"] }
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+regex-lite = "0.1"
+# Note: dns-lookup, ring, aes, flate2, and maxminddb moved to rustnet-core,
+# which is the only place they are used. They are re-exported transitively
+# through the `crate::network` facade in src/network/mod.rs.
+
+[target.'cfg(target_os = "linux")'.dependencies]
+# procfs + libbpf-rs moved to the rustnet-host crate (process attribution).
+landlock = { version = "0.4", optional = true }
+caps = { version = "0.5", optional = true }
+
+[target.'cfg(windows)'.dependencies]
+windows = { version = "0.62", features = [
+ "Win32_Foundation",
+ "Win32_NetworkManagement_IpHelper",
+ "Win32_NetworkManagement_Ndis",
+ "Win32_Networking_WinSock",
+ "Win32_Security",
+ "Win32_System_JobObjects",
+ "Win32_System_LibraryLoader",
+ "Win32_System_Threading",
+] }
+
+# FreeBSD support uses the system's sockstat command for process lookup
+# and native libpcap (via pcap crate) for packet capture.
+# No additional FreeBSD-specific dependencies required at this time.
+
+[build-dependencies]
+anyhow.workspace = true
+clap = { version = "4.6", features = ["derive"] }
+clap_complete = "4.6"
+clap_mangen = "0.3"
+
+[target.'cfg(windows)'.build-dependencies]
+http_req = "0.14"
+zip = "8.6"
+sha2 = "0.11"
+windows = { version = "0.62", features = [
+ "Win32_Foundation",
+ "Win32_NetworkManagement_IpHelper",
+ "Win32_NetworkManagement_Ndis",
+ "Win32_Networking_WinSock",
+ "Win32_Security",
+ "Win32_System_LibraryLoader",
+ "Win32_System_Threading",
+] }
+
+[features]
+# eBPF is enabled by default for enhanced performance on Linux.
+# On non-Linux platforms, this feature has no effect as all eBPF code
+# and dependencies are Linux-specific (guarded by target_os checks).
+# Landlock provides security sandboxing on Linux 5.13+.
+# macos-sandbox provides Seatbelt sandboxing on macOS 10.5+ (no extra deps).
+default = ["ebpf", "landlock", "macos-sandbox"]
+linux-default = ["ebpf"] # Deprecated: kept for backwards compatibility
+# eBPF now lives in the rustnet-host crate; this is a passthrough so
+# `--features ebpf` (and the default) keep enabling it as before.
+ebpf = ["rustnet-host/ebpf"]
+landlock = ["dep:landlock", "dep:caps"]
+macos-sandbox = []
+# Kubernetes pod and container resolution from cgroup paths and (Phase 2) the
+# on-disk kubelet pods directory. Linux-only at runtime; no extra dependencies.
+kubernetes = ["rustnet-core/kubernetes"]
+
+# Minimal cross configuration to override dependency conflicts
+[workspace.metadata.cross.build.env]
+passthrough = [
+ "CARGO_INCREMENTAL",
+ "CARGO_NET_RETRY",
+ "CARGO_NET_TIMEOUT",
+]
+
+[package.metadata.deb]
+maintainer = "domcyrus "
+copyright = "2024, domcyrus "
+license-file = ["LICENSE", "4"]
+extended-description = """\
+A real-time network monitoring terminal UI tool built with Rust.
+
+Features:
+- Real-time network monitoring with detailed state information
+- Deep packet inspection for HTTP/HTTPS, DNS, SSH, and QUIC
+- Connection lifecycle management with configurable timeouts
+- Process identification and service name resolution
+- Advanced filtering with vim/fzf-style search
+- Multi-threaded processing for optimal performance
+- eBPF-enhanced process detection on Linux (with automatic fallback)
+"""
+depends = "libpcap0.8, libelf1"
+section = "net"
+priority = "optional"
+assets = [
+ [
+ "target/release/rustnet",
+ "usr/bin/",
+ "755",
+ ],
+ [
+ "README.md",
+ "usr/share/doc/rustnet-monitor/",
+ "644",
+ ],
+ [
+ "crates/rustnet-core/assets/services",
+ "usr/share/rustnet-monitor/",
+ "644",
+ ],
+ [
+ "resources/packaging/linux/graphics/rustnet.png",
+ "usr/share/icons/hicolor/256x256/apps/",
+ "644",
+ ],
+ [
+ "resources/packaging/linux/rustnet.desktop",
+ "usr/share/applications/",
+ "644",
+ ],
+]
+conf-files = []
+
+[package.metadata.generate-rpm]
+assets = [
+ { source = "target/release/rustnet", dest = "/usr/bin/rustnet", mode = "755" },
+ { source = "README.md", dest = "/usr/share/doc/rustnet-monitor/README.md", mode = "644" },
+ { source = "crates/rustnet-core/assets/services", dest = "/usr/share/rustnet-monitor/services", mode = "644" },
+ { source = "resources/packaging/linux/graphics/rustnet.png", dest = "/usr/share/icons/hicolor/256x256/apps/rustnet.png", mode = "644" },
+ { source = "resources/packaging/linux/rustnet.desktop", dest = "/usr/share/applications/rustnet.desktop", mode = "644" },
+]
+[package.metadata.generate-rpm.requires]
+libpcap = "*"
+elfutils-libelf = "*"
+
+[package.metadata.wix]
+upgrade-guid = "455c823b-9665-43e0-baa4-bd0fcb762463"
+path-guid = "d3a2452e-f04f-4d4f-becf-c3580f49f8fc"
+license = false
+eula = false
+
+[dev-dependencies]
+criterion = { version = "0.8", features = ["html_reports"] }
+insta = { version = "1", features = ["filters"] }
+rustc-hash = "2"
+
+[[bench]]
+name = "packet_parsing"
+harness = false
+
+[[bench]]
+name = "connection_merge"
+harness = false
+
+[[bench]]
+name = "snapshot"
+harness = false
+
+[[bench]]
+name = "rate_tracker"
+harness = false
+
+[[bench]]
+name = "tracker_ingest"
+harness = false
+
+[profile.release]
+lto = "thin"
+codegen-units = 1
+
+[profile.profiling]
+inherits = "release"
+debug = 2
+strip = false
diff --git a/Cross.toml b/Cross.toml
new file mode 100644
index 0000000..74cca66
--- /dev/null
+++ b/Cross.toml
@@ -0,0 +1,21 @@
+[target.aarch64-unknown-linux-gnu]
+image = "ghcr.io/cross-rs/aarch64-unknown-linux-gnu:edge"
+pre-build = [
+ "dpkg --add-architecture $CROSS_DEB_ARCH",
+ "apt-get update -y",
+ "apt-get install -y libelf-dev:arm64 zlib1g-dev:arm64 libpcap-dev:arm64 gcc-aarch64-linux-gnu protobuf-compiler libseccomp-dev:arm64 libbpf-dev clang llvm"
+]
+
+[target.armv7-unknown-linux-gnueabihf]
+image = "ghcr.io/cross-rs/armv7-unknown-linux-gnueabihf:edge"
+pre-build = [
+ "dpkg --add-architecture $CROSS_DEB_ARCH",
+ "apt-get update -y",
+ "apt-get install -y libelf-dev:armhf zlib1g-dev:armhf libpcap-dev:armhf gcc-arm-linux-gnueabihf protobuf-compiler libseccomp-dev:armhf libbpf-dev clang llvm"
+]
+
+[target.x86_64-unknown-freebsd]
+# FreeBSD cross-compilation uses a Linux container with FreeBSD sysroot
+# The sysroot should already contain libpcap
+# No pre-build steps needed as we can't run FreeBSD package manager in Linux container
+
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..e407a94
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,111 @@
+# Multi-stage Docker build for RustNet
+# Base images are pinned by digest for reproducible, tamper-evident builds.
+FROM rust:1.96-slim@sha256:31ee7fc65186be7e0e0ccb3f2ca305f14e4739e7642a1ae65753aa5d7b874523 AS builder
+
+# Install rustfmt component (required for eBPF compilation)
+RUN rustup component add rustfmt
+
+# Install build dependencies
+RUN apt-get update && apt-get install -y \
+ libpcap-dev \
+ libelf-dev \
+ zlib1g-dev \
+ clang \
+ llvm \
+ make \
+ pkg-config \
+ && rm -rf /var/lib/apt/lists/*
+
+# Set working directory
+WORKDIR /app
+
+# Copy Cargo files first for better caching
+COPY Cargo.toml Cargo.lock ./
+
+# Copy build script (manpage/completions; npcap on Windows)
+COPY build.rs ./
+
+# Copy source code
+COPY src ./src
+COPY benches ./benches
+# Workspace member crates. rustnet-core holds the baked-in oui.gz / services
+# assets (include_bytes!/include_str!); rustnet-host holds the eBPF programs and
+# bundled vmlinux headers compiled by its own build.rs.
+COPY crates ./crates
+
+# Build the application in release mode (eBPF is enabled by default on Linux).
+# Optional features can be added with --build-arg CARGO_FEATURES=kubernetes
+# (additive: default features stay on). The CI Kubernetes image variant passes
+# CARGO_FEATURES=kubernetes; the default image leaves it empty.
+ARG CARGO_FEATURES=""
+RUN if [ -n "$CARGO_FEATURES" ]; then \
+ cargo build --release --features "$CARGO_FEATURES"; \
+ else \
+ cargo build --release; \
+ fi
+
+# Runtime stage - use trixie-slim to match GLIBC version from builder
+# Pinned by digest for a reproducible, tamper-evident base image.
+FROM debian:trixie-slim@sha256:28de0877c2189802884ccd20f15ee41c203573bd87bb6b883f5f46362d24c5c2
+
+# Install runtime dependencies
+# libcap2-bin provides setcap, used below to grant packet-capture capabilities
+# to the binary so it can run as a non-root user.
+RUN apt-get update && apt-get install -y \
+ libpcap0.8 \
+ libelf1 \
+ zlib1g \
+ ca-certificates \
+ libcap2-bin \
+ && rm -rf /var/lib/apt/lists/*
+
+# Create a non-root user. The container runs as this user (see USER below);
+# packet-capture privileges are granted via file capabilities on the binary
+# rather than by running as root.
+RUN useradd -r -s /bin/false rustnet
+
+# Set working directory
+WORKDIR /app
+
+# Copy the binary from builder stage
+COPY --from=builder /app/target/release/rustnet /usr/local/bin/rustnet
+
+# Copy the services asset for reference (the binary already embeds it at build time)
+COPY --from=builder /app/crates/rustnet-core/assets/services ./assets/services
+
+# Create logs directory
+RUN mkdir -p /app/logs && chown rustnet:rustnet /app/logs
+
+# Set executable permissions and grant CAP_NET_RAW as a file capability so the
+# binary can capture packets as a non-root user. NET_RAW is in Docker's default
+# capability bounding set, so `docker run rustnet` works without extra flags.
+#
+# Only NET_RAW is baked in on purpose: a file capability that is NOT also in the
+# container's bounding set makes execve() fail with EPERM. BPF/PERFMON are not
+# in Docker's default set, so eBPF is handled at runtime instead (see below).
+RUN chmod +x /usr/local/bin/rustnet \
+ && setcap 'cap_net_raw=ep' /usr/local/bin/rustnet
+
+# Expose no ports by default (rustnet is for monitoring, not serving)
+# Network access is handled via host networking or packet capture capabilities
+
+# Add labels for better image metadata
+LABEL org.opencontainers.image.title="RustNet"
+LABEL org.opencontainers.image.description="A cross-platform network monitoring tool with deep packet inspection"
+LABEL org.opencontainers.image.source="https://github.com/domcyrus/rustnet"
+LABEL org.opencontainers.image.licenses="Apache License, Version 2.0"
+
+# RustNet runs as the non-root 'rustnet' user. CAP_NET_RAW is baked into the
+# binary as a file capability and is part of Docker's default bounding set, so
+# basic packet capture works out of the box:
+# docker run rustnet
+# eBPF-based process attribution needs BPF+PERFMON, which are NOT in the default
+# bounding set and can't be granted to a non-root user via file capabilities.
+# Enable eBPF by running as root with the extra caps (modern kernels 5.8+):
+# docker run --user root --cap-add=BPF --cap-add=PERFMON rustnet
+# Legacy kernels require broad CAP_SYS_ADMIN for eBPF. RustNet does not
+# recommend granting it by default; without eBPF caps, rustnet falls back to
+# /proc-based process detection.
+# CAP_NET_ADMIN is NOT required (read-only, non-promiscuous capture).
+USER rustnet
+ENTRYPOINT ["rustnet"]
diff --git a/Dockerfile.static b/Dockerfile.static
new file mode 100644
index 0000000..42bb80a
--- /dev/null
+++ b/Dockerfile.static
@@ -0,0 +1,61 @@
+# Dockerfile for building static musl-linked RustNet binary
+#
+# Usage (with eBPF - default, recommended):
+# docker build -f Dockerfile.static -t rustnet-static .
+# docker run --rm -v $(pwd)/dist:/dist rustnet-static cp /build/target/release/rustnet /dist/
+#
+# Usage (without eBPF - smaller binary, ~5.2MB vs ~6.5MB):
+# docker build -f Dockerfile.static --build-arg FEATURES="--no-default-features" -t rustnet-static .
+
+FROM rust:alpine
+
+ARG FEATURES=""
+
+# Install build dependencies
+# - musl-dev: musl C library headers
+# - libpcap-dev: libpcap headers and static library (/usr/lib/libpcap.a)
+# - pkgconfig: for finding library paths
+# - build-base: basic build tools (make, gcc, etc.)
+# - perl: required by some build scripts (ring crate)
+# - elfutils-dev: libelf for eBPF (includes static library)
+# - zlib-dev/zlib-static: compression library
+# - zstd-dev/zstd-static: Zstandard compression (required by elfutils 0.189+)
+# - clang/llvm: for eBPF compilation
+# - linux-headers: kernel headers for eBPF
+RUN apk add --no-cache \
+ musl-dev \
+ libpcap-dev \
+ pkgconfig \
+ build-base \
+ perl \
+ elfutils-dev \
+ zlib-dev \
+ zlib-static \
+ zstd-dev \
+ zstd-static \
+ clang \
+ llvm \
+ linux-headers
+
+# Add rustfmt for eBPF skeleton generation
+RUN rustup component add rustfmt
+
+WORKDIR /build
+
+# Copy source code
+COPY . .
+
+# Configure static linking for zstd
+# This fixes the elfutils 0.189+ undeclared dependency on zstd
+# See: https://github.com/libbpf/bpftool/issues/152
+RUN mkdir -p .cargo && printf '[target.x86_64-unknown-linux-musl]\nrustflags = ["-C", "link-arg=-l:libzstd.a"]\n' > .cargo/config.toml
+
+# Build configuration:
+# - In Alpine/musl, binaries are statically linked by default
+# - FEATURES arg controls whether eBPF is included (default) or disabled
+# - --release: Optimized build
+RUN cargo build --release ${FEATURES}
+
+# Verify the binary is statically linked
+RUN file target/release/rustnet && \
+ ldd target/release/rustnet 2>&1 || echo "Binary is statically linked"
diff --git a/INSTALL.md b/INSTALL.md
new file mode 100644
index 0000000..bd173ce
--- /dev/null
+++ b/INSTALL.md
@@ -0,0 +1,1160 @@
+English | 简体中文
+
+# Installation Guide
+
+This guide covers all installation methods for RustNet across different platforms.
+
+> **Tip:** For an at-a-glance view of which distributions package RustNet and which version each carries, see [RustNet on Repology](https://repology.org/project/rustnet/versions).
+
+## Table of Contents
+
+- [Installing from Release Packages](#installing-from-release-packages)
+ - [macOS DMG Installation](#macos-dmg-installation)
+ - [Windows MSI Installation](#windows-msi-installation)
+ - [Windows Chocolatey Installation](#windows-chocolatey-installation)
+ - [Linux Package Installation](#linux-package-installation)
+ - [FreeBSD Installation](#freebsd-installation)
+ - [Android (Termux) Installation](#android-termux-installation)
+- [Install via Cargo](#install-via-cargo)
+- [Building from Source](#building-from-source)
+- [Using Docker](#using-docker)
+- [Prerequisites](#prerequisites)
+- [Permissions Setup](#permissions-setup)
+- [GeoIP Databases (Optional)](#geoip-databases-optional)
+- [Troubleshooting](#troubleshooting)
+
+## Installing from Release Packages
+
+Pre-built packages are available for each release on the [GitHub Releases](https://github.com/domcyrus/rustnet/releases) page.
+
+### macOS DMG Installation
+
+> ** Prefer Homebrew?** If you have Homebrew installed, using `brew install` is easier and avoids Gatekeeper bypass steps. See [Homebrew Installation](#homebrew-installation) for instructions.
+
+1. **Download** the appropriate DMG for your architecture:
+ - `Rustnet_macOS_AppleSilicon.dmg` for Apple Silicon Macs (M1/M2/M3)
+ - `Rustnet_macOS_Intel.dmg` for Intel-based Macs
+
+2. **Open the DMG** and drag Rustnet.app to your Applications folder
+
+3. **Bypass Gatekeeper** (for unsigned builds):
+ - When you first try to open RustNet, macOS will block it because the app is not signed
+ - Go to **System Settings → Privacy & Security**
+ - Scroll down to find the message about RustNet being blocked
+ - Click **"Open Anyway"** to allow the application to run
+ - You may need to confirm this choice when launching the app again
+
+4. **Run RustNet**:
+ - Double-click Rustnet.app to launch it in a Terminal window with sudo
+ - Or run from command line: `sudo /Applications/Rustnet.app/Contents/MacOS/rustnet`
+
+5. **Optional: Create a symlink for shell access**:
+ ```bash
+ # Create a symlink so you can run 'rustnet' from anywhere
+ sudo ln -s /Applications/Rustnet.app/Contents/MacOS/rustnet /usr/local/bin/rustnet
+
+ # Now you can run from any terminal:
+ sudo rustnet
+ ```
+
+6. **Optional: Setup BPF permissions** (to avoid needing sudo):
+ - Install Wireshark's BPF permission helper: `brew install --cask wireshark-chmodbpf`
+ - Log out and back in for group changes to take effect
+ - See the [Permissions Setup](#permissions-setup) section for detailed instructions
+
+### Windows MSI Installation
+
+1. **Install Npcap Runtime** (required for packet capture):
+ - Download from https://npcap.com/dist/
+ - Run the installer and select **"WinPcap API compatible mode"**
+
+2. **Download and install** the appropriate MSI package:
+ - `Rustnet_Windows_64-bit.msi` for 64-bit Windows
+ - `Rustnet_Windows_32-bit.msi` for 32-bit Windows
+
+3. **Run the installer** and follow the installation wizard
+
+4. **Run RustNet**:
+ - Open Command Prompt or PowerShell
+ - Run: `rustnet.exe`
+ - If Npcap is not installed or not in WinPcap compatible mode, RustNet will display a helpful error message with installation instructions
+ - Note: Depending on your Npcap installation settings, you may or may not need Administrator privileges
+
+### Windows Chocolatey Installation
+
+The easiest way to install RustNet on Windows is via [Chocolatey](https://community.chocolatey.org/packages/rustnet):
+
+```powershell
+# Run in Administrator PowerShell
+choco install rustnet
+```
+
+**Note:** You still need to install [Npcap](https://npcap.com) separately with "WinPcap API compatible mode" enabled.
+
+### Linux Package Installation
+
+#### Ubuntu PPA (Recommended for Ubuntu 25.10 Questing and 26.04 LTS Resolute)
+
+The easiest way to install RustNet on Ubuntu is via the official PPA. The PPA publishes builds for the following Ubuntu series:
+
+- Ubuntu 25.10 (Questing Quokka)
+- Ubuntu 26.04 LTS (Resolute Raccoon)
+
+```bash
+# Add the RustNet PPA
+sudo add-apt-repository ppa:domcyrus/rustnet
+
+# Update package list
+sudo apt update
+
+# Install rustnet
+sudo apt install rustnet
+
+# Run with sudo
+sudo rustnet
+
+# Optional: Grant capabilities to run without sudo (modern kernel 5.8+)
+sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' /usr/bin/rustnet
+rustnet
+```
+
+**Important:** The PPA supports only the two series listed above (Ubuntu 25.10 Questing and 26.04 LTS Resolute) because the build requires Rust 1.88+ (used for let-chains in the project). Earlier Ubuntu versions don't ship a recent enough `rustc` in their repositories. For older Ubuntu versions, use the [.deb packages](#debianubuntu-deb-packages) from GitHub releases or [build from source](#building-from-source).
+
+#### Debian/Ubuntu (.deb packages)
+
+For manual installation or non-Ubuntu Debian-based distributions:
+
+```bash
+# Download the appropriate package for your architecture:
+# - Rustnet_LinuxDEB_amd64.deb (x86_64)
+# - Rustnet_LinuxDEB_arm64.deb (ARM64)
+# - Rustnet_LinuxDEB_armhf.deb (ARMv7)
+
+# Install the package (capabilities are automatically configured)
+sudo dpkg -i Rustnet_LinuxDEB_amd64.deb
+
+# Install dependencies if needed
+sudo apt-get install -f
+
+# Run without sudo (capabilities were set by post-install script)
+rustnet
+
+# Verify capabilities
+getcap /usr/bin/rustnet
+```
+
+**Note:** The .deb package automatically sets Linux capabilities via post-install script, so you can run RustNet without sudo.
+
+#### RedHat/Fedora/CentOS (.rpm packages)
+
+For manual installation or distributions not using COPR:
+
+```bash
+# Download the appropriate package for your architecture:
+# - Rustnet_LinuxRPM_x86_64.rpm
+# - Rustnet_LinuxRPM_aarch64.rpm
+
+# Install the package (capabilities are automatically configured)
+sudo rpm -i Rustnet_LinuxRPM_x86_64.rpm
+# Or with dnf/yum:
+sudo dnf install Rustnet_LinuxRPM_x86_64.rpm
+
+# Run without sudo (capabilities were set by post-install script)
+rustnet
+
+# Verify capabilities
+getcap /usr/bin/rustnet
+```
+
+**Note:** The .rpm package automatically sets Linux capabilities via post-install script, so you can run RustNet without sudo.
+
+#### Arch Linux
+
+This package is included in the Arch Linux Extra repository ([link](https://archlinux.org/packages/extra/x86_64/rustnet/)). It can be installed using pacman:
+```bash
+sudo pacman -S rustnet
+```
+
+Alternatively, two AUR packages are available:
+- [`rustnet-bin`](https://aur.archlinux.org/packages/rustnet-bin) - Pre-compiled binary from GitHub Releases
+- [`rustnet-git`](https://aur.archlinux.org/packages/rustnet-git) - Build from source and use the latest commit (maintained by [@DeepChirp](https://github.com/DeepChirp))
+
+Install with your preferred AUR helper:
+```bash
+# Pre-compiled binary from GitHub Releases
+yay -S rustnet-bin
+
+# OR source build from the latest commit
+yay -S rustnet-git
+```
+
+#### Nix / NixOS
+
+RustNet is available in [nixpkgs](https://search.nixos.org/packages?query=rustnet), including the **stable** channels (as well as `nixpkgs-unstable`).
+
+**Try it without installing (ephemeral shell):**
+
+```bash
+nix-shell -p rustnet
+# Then inside the shell:
+sudo rustnet
+```
+
+**Persistent install on NixOS** — add to `/etc/nixos/configuration.nix`:
+
+```nix
+environment.systemPackages = with pkgs; [
+ rustnet
+];
+```
+
+Then run `sudo nixos-rebuild switch`.
+
+**Note on permissions:** NixOS's `/nix/store` is read-only, so `sudo setcap` on the binary won't persist across rebuilds. The simplest option is `sudo rustnet`. For sudo-less operation, define a NixOS [security.wrappers](https://search.nixos.org/options?channel=unstable&query=security.wrappers) entry that carries the capabilities:
+
+```nix
+security.wrappers.rustnet = {
+ source = "${pkgs.rustnet}/bin/rustnet";
+ owner = "root";
+ group = "root";
+ capabilities = "cap_net_raw,cap_bpf,cap_perfmon+eip";
+};
+```
+
+Then run `rustnet` via the wrapper path (`/run/wrappers/bin/rustnet`).
+
+> **Coming soon — dedicated NixOS module.** A [`programs.rustnet`
+> module](https://github.com/NixOS/nixpkgs/pull/517620) is in review for
+> nixpkgs. Once merged it wraps exactly the capabilities above for you, so the
+> whole setup becomes:
+>
+> ```nix
+> programs.rustnet.enable = true;
+> ```
+>
+> This grants `cap_net_raw`, `cap_bpf`, and `cap_perfmon` (but **not**
+> `cap_net_admin`, since RustNet never needs promiscuous mode) through
+> `security.wrappers` — the same pattern `programs.mtr` and
+> `programs.wireshark` use — letting you run `rustnet` without sudo.
+
+#### Fedora (COPR - Recommended for Fedora 42+)
+
+The easiest way to install RustNet on Fedora is via the official COPR repository.
+
+```bash
+# Enable the COPR repository
+sudo dnf copr enable domcyrus/rustnet
+
+# Install rustnet
+sudo dnf install rustnet
+
+# Run with sudo
+sudo rustnet
+
+# Optional: Grant capabilities to run without sudo (modern kernel 5.8+)
+sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' /usr/bin/rustnet
+rustnet
+```
+
+**Important:** The COPR only supports Fedora 42 and 43 due to the Rust 1.88+ requirement. CentOS and RHEL don't have recent enough Rust compilers in their repositories. For those distributions, use the [.rpm packages](#redhatfedoracentos-rpm-packages) from GitHub releases or [build from source](#building-from-source).
+
+#### openSUSE Tumbleweed (OBS)
+
+RustNet is built for openSUSE Tumbleweed (x86_64 and aarch64) via the [openSUSE Build Service](https://build.opensuse.org/package/show/home:domcyrus:rustnet/rustnet).
+
+```bash
+sudo zypper addrepo https://download.opensuse.org/repositories/home:/domcyrus:/rustnet/openSUSE_Tumbleweed/home:domcyrus:rustnet.repo
+sudo zypper refresh
+sudo zypper install rustnet
+
+# Run with sudo
+sudo rustnet
+
+# Optional: Grant capabilities to run without sudo (modern kernel 5.8+)
+sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' /usr/bin/rustnet
+rustnet
+```
+
+#### Homebrew Installation
+
+**On macOS:**
+```bash
+brew install rustnet
+
+# Follow the caveats displayed after installation for permission setup
+```
+
+**On Linux:**
+```bash
+brew install rustnet
+
+# Grant capabilities to the Homebrew-installed binary (modern kernel 5.8+)
+sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' $(brew --prefix)/bin/rustnet
+
+# Run without sudo
+rustnet
+```
+
+#### Static Binary (Portable - Any Linux Distribution)
+
+For maximum portability, static binaries are available that work on **any Linux distribution** regardless of GLIBC version. These are fully self-contained and require no system dependencies.
+
+```bash
+# Download the static binary for your architecture:
+# - rustnet-vX.Y.Z-x86_64-unknown-linux-musl.tar.gz (x86_64)
+# - rustnet-vX.Y.Z-aarch64-unknown-linux-musl.tar.gz (ARM64)
+
+# Extract the archive
+tar xzf rustnet-vX.Y.Z-x86_64-unknown-linux-musl.tar.gz
+
+# Move binary to PATH
+sudo mv rustnet-vX.Y.Z-x86_64-unknown-linux-musl/rustnet /usr/local/bin/
+
+# Grant capabilities (modern kernel 5.8+)
+sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' /usr/local/bin/rustnet
+
+# Run without sudo
+rustnet
+```
+
+**When to use static binaries:**
+- Older distributions with outdated GLIBC (e.g., CentOS 7, older Ubuntu)
+- Minimal/containerized environments
+- Air-gapped systems where installing dependencies is difficult
+- When you want a single portable binary
+
+### FreeBSD Installation
+
+FreeBSD support is available starting from version 0.15.0.
+
+#### From Ports or Packages (Future)
+
+Once available in FreeBSD ports:
+```bash
+# Using pkg (binary packages)
+pkg install rustnet
+
+# Or build from ports
+cd /usr/ports/net/rustnet && make install clean
+```
+
+#### From GitHub Releases
+
+Download the FreeBSD binary from the [rustnet-bsd releases](https://github.com/domcyrus/rustnet-bsd/releases):
+
+```bash
+# Download the appropriate package
+fetch https://github.com/domcyrus/rustnet-bsd/releases/download/vX.Y.Z/rustnet-vX.Y.Z-x86_64-unknown-freebsd.tar.gz
+
+# Extract the archive
+tar xzf rustnet-vX.Y.Z-x86_64-unknown-freebsd.tar.gz
+
+# Move binary to PATH
+sudo mv rustnet-vX.Y.Z-x86_64-unknown-freebsd/rustnet /usr/local/bin/
+
+# Make it executable
+sudo chmod +x /usr/local/bin/rustnet
+
+# Run with sudo
+sudo rustnet
+```
+
+#### Building from Source on FreeBSD
+
+```bash
+# Install dependencies
+pkg install rust libpcap
+
+# Clone the repository
+git clone https://github.com/domcyrus/rustnet.git
+cd rustnet
+
+# Build in release mode
+cargo build --release
+
+# The executable will be in target/release/rustnet
+sudo ./target/release/rustnet
+```
+
+#### Permission Setup for FreeBSD
+
+FreeBSD requires access to BPF (Berkeley Packet Filter) devices for packet capture.
+
+**Option 1: Run with sudo (Simplest)**
+```bash
+sudo rustnet
+```
+
+**Option 2: Add user to the bpf group (Recommended)**
+```bash
+# Add your user to the bpf group
+sudo pw groupmod bpf -m $(whoami)
+
+# Log out and back in for group changes to take effect
+
+# Now run without sudo
+rustnet
+```
+
+**Option 3: Change BPF device permissions (Temporary)**
+```bash
+# This will reset on reboot
+sudo chmod o+rw /dev/bpf*
+
+# Now run without sudo
+rustnet
+```
+
+**Verifying FreeBSD Permissions:**
+```bash
+# Check if you're in the bpf group
+groups | grep bpf
+
+# Check BPF device permissions
+ls -la /dev/bpf*
+
+# Test without sudo
+rustnet --help
+```
+
+### Android (Termux) Installation
+
+RustNet can run on Android devices via [Termux](https://termux.dev/en/), provided the device is rooted.
+
+Because Android strictly controls network and process information, RustNet requires `root` access (`su`) to capture packets and identify processes. A specialized Android build is provided that statically links dependencies and disables Linux-specific features (like eBPF and Landlock) that are incompatible with Android's kernel environment.
+
+#### Prerequisites
+1. **Rooted** Android device (e.g., via Magisk or KernelSU)
+2. **Termux** installed (from F-Droid or GitHub, *not* Google Play)
+
+#### Installation Steps
+
+1. **Install required packages in Termux:**
+ ```bash
+ pkg update
+ pkg install tsu wget tar
+ ```
+
+2. **Download the Android binary:**
+ ```bash
+ # Download the Android-specific static binary from GitHub Releases
+ wget https://github.com/domcyrus/rustnet/releases/download/vX.Y.Z/rustnet-vX.Y.Z-aarch64-linux-android-musl.tar.gz
+ ```
+
+3. **Extract and install:**
+ ```bash
+ tar xzf rustnet-vX.Y.Z-aarch64-linux-android-musl.tar.gz
+
+ # Move it to a directory in your PATH
+ mv rustnet-vX.Y.Z-aarch64-linux-android-musl/rustnet $PREFIX/bin/
+ chmod +x $PREFIX/bin/rustnet
+ ```
+
+4. **Run RustNet as root:**
+ ```bash
+ # You must run RustNet with root privileges for it to function on Android
+ sudo rustnet
+ ```
+ *Note: On first run, your root manager (e.g., Magisk) will prompt you to grant Superuser access to Termux.*
+
+## Install via Cargo
+
+```bash
+# Install directly from crates.io
+cargo install rustnet-monitor
+
+# The binary will be installed to ~/.cargo/bin/rustnet
+# Make sure ~/.cargo/bin is in your PATH
+```
+
+After installation, see the [Permissions Setup](#permissions-setup) section to configure permissions.
+
+## Building from Source
+
+### Prerequisites
+
+- Rust 2024 edition or later (install from [rustup.rs](https://rustup.rs/))
+- Platform-specific dependencies:
+ - **Linux (Debian/Ubuntu)**:
+ ```bash
+ sudo apt-get install build-essential pkg-config libpcap-dev libelf-dev zlib1g-dev clang llvm
+ ```
+ - **Linux (RedHat/CentOS/Fedora)**:
+ ```bash
+ sudo yum install make pkgconfig libpcap-devel elfutils-libelf-devel zlib-devel clang llvm
+ ```
+ - **macOS**: Install Xcode Command Line Tools: `xcode-select --install`
+ - **FreeBSD**: `pkg install rust libpcap`
+ - **Windows**: Install Npcap and Npcap SDK (see [Windows Build Setup](#windows-build-setup) below)
+
+### Basic Build
+
+```bash
+# Clone the repository
+git clone https://github.com/domcyrus/rustnet.git
+cd rustnet
+
+# Build in release mode (eBPF is enabled by default on Linux)
+cargo build --release
+
+# To build WITHOUT eBPF support (procfs-only mode on Linux)
+cargo build --release --no-default-features
+
+# The executable will be in target/release/rustnet
+```
+
+To build without eBPF (procfs-only mode), use `cargo build --release --no-default-features`.
+
+### Windows Build Setup
+
+Building RustNet on Windows requires the Npcap SDK and proper environment configuration:
+
+#### Build Requirements
+
+1. **Download and Install Npcap SDK**:
+ - Download the Npcap SDK from https://npcap.com/dist/
+ - Extract the SDK to a directory (e.g., `C:\npcap-sdk`)
+
+2. **Set Environment Variables**:
+ - Set the `LIB` environment variable to include the SDK's library path:
+ ```cmd
+ set LIB=%LIB%;C:\npcap-sdk\Lib\x64
+ ```
+ - For PowerShell:
+ ```powershell
+ $env:LIB = "$env:LIB;C:\npcap-sdk\Lib\x64"
+ ```
+ - For permanent setup, add this to your system environment variables
+
+3. **Build RustNet**:
+ ```cmd
+ cargo build --release
+ ```
+
+#### Runtime Requirements
+
+1. **Install Npcap Runtime**:
+ - Download the Npcap installer from https://npcap.com/dist/
+ - Run the installer and **select "WinPcap API compatible mode"** during installation
+ - This ensures compatibility with the packet capture library
+
+2. **Run RustNet**:
+ ```cmd
+ rustnet.exe
+ ```
+
+**Note**: Depending on your Npcap installation settings, you may or may not need Administrator privileges. If you didn't select the option to restrict packet capture to administrators during Npcap installation, RustNet can run with normal user privileges.
+
+## Using Docker
+
+RustNet is available as a Docker container from GitHub Container Registry. The
+image runs as a **non-root** user by default, with `CAP_NET_RAW` baked into the
+binary as a file capability — so basic packet capture works with no extra flags.
+
+```bash
+# Pull the latest image
+docker pull ghcr.io/domcyrus/rustnet:latest
+
+# Or pull a specific version
+docker pull ghcr.io/domcyrus/rustnet:0.7.0
+
+# Option A — basic monitoring (non-root, recommended)
+# Captures packets via the built-in CAP_NET_RAW file capability. Process
+# attribution uses /proc (eBPF disabled). No --cap-add needed.
+docker run --rm -it --net=host ghcr.io/domcyrus/rustnet:latest
+
+# Option B — full eBPF process attribution (runs as root + extra caps)
+# eBPF needs CAP_BPF + CAP_PERFMON. A non-root user can't make these effective
+# from --cap-add alone, so enable them by also running as root.
+docker run --rm -it --user root \
+ --cap-add=NET_RAW --cap-add=BPF --cap-add=PERFMON --net=host \
+ ghcr.io/domcyrus/rustnet:latest
+
+# Run with a specific interface (either option; -i flag at the end)
+docker run --rm -it --net=host ghcr.io/domcyrus/rustnet:latest -i eth0
+
+# Alternative: privileged mode (simplest, least secure)
+docker run --rm -it --privileged --net=host \
+ ghcr.io/domcyrus/rustnet:latest
+
+# View available options
+docker run --rm ghcr.io/domcyrus/rustnet:latest --help
+```
+
+**Note:** Basic capture (Option A) needs no special flags — the image carries
+`CAP_NET_RAW` as a file capability and runs non-root. eBPF-based process
+attribution (Option B) additionally needs `CAP_BPF` + `CAP_PERFMON`; because
+those can't be granted to a non-root user via file capabilities, enable them by
+running as root (`--user root`) with the matching `--cap-add` flags. Either way,
+rustnet drops these capabilities and sandboxes itself immediately after startup.
+Host networking (`--net=host`) is recommended for monitoring all interfaces.
+
+## Permissions Setup
+
+RustNet requires elevated privileges to capture network packets because accessing network interfaces for packet capture is a privileged operation on all modern operating systems. This section explains how to properly grant these permissions on different platforms.
+
+> ### **Security Advantage: Read-Only Network Access on Linux**
+>
+> **RustNet uses read-only packet capture without promiscuous mode on all platforms.** This means:
+>
+> **Linux:** Requires only **`CAP_NET_RAW`** capability - **NOT** full root or `CAP_NET_ADMIN`
+> **Principle of Least Privilege:** Minimal permissions needed for packet capture
+> **No Promiscuous Mode:** Only captures packets to/from the host (not all network traffic)
+> **Read-Only:** Cannot modify or inject packets
+> **Enhanced Security:** Reduced attack surface compared to full root access
+>
+> **macOS Note:** PKTAP (for process metadata) requires root privileges, but you can run without sudo using the `lsof` fallback for basic packet capture.
+
+### Why Permissions Are Required
+
+Network packet capture requires access to:
+
+- **Raw sockets** for low-level network access (read-only, non-promiscuous mode)
+- **Network interfaces** for packet capture
+- **BPF (Berkeley Packet Filter) devices** on macOS/BSD systems
+- **Network namespaces** on some Linux configurations
+
+These capabilities are restricted to prevent malicious software from intercepting network traffic.
+
+### macOS Permission Setup
+
+On macOS, packet capture requires access to BPF (Berkeley Packet Filter) devices located at `/dev/bpf*`.
+
+**Note:** macOS PKTAP (for extracting process metadata from packets) requires **root/sudo** privileges. Without sudo, RustNet uses `lsof` as a fallback for process detection (slower but works without root).
+
+#### Option 1: Run with sudo (Simplest)
+
+```bash
+# Build and run with sudo
+cargo build --release
+sudo ./target/release/rustnet
+```
+
+#### Option 2: BPF Group Access (Recommended)
+
+Add your user to the `access_bpf` group for passwordless packet capture:
+
+**Using Wireshark's ChmodBPF (For basic packet capture):**
+
+```bash
+# Install Wireshark's BPF permission helper
+brew install --cask wireshark-chmodbpf
+
+# Log out and back in for group changes to take effect
+# Then run rustnet without sudo:
+rustnet # Uses lsof for process detection (slower)
+
+# For PKTAP support with process metadata from packet headers, use sudo:
+sudo rustnet # Uses PKTAP for faster process detection
+```
+
+**Note**: `wireshark-chmodbpf` grants access to `/dev/bpf*` for packet capture, but **PKTAP** is a separate privileged kernel interface that requires root privileges regardless of BPF permissions. The TUI will display which detection method is active ("pktap" with sudo, or "lsof" without).
+
+**Manual BPF Group Setup:**
+
+```bash
+# Create the access_bpf group (if it doesn't exist)
+sudo dseditgroup -o create access_bpf
+
+# Add your user to the group
+sudo dseditgroup -o edit -a $USER -t user access_bpf
+
+# Set permissions on BPF devices (this needs to be done after each reboot)
+sudo chmod g+rw /dev/bpf*
+sudo chgrp access_bpf /dev/bpf*
+
+# Log out and back in for group membership to take effect
+```
+
+#### Option 3: Homebrew Installation
+
+If installed via Homebrew, the formula will provide detailed setup instructions:
+
+```bash
+brew install rustnet
+# Follow the caveats displayed after installation
+```
+
+### Linux Permission Setup (Read-Only Access - No Root Required!)
+
+**Linux Advantage:** RustNet requires **only `CAP_NET_RAW`** for packet capture - far less than full root access!
+
+On Linux, packet capture requires only the `CAP_NET_RAW` capability for read-only, non-promiscuous packet capture. For eBPF-enhanced process tracking, additional capabilities (`CAP_BPF` and `CAP_PERFMON`) are needed, but **`CAP_NET_ADMIN` is NOT required**.
+
+#### Option 1: Run with sudo (Simplest)
+
+```bash
+# Build and run with sudo
+cargo build --release
+sudo ./target/release/rustnet
+```
+
+#### Option 2: Grant Capabilities (Recommended)
+
+Grant specific network capabilities to the binary without full root privileges:
+
+**For source builds:**
+
+```bash
+# Build the binary first
+cargo build --release
+
+# Grant capabilities to the binary (modern kernel 5.8+, with eBPF support)
+sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' ./target/release/rustnet
+
+# Now run without sudo
+./target/release/rustnet
+```
+
+**For cargo-installed binaries:**
+
+```bash
+# If installed via cargo install rustnet-monitor (modern kernel 5.8+)
+sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' ~/.cargo/bin/rustnet
+
+# Now run without sudo
+rustnet
+```
+
+**For eBPF-enabled builds (enhanced Linux performance - enabled by default):**
+
+eBPF is enabled by default on Linux and provides lower-overhead process identification using kernel probes:
+
+```bash
+# Build in release mode (eBPF is enabled by default)
+cargo build --release
+
+# Modern Linux (5.8+) - works with just these three capabilities:
+sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' ./target/release/rustnet
+./target/release/rustnet
+
+# Packet capture only - eBPF falls back to procfs:
+sudo setcap 'cap_net_raw+eip' ./target/release/rustnet
+./target/release/rustnet
+
+# Check TUI Statistics panel - should show "Process Detection: eBPF + procfs"
+```
+
+**Capability requirements:**
+
+**Base capability (always required):**
+- `CAP_NET_RAW` - Raw socket access for read-only packet capture (non-promiscuous mode)
+
+**eBPF-specific capabilities (Linux 5.8+):**
+- `CAP_BPF` - BPF program loading and map operations
+- `CAP_PERFMON` - Performance monitoring and tracing operations
+
+**Legacy Linux (pre-5.8):**
+Older kernels require broad `CAP_SYS_ADMIN` for eBPF operations. RustNet does
+not recommend or automatically grant it. Use `CAP_NET_RAW` only and let process
+attribution fall back to procfs unless you explicitly accept the extra risk.
+
+**Note:** CAP_NET_ADMIN is NOT required. RustNet uses read-only packet capture without promiscuous mode.
+
+**Fallback behavior**: If eBPF cannot load (e.g., insufficient capabilities, incompatible kernel), the application automatically uses procfs-only mode. The TUI Statistics panel displays which detection method is active:
+- `Process Detection: eBPF + procfs` - eBPF successfully loaded
+- `Process Detection: procfs` - Using procfs fallback
+
+**Note:** eBPF is enabled by default on Linux builds and may have limitations with process name display. See [ARCHITECTURE.md](ARCHITECTURE.md) for details on eBPF implementation. To build without eBPF, use `cargo build --release --no-default-features`.
+
+**For system-wide installation:**
+
+```bash
+# If installed via package manager or copied to /usr/local/bin (modern kernel 5.8+)
+sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' /usr/local/bin/rustnet
+rustnet
+```
+
+### Windows Permission Setup
+
+Windows support is currently limited, but when available:
+
+- RustNet will require **Administrator privileges**
+- Must install **WinPcap** or **Npcap** for packet capture
+- Run Command Prompt or PowerShell "As Administrator"
+
+### Verifying Permissions
+
+To verify that permissions are set up correctly:
+
+#### macOS
+
+```bash
+# Check BPF device permissions
+ls -la /dev/bpf*
+
+# Check group membership
+groups | grep access_bpf
+
+# Test without sudo
+rustnet --help
+```
+
+#### Linux
+
+```bash
+# Check capabilities on the binary
+# For source builds:
+getcap ./target/release/rustnet
+
+# For cargo-installed binaries:
+getcap ~/.cargo/bin/rustnet
+
+# For system-wide installations:
+getcap $(which rustnet)
+
+# eBPF enabled: Should show cap_net_raw,cap_bpf,cap_perfmon+eip
+# Packet capture only: Should show cap_net_raw=eip
+
+# Test without sudo
+rustnet --help
+```
+
+## GeoIP Databases (Optional)
+
+RustNet supports GeoIP lookups to show country codes, city names, and ASN information for remote IPs. To enable this, install the [GeoLite2](https://dev.maxmind.com/geoip/geolite2-free-geolocation-data) databases using MaxMind's `geoipupdate` tool (requires a free [MaxMind account](https://www.maxmind.com/en/geolite2/signup)).
+
+**Available databases:**
+
+| Database | Provides | Flag |
+|---|---|---|
+| `GeoLite2-Country.mmdb` | Country code and name | *(auto-discovered)* |
+| `GeoLite2-ASN.mmdb` | ASN number and organization | *(auto-discovered)* |
+| `GeoLite2-City.mmdb` | City name, postal code, **and** country | *(auto-discovered)* |
+
+> **Tip:** `GeoLite2-City` is a superset of `GeoLite2-Country`. If you install the City database you do not need to also install the Country database.
+
+### Configuring which databases to download
+
+In your `GeoIP.conf`, set `EditionIDs` to include the databases you want:
+
+```
+# Country + ASN only:
+EditionIDs GeoLite2-Country GeoLite2-ASN
+
+# City + ASN (City includes country data):
+EditionIDs GeoLite2-City GeoLite2-ASN
+
+# All three:
+EditionIDs GeoLite2-Country GeoLite2-ASN GeoLite2-City
+```
+
+### macOS (Homebrew)
+
+```bash
+brew install geoipupdate
+# Edit the config with your MaxMind account credentials and EditionIDs:
+# $(brew --prefix)/etc/GeoIP.conf
+geoipupdate
+```
+
+Databases are installed to `$(brew --prefix)/share/GeoIP/`.
+
+### Ubuntu/Debian
+
+```bash
+sudo apt-get install geoipupdate
+# Edit /etc/GeoIP.conf with your MaxMind account credentials and EditionIDs
+sudo geoipupdate
+```
+
+Databases are installed to `/usr/share/GeoIP/`.
+
+### Fedora/RHEL
+
+```bash
+sudo dnf install geoipupdate
+# Edit /etc/GeoIP.conf with your MaxMind account credentials and EditionIDs
+sudo geoipupdate
+```
+
+Databases are installed to `/usr/share/GeoIP/`.
+
+### Arch Linux
+
+```bash
+sudo pacman -S geoipupdate
+# Edit /etc/GeoIP.conf with your MaxMind account credentials and EditionIDs
+sudo geoipupdate
+```
+
+Databases are installed to `/usr/share/GeoIP/`.
+
+### FreeBSD
+
+```bash
+pkg install geoipupdate
+# Edit /usr/local/etc/GeoIP.conf with your MaxMind account credentials and EditionIDs
+sudo geoipupdate
+```
+
+Databases are installed to `/usr/local/share/GeoIP/`.
+
+### Manual Specification
+
+If your databases are in a non-standard location, specify them directly:
+
+```bash
+# Country + ASN:
+rustnet --geoip-country /path/to/GeoLite2-Country.mmdb --geoip-asn /path/to/GeoLite2-ASN.mmdb
+
+# City + ASN (City includes country data):
+rustnet --geoip-city /path/to/GeoLite2-City.mmdb --geoip-asn /path/to/GeoLite2-ASN.mmdb
+```
+
+RustNet auto-discovers databases from standard locations. Run `rustnet --help` to see the full search path list.
+
+## Troubleshooting
+
+### Common Installation Issues
+
+#### Permission Denied Errors
+
+**On macOS:**
+
+- Ensure you're in the `access_bpf` group: `groups | grep access_bpf`
+- Check BPF device permissions: `ls -la /dev/bpf0`
+- Try running with sudo to confirm it's a permission issue
+- Log out and back in after group changes
+
+**On Linux:**
+
+- Check if capabilities are set: `getcap $(which rustnet)` or `getcap ~/.cargo/bin/rustnet`
+- Verify libpcap is installed: `ldconfig -p | grep pcap`
+- Try running with sudo to confirm it's a permission issue: `sudo $(which rustnet)`
+
+#### No Suitable Capture Interfaces Found
+
+- Check available interfaces: `ip link show` (Linux) or `ifconfig` (macOS)
+- Try specifying an interface explicitly: `rustnet -i eth0`
+- Ensure the interface is up and has an IP address
+- Some virtual interfaces may not support packet capture
+
+#### Operation Not Permitted (with capabilities set)
+
+- Capabilities may have been removed by system updates
+- Re-apply capabilities (modern): `sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' $(which rustnet)`
+- Some filesystems don't support extended attributes (capabilities)
+- Try copying the binary to a different filesystem (e.g., from NFS to local disk)
+
+#### eBPF Unavailable Despite Capabilities Being Set
+
+If RustNet shows `Process Detection: procfs` with a degradation message even
+after running `setcap`, work through the following checks. The TUI surfaces
+the actual reason on the second status line — use it to jump to the right
+section below.
+
+**1. `file caps ignored: binary on a nosuid mount`**
+
+The kernel silently ignores file capabilities for binaries that live on a
+filesystem mounted with the `nosuid` option. Common culprits: `/home` on
+hardened distros, `/tmp`, removable media, some bind-mounts inside
+containers.
+
+```bash
+# Find the mount that holds the binary and check its options
+findmnt -T $(realpath $(which rustnet)) -o TARGET,OPTIONS
+# If the OPTIONS column contains "nosuid", caps will not work.
+
+# Fix: install or copy the binary to a mount without nosuid
+sudo install -m 0755 $(which rustnet) /usr/local/bin/rustnet
+sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' /usr/local/bin/rustnet
+/usr/local/bin/rustnet
+```
+
+**2. `BPF denied (check perf_event_paranoid / AppArmor / unprivileged_bpf_disabled)`**
+
+Caps were granted, but the kernel returned `EPERM` or `EACCES`. Three layers
+can do that — check them in this order:
+
+```bash
+# 2a. perf_event_paranoid (THE most common cause on Debian).
+# Debian 13 ships with kernel.perf_event_paranoid=3, which blocks
+# perf_event_open(2) — and therefore kprobe attach — for non-root
+# users *even with CAP_PERFMON*. Upstream kernels only go up to 2,
+# where CAP_PERFMON correctly bypasses the restriction.
+#
+# Ubuntu uses a different patch (paranoid=4) that was updated in
+# late 2025 to honor CAP_PERFMON, so on recent Ubuntu kernels
+# (Jammy 5.15.0-165+, Noble 6.8.0-91+, Plucky 6.14.0-37+,
+# Questing 6.17.0-14+, Resolute 6.18.0-8+) `setcap` alone is
+# enough — no sysctl change required. Debian's equivalent patch
+# (bug #994044) was never updated and was archived in 2025
+# without a fix, so Debian users still need the workaround below.
+sysctl kernel.perf_event_paranoid
+# If the value is 3 (Debian) or 4 on an old Ubuntu kernel, drop it to 2:
+sudo sysctl kernel.perf_event_paranoid=2
+# Make it persist across reboot:
+echo 'kernel.perf_event_paranoid = 2' | \
+ sudo tee /etc/sysctl.d/99-rustnet.conf
+
+# 2b. AppArmor confining rustnet (Debian/Ubuntu install AppArmor by default).
+sudo aa-status | grep rustnet
+# If listed, either disable the profile or add a rule allowing capability bpf,
+# capability perfmon, and the bpf() syscall for this binary.
+
+# 2c. unprivileged_bpf_disabled (Debian sets =2; file caps should bypass).
+sysctl kernel.unprivileged_bpf_disabled
+
+# Confirm caps actually became effective at exec:
+grep ^Cap /proc/$(pgrep -n rustnet)/status
+# CapEff must include CAP_BPF (bit 39) and CAP_PERFMON (bit 38).
+```
+
+**3. `kprobe attach failed: `**
+
+The kernel is missing the symbol the eBPF probe wants to attach to. This is
+usually a kernel-config issue (e.g. CONFIG_IPV6 disabled, CONFIG_KPROBES
+off, or the symbol was inlined). RustNet currently attaches to
+`tcp_connect`, `inet_csk_accept`, `udp_sendmsg`, `tcp_v6_connect`,
+`udpv6_sendmsg`, `ping_v4_sendmsg`, and `ping_v6_sendmsg`.
+
+```bash
+# Check whether the failing symbol exists in the running kernel:
+sudo grep '' /proc/kallsyms
+```
+
+If the symbol is genuinely missing, eBPF process detection will not work
+on this kernel build; procfs fallback continues to function.
+
+**4. `kernel BTF unavailable`**
+
+CO-RE relocations require `/sys/kernel/btf/vmlinux`. On stripped-down
+kernels (some embedded / minimal cloud images) this file is absent.
+
+```bash
+ls /sys/kernel/btf/vmlinux
+# If missing: install the kernel-debuginfo / linux-image package matching
+# your running kernel, or rebuild the kernel with CONFIG_DEBUG_INFO_BTF=y.
+```
+
+**5. Inside Docker / Podman**
+
+Even with file capabilities, the *bounding* set inside the container must
+contain `CAP_BPF` and `CAP_PERFMON`, or they get masked at exec:
+
+```bash
+docker run --cap-add=NET_RAW --cap-add=BPF --cap-add=PERFMON \
+ --net=host --pid=host rustnet
+# Optionally, if your container's seccomp profile blocks bpf(2):
+# --security-opt seccomp=unconfined
+# Or if AppArmor mediates BPF:
+# --security-opt apparmor=unconfined
+```
+
+**6. `eBPF load failed: ...`**
+
+The catch-all branch carries the raw libbpf error text. Re-run with
+`RUST_LOG=debug rustnet 2>&1 | tee rustnet.log` and inspect the full
+chain — it usually contains an `errno` name (`EPERM`, `EACCES`, `ENOSPC`
+for memlock, etc.) that points at the root cause.
+
+#### Windows: Npcap Not Found
+
+- Ensure Npcap is installed from https://npcap.com/dist/
+- During Npcap installation, select **"WinPcap API compatible mode"**
+- Verify Npcap service is running: `sc query npcap`
+- Try reinstalling Npcap with administrator privileges
+
+#### Build Errors
+
+**Windows - Npcap SDK not found:**
+- Ensure the `LIB` environment variable includes the Npcap SDK path
+- Check that the SDK is extracted to a directory without spaces
+- Use the correct architecture (x64 vs x86) for your Rust toolchain
+
+**Linux build fails:**
+```bash
+# Install all required dependencies
+# Debian/Ubuntu
+sudo apt-get install build-essential pkg-config libpcap-dev libelf-dev zlib1g-dev clang llvm
+
+# RedHat/CentOS/Fedora
+sudo yum install make pkgconfig libpcap-devel elfutils-libelf-devel zlib-devel clang llvm
+```
+
+#### Windows: Graphs Display Incorrectly in PowerShell
+
+If graphs and sparklines appear corrupted (showing question marks or garbled characters) in PowerShell, this is a **font issue**, not a RustNet bug. The default console fonts (Consolas, Lucida Console) lack support for Unicode Braille characters used for graph rendering.
+
+**Solution:** Install a font with Unicode Braille support:
+
+1. Download and install [Iosevka](https://typeof.net/Iosevka/) or any [Nerd Font](https://www.nerdfonts.com/)
+2. Open PowerShell Properties (right-click title bar → Properties)
+3. Select the installed font in the Font tab
+4. Restart PowerShell
+
+**Alternative:** Use [Windows Terminal](https://aka.ms/terminal) which has better Unicode support out of the box.
+
+See also: [ratatui#457](https://github.com/ratatui/ratatui/issues/457), [gtop#21](https://github.com/aksakalli/gtop/issues/21)
+
+#### macOS: High Terminal CPU or Gappy Graphs (iTerm2)
+
+RustNet's graphs are drawn with Unicode Braille characters. The classic
+macOS monospace fonts (Monaco, Menlo) have no Braille glyphs, so iTerm2
+renders every graph cell through font fallback. This is slow (iTerm2 can
+use 10-20% CPU just displaying the graphs) and the fallback glyphs often
+leave visible gaps in the waves.
+
+**Solution:** Give iTerm2 a font with Braille coverage for non-ASCII text:
+
+1. Install a [Nerd Font](https://www.nerdfonts.com/), e.g.:
+
+ ```bash
+ brew install --cask font-jetbrains-mono-nerd-font
+ ```
+
+2. In iTerm2: **Settings → Profiles → Text**, enable **"Use a different
+ font for non-ASCII text"** and select the Nerd Font. Your regular
+ text keeps its current font; only symbols and graph glyphs use the
+ new one.
+3. Optional: **Settings → General → Preferences → "Maximize throughput"**
+ caps iTerm2's redraw rate at 30 fps, which further reduces CPU with
+ frequently updating TUIs.
+
+**Alternative:** Use a GPU-accelerated terminal such as
+[WezTerm](https://wezterm.org/), [Ghostty](https://ghostty.org/), or
+[kitty](https://sw.kovidgoyal.net/kitty/). These render RustNet with
+much lower CPU than iTerm2, and WezTerm ships JetBrains Mono with symbol
+fallback built in, so the graphs render correctly with zero
+configuration.
+
+### Getting Help
+
+If you encounter issues not covered here:
+
+1. Enable debug logging: `rustnet --log-level debug`
+2. Check the log file in the `logs/` directory
+3. Open an issue on [GitHub](https://github.com/domcyrus/rustnet/issues) with:
+ - Your operating system and version
+ - Installation method used
+ - Error messages from logs
+ - Output of permission verification commands
+
+### Security Best Practices
+
+1. **Use capabilities instead of sudo** when possible (Linux)
+2. **Use group-based access** instead of running as root (macOS)
+3. **Regularly audit** which users have packet capture privileges
+4. **Consider network segmentation** if running on production systems
+5. **Monitor log files** for unauthorized usage
+6. **Remove capabilities** when RustNet is no longer needed:
+
+ ```bash
+ # Linux: Remove capabilities
+ sudo setcap -r /path/to/rustnet
+
+ # macOS: Remove from group
+ sudo dseditgroup -o edit -d $USER -t user access_bpf
+ ```
+
+### Integration with System Monitoring
+
+For production environments, consider:
+
+- **Audit logging** of packet capture access
+- **Network monitoring policies** and compliance requirements
+- **User access reviews** for privileged network access
+- **Automated capability management** in configuration management systems
+
+This permissions setup ensures RustNet can capture packets while maintaining security best practices and principle of least privilege.
diff --git a/INSTALL.zh-CN.md b/INSTALL.zh-CN.md
new file mode 100644
index 0000000..20555dc
--- /dev/null
+++ b/INSTALL.zh-CN.md
@@ -0,0 +1,1123 @@
+English | 简体中文
+
+# 安装指南
+
+本文档涵盖 RustNet 在各平台上的所有安装方法。
+
+> **提示:** 想一眼看清哪些发行版打包了 RustNet、各自分发的版本号是多少,请查看 [Repology 上的 RustNet 页面](https://repology.org/project/rustnet/versions)。
+
+## 目录
+
+- [从发布包安装](#installing-from-release-packages)
+ - [macOS DMG 安装](#macos-dmg-installation)
+ - [Windows MSI 安装](#windows-msi-installation)
+ - [Windows Chocolatey 安装](#windows-chocolatey-installation)
+ - [Linux 包安装](#linux-package-installation)
+ - [FreeBSD 安装](#freebsd-installation)
+ - [Android(Termux)安装](#android-termux-installation)
+- [通过 Cargo 安装](#install-via-cargo)
+- [从源码构建](#building-from-source)
+- [使用 Docker](#using-docker)
+- [前置要求](#prerequisites)
+- [权限配置](#permissions-setup)
+- [GeoIP 数据库(可选)](#geoip-databases-optional)
+- [故障排查](#troubleshooting)
+
+## 从发布包安装
+
+预构建包可在每个版本的 [GitHub Releases](https://github.com/domcyrus/rustnet/releases) 页面下载。
+
+### macOS DMG 安装
+
+> **更喜欢 Homebrew?** 如果你已安装 Homebrew,使用 `brew install` 更简单,且无需绕过 Gatekeeper 步骤。参见 [Homebrew 安装](#homebrew-installation)了解详情。
+
+1. **下载**适合你架构的 DMG:
+ - Apple Silicon Mac(M1/M2/M3)使用 `Rustnet_macOS_AppleSilicon.dmg`
+ - Intel Mac 使用 `Rustnet_macOS_Intel.dmg`
+
+2. **打开 DMG** 并将 Rustnet.app 拖拽到 Applications 文件夹
+
+3. **绕过 Gatekeeper**(针对未签名构建):
+ - 首次尝试打开 RustNet 时,macOS 会阻止它,因为应用未签名
+ - 前往 **系统设置 → 隐私与安全性**
+ - 向下滚动找到 RustNet 被阻止的消息
+ - 点击 **"仍要打开"** 以允许应用运行
+ - 再次启动应用时可能需要确认此选择
+
+4. **运行 RustNet**:
+ - 双击 Rustnet.app 以在带 sudo 的终端窗口中启动
+ - 或从命令行运行:`sudo /Applications/Rustnet.app/Contents/MacOS/rustnet`
+
+5. **可选:创建 shell 访问的符号链接**:
+ ```bash
+ # 创建符号链接,以便在任何位置运行 'rustnet'
+ sudo ln -s /Applications/Rustnet.app/Contents/MacOS/rustnet /usr/local/bin/rustnet
+
+ # 现在你可以从任何终端运行:
+ sudo rustnet
+ ```
+
+6. **可选:配置 BPF 权限**(以避免需要 sudo):
+ - 安装 Wireshark 的 BPF 权限助手:`brew install --cask wireshark-chmodbpf`
+ - 注销并重新登录以使组变更生效
+ - 详细说明参见[权限配置](#permissions-setup)章节
+
+### Windows MSI 安装
+
+1. **安装 Npcap Runtime**(包捕获必需):
+ - 从 https://npcap.com/dist/ 下载
+ - 运行安装程序并选择 **"WinPcap API compatible mode"**
+
+2. **下载并安装**适合的 MSI 包:
+ - 64 位 Windows 使用 `Rustnet_Windows_64-bit.msi`
+ - 32 位 Windows 使用 `Rustnet_Windows_32-bit.msi`
+
+3. **运行安装程序**并按照安装向导操作
+
+4. **运行 RustNet**:
+ - 打开命令提示符或 PowerShell
+ - 运行:`rustnet.exe`
+ - 如果未安装 Npcap 或未处于 WinPcap 兼容模式,RustNet 会显示一条有用的错误消息及安装说明
+ - 注意:根据你的 Npcap 安装设置,你可能需要或不需要 Administrator 特权
+
+### Windows Chocolatey 安装
+
+在 Windows 上安装 RustNet 最简单的方式是通过 [Chocolatey](https://community.chocolatey.org/packages/rustnet):
+
+```powershell
+# 在 Administrator PowerShell 中运行
+choco install rustnet
+```
+
+**注意:** 你仍需要单独安装 [Npcap](https://npcap.com),并启用 "WinPcap API compatible mode"。
+
+### Linux 包安装
+
+#### Ubuntu PPA(推荐用于 Ubuntu 25.10 Questing 和 26.04 LTS Resolute)
+
+在 Ubuntu 上安装 RustNet 最简单的方式是通过官方 PPA。该 PPA 为以下 Ubuntu 系列发布构建:
+
+- Ubuntu 25.10(Questing Quokka)
+- Ubuntu 26.04 LTS(Resolute Raccoon)
+
+```bash
+# 添加 RustNet PPA
+sudo add-apt-repository ppa:domcyrus/rustnet
+
+# 更新包列表
+sudo apt update
+
+# 安装 rustnet
+sudo apt install rustnet
+
+# 使用 sudo 运行
+sudo rustnet
+
+# 可选:授予 Linux capabilities 以无需 sudo 运行(现代内核 5.8+)
+sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' /usr/bin/rustnet
+rustnet
+```
+
+**重要:** 该 PPA 仅支持上述两个系列(Ubuntu 25.10 Questing 和 26.04 LTS Resolute),因为构建需要 Rust 1.88+(项目中使用了 let-chains)。早期 Ubuntu 版本的仓库中没有足够新的 `rustc`。对于旧版 Ubuntu,请使用 GitHub releases 中的 [.deb 包](#debianubuntu-deb-packages)或[从源码构建](#building-from-source)。
+
+#### Debian/Ubuntu(.deb 包)
+
+用于手动安装或非 Ubuntu 的 Debian 系发行版:
+
+```bash
+# 下载适合你架构的包:
+# - Rustnet_LinuxDEB_amd64.deb(x86_64)
+# - Rustnet_LinuxDEB_arm64.deb(ARM64)
+# - Rustnet_LinuxDEB_armhf.deb(ARMv7)
+
+# 安装包(Linux capabilities 会自动配置)
+sudo dpkg -i Rustnet_LinuxDEB_amd64.deb
+
+# 如有需要安装依赖
+sudo apt-get install -f
+
+# 无需 sudo 运行(post-install 脚本已设置 Linux capabilities)
+rustnet
+
+# 验证 Linux capabilities
+getcap /usr/bin/rustnet
+```
+
+**注意:** .deb 包通过 post-install 脚本自动设置 Linux capabilities,因此你可以无需 sudo 运行 RustNet。
+
+#### RedHat/Fedora/CentOS(.rpm 包)
+
+用于手动安装或不使用 COPR 的发行版:
+
+```bash
+# 下载适合你架构的包:
+# - Rustnet_LinuxRPM_x86_64.rpm
+# - Rustnet_LinuxRPM_aarch64.rpm
+
+# 安装包(Linux capabilities 会自动配置)
+sudo rpm -i Rustnet_LinuxRPM_x86_64.rpm
+# 或使用 dnf/yum:
+sudo dnf install Rustnet_LinuxRPM_x86_64.rpm
+
+# 无需 sudo 运行(post-install 脚本已设置 Linux capabilities)
+rustnet
+
+# 验证 Linux capabilities
+getcap /usr/bin/rustnet
+```
+
+**注意:** .rpm 包通过 post-install 脚本自动设置 Linux capabilities,因此你可以无需 sudo 运行 RustNet。
+
+#### Arch Linux
+
+该包已包含在 Arch Linux Extra 仓库中([链接](https://archlinux.org/packages/extra/x86_64/rustnet/))。可使用 pacman 安装:
+```bash
+sudo pacman -S rustnet
+```
+
+此外,还有两个 AUR 包可用:
+- [`rustnet-bin`](https://aur.archlinux.org/packages/rustnet-bin) —— 来自 GitHub Releases 的预编译二进制文件
+- [`rustnet-git`](https://aur.archlinux.org/packages/rustnet-git) —— 从源码构建并使用最新提交(由 [@DeepChirp](https://github.com/DeepChirp) 维护)
+
+使用你喜欢的 AUR 助手安装:
+```bash
+# 来自 GitHub Releases 的预编译二进制文件
+yay -S rustnet-bin
+
+# 或使用最新提交的源码构建
+yay -S rustnet-git
+```
+
+#### Nix / NixOS
+
+RustNet 已收录在 [nixpkgs](https://search.nixos.org/packages?query=rustnet) 中,**stable** 通道(以及 `nixpkgs-unstable`)均已提供。
+
+**在不安装的情况下试用(临时 shell):**
+
+```bash
+nix-shell -p rustnet
+# 然后在 shell 中执行:
+sudo rustnet
+```
+
+**在 NixOS 上持久安装** —— 在 `/etc/nixos/configuration.nix` 中添加:
+
+```nix
+environment.systemPackages = with pkgs; [
+ rustnet
+];
+```
+
+然后运行 `sudo nixos-rebuild switch`。
+
+**关于权限:** NixOS 的 `/nix/store` 是只读的,因此对二进制文件执行 `sudo setcap` 不会在系统重建后保留。最简单的方式是 `sudo rustnet`。如果希望无需 sudo 运行,可以定义一个携带相应 Linux capabilities 的 NixOS [security.wrappers](https://search.nixos.org/options?channel=unstable&query=security.wrappers) 项:
+
+```nix
+security.wrappers.rustnet = {
+ source = "${pkgs.rustnet}/bin/rustnet";
+ owner = "root";
+ group = "root";
+ capabilities = "cap_net_raw,cap_bpf,cap_perfmon+eip";
+};
+```
+
+然后通过 wrapper 路径执行 `rustnet`(`/run/wrappers/bin/rustnet`)。
+
+> **即将推出 —— 专用 NixOS 模块。** nixpkgs 正在评审一个
+> [`programs.rustnet` 模块](https://github.com/NixOS/nixpkgs/pull/517620)。
+> 合并后它会自动为你封装上述 capabilities,整个配置即可简化为:
+>
+> ```nix
+> programs.rustnet.enable = true;
+> ```
+>
+> 该模块通过 `security.wrappers` 授予 `cap_net_raw`、`cap_bpf` 和
+> `cap_perfmon`(但**不**授予 `cap_net_admin`,因为 RustNet 从不需要混杂模式),
+> 采用与 `programs.mtr`、`programs.wireshark` 相同的模式,让你无需 sudo 即可运行
+> `rustnet`。
+
+#### Fedora(COPR - 推荐用于 Fedora 42+)
+
+在 Fedora 上安装 RustNet 最简单的方式是通过官方 COPR 仓库。
+
+```bash
+# 启用 COPR 仓库
+sudo dnf copr enable domcyrus/rustnet
+
+# 安装 rustnet
+sudo dnf install rustnet
+
+# 使用 sudo 运行
+sudo rustnet
+
+# 可选:授予 Linux capabilities 以无需 sudo 运行(现代内核 5.8+)
+sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' /usr/bin/rustnet
+rustnet
+```
+
+**重要:** 由于 Rust 1.88+ 的要求,COPR 仅支持 Fedora 42 和 43。CentOS 和 RHEL 的仓库中没有足够新的 Rust 编译器。对于这些发行版,请使用 GitHub releases 中的 [.rpm 包](#redhatfedoracentos-rpm-packages)或[从源码构建](#building-from-source)。
+
+#### openSUSE Tumbleweed(OBS)
+
+RustNet 通过 [openSUSE Build Service](https://build.opensuse.org/package/show/home:domcyrus:rustnet/rustnet) 为 openSUSE Tumbleweed(x86_64 和 aarch64)构建。
+
+```bash
+sudo zypper addrepo https://download.opensuse.org/repositories/home:/domcyrus:/rustnet/openSUSE_Tumbleweed/home:domcyrus:rustnet.repo
+sudo zypper refresh
+sudo zypper install rustnet
+
+# 使用 sudo 运行
+sudo rustnet
+
+# 可选:授予 Linux capabilities 以无需 sudo 运行(现代内核 5.8+)
+sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' /usr/bin/rustnet
+rustnet
+```
+
+#### Homebrew 安装
+
+**在 macOS 上:**
+```bash
+brew install rustnet
+
+# 按照安装后显示的提示进行权限配置
+```
+
+**在 Linux 上:**
+```bash
+brew install rustnet
+
+# 为 Homebrew 安装的二进制文件授予 Linux capabilities(现代内核 5.8+)
+sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' $(brew --prefix)/bin/rustnet
+
+# 无需 sudo 运行
+rustnet
+```
+
+#### 静态二进制文件(可移植 - 任意 Linux 发行版)
+
+为获得最大可移植性,静态二进制文件可在**任意 Linux 发行版**上运行,不受 GLIBC 版本限制。它们完全自包含,不需要任何系统依赖。
+
+```bash
+# 下载适合你架构的静态二进制文件:
+# - rustnet-vX.Y.Z-x86_64-unknown-linux-musl.tar.gz(x86_64)
+# - rustnet-vX.Y.Z-aarch64-unknown-linux-musl.tar.gz(ARM64)
+
+# 解压归档
+tar xzf rustnet-vX.Y.Z-x86_64-unknown-linux-musl.tar.gz
+
+# 将二进制文件移动到 PATH
+sudo mv rustnet-vX.Y.Z-x86_64-unknown-linux-musl/rustnet /usr/local/bin/
+
+# 授予 Linux capabilities(现代内核 5.8+)
+sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' /usr/local/bin/rustnet
+
+# 无需 sudo 运行
+rustnet
+```
+
+**何时使用静态二进制文件:**
+- GLIBC 过时的旧发行版(例如 CentOS 7、旧版 Ubuntu)
+- 最小化/容器化环境
+- 难以安装依赖的气隙系统
+- 当你需要一个单一的可移植二进制文件时
+
+### FreeBSD 安装
+
+FreeBSD 支持从版本 0.15.0 开始提供。
+
+#### 从 Ports 或 Packages(未来)
+
+一旦进入 FreeBSD ports:
+```bash
+# 使用 pkg(二进制包)
+pkg install rustnet
+
+# 或从 ports 构建
+cd /usr/ports/net/rustnet && make install clean
+```
+
+#### 从 GitHub Releases
+
+从 [rustnet-bsd releases](https://github.com/domcyrus/rustnet-bsd/releases) 下载 FreeBSD 二进制文件:
+
+```bash
+# 下载适合的包
+fetch https://github.com/domcyrus/rustnet-bsd/releases/download/vX.Y.Z/rustnet-vX.Y.Z-x86_64-unknown-freebsd.tar.gz
+
+# 解压归档
+tar xzf rustnet-vX.Y.Z-x86_64-unknown-freebsd.tar.gz
+
+# 将二进制文件移动到 PATH
+sudo mv rustnet-vX.Y.Z-x86_64-unknown-freebsd/rustnet /usr/local/bin/
+
+# 使其可执行
+sudo chmod +x /usr/local/bin/rustnet
+
+# 使用 sudo 运行
+sudo rustnet
+```
+
+#### 在 FreeBSD 上从源码构建
+
+```bash
+# 安装依赖
+pkg install rust libpcap
+
+# 克隆仓库
+git clone https://github.com/domcyrus/rustnet.git
+cd rustnet
+
+# Release 模式构建
+cargo build --release
+
+# 可执行文件位于 target/release/rustnet
+sudo ./target/release/rustnet
+```
+
+#### FreeBSD 权限配置
+
+FreeBSD 需要访问 BPF(Berkeley Packet Filter)设备来进行数据包捕获。
+
+**选项 1:使用 sudo 运行(最简单)**
+```bash
+sudo rustnet
+```
+
+**选项 2:将用户添加到 bpf 组(推荐)**
+```bash
+# 将你的用户添加到 bpf 组
+sudo pw groupmod bpf -m $(whoami)
+
+# 注销并重新登录以使组变更生效
+
+# 现在无需 sudo 运行
+rustnet
+```
+
+**选项 3:更改 BPF 设备权限(临时)**
+```bash
+# 重启后会重置
+sudo chmod o+rw /dev/bpf*
+
+# 现在无需 sudo 运行
+rustnet
+```
+
+**验证 FreeBSD 权限:**
+```bash
+# 检查是否在 bpf 组中
+groups | grep bpf
+
+# 检查 BPF 设备权限
+ls -la /dev/bpf*
+
+# 不使用 sudo 测试
+rustnet --help
+```
+
+### Android(Termux)安装
+
+RustNet 可以通过 [Termux](https://termux.dev/en/) 在 Android 设备上运行,前提是设备已 root。
+
+由于 Android 严格控制网络和进程信息,RustNet 需要 `root` 访问权限(`su`)才能捕获数据包和识别进程。提供一个专门的 Android 构建,静态链接依赖并禁用与 Android 内核环境不兼容的 Linux 特定功能(如 eBPF 和 Landlock)。
+
+#### 前置要求
+1. **已 Root** 的 Android 设备(例如通过 Magisk 或 KernelSU)
+2. 已安装 **Termux**(从 F-Droid 或 GitHub 获取,*不要*从 Google Play 获取)
+
+#### 安装步骤
+
+1. **在 Termux 中安装所需包:**
+ ```bash
+ pkg update
+ pkg install tsu wget tar
+ ```
+
+2. **下载 Android 二进制文件:**
+ ```bash
+ # 从 GitHub Releases 下载 Android 专用静态二进制文件
+ wget https://github.com/domcyrus/rustnet/releases/download/vX.Y.Z/rustnet-vX.Y.Z-aarch64-linux-android-musl.tar.gz
+ ```
+
+3. **解压并安装:**
+ ```bash
+ tar xzf rustnet-vX.Y.Z-aarch64-linux-android-musl.tar.gz
+
+ # 将其移动到 PATH 中的目录
+ mv rustnet-vX.Y.Z-aarch64-linux-android-musl/rustnet $PREFIX/bin/
+ chmod +x $PREFIX/bin/rustnet
+ ```
+
+4. **以 root 身份运行 RustNet:**
+ ```bash
+ # 你必须以 root 权限运行 RustNet,才能在 Android 上正常工作
+ sudo rustnet
+ ```
+ *注意:首次运行时,你的 root 管理器(例如 Magisk)会提示你授予 Termux Superuser 访问权限。*
+
+## 通过 Cargo 安装
+
+```bash
+# 直接从 crates.io 安装
+cargo install rustnet-monitor
+
+# 二进制文件将安装到 ~/.cargo/bin/rustnet
+# 确保 ~/.cargo/bin 在你的 PATH 中
+```
+
+安装后,参见[权限配置](#permissions-setup)章节配置权限。
+
+## 从源码构建
+
+### 前置要求
+
+- Rust 2024 edition 或更高版本(从 [rustup.rs](https://rustup.rs/) 安装)
+- 平台特定依赖:
+ - **Linux(Debian/Ubuntu)**:
+ ```bash
+ sudo apt-get install build-essential pkg-config libpcap-dev libelf-dev zlib1g-dev clang llvm
+ ```
+ - **Linux(RedHat/CentOS/Fedora)**:
+ ```bash
+ sudo yum install make pkgconfig libpcap-devel elfutils-libelf-devel zlib-devel clang llvm
+ ```
+ - **macOS**:安装 Xcode Command Line Tools:`xcode-select --install`
+ - **FreeBSD**:`pkg install rust libpcap`
+ - **Windows**:安装 Npcap 和 Npcap SDK(参见下方的 [Windows 构建配置](#windows-build-setup))
+
+### 基本构建
+
+```bash
+# 克隆仓库
+git clone https://github.com/domcyrus/rustnet.git
+cd rustnet
+
+# Release 模式构建(Linux 上默认启用 eBPF)
+cargo build --release
+
+# 构建不带 eBPF 支持(仅 Linux procfs 模式)
+cargo build --release --no-default-features
+
+# 可执行文件位于 target/release/rustnet
+```
+
+不带 eBPF(仅 procfs 模式)构建时,使用 `cargo build --release --no-default-features`。
+
+### Windows 构建配置
+
+在 Windows 上构建 RustNet 需要 Npcap SDK 和正确的环境配置:
+
+#### 构建需求
+
+1. **下载并安装 Npcap SDK**:
+ - 从 https://npcap.com/dist/ 下载 Npcap SDK
+ - 将 SDK 解压到一个目录(例如 `C:\npcap-sdk`)
+
+2. **设置环境变量**:
+ - 将 `LIB` 环境变量设置为包含 SDK 的库路径:
+ ```cmd
+ set LIB=%LIB%;C:\npcap-sdk\Lib\x64
+ ```
+ - 对于 PowerShell:
+ ```powershell
+ $env:LIB = "$env:LIB;C:\npcap-sdk\Lib\x64"
+ ```
+ - 要永久设置,请添加到你的系统环境变量中
+
+3. **构建 RustNet**:
+ ```cmd
+ cargo build --release
+ ```
+
+#### 运行时需求
+
+1. **安装 Npcap Runtime**:
+ - 从 https://npcap.com/dist/ 下载 Npcap 安装程序
+ - 运行安装程序并在安装期间**选择 "WinPcap API compatible mode"**
+ - 这确保与包捕获库的兼容性
+
+2. **运行 RustNet**:
+ ```cmd
+ rustnet.exe
+ ```
+
+**注意**:根据你的 Npcap 安装设置,你可能需要或不需要 Administrator 特权。如果你在 Npcap 安装期间没有选择限制数据包捕获到管理员的选项,RustNet 可以用普通用户权限运行。
+
+## 使用 Docker
+
+RustNet 可作为 Docker 容器从 GitHub Container Registry 获取:
+
+```bash
+# 拉取最新镜像
+docker pull ghcr.io/domcyrus/rustnet:latest
+
+# 或拉取特定版本
+docker pull ghcr.io/domcyrus/rustnet:0.7.0
+
+# 使用 eBPF 支持所需的 Linux capabilities 运行(latest)
+docker run --rm -it --cap-add=NET_RAW --cap-add=BPF --cap-add=PERFMON --net=host \
+ ghcr.io/domcyrus/rustnet:latest
+
+# 使用特定版本运行
+docker run --rm -it --cap-add=NET_RAW --cap-add=BPF --cap-add=PERFMON --net=host \
+ ghcr.io/domcyrus/rustnet:0.7.0
+
+# 使用指定接口运行
+docker run --rm -it --cap-add=NET_RAW --cap-add=BPF --cap-add=PERFMON --net=host \
+ ghcr.io/domcyrus/rustnet:latest -i eth0
+
+# 替代方案:使用 privileged 模式(安全性较低但更简单)
+docker run --rm -it --privileged --net=host \
+ ghcr.io/domcyrus/rustnet:latest
+
+# 查看可用选项
+docker run --rm ghcr.io/domcyrus/rustnet:latest --help
+```
+
+**注意:** 容器需要 Linux capabilities(`NET_RAW`、`BPF` 和 `PERFMON`)或 privileged 模式才能进行带 eBPF 支持的数据包捕获。推荐使用主机网络(`--net=host`)以监控所有网络接口。
+
+## 权限配置
+
+RustNet 需要提升的特权来捕获网络数据包,因为在所有现代操作系统上,访问网络接口进行数据包捕获是一项特权操作。本章节解释如何在不同平台上正确授予这些权限。
+
+> ### **安全优势:Linux 上的只读网络访问**
+>
+> **RustNet 在所有平台上使用只读数据包捕获,不启用混杂模式。** 这意味着:
+>
+> **Linux:** 仅需要 **`CAP_NET_RAW`** 这项 Linux capability —— **不需要**完整的 root 或 `CAP_NET_ADMIN`
+> **最小权限原则:** 数据包捕获所需的最小权限
+> **无混杂模式:** 仅捕获往返于主机的数据包(而非所有网络流量)
+> **只读:** 不能修改或注入数据包
+> **增强安全性:** 与完整 root 访问相比,攻击面更小
+>
+> **macOS 注意:** PKTAP(用于进程元数据)需要 root 特权,但你可以在不使用 sudo 的情况下运行,使用 `lsof` 回退进行基本数据包捕获。
+
+### 为什么需要权限
+
+网络数据包捕获需要访问:
+
+- **Raw socket** 用于低层网络访问(只读、非混杂模式)
+- **网络接口** 用于数据包捕获
+- macOS/BSD 系统上的 **BPF(Berkeley Packet Filter)设备**
+- 某些 Linux 配置上的 **网络命名空间**
+
+这些 Linux capabilities 受到限制,以防止恶意软件拦截网络流量。
+
+### macOS 权限配置
+
+在 macOS 上,数据包捕获需要访问位于 `/dev/bpf*` 的 BPF(Berkeley Packet Filter)设备。
+
+**注意:** macOS PKTAP(用于从数据包中提取进程元数据)需要 **root/sudo** 特权。不使用 sudo 时,RustNet 使用 `lsof` 作为进程检测的回退(较慢,但无需 root)。
+
+#### 选项 1:使用 sudo 运行(最简单)
+
+```bash
+# 使用 sudo 构建并运行
+cargo build --release
+sudo ./target/release/rustnet
+```
+
+#### 选项 2:BPF 组访问(推荐)
+
+将用户添加到 `access_bpf` 组以实现免密码数据包捕获:
+
+**使用 Wireshark 的 ChmodBPF(用于基本数据包捕获):**
+
+```bash
+# 安装 Wireshark 的 BPF 权限助手
+brew install --cask wireshark-chmodbpf
+
+# 注销并重新登录以使组变更生效
+# 然后无需 sudo 运行 rustnet:
+rustnet # 使用 lsof 进行进程检测(较慢)
+
+# 如需 PKTAP 支持以从包头部获取进程元数据,请使用 sudo:
+sudo rustnet # 使用 PKTAP 进行更快的进程检测
+```
+
+**注意**:`wireshark-chmodbpf` 授予对 `/dev/bpf*` 的数据包捕获访问权限,但 **PKTAP** 是一个独立的特权内核接口,无论 BPF 权限如何都需要 root 特权。TUI 会显示当前使用的检测方法(使用 sudo 时为 "pktap",不使用 sudo 时为 "lsof")。
+
+**手动 BPF 组配置:**
+
+```bash
+# 创建 access_bpf 组(如果不存在)
+sudo dseditgroup -o create access_bpf
+
+# 将用户添加到组中
+sudo dseditgroup -o edit -a $USER -t user access_bpf
+
+# 设置 BPF 设备权限(每次重启后都需要执行)
+sudo chmod g+rw /dev/bpf*
+sudo chgrp access_bpf /dev/bpf*
+
+# 注销并重新登录以使组成员身份生效
+```
+
+#### 选项 3:Homebrew 安装
+
+如果通过 Homebrew 安装,formula 会提供详细的配置说明:
+
+```bash
+brew install rustnet
+# 按照安装后显示的提示操作
+```
+
+### Linux 权限配置(只读访问 - 无需 Root!)
+
+**Linux 优势:** RustNet 进行数据包捕获**仅需要 `CAP_NET_RAW`** —— 远少于完整的 root 访问!
+
+在 Linux 上,数据包捕获仅需要 `CAP_NET_RAW` 这项 Linux capability,用于只读、非混杂数据包捕获。对于 eBPF 增强型进程追踪,需要额外的 Linux capabilities(`CAP_BPF` 和 `CAP_PERFMON`),但**不需要 `CAP_NET_ADMIN`**。
+
+#### 选项 1:使用 sudo 运行(最简单)
+
+```bash
+# 使用 sudo 构建并运行
+cargo build --release
+sudo ./target/release/rustnet
+```
+
+#### 选项 2:授予 Linux capabilities(推荐)
+
+为二进制文件授予特定的 Linux capabilities,而无需完整的 root 特权:
+
+**对于源码构建:**
+
+```bash
+# 先构建二进制文件
+cargo build --release
+
+# 为二进制文件授予 Linux capabilities(现代内核 5.8+,带 eBPF 支持)
+sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' ./target/release/rustnet
+
+# 现在无需 sudo 运行
+./target/release/rustnet
+```
+
+**对于 cargo 安装的二进制文件:**
+
+```bash
+# 如果通过 cargo install rustnet-monitor 安装(现代内核 5.8+)
+sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' ~/.cargo/bin/rustnet
+
+# 现在无需 sudo 运行
+rustnet
+```
+
+**对于启用 eBPF 的构建(增强型 Linux 性能 - 默认启用):**
+
+eBPF 在 Linux 构建上默认启用,使用内核探针提供低开销的进程识别:
+
+```bash
+# Release 模式构建(默认启用 eBPF)
+cargo build --release
+
+# 现代 Linux(5.8+)- 仅需这三个 Linux capabilities:
+sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' ./target/release/rustnet
+./target/release/rustnet
+
+# 仅包捕获(eBPF 会回退到 procfs)
+sudo setcap 'cap_net_raw+eip' ./target/release/rustnet
+./target/release/rustnet
+
+# 检查 TUI 统计面板 - 应显示 "Process Detection: eBPF + procfs"
+```
+
+**Linux capabilities 需求:**
+
+**基础 Linux capabilities(始终需要):**
+- `CAP_NET_RAW` —— 用于只读数据包捕获的 raw socket 访问(非混杂模式)
+
+**eBPF 所需的 Linux capabilities(根据内核版本选择):**
+
+**现代 Linux(5.8+):**
+- `CAP_BPF` —— BPF 程序加载和 map 操作
+- `CAP_PERFMON` —— 性能监控和追踪操作
+
+**旧版 Linux(pre-5.8):**
+- eBPF 操作需要宽泛的 `CAP_SYS_ADMIN`。不建议默认授予它;请使用
+ `CAP_NET_RAW` 进行包捕获,并让 RustNet 回退到 procfs 进程检测,除非你明确接受该风险。
+
+**注意:** 不需要 CAP_NET_ADMIN。RustNet 使用不带混杂模式的只读数据包捕获。
+
+**回退行为**:如果 eBPF 无法加载(例如 Linux capabilities 不足、内核不兼容),应用会自动使用仅 procfs 模式。TUI 统计面板显示当前使用的检测方法:
+- `Process Detection: eBPF + procfs` —— eBPF 成功加载
+- `Process Detection: procfs` —— 使用 procfs 回退
+
+**注意:** eBPF 在 Linux 构建上默认启用,进程名显示可能存在局限性。有关 eBPF 实现的详情参见 [ARCHITECTURE.zh-CN.md](ARCHITECTURE.zh-CN.md)。要构建不带 eBPF 的版本,使用 `cargo build --release --no-default-features`。
+
+**对于系统级安装:**
+
+```bash
+# 如果通过包管理器安装或复制到 /usr/local/bin(现代内核 5.8+)
+sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' /usr/local/bin/rustnet
+rustnet
+```
+
+### Windows 权限配置
+
+Windows 支持目前有限,但可用时:
+
+- RustNet 需要 **Administrator 特权**
+- 必须安装 **WinPcap** 或 **Npcap** 用于数据包捕获
+- 以 Administrator 身份运行命令提示符或 PowerShell
+
+### 验证权限
+
+要验证权限是否正确配置:
+
+#### macOS
+
+```bash
+# 检查 BPF 设备权限
+ls -la /dev/bpf*
+
+# 检查组成员身份
+groups | grep access_bpf
+
+# 不使用 sudo 测试
+rustnet --help
+```
+
+#### Linux
+
+```bash
+# 检查二进制文件上的 Linux capabilities
+# 对于源码构建:
+getcap ./target/release/rustnet
+
+# 对于 cargo 安装的二进制文件:
+getcap ~/.cargo/bin/rustnet
+
+# 对于系统级安装:
+getcap $(which rustnet)
+
+# 现代(5.8+):应显示 cap_net_raw,cap_bpf,cap_perfmon+eip
+# 如果 eBPF capability 不可用:应显示 cap_net_raw+eip
+
+# 不使用 sudo 测试
+rustnet --help
+```
+
+## GeoIP 数据库(可选)
+
+RustNet 支持 GeoIP 查询以显示远程 IP 的国家代码、城市名称和 ASN 信息。要启用此功能,请使用 MaxMind 的 `geoipupdate` 工具安装 [GeoLite2](https://dev.maxmind.com/geoip/geolite2-free-geolocation-data) 数据库(需要免费的 [MaxMind 账户](https://www.maxmind.com/en/geolite2/signup))。
+
+**可用数据库:**
+
+| 数据库 | 提供内容 | 标志 |
+|---|---|---|
+| `GeoLite2-Country.mmdb` | 国家代码和名称 | *(自动发现)* |
+| `GeoLite2-ASN.mmdb` | ASN 编号和组织 | *(自动发现)* |
+| `GeoLite2-City.mmdb` | 城市名称、邮政编码、**以及**国家 | *(自动发现)* |
+
+> **提示:** `GeoLite2-City` 是 `GeoLite2-Country` 的超集。如果你安装了 City 数据库,就不需要再安装 Country 数据库。
+
+### 配置要下载的数据库
+
+在你的 `GeoIP.conf` 中,将 `EditionIDs` 设置为你想要的数据库:
+
+```
+# 仅 Country + ASN:
+EditionIDs GeoLite2-Country GeoLite2-ASN
+
+# City + ASN(City 已包含国家数据):
+EditionIDs GeoLite2-City GeoLite2-ASN
+
+# 全部三个:
+EditionIDs GeoLite2-Country GeoLite2-ASN GeoLite2-City
+```
+
+### macOS(Homebrew)
+
+```bash
+brew install geoipupdate
+# 使用你的 MaxMind 账户凭据和 EditionIDs 编辑配置:
+# $(brew --prefix)/etc/GeoIP.conf
+geoipupdate
+```
+
+数据库安装到 `$(brew --prefix)/share/GeoIP/`。
+
+### Ubuntu/Debian
+
+```bash
+sudo apt-get install geoipupdate
+# 使用你的 MaxMind 账户凭据和 EditionIDs 编辑 /etc/GeoIP.conf
+sudo geoipupdate
+```
+
+数据库安装到 `/usr/share/GeoIP/`。
+
+### Fedora/RHEL
+
+```bash
+sudo dnf install geoipupdate
+# 使用你的 MaxMind 账户凭据和 EditionIDs 编辑 /etc/GeoIP.conf
+sudo geoipupdate
+```
+
+数据库安装到 `/usr/share/GeoIP/`。
+
+### Arch Linux
+
+```bash
+sudo pacman -S geoipupdate
+# 使用你的 MaxMind 账户凭据和 EditionIDs 编辑 /etc/GeoIP.conf
+sudo geoipupdate
+```
+
+数据库安装到 `/usr/share/GeoIP/`。
+
+### FreeBSD
+
+```bash
+pkg install geoipupdate
+# 使用你的 MaxMind 账户凭据和 EditionIDs 编辑 /usr/local/etc/GeoIP.conf
+sudo geoipupdate
+```
+
+数据库安装到 `/usr/local/share/GeoIP/`。
+
+### 手动指定
+
+如果你的数据库在非标准位置,请直接指定:
+
+```bash
+# Country + ASN:
+rustnet --geoip-country /path/to/GeoLite2-Country.mmdb --geoip-asn /path/to/GeoLite2-ASN.mmdb
+
+# City + ASN(City 已包含国家数据):
+rustnet --geoip-city /path/to/GeoLite2-City.mmdb --geoip-asn /path/to/GeoLite2-ASN.mmdb
+```
+
+RustNet 从标准位置自动发现数据库。运行 `rustnet --help` 查看完整的搜索路径列表。
+
+## 故障排查
+
+### 常见安装问题
+
+#### 权限被拒绝错误
+
+**在 macOS 上:**
+
+- 确保你在 `access_bpf` 组中:`groups | grep access_bpf`
+- 检查 BPF 设备权限:`ls -la /dev/bpf0`
+- 尝试使用 sudo 运行以确认是权限问题
+- 组变更后注销并重新登录
+
+**在 Linux 上:**
+
+- 检查 Linux capabilities 是否已设置:`getcap $(which rustnet)` 或 `getcap ~/.cargo/bin/rustnet`
+- 验证 libpcap 是否已安装:`ldconfig -p | grep pcap`
+- 尝试使用 sudo 运行以确认是权限问题:`sudo $(which rustnet)`
+
+#### 未找到合适的捕获接口
+
+- 检查可用接口:`ip link show`(Linux)或 `ifconfig`(macOS)
+- 尝试显式指定接口:`rustnet -i eth0`
+- 确保接口已启动并拥有 IP 地址
+- 某些虚拟接口可能不支持数据包捕获
+
+#### 操作不被允许(已设置 Linux capabilities)
+
+- Linux capabilities 可能已被系统更新移除
+- 重新应用 Linux capabilities(现代):`sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' $(which rustnet)`
+- 某些文件系统不支持扩展属性(Linux capabilities)
+- 尝试将二进制文件复制到不同的文件系统(例如从 NFS 复制到本地磁盘)
+
+#### 已设置 Linux capabilities 但 eBPF 不可用
+
+如果 RustNet 在运行 `setcap` 后仍显示 `Process Detection: procfs` 并伴随降级消息,请按以下步骤排查。TUI 在第二行状态栏显示实际原因 —— 用它跳转到下方正确的章节。
+
+**1. `file caps ignored: binary on a nosuid mount`**
+
+内核会静默忽略位于以 `nosuid` 选项挂载的文件系统上的二进制文件的 Linux capabilities。常见 culprit:`/home` 在加固发行版上、`/tmp`、可移动介质、容器内的某些 bind-mount。
+
+```bash
+# 查找持有该二进制文件的挂载点并检查其选项
+findmnt -T $(realpath $(which rustnet)) -o TARGET,OPTIONS
+# 如果 OPTIONS 列包含 "nosuid",Linux capabilities 将不会生效。
+
+# 修复:将二进制文件安装或复制到没有 nosuid 的挂载点
+sudo install -m 0755 $(which rustnet) /usr/local/bin/rustnet
+sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' /usr/local/bin/rustnet
+/usr/local/bin/rustnet
+```
+
+**2. `BPF denied (check perf_event_paranoid / AppArmor / unprivileged_bpf_disabled)`**
+
+Linux capabilities 已授予,但内核返回了 `EPERM` 或 `EACCES`。三层可能阻止它 —— 按此顺序检查:
+
+```bash
+# 2a. perf_event_paranoid(Debian 上最常见的原因)。
+# Debian 13 默认 kernel.perf_event_paranoid=3,这会阻止
+# 非 root 用户的 perf_event_open(2) —— 因此也阻止 kprobe attach ——
+# *即使有 CAP_PERFMON*。上游内核最高只到 2,
+# 其中 CAP_PERFMON 正确绕过限制。
+#
+# Ubuntu 使用了不同的补丁(paranoid=4),该补丁在
+# 2025 年底更新以尊重 CAP_PERFMON,因此在较新的 Ubuntu 内核上
+# (Jammy 5.15.0-165+、Noble 6.8.0-91+、Plucky 6.14.0-37+、
+# Questing 6.17.0-14+、Resolute 6.18.0-8+)仅 `setcap` 就
+# 足够了 —— 无需 sysctl 更改。Debian 的等效补丁
+# (bug #994044)从未更新,并在 2025 年被归档
+# 且未修复,因此 Debian 用户仍需要下方的变通方法。
+sysctl kernel.perf_event_paranoid
+# 如果值为 3(Debian)或旧 Ubuntu 内核上的 4,将其降到 2:
+sudo sysctl kernel.perf_event_paranoid=2
+# 使其在重启后持久:
+echo 'kernel.perf_event_paranoid = 2' | \
+ sudo tee /etc/sysctl.d/99-rustnet.conf
+
+# 2b. AppArmor 限制 rustnet(Debian/Ubuntu 默认安装 AppArmor)。
+sudo aa-status | grep rustnet
+# 如果已列出,要么禁用该配置文件,要么添加一条规则允许
+# Linux capability `bpf`、Linux capability `perfmon`,以及该二进制文件的 bpf() 系统调用。
+
+# 2c. unprivileged_bpf_disabled(Debian 设置为 =2;文件 Linux capabilities 应能绕过)。
+sysctl kernel.unprivileged_bpf_disabled
+
+# 确认 Linux capabilities 在 exec 时实际生效:
+grep ^Cap /proc/$(pgrep -n rustnet)/status
+# CapEff 必须包含 CAP_BPF(位 39)和 CAP_PERFMON(位 38)。
+```
+
+**3. `kprobe attach failed: `**
+
+内核缺少 eBPF 探针想要附加的符号。这通常是内核配置问题(例如 CONFIG_IPV6 禁用、CONFIG_KPROBES 关闭,或符号被内联)。RustNet 当前附加到
+`tcp_connect`、`inet_csk_accept`、`udp_sendmsg`、`tcp_v6_connect`、
+`udpv6_sendmsg`、`ping_v4_sendmsg` 和 `ping_v6_sendmsg`。
+
+```bash
+# 检查失败符号是否存在于运行中的内核中:
+sudo grep '' /proc/kallsyms
+```
+
+如果符号确实缺失,eBPF 进程检测将无法在此内核构建上工作;procfs 回退继续正常工作。
+
+**4. `kernel BTF unavailable`**
+
+CO-RE 重定位需要 `/sys/kernel/btf/vmlinux`。在精简内核上(某些嵌入式 / 最小云镜像)该文件不存在。
+
+```bash
+ls /sys/kernel/btf/vmlinux
+# 如果缺失:安装与你的运行内核匹配的 kernel-debuginfo / linux-image 包,
+# 或使用 CONFIG_DEBUG_INFO_BTF=y 重新构建内核。
+```
+
+**5. 在 Docker / Podman 中**
+
+即使有文件 Linux capabilities,容器内的*bounding*集合也必须包含 `CAP_BPF` 和 `CAP_PERFMON`,否则它们在 exec 时会被屏蔽:
+
+```bash
+docker run --cap-add=NET_RAW --cap-add=BPF --cap-add=PERFMON \
+ --net=host --pid=host rustnet
+# 可选,如果你的容器的 seccomp 配置文件阻止 bpf(2):
+# --security-opt seccomp=unconfined
+# 或如果 AppArmor 监管 BPF:
+# --security-opt apparmor=unconfined
+```
+
+**6. `eBPF load failed: ...`**
+
+兜底分支携带原始 libbpf 错误文本。使用
+`RUST_LOG=debug rustnet 2>&1 | tee rustnet.log` 重新运行并检查完整
+链 —— 它通常包含一个 `errno` 名称(`EPERM`、`EACCES`、`ENOSPC`
+表示 memlock 等),指向根本原因。
+
+#### Windows:未找到 Npcap
+
+- 确保从 https://npcap.com/dist/ 安装了 Npcap
+- 在 Npcap 安装期间,选择 **"WinPcap API compatible mode"**
+- 验证 Npcap 服务正在运行:`sc query npcap`
+- 尝试使用管理员权限重新安装 Npcap
+
+#### 构建错误
+
+**Windows - 未找到 Npcap SDK:**
+- 确保 `LIB` 环境变量包含 Npcap SDK 路径
+- 检查 SDK 是否解压到没有空格的目录
+- 为你的 Rust 工具链使用正确的架构(x64 与 x86)
+
+**Linux 构建失败:**
+```bash
+# 安装所有必需的依赖
+# Debian/Ubuntu
+sudo apt-get install build-essential pkg-config libpcap-dev libelf-dev zlib1g-dev clang llvm
+
+# RedHat/CentOS/Fedora
+sudo yum install make pkgconfig libpcap-devel elfutils-libelf-devel zlib-devel clang llvm
+```
+
+#### Windows:图表在 PowerShell 中显示不正确
+
+如果图表和 sparkline 在 PowerShell 中显示损坏(显示问号或乱码字符),这是**字体问题**,不是 RustNet 的 bug。默认的控制台字体(Consolas、Lucida Console)缺少图表渲染使用的 Unicode Braille 字符支持。
+
+**解决方案:** 安装支持 Unicode Braille 的字体:
+
+1. 下载并安装 [Iosevka](https://typeof.net/Iosevka/) 或任意 [Nerd Font](https://www.nerdfonts.com/)
+2. 打开 PowerShell 属性(右键标题栏 → 属性)
+3. 在字体选项卡中选择已安装的字体
+4. 重启 PowerShell
+
+**替代方案:** 使用 [Windows Terminal](https://aka.ms/terminal),它开箱即用地提供更好的 Unicode 支持。
+
+参见:[ratatui#457](https://github.com/ratatui/ratatui/issues/457)、[gtop#21](https://github.com/aksakalli/gtop/issues/21)
+
+#### macOS:终端 CPU 占用高或图表有缺口(iTerm2)
+
+RustNet 的图表使用 Unicode Braille 字符绘制。macOS 经典的等宽字体(Monaco、Menlo)不包含 Braille 字形,因此 iTerm2 会对每个图表单元格进行字体回退渲染。这不仅很慢(仅显示图表就可能占用 iTerm2 10-20% 的 CPU),回退字形还常常在波形中留下明显的缺口。
+
+**解决方案:** 为 iTerm2 的非 ASCII 文本指定一个包含 Braille 字形的字体:
+
+1. 安装一个 [Nerd Font](https://www.nerdfonts.com/),例如:
+
+ ```bash
+ brew install --cask font-jetbrains-mono-nerd-font
+ ```
+
+2. 在 iTerm2 中:**Settings → Profiles → Text**,启用 **"Use a different font for non-ASCII text"**(为非 ASCII 文本使用不同的字体)并选择该 Nerd Font。常规文本保持当前字体不变;只有符号和图表字形使用新字体。
+3. 可选:**Settings → General → Preferences → "Maximize throughput"**(最大化吞吐量)将 iTerm2 的重绘率限制在 30 fps,可进一步降低频繁刷新的 TUI 应用的 CPU 占用。
+
+**替代方案:** 使用 GPU 加速的终端,如 [WezTerm](https://wezterm.org/)、[Ghostty](https://ghostty.org/) 或 [kitty](https://sw.kovidgoyal.net/kitty/)。它们渲染 RustNet 的 CPU 占用远低于 iTerm2,其中 WezTerm 内置 JetBrains Mono 及符号回退字体,图表开箱即用即可正确渲染。
+
+### 获取帮助
+
+如果你遇到此处未涵盖的问题:
+
+1. 启用调试日志:`rustnet --log-level debug`
+2. 检查 `logs/` 目录中的日志文件
+3. 在 [GitHub](https://github.com/domcyrus/rustnet/issues) 上开 issue,并提供:
+ - 你的操作系统和版本
+ - 使用的安装方法
+ - 日志中的错误消息
+ - 权限验证命令的输出
+
+### 安全最佳实践
+
+1. **尽可能使用 Linux capabilities,而不是 sudo**(Linux)
+2. **使用基于组的访问而非以 root 运行**(macOS)
+3. **定期审计**哪些用户拥有数据包捕获特权
+4. **考虑网络分段**如果在生产系统上运行
+5. **监控日志文件**以发现未授权的使用
+6. **在不再需要 RustNet 时移除相关的 Linux capabilities**:
+
+ ```bash
+ # Linux:移除 Linux capabilities
+ sudo setcap -r /path/to/rustnet
+
+ # macOS:从组中移除
+ sudo dseditgroup -o edit -d $USER -t user access_bpf
+ ```
+
+### 与系统监控集成
+
+对于生产环境,请考虑:
+
+- 数据包捕获访问的**审计日志**
+- **网络监控策略**和合规要求
+- 特权网络访问的**用户访问审查**
+- 配置管理系统中的**自动化 Linux capabilities 管理**
+
+此权限配置确保 RustNet 能够在捕获数据包的同时遵循安全最佳实践和最小权限原则。
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/PROFILING.md b/PROFILING.md
new file mode 100644
index 0000000..2a6f535
--- /dev/null
+++ b/PROFILING.md
@@ -0,0 +1,196 @@
+English | 简体中文
+
+# RustNet Performance Profiling Guide
+
+This guide explains how to profile RustNet to identify performance bottlenecks.
+
+## Quick Start
+
+### CPU Profiling with perf + flamegraph
+
+The easiest way to profile CPU usage on Linux:
+
+```bash
+# 1. Install flamegraph tools
+cargo install flamegraph
+
+# 2. Build a release binary with debug symbols
+# IMPORTANT: Debug symbols are required for meaningful flamegraphs!
+CARGO_PROFILE_RELEASE_DEBUG=true cargo build --release
+
+# Or add this to Cargo.toml temporarily:
+# [profile.release]
+# debug = true
+
+# 3. Run with profiling (requires sudo for perf)
+# Note: Use full path to flamegraph since sudo doesn't have your user's PATH
+# IMPORTANT: Use -- before the command to profile
+sudo -E ~/.cargo/bin/flamegraph -- ./target/release/rustnet
+
+# Or specify interface and other args after the binary
+sudo -E ~/.cargo/bin/flamegraph -- ./target/release/rustnet -i eth0
+
+# Alternatively, preserve PATH for cleaner commands:
+sudo env "PATH=$PATH" flamegraph -- ./target/release/rustnet
+
+# 4. Open the generated flamegraph.svg in a browser
+firefox flamegraph.svg
+```
+
+### Alternative: Using perf directly
+
+If you prefer to use `perf` directly:
+
+```bash
+# Build with debug symbols
+cargo build --release
+
+# Record performance data (run for 30-60 seconds, then Ctrl+C to stop)
+sudo perf record -F 99 -g ./target/release/rustnet -i eth0
+
+# Generate flamegraph (requires FlameGraph scripts)
+# Install from: https://github.com/brendangregg/FlameGraph
+perf script | stackcollapse-perf.pl | flamegraph.pl > flamegraph.svg
+
+# Or view in perf's TUI
+sudo perf report
+```
+
+### Profiling a Running Instance
+
+If RustNet is already running:
+
+```bash
+# Find the PID
+ps aux | grep rustnet
+
+# Profile the running process for 60 seconds
+sudo -E ~/.cargo/bin/flamegraph -p --output rustnet-live.svg
+
+# Or with perf directly
+sudo perf record -F 99 -g -p sleep 60
+sudo perf report
+```
+
+## Interpreting Flamegraphs
+
+Look for:
+- **Wide bars at the bottom**: Functions that consume a lot of total CPU time
+- **Tall stacks**: Deep call chains (potential optimization targets)
+- **Hot spots**: Functions with many samples (bright colors in some viewers)
+
+Common hot spots:
+- `packet_parser::parse_packet`: Normal - this is the core packet processing
+- `DashMap::iter` or `iter_mut`: If this is a large portion, consider reducing iteration frequency
+- `clone`: If excessive, reduce unnecessary cloning
+- System calls (`read`, `write`, `ioctl`): Filesystem or network I/O overhead
+
+## Criterion Benchmarks
+
+Microbenchmarks for core operations live in `benches/`. Run them with:
+
+| Benchmark | Command |
+|-----------|---------|
+| Packet parsing | `cargo bench --bench packet_parsing` |
+| Connection merge | `cargo bench --bench connection_merge` |
+| Snapshot creation | `cargo bench --bench snapshot` |
+| All benchmarks | `cargo bench` |
+| Struct sizes | `cargo test --lib struct_sizes -- --nocapture` |
+
+Criterion produces HTML reports in `target/criterion/` with statistical comparison between runs.
+
+## Ad-hoc Benchmarking
+
+For consistent benchmarks:
+
+```bash
+# Run with consistent traffic
+sudo ./target/release/rustnet --interface eth0 &
+PID=$!
+
+# Monitor CPU usage
+top -p $PID
+
+# Or use perf stat for detailed metrics
+sudo perf stat -p $PID sleep 60
+
+# Stop the application
+sudo kill $PID
+```
+
+## Performance Regression Testing
+
+After making changes, compare before/after:
+
+```bash
+# Baseline (before changes)
+sudo perf stat -r 3 timeout 60s ./target/release/rustnet-before > /dev/null
+
+# After changes
+sudo perf stat -r 3 timeout 60s ./target/release/rustnet > /dev/null
+```
+
+Key metrics to compare:
+- CPU cycles
+- Instructions per cycle (IPC)
+- Cache misses
+- Context switches
+
+## Troubleshooting Flamegraphs
+
+### Empty or Single-Entry Flamegraph
+
+If your flamegraph only shows "rustnet (100%)" with no details:
+
+**Problem**: Debug symbols are missing from the release build.
+
+**Solution**:
+```bash
+# Rebuild with debug symbols
+CARGO_PROFILE_RELEASE_DEBUG=true cargo build --release
+
+# Or add to Cargo.toml:
+[profile.release]
+debug = true
+
+# Then re-profile
+sudo -E ~/.cargo/bin/flamegraph -- ./target/release/rustnet
+```
+
+### Flamegraph Shows Only Kernel Functions
+
+**Problem**: Running with insufficient permissions or perf can't access user-space symbols.
+
+**Solution**:
+```bash
+# Check perf_event_paranoid setting
+cat /proc/sys/kernel/perf_event_paranoid
+
+# If it's > 1, temporarily lower it (requires root):
+sudo sysctl kernel.perf_event_paranoid=1
+
+# Or run as root
+sudo -E ~/.cargo/bin/flamegraph -- ./target/release/rustnet
+```
+
+### Very Short Flamegraph (< 1000 samples)
+
+**Problem**: Profiling session too short, not enough data collected.
+
+**Solution**:
+```bash
+# Let rustnet run for at least 30-60 seconds before stopping
+# The more network traffic, the better the profile
+
+# For longer profiling:
+timeout 60 sudo -E ~/.cargo/bin/flamegraph -- ./target/release/rustnet
+```
+
+## Debugging Slow TUI
+
+If the TUI feels sluggish:
+
+1. **Check refresh rate**: Default is 1000ms, can be adjusted with `--refresh-interval`
+2. **Check connection count**: High connection counts increase sorting overhead
+3. **Profile the UI loop**: Look for hot spots in `run_ui_loop`, `draw`, or `sort_connections`
+4. **Monitor thread contention**: Check if packet processing threads are blocking the snapshot provider
diff --git a/PROFILING.zh-CN.md b/PROFILING.zh-CN.md
new file mode 100644
index 0000000..bc01393
--- /dev/null
+++ b/PROFILING.zh-CN.md
@@ -0,0 +1,196 @@
+English | 简体中文
+
+# RustNet 性能分析指南
+
+本指南介绍如何对 RustNet 进行性能分析,以定位性能瓶颈。
+
+## 快速开始
+
+### 使用 perf + flamegraph 进行 CPU 分析
+
+在 Linux 上分析 CPU 占用最简单的方式:
+
+```bash
+# 1. 安装 flamegraph 工具
+cargo install flamegraph
+
+# 2. 构建带调试符号的 release 二进制
+# 重要:要生成有意义的火焰图,必须包含调试符号!
+CARGO_PROFILE_RELEASE_DEBUG=true cargo build --release
+
+# 或临时在 Cargo.toml 中加入:
+# [profile.release]
+# debug = true
+
+# 3. 在性能分析下运行(perf 需要 sudo)
+# 注意:使用 flamegraph 的完整路径,因为 sudo 不会带上你用户的 PATH
+# 重要:在要分析的命令前加 --
+sudo -E ~/.cargo/bin/flamegraph -- ./target/release/rustnet
+
+# 或在二进制之后指定接口及其他参数
+sudo -E ~/.cargo/bin/flamegraph -- ./target/release/rustnet -i eth0
+
+# 或者保留 PATH 以使用更简洁的命令:
+sudo env "PATH=$PATH" flamegraph -- ./target/release/rustnet
+
+# 4. 在浏览器中打开生成的 flamegraph.svg
+firefox flamegraph.svg
+```
+
+### 备选方案:直接使用 perf
+
+如果你更习惯直接使用 `perf`:
+
+```bash
+# 构建带调试符号的版本
+cargo build --release
+
+# 记录性能数据(运行 30-60 秒,然后按 Ctrl+C 停止)
+sudo perf record -F 99 -g ./target/release/rustnet -i eth0
+
+# 生成火焰图(需要 FlameGraph 脚本)
+# 从以下地址安装:https://github.com/brendangregg/FlameGraph
+perf script | stackcollapse-perf.pl | flamegraph.pl > flamegraph.svg
+
+# 或在 perf 的 TUI 中查看
+sudo perf report
+```
+
+### 分析正在运行的实例
+
+如果 RustNet 已经在运行:
+
+```bash
+# 查找 PID
+ps aux | grep rustnet
+
+# 对运行中的进程分析 60 秒
+sudo -E ~/.cargo/bin/flamegraph -p --output rustnet-live.svg
+
+# 或直接使用 perf
+sudo perf record -F 99 -g -p sleep 60
+sudo perf report
+```
+
+## 解读火焰图
+
+重点关注:
+- **底部的宽条**:消耗大量总 CPU 时间的函数
+- **高耸的栈**:很深的调用链(潜在的优化目标)
+- **热点**:采样次数很多的函数(在某些查看器中显示为鲜亮的颜色)
+
+常见热点:
+- `packet_parser::parse_packet`:正常——这是核心的数据包处理
+- `DashMap::iter` 或 `iter_mut`:如果占比很大,考虑降低迭代频率
+- `clone`:如果过多,减少不必要的克隆
+- 系统调用(`read`、`write`、`ioctl`):文件系统或网络 I/O 开销
+
+## Criterion 基准测试
+
+核心操作的微基准测试位于 `benches/`。运行方式:
+
+| 基准测试 | 命令 |
+|-----------|---------|
+| 数据包解析 | `cargo bench --bench packet_parsing` |
+| 连接合并 | `cargo bench --bench connection_merge` |
+| 快照创建 | `cargo bench --bench snapshot` |
+| 全部基准测试 | `cargo bench` |
+| 结构体大小 | `cargo test --lib struct_sizes -- --nocapture` |
+
+Criterion 会在 `target/criterion/` 中生成 HTML 报告,并对多次运行结果进行统计比较。
+
+## 临时基准测试
+
+要获得稳定一致的基准测试:
+
+```bash
+# 在稳定的流量下运行
+sudo ./target/release/rustnet --interface eth0 &
+PID=$!
+
+# 监控 CPU 占用
+top -p $PID
+
+# 或使用 perf stat 获取详细指标
+sudo perf stat -p $PID sleep 60
+
+# 停止应用
+sudo kill $PID
+```
+
+## 性能回归测试
+
+在改动之后,对比改动前后:
+
+```bash
+# 基线(改动前)
+sudo perf stat -r 3 timeout 60s ./target/release/rustnet-before > /dev/null
+
+# 改动后
+sudo perf stat -r 3 timeout 60s ./target/release/rustnet > /dev/null
+```
+
+需要对比的关键指标:
+- CPU 周期数
+- 每周期指令数(IPC)
+- 缓存未命中
+- 上下文切换
+
+## 火焰图问题排查
+
+### 火焰图为空或只有单个条目
+
+如果你的火焰图只显示 “rustnet (100%)” 而没有任何细节:
+
+**问题**:release 构建缺少调试符号。
+
+**解决方案**:
+```bash
+# 带调试符号重新构建
+CARGO_PROFILE_RELEASE_DEBUG=true cargo build --release
+
+# 或在 Cargo.toml 中加入:
+[profile.release]
+debug = true
+
+# 然后重新分析
+sudo -E ~/.cargo/bin/flamegraph -- ./target/release/rustnet
+```
+
+### 火焰图只显示内核函数
+
+**问题**:运行权限不足,或 perf 无法访问用户态符号。
+
+**解决方案**:
+```bash
+# 检查 perf_event_paranoid 设置
+cat /proc/sys/kernel/perf_event_paranoid
+
+# 如果它大于 1,临时调低(需要 root):
+sudo sysctl kernel.perf_event_paranoid=1
+
+# 或以 root 运行
+sudo -E ~/.cargo/bin/flamegraph -- ./target/release/rustnet
+```
+
+### 火焰图过短(采样少于 1000 个)
+
+**问题**:分析会话太短,采集的数据不足。
+
+**解决方案**:
+```bash
+# 在停止前,让 rustnet 至少运行 30-60 秒
+# 网络流量越多,分析结果越好
+
+# 如需更长时间的分析:
+timeout 60 sudo -E ~/.cargo/bin/flamegraph -- ./target/release/rustnet
+```
+
+## 排查 TUI 卡顿
+
+如果 TUI 感觉迟钝:
+
+1. **检查刷新频率**:默认是 1000ms,可通过 `--refresh-interval` 调整
+2. **检查连接数量**:连接数过高会增加排序开销
+3. **分析 UI 循环**:在 `run_ui_loop`、`draw` 或 `sort_connections` 中查找热点
+4. **监控线程争用**:检查数据包处理线程是否阻塞了快照提供者
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ddeb371
--- /dev/null
+++ b/README.md
@@ -0,0 +1,340 @@
+
+
RustNet
+
+ Per-process network monitoring for your terminal: live TCP, UDP, and QUIC connections with deep packet inspection, sandboxed by default.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ English | 简体中文
+
+
+
+
+
+
+
+ Real-time visibility into every connection your machine makes, who owns it, and what protocol it's speaking. No tcpdump, X11 forwarding, or root piping.
+
+
+## Features
+
+- **Per-process attribution**: Every TCP, UDP, and QUIC connection mapped to its owning process, via eBPF on Linux, PKTAP on macOS, native APIs on Windows and FreeBSD. Wireshark and tcpdump can't do this; `netstat` / `ss` can't show live state.
+- **Deep packet inspection**: Identify HTTP, HTTPS/TLS with SNI, DNS, SSH, FTP, QUIC, MQTT, BitTorrent, STUN, NTP, mDNS, LLMNR, DHCP, SNMP, SSDP, and NetBIOS, without external dissectors.
+- **Annotated PCAPNG export**: `--pcapng-export` writes a Wireshark-ready capture with process, PID, direction, DPI/SNI, and GeoIP embedded as per-packet comments. Open it in Wireshark and every packet already names its owning process, with no post-processing. Classic `--pcap-export` with a JSONL sidecar for offline correlation is also available.
+- **Security sandboxing**: Landlock (Linux 5.13+), Seatbelt (macOS), token privilege drop + job-object child-process block (Windows). Drops privileges immediately after libpcap initializes. See [SECURITY.md](SECURITY.md).
+- **TCP network analytics**: Real-time retransmissions, out-of-order packets, and fast-retransmit detection, per-connection and aggregate.
+- **Smart connection lifecycle**: Protocol-aware timeouts with white → yellow → red staleness indicators. Toggle `t` to keep historic (closed) connections visible for forensics.
+- **Vim/fzf-style filtering**: `port:`, `src:`, `dst:`, `sni:`, `process:`, `state:`, `proto:`, plus regex via `/(?i)pattern/`.
+- **GeoIP enrichment**: Country lookups via local MaxMind GeoLite2. No network calls.
+- **Kubernetes attribution** (optional `kubernetes` feature): connections mapped to their pod, namespace, and container, shown in the details pane, JSON/PCAPNG exports, and the `pod:`, `ns:`, `container:` filters. Enabled in the official Docker image; on a cluster, use the [kubectl-rustnet](https://github.com/domcyrus/kubectl-rustnet) plugin to run it as an ephemeral debug pod. See [USAGE.md](USAGE.md#--kubernetes-mode-optional-feature).
+- **Cross-platform**: Linux, macOS, Windows, FreeBSD.
+
+## Why RustNet?
+
+RustNet fills the gap between simple connection tools (`netstat`, `ss`) and packet analyzers (`Wireshark`, `tcpdump`):
+
+- **Process attribution**: See which application owns each connection. Wireshark cannot provide this because it only sees packets, not sockets.
+- **Connection-centric view**: Track states, bandwidth, and protocols per connection in real-time
+- **SSH-friendly**: TUI works over SSH so you can quickly see what's happening on a remote server without forwarding X11 or capturing traffic
+
+RustNet complements packet capture tools. Use RustNet to see *what's making connections*. For direct Wireshark inspection, `--pcapng-export` writes live best-effort packet comments with PID/process context. For cleanup-time correlation, use `--pcap-export` plus the JSONL sidecar and optional `scripts/pcap_enrich.py`. See [PCAP Export](USAGE.md#pcap-export) and [Comparison with Similar Tools](ARCHITECTURE.md#comparison-with-similar-tools) for details.
+
+Built on ratatui, libpcap, eBPF (libbpf-rs), DashMap, crossbeam, ring, MaxMind GeoLite2, and Landlock. See [ARCHITECTURE.md](ARCHITECTURE.md#dependencies) for the full dependency breakdown.
+
+
+eBPF Enhanced Process Identification (Linux Default)
+
+RustNet uses kernel eBPF programs by default on Linux for enhanced performance and lower overhead process identification. However, this comes with important limitations:
+
+**Process Name Limitations:**
+- eBPF uses the kernel's `comm` field, which is limited to 16 characters
+- Shows the task/thread command name, not the full executable path
+- Multi-threaded applications often show thread names instead of the main process name
+
+**Real-world Examples:**
+- **Firefox**: May appear as "Socket Thread", "Web Content", "Isolated Web Co", or "MainThread"
+- **Chrome**: May appear as "ThreadPoolForeg", "Chrome_IOThread", "BrokerProcess", or "SandboxHelper"
+- **Electron apps**: Often show as "electron", "node", or internal thread names
+- **System processes**: Show truncated names like "systemd-resolve" → "systemd-resolve"
+
+**Fallback Behavior:**
+- When eBPF fails to load or lacks sufficient permissions, RustNet automatically falls back to standard procfs-based process identification
+- Standard mode provides full process names but with higher CPU overhead
+- eBPF is enabled by default; no special build flags needed
+
+To disable eBPF and use procfs-only mode, build with:
+```bash
+cargo build --release --no-default-features
+```
+
+See [ARCHITECTURE.md](ARCHITECTURE.md) for technical information.
+
+
+
+
+Interface Statistics Monitoring
+
+RustNet provides real-time network interface statistics across all supported platforms:
+
+- **Overview Tab**: Shows active interfaces with current rates, errors, and drops
+- **Interfaces Tab** (press `3`): Detailed table with comprehensive metrics for all interfaces
+- **Cross-Platform**: Linux (sysfs), macOS/FreeBSD (getifaddrs), Windows (GetIfTable2 API)
+- **Smart Filtering**: Windows automatically excludes virtual/filter adapters
+
+See [USAGE.md](USAGE.md#interface-statistics) for detailed documentation on interpreting interface statistics and platform-specific behavior.
+
+**Metrics Available:**
+- Total bytes and packets (RX/TX)
+- Error counters (receive and transmit)
+- Packet drops (queue overflows)
+- Collisions (legacy, rarely used on modern networks)
+
+Stats are collected every 2 seconds in a background thread with minimal performance impact.
+
+
+
+## Screenshots
+
+
+
+ Overview Connections table with live stats and sparklines
+ Details Per-connection SNI, cipher, GeoIP, DPI
+
+
+ Graph Traffic chart, app distribution, top processes
+ Interfaces Per-interface RX/TX history with errors and drops
+
+
+
+## Quick Start
+
+### Installation
+
+**Homebrew (macOS / Linux):**
+```bash
+brew install rustnet
+```
+
+**Ubuntu (25.10+):**
+```bash
+sudo add-apt-repository ppa:domcyrus/rustnet
+sudo apt update && sudo apt install rustnet
+```
+
+**Fedora (42+):**
+```bash
+sudo dnf copr enable domcyrus/rustnet
+sudo dnf install rustnet
+```
+
+**openSUSE Tumbleweed:**
+```bash
+sudo zypper addrepo https://download.opensuse.org/repositories/home:/domcyrus:/rustnet/openSUSE_Tumbleweed/home:domcyrus:rustnet.repo
+sudo zypper refresh
+sudo zypper install rustnet
+```
+
+**Arch Linux:**
+```bash
+sudo pacman -S rustnet
+```
+
+**Nix / NixOS:**
+```bash
+nix-shell -p rustnet
+# Then inside the shell: sudo rustnet
+```
+
+**From crates.io:**
+```bash
+cargo install rustnet-monitor
+```
+
+**Windows (Chocolatey):**
+```powershell
+# Run in Administrator PowerShell
+# Requires Npcap (https://npcap.com) installed with "WinPcap API-compatible Mode" enabled
+choco install rustnet
+```
+
+**Other platforms:**
+- **FreeBSD**: Download from [rustnet-bsd releases](https://github.com/domcyrus/rustnet-bsd/releases)
+- **Docker, source builds, other Linux distros**: See [INSTALL.md](INSTALL.md) for detailed instructions
+
+### Running RustNet
+
+Packet capture requires elevated privileges:
+
+```bash
+# Quick start (all platforms)
+sudo rustnet
+
+# Linux: Grant capabilities to run without sudo (recommended)
+sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' $(which rustnet)
+rustnet
+```
+
+**Common options:**
+```bash
+rustnet -i eth0 # Specify network interface
+rustnet --show-localhost # Show localhost connections
+rustnet --no-resolve-dns # Disable reverse DNS lookups (enabled by default)
+rustnet -r 500 # Set refresh interval (ms)
+rustnet --theme classic # Original full-color palette (default: muted)
+rustnet --pcapng-export capture.pcapng # Annotated PCAPNG for Wireshark
+```
+
+See [INSTALL.md](INSTALL.md) for detailed permission setup and [USAGE.md](USAGE.md) for complete options.
+
+> If you set capabilities but the TUI still shows `eBPF unavailable`, see
+> [eBPF Unavailable Despite Capabilities Being Set](INSTALL.md#ebpf-unavailable-despite-capabilities-being-set)
+> in the troubleshooting section.
+
+## Keyboard Controls
+
+| Key | Action |
+|-----|--------|
+| `q` | Quit (press twice to confirm) |
+| `Ctrl+C` | Quit immediately |
+| `x` | Clear all connections (press twice to confirm) |
+| `Tab` or `]` | Next tab |
+| `Shift+Tab` or `[` | Previous tab |
+| `1`–`5` | Jump to Overview / Details / Interfaces / Graph / Help |
+| `↑/k` `↓/j` | Navigate up/down |
+| `g` `G` | Jump to first/last connection |
+| `Enter` | View connection details |
+| `Esc` | Go back or clear filter |
+| `c` | Copy remote address |
+| `p` | Toggle service names/ports |
+| `d` | Toggle hostnames/IPs |
+| `s` `S` | Cycle sort columns / toggle direction |
+| `a` | Toggle process grouping |
+| `Space` | Expand/collapse process group |
+| `←/→` or `h/l` | Collapse/expand group |
+| `PageUp/PageDown` or `Ctrl+B/F` | Page navigation |
+| `t` | Toggle historic (closed) connections |
+| `i` | Toggle the System info sidebar |
+| `r` | Reset view (grouping, sort, filter) |
+| `/` | Enter filter mode |
+| `h` | Toggle help |
+
+See [USAGE.md](USAGE.md) for detailed keyboard controls and navigation tips.
+
+## Filtering & Sorting
+
+**Quick filtering examples:**
+```
+/google # Search for "google" anywhere
+/port:443 # Filter by port
+/process:firefox # Filter by process
+/state:established # Filter by connection state
+/dport:443 sni:github.com # Combine filters
+```
+
+**Sorting:**
+- Press `s` to cycle through sortable columns (Process, Addresses, Service, Application, State, Bandwidth)
+- Press `S` (Shift+s) to toggle sort direction
+- Find bandwidth hogs: Press `s` until "Bandwidth Total ↓" appears (sorts by combined up+down speed)
+
+See [USAGE.md](USAGE.md) for complete filtering syntax and sorting guide.
+
+
+Advanced Filtering Examples
+
+**Keyword filters:**
+- `port:44` - Ports containing "44" (443, 8080, 4433)
+- `sport:80` - Source ports containing "80"
+- `dport:443` - Destination ports containing "443"
+- `src:192.168` - Source IPs containing "192.168"
+- `dst:github.com` - Destinations containing "github.com"
+- `process:ssh` - Process names containing "ssh"
+- `sni:api` - SNI hostnames containing "api"
+- `app:openssh` - SSH connections using OpenSSH
+- `state:established` - Filter by protocol state
+- `proto:tcp` - Filter by protocol type
+
+**State filtering:**
+- `state:syn_recv` - Half-open connections (SYN flood detection)
+- `state:established` - Established connections only
+- `state:quic_connected` - Active QUIC connections
+- `state:dns_query` - DNS query connections
+
+**Combined examples:**
+- `sport:80 process:nginx` - Nginx connections from port 80
+- `dport:443 sni:google.com` - HTTPS to Google
+- `process:firefox state:quic_connected` - Firefox QUIC connections
+- `dport:22 app:openssh state:established` - Established OpenSSH connections
+
+
+
+
+Connection Lifecycle & Visual Indicators
+
+RustNet uses smart timeouts and visual warnings before removing connections:
+
+**Visual staleness indicators:**
+- **White**: Active (< 75% of timeout)
+- **Yellow**: Stale (75-90% of timeout)
+- **Red**: Critical (> 90% of timeout)
+
+**Protocol-aware timeouts:**
+- **HTTP/HTTPS**: 10 minutes (supports keep-alive)
+- **SSH**: 30 minutes (long sessions)
+- **TCP active**: 10 minutes, idle: 5 minutes
+- **QUIC connected**: 3 minutes (or peer's transport-param idle timeout, when present); `Initial`/`Handshaking`: 60 seconds
+- **DNS**: 30 seconds
+- **TCP CLOSED**: 5 seconds
+
+Example: An HTTP connection turns yellow at 7.5 min, red at 9 min, and is removed at 10 min.
+
+See [USAGE.md](USAGE.md) for complete timeout details.
+
+
+
+## Documentation
+
+- **[INSTALL.md](INSTALL.md)** - Detailed installation instructions for all platforms, permission setup, and troubleshooting
+- **[USAGE.md](USAGE.md)** - Complete usage guide including command-line options, filtering, sorting, and logging
+- **[SECURITY.md](SECURITY.md)** - Security features including Landlock sandboxing and privilege management
+- **[ARCHITECTURE.md](ARCHITECTURE.md)** - Technical architecture, platform implementations, and performance details
+- **[PROFILING.md](PROFILING.md)** - Performance profiling guide with flamegraph setup and optimization tips
+- **[ROADMAP.md](ROADMAP.md)** - Planned features and future improvements
+- **[RELEASE.md](RELEASE.md)** - Release process for maintainers
+
+## Contributing
+
+Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on how to contribute.
+
+See [CONTRIBUTORS.md](CONTRIBUTORS.md) for a list of people who have contributed to this project.
+
+## License
+
+This project is licensed under the Apache License, Version 2.0 - see the [LICENSE](LICENSE) file for details.
+
+## Acknowledgments
+
+- Built with [ratatui](https://github.com/ratatui-org/ratatui) for the terminal UI
+- Packet capture powered by [libpcap](https://www.tcpdump.org/)
+- Inspired by tools like `tshark/wireshark/tcpdump`, `sniffnet`, `netstat`, `ss`, `iftop`, and [bandwhich](https://github.com/imsnif/bandwhich)
+- Some code is vibe coded (OMG) / may the LLM gods be with you
+
+---
+
+## Documentation Moved
+
+Some sections have been moved to dedicated files for better organization:
+
+- **Permissions Setup**: Now in [INSTALL.md - Permissions Setup](INSTALL.md#permissions-setup)
+- **Installation Instructions**: Now in [INSTALL.md](INSTALL.md)
+- **Detailed Usage**: Now in [USAGE.md](USAGE.md)
+- **Architecture Details**: Now in [ARCHITECTURE.md](ARCHITECTURE.md)
diff --git a/README.wehub.md b/README.wehub.md
new file mode 100644
index 0000000..b9b625e
--- /dev/null
+++ b/README.wehub.md
@@ -0,0 +1,7 @@
+# WeHub 来源说明
+
+- 原始项目:`domcyrus/rustnet`
+- 原始仓库:https://github.com/domcyrus/rustnet
+- 导入方式:上游默认分支的最新快照
+- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准
+- 本文件仅用于记录来源,不代表 WeHub 是原项目作者
diff --git a/README.zh-CN.md b/README.zh-CN.md
new file mode 100644
index 0000000..0ed642f
--- /dev/null
+++ b/README.zh-CN.md
@@ -0,0 +1,337 @@
+
+
RustNet
+
+ 面向终端的进程级网络监控工具:实时呈现 TCP、UDP、QUIC 连接,自带深度包检测,默认沙箱隔离运行。
+
+
+
+
+
+
+
+
+
+
+
+
+
+ English | 简体中文
+
+
+
+
+
+
+
+ 实时洞察机器对外发起的每一条连接:谁在使用它、走的是什么协议。无需 tcpdump,无需 X11 转发,也不必把 root 权限传递下去。
+
+
+## 功能特性
+
+- **进程级归属识别**:每一条 TCP、UDP、QUIC 连接都能追溯到所属进程。Linux 使用 eBPF,macOS 使用 PKTAP,Windows 与 FreeBSD 则走原生 API。Wireshark 与 tcpdump 做不到这一点;`netstat` / `ss` 也无法展示实时状态。
+- **深度包检测**:无需外部解析器即可识别 HTTP、带 SNI 的 HTTPS/TLS、DNS、SSH、FTP、QUIC、MQTT、BitTorrent、STUN、NTP、mDNS、LLMNR、DHCP、SNMP、SSDP 及 NetBIOS。
+- **安全沙箱**:Linux 5.13+ 使用 Landlock,macOS 使用 Seatbelt,Windows 通过 token 降权 + job-object 阻止子进程创建。libpcap 初始化完成后立即丢弃特权。详见 [SECURITY.zh-CN.md](SECURITY.zh-CN.md)。
+- **TCP 网络分析**:实时统计重传、乱序包、快重传,既有逐连接视图也有汇总视图。
+- **智能连接生命周期**:按协议设置超时,以白 → 黄 → 红的颜色指示过期程度。按 `t` 可保留历史(已关闭)连接以便事后追溯。
+- **Vim / fzf 风格过滤**:支持 `port:`、`src:`、`dst:`、`sni:`、`process:`、`state:`、`proto:`,以及 `/(?i)pattern/` 形式的正则。
+- **GeoIP 增强**:基于本地 MaxMind GeoLite2 数据库查询国家信息,不发起任何网络请求。
+- **跨平台**:Linux、macOS、Windows、FreeBSD。
+
+## 为什么选 RustNet?
+
+RustNet 填补了简单连接工具(`netstat`、`ss`)与数据包分析器(`Wireshark`、`tcpdump`)之间的空白:
+
+- **进程归属**:看清每条连接归哪个应用所有。Wireshark 看不到这一层,因为它只看包,不看 socket。
+- **以连接为中心的视图**:逐连接实时追踪状态、带宽与协议。
+- **SSH 友好**:TUI 可直接在 SSH 会话中运行,远端服务器上发生了什么一眼可见,不必转发 X11 或抓包再回传。
+
+RustNet 与抓包工具是互补关系。用 RustNet 看清*谁在发起连接*;若要直接在 Wireshark 中查看,可用 `--pcapng-export` 写出带 RustNet 数据包注释的 PCAPNG;若更重视清理阶段的元数据完整性,可用 `--pcap-export` 加 JSONL sidecar,再借 `scripts/pcap_enrich.py` 富化。参见 [USAGE.zh-CN.md 的 PCAP 导出章节](USAGE.zh-CN.md#pcap-export) 与 [ARCHITECTURE.zh-CN.md 的同类工具对比章节](ARCHITECTURE.zh-CN.md#comparison-with-similar-tools)。
+
+基于 ratatui、libpcap、eBPF(libbpf-rs)、DashMap、crossbeam、ring、MaxMind GeoLite2 与 Landlock 构建。完整依赖清单见 [ARCHITECTURE.zh-CN.md](ARCHITECTURE.zh-CN.md#dependencies)。
+
+
+基于 eBPF 的增强型进程识别(Linux 默认)
+
+RustNet 在 Linux 上默认使用内核 eBPF 程序进行进程识别,从而获得更高的性能与更低的开销。但这种方式也有一些重要的限制需要了解:
+
+**进程名长度限制:**
+- eBPF 使用内核的 `comm` 字段,该字段最多只有 16 个字符
+- 显示的是任务 / 线程的命令名,而非完整的可执行路径
+- 多线程应用往往展示线程名而非主进程名
+
+**真实场景示例:**
+- **Firefox**:可能显示为 "Socket Thread"、"Web Content"、"Isolated Web Co" 或 "MainThread"
+- **Chrome**:可能显示为 "ThreadPoolForeg"、"Chrome_IOThread"、"BrokerProcess" 或 "SandboxHelper"
+- **Electron 应用**:经常显示为 "electron"、"node" 或内部线程名
+- **系统进程**:展示截断后的名字,如 "systemd-resolve" → "systemd-resolve"
+
+**回退行为:**
+- 当 eBPF 加载失败或权限不足时,RustNet 会自动回退到基于 procfs 的标准进程识别方式
+- 标准模式可以拿到完整进程名,但 CPU 开销更高
+- eBPF 默认启用,无需任何特殊编译参数
+
+如需关闭 eBPF、仅使用 procfs 模式,请这样构建:
+```bash
+cargo build --release --no-default-features
+```
+
+技术细节见 [ARCHITECTURE.zh-CN.md](ARCHITECTURE.zh-CN.md)。
+
+
+
+
+网络接口统计监控
+
+RustNet 在所有支持的平台上提供实时的网络接口统计:
+
+- **概览标签页**:展示当前活跃的接口,包含速率、错误数与丢包数
+- **接口标签页**(按 `3`):以详细表格呈现各接口的完整指标
+- **跨平台**:Linux(sysfs)、macOS / FreeBSD(getifaddrs)、Windows(GetIfTable2 API)
+- **智能过滤**:Windows 上自动剔除虚拟 / 过滤类适配器
+
+如何解读接口统计以及各平台的差异,详见 [USAGE.zh-CN.md](USAGE.zh-CN.md#interface-statistics)。
+
+**可用指标:**
+- 总字节数与包数(RX / TX)
+- 错误计数(收 / 发)
+- 丢包数(队列溢出)
+- 冲突数(传统指标,现代网络中很少出现)
+
+数据由后台线程每 2 秒采集一次,对性能影响极小。
+
+
+
+## 截图
+
+
+
+ 概览 连接列表与实时统计、迷你折线图
+ 详情 逐连接展示 SNI、加密套件、GeoIP、DPI
+
+
+ 图表 流量曲线、应用分布、Top 进程
+ 接口 各接口 RX / TX 历史曲线、错误与丢包
+
+
+
+## 快速上手
+
+### 安装
+
+**Homebrew(macOS / Linux):**
+```bash
+brew install rustnet
+```
+
+**Ubuntu(25.10+):**
+```bash
+sudo add-apt-repository ppa:domcyrus/rustnet
+sudo apt update && sudo apt install rustnet
+```
+
+**Fedora(42+):**
+```bash
+sudo dnf copr enable domcyrus/rustnet
+sudo dnf install rustnet
+```
+
+**openSUSE Tumbleweed:**
+```bash
+sudo zypper addrepo https://download.opensuse.org/repositories/home:/domcyrus:/rustnet/openSUSE_Tumbleweed/home:domcyrus:rustnet.repo
+sudo zypper refresh
+sudo zypper install rustnet
+```
+
+**Arch Linux:**
+```bash
+sudo pacman -S rustnet
+```
+
+**Nix / NixOS:**
+```bash
+nix-shell -p rustnet
+# 然后在 shell 中执行: sudo rustnet
+```
+
+**通过 crates.io:**
+```bash
+cargo install rustnet-monitor
+```
+
+**Windows(Chocolatey):**
+```powershell
+# 需在管理员权限的 PowerShell 中执行
+# 需要先安装 Npcap(https://npcap.com),并启用 "WinPcap API-compatible Mode"
+choco install rustnet
+```
+
+**其他平台:**
+- **FreeBSD**:从 [rustnet-bsd releases](https://github.com/domcyrus/rustnet-bsd/releases) 下载
+- **Docker、源码构建、其他 Linux 发行版**:详见 [INSTALL.zh-CN.md](INSTALL.zh-CN.md)
+
+### 运行 RustNet
+
+抓包需要更高的权限:
+
+```bash
+# 快速启动(所有平台)
+sudo rustnet
+
+# Linux:为可执行文件赋予 Linux capabilities,即可免 sudo 运行(推荐)
+sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' $(which rustnet)
+rustnet
+```
+
+**常用参数:**
+```bash
+rustnet -i eth0 # 指定网络接口
+rustnet --show-localhost # 显示 localhost 上的连接
+rustnet --no-resolve-dns # 关闭反向 DNS 解析(默认开启)
+rustnet -r 500 # 设置刷新间隔(毫秒)
+rustnet --theme classic # 原始全彩调色板(默认:muted)
+rustnet --pcapng-export capture.pcapng # 导出带注释的 PCAPNG
+```
+
+权限配置详情见 [INSTALL.zh-CN.md](INSTALL.zh-CN.md),完整参数说明见 [USAGE.zh-CN.md](USAGE.zh-CN.md)。
+
+> 如果已经设置了 Linux capabilities,但 TUI 仍然提示 `eBPF unavailable`,请参阅 [INSTALL.zh-CN.md 的排障章节](INSTALL.zh-CN.md#ebpf-unavailable-despite-capabilities-being-set)。
+
+## 键盘控制
+
+| 按键 | 作用 |
+|-----|--------|
+| `q` | 退出(连按两次确认) |
+| `Ctrl+C` | 立即退出 |
+| `x` | 清空所有连接(连按两次确认) |
+| `Tab` 或 `]` | 下一个标签页 |
+| `Shift+Tab` 或 `[` | 上一个标签页 |
+| `1`–`5` | 直接跳转到 Overview / Details / Interfaces / Graph / Help |
+| `↑/k` `↓/j` | 上下移动 |
+| `g` `G` | 跳到第一条 / 最后一条连接 |
+| `Enter` | 查看连接详情 |
+| `Esc` | 返回或清除过滤器 |
+| `c` | 复制远端地址 |
+| `p` | 在服务名与端口之间切换 |
+| `d` | 在主机名与 IP 之间切换 |
+| `s` `S` | 切换排序列 / 切换排序方向 |
+| `a` | 切换按进程分组 |
+| `Space` | 展开 / 折叠进程分组 |
+| `←/→` 或 `h/l` | 折叠 / 展开当前分组 |
+| `PageUp/PageDown` 或 `Ctrl+B/F` | 翻页 |
+| `t` | 切换是否显示历史(已关闭)连接 |
+| `i` | 切换 System 信息侧边栏 |
+| `r` | 重置视图(分组、排序、过滤) |
+| `/` | 进入过滤模式 |
+| `h` | 切换帮助 |
+
+完整键位说明与导航技巧见 [USAGE.zh-CN.md](USAGE.zh-CN.md)。
+
+## 过滤与排序
+
+**快速过滤示例:**
+```
+/google # 全局搜索 "google"
+/port:443 # 按端口过滤
+/process:firefox # 按进程过滤
+/state:established # 按连接状态过滤
+/dport:443 sni:github.com # 组合多个过滤条件
+```
+
+**排序:**
+- 按 `s` 在可排序的列之间循环切换(进程、地址、服务、应用、状态、带宽)
+- 按 `S`(Shift+s)切换升序 / 降序
+- 想抓出带宽大户:连续按 `s` 直到显示 "Bandwidth Total ↓"(按上下行合计速度排序)
+
+完整的过滤语法与排序说明见 [USAGE.zh-CN.md](USAGE.zh-CN.md)。
+
+
+高级过滤示例
+
+**关键字过滤:**
+- `port:44` —— 端口号包含 "44" 的连接(443、8080、4433)
+- `sport:80` —— 源端口包含 "80"
+- `dport:443` —— 目的端口包含 "443"
+- `src:192.168` —— 源 IP 包含 "192.168"
+- `dst:github.com` —— 目的地址包含 "github.com"
+- `process:ssh` —— 进程名包含 "ssh"
+- `sni:api` —— SNI 主机名包含 "api"
+- `app:openssh` —— 使用 OpenSSH 的 SSH 连接
+- `state:established` —— 按协议状态过滤
+- `proto:tcp` —— 按协议类型过滤
+
+**状态过滤:**
+- `state:syn_recv` —— 半开连接(可用于发现 SYN flood)
+- `state:established` —— 仅显示已建立的连接
+- `state:quic_connected` —— 活跃的 QUIC 连接
+- `state:dns_query` —— DNS 查询连接
+
+**组合示例:**
+- `sport:80 process:nginx` —— Nginx 从 80 端口发出的连接
+- `dport:443 sni:google.com` —— 到 Google 的 HTTPS
+- `process:firefox state:quic_connected` —— Firefox 的 QUIC 连接
+- `dport:22 app:openssh state:established` —— 已建立的 OpenSSH 连接
+
+
+
+
+连接生命周期与可视化指示
+
+RustNet 在移除连接前会先用智能超时机制与颜色给出预警:
+
+**过期程度的颜色指示:**
+- **白色**:活跃(< 75% 的超时时间)
+- **黄色**:开始过期(75% – 90% 的超时时间)
+- **红色**:即将过期(> 90% 的超时时间)
+
+**按协议设定的超时:**
+- **HTTP / HTTPS**:10 分钟(支持 keep-alive)
+- **SSH**:30 分钟(适配长会话)
+- **TCP 活跃**:10 分钟;**TCP 空闲**:5 分钟
+- **QUIC 已连接**:3 分钟(若对端通过 transport 参数声明了 idle timeout,则以对端为准);`Initial` / `Handshaking` 阶段:60 秒
+- **DNS**:30 秒
+- **TCP CLOSED**:5 秒
+
+举例:一条 HTTP 连接会在第 7.5 分钟变黄,第 9 分钟变红,第 10 分钟被移除。
+
+完整超时说明见 [USAGE.zh-CN.md](USAGE.zh-CN.md)。
+
+
+
+## 文档
+
+- **[INSTALL.zh-CN.md](INSTALL.zh-CN.md)** —— 各平台的详细安装说明、权限配置与排障
+- **[USAGE.zh-CN.md](USAGE.zh-CN.md)** —— 完整使用手册,涵盖命令行参数、过滤、排序与日志
+- **[SECURITY.zh-CN.md](SECURITY.zh-CN.md)** —— 安全特性,包括 Landlock 沙箱与权限管理
+- **[ARCHITECTURE.zh-CN.md](ARCHITECTURE.zh-CN.md)** —— 技术架构、各平台实现与性能细节
+- **[CONTRIBUTING.zh-CN.md](CONTRIBUTING.zh-CN.md)** —— 贡献指南,包括工作流、质量要求与 AI 辅助贡献规范
+- **[PROFILING.zh-CN.md](PROFILING.zh-CN.md)** —— 性能分析指南,含 flamegraph 配置与优化建议
+- **[ROADMAP.md](ROADMAP.md)** —— 已规划的功能与后续改进
+- **[RELEASE.md](RELEASE.md)** —— 维护者发布流程
+
+## 参与贡献
+
+欢迎贡献!请阅读 [CONTRIBUTING.zh-CN.md](CONTRIBUTING.zh-CN.md) 了解贡献流程。
+
+历来的贡献者名单见 [CONTRIBUTORS.md](CONTRIBUTORS.md)。
+
+## 许可证
+
+本项目采用 Apache License 2.0 许可证,详见 [LICENSE](LICENSE) 文件。
+
+## 致谢
+
+- 终端 UI 基于 [ratatui](https://github.com/ratatui-org/ratatui) 构建
+- 抓包能力由 [libpcap](https://www.tcpdump.org/) 提供
+- 灵感来自 `tshark/wireshark/tcpdump`、`sniffnet`、`netstat`、`ss`、`iftop`,以及 [bandwhich](https://github.com/imsnif/bandwhich)
+- 部分代码靠手感写出(OMG)/ 愿 LLM 之神与你同在
+
+---
+
+## 已迁移的文档
+
+部分章节已迁移到独立文件,以便更好地组织内容:
+
+- **权限配置**:迁移至 [INSTALL.zh-CN.md 的权限配置章节](INSTALL.zh-CN.md#permissions-setup)
+- **安装说明**:迁移至 [INSTALL.zh-CN.md](INSTALL.zh-CN.md)
+- **详细用法**:迁移至 [USAGE.zh-CN.md](USAGE.zh-CN.md)
+- **架构细节**:迁移至 [ARCHITECTURE.zh-CN.md](ARCHITECTURE.zh-CN.md)
diff --git a/RELEASE.md b/RELEASE.md
new file mode 100644
index 0000000..254e1d0
--- /dev/null
+++ b/RELEASE.md
@@ -0,0 +1,218 @@
+# Release Process
+
+This document is for maintainers releasing new versions of RustNet.
+
+## Changelog Maintenance (ongoing, not at release time)
+
+Notable changes are added to the `## [Unreleased]` section of `CHANGELOG.md` in
+the same PR that makes them — don't wait until release time and reconstruct the
+list from git history. Cutting a release then just renames that section (see
+step 3 below). `pre-release-check.sh` warns if `[Unreleased]` still has content
+after the rename, and the release workflow's notes extraction ignores it.
+
+## Creating a New Release
+
+### 1. Run Pre-Release Checks
+
+After updating versions and changelog, run the pre-release validation script:
+
+```bash
+./scripts/pre-release-check.sh 1.2.0
+```
+
+This validates version consistency, changelog entries, code quality (fmt/clippy/test),
+Dockerfile correctness, and git status. Fix any errors before proceeding.
+
+### 2. Test Platform Builds
+
+Before tagging, verify all platform builds succeed on the current main branch:
+
+```bash
+# Ensure you're on the main branch with latest changes
+git checkout main
+git pull origin main
+```
+
+1. Go to [Actions > Test Platform Builds](../../actions/workflows/test-platform-builds.yml)
+2. Click "Run workflow"
+3. Select `all` to test all platforms (including static Linux builds)
+4. Wait for the workflow to complete successfully
+
+This catches cross-platform and static linking issues before you invest time in release prep.
+
+### 3. Prepare the Release
+
+> **Two version tracks since the workspace split.** `Cargo.toml` carries two
+> versions: the binary's `[package] version` (line ~30, the user-facing `1.x`
+> line that tags and packages follow) and `[workspace.package] version` (line
+> ~9, the `0.x` library crates `rustnet-core`/`-capture`/`-host`, single source
+> of truth also referenced from `[workspace.dependencies]`). A normal feature
+> release bumps **only the binary `[package] version`** and `rpm/rustnet.spec`.
+> Bump the library version separately, and only when the libraries actually
+> change in a release-worthy way.
+
+Update the binary version in `Cargo.toml` and `rpm/rustnet.spec`, and turn the
+accumulated `[Unreleased]` changelog section into the release entry:
+
+```bash
+# Update Cargo.toml [package] version (e.g., version = "1.4.0") — NOT the
+# [workspace.package] version unless you intend to bump the library crates.
+# Update rpm/rustnet.spec Version field (e.g., Version: 1.4.0)
+
+# In CHANGELOG.md:
+# 1. Rename "## [Unreleased]" to "## [0.3.0] - YYYY-MM-DD" (review/polish the entries)
+# 2. Add a fresh, empty "## [Unreleased]" section above it
+# 3. Update the comparison links at the bottom:
+# [Unreleased]: https://github.com/domcyrus/rustnet/compare/v0.3.0...HEAD
+# [0.3.0]: https://github.com/domcyrus/rustnet/compare/v0.2.0...v0.3.0
+
+# Update Cargo.lock and test the build
+cargo build --release
+cargo test
+```
+
+### 4. Commit Release Changes
+
+```bash
+# Stage and commit the version and changelog changes
+git add Cargo.toml Cargo.lock CHANGELOG.md rpm/rustnet.spec
+git commit -m "Release v0.3.0
+
+- Feature or fix summary here
+- Another change here
+- And more changes"
+```
+
+### 5. Create and Push Git Tag
+
+```bash
+# Create an annotated tag matching the version in Cargo.toml
+git tag -a v0.3.0 -m "Release v0.3.0
+
+- Feature or fix summary here
+- Another change here
+- And more changes"
+
+# Push both the commit and the tag
+git push origin main
+git push origin v0.3.0
+```
+
+**That's it!** The GitHub Actions workflow will automatically:
+- Build binaries for all platforms (Linux, macOS, Windows - multiple architectures)
+- Create installer packages (DEB, RPM, DMG, MSI)
+- Extract release notes from CHANGELOG.md
+- Create a draft GitHub release with all artifacts attached
+- Upload all binaries and installers to the release
+- **Publish the release** (un-draft) once all assets are uploaded
+- Trigger downstream package updates (Homebrew, Chocolatey, FreeBSD, PPA, COPR, AUR, Docker, crates.io)
+
+### 6. Verify the Release
+
+Once the GitHub Actions workflow completes (~15-20 minutes):
+
+1. Go to the [GitHub repository releases page](https://github.com/domcyrus/rustnet/releases)
+2. Verify the release is published (no longer a draft) with all assets
+3. Review the automatically extracted release notes
+4. Check downstream package updates completed (Homebrew, Chocolatey, etc.)
+
+## Automated Release Workflow
+
+The release process is fully automated via [`.github/workflows/release.yml`](.github/workflows/release.yml):
+
+**Triggers:**
+- Pushing a tag matching `v[0-9]+.[0-9]+.[0-9]+` (e.g., `v0.3.0`, `v1.2.3`)
+- Manual workflow dispatch
+
+**What it does:**
+1. **Builds cross-platform binaries:**
+ - Linux: x64, ARM64, ARMv7 (with eBPF support)
+ - macOS: Intel (x64) and Apple Silicon (ARM64)
+ - Windows: 64-bit and 32-bit
+
+2. **Creates installer packages:**
+ - **Linux:** DEB packages (amd64, arm64, armhf) and RPM packages (x86_64, aarch64)
+ - **macOS:** DMG installers with app bundles (supports code signing/notarization if secrets configured)
+ - **Windows:** MSI installers (64-bit and 32-bit)
+
+3. **Extracts release notes:**
+ - Automatically parses `CHANGELOG.md` to extract the version-specific section
+ - Falls back to auto-generated notes if no changelog entry is found
+
+4. **Creates GitHub release:**
+ - Creates a draft release with the tag name as title
+ - Attaches all binaries and installer packages
+ - Uses extracted changelog content as release notes
+
+5. **Publishes the workspace to crates.io** (via
+ [`.github/workflows/publish.yml`](.github/workflows/publish.yml), after the
+ GitHub release is published): the four crates are published in dependency
+ order — `rustnet-core` → `rustnet-capture` → `rustnet-host` →
+ `rustnet-monitor` — waiting for each to appear in the index before publishing
+ a dependent. The step is idempotent (it skips any `crate@version` already on
+ crates.io), so a re-run after a partial failure is safe. The library crates
+ use the `[workspace.package]` version; the binary uses its `[package]`
+ version.
+
+## Important: Never Move a Tag After Release
+
+**Never force-push or move a tag after the release pipeline has started.** Moving a tag
+causes GitHub to regenerate source tarballs with different SHA checksums, which breaks
+every downstream package manager that already cached the original checksums:
+
+- **AUR/Homebrew/Chocolatey**: checksum verification failures for end users
+- **Launchpad PPA**: rejects uploads with the same version but different file contents
+- **crates.io**: already published and cannot be re-published with the same version
+
+If a fix is needed after tagging, **create a patch release** (e.g., `v1.1.1`) instead.
+
+## Release Checklist
+
+Before pushing the tag, ensure:
+
+- [ ] Pre-release checks pass: `./scripts/pre-release-check.sh x.y.z`
+- [ ] Test Platform Builds workflow passes for all platforms (including static)
+- [ ] Binary version updated in `Cargo.toml` `[package]` (not `[workspace.package]` unless bumping the library crates)
+- [ ] Version number updated in `rpm/rustnet.spec` (line 5: `Version: x.y.z`)
+- [ ] `Cargo.lock` updated (via `cargo build`)
+- [ ] `CHANGELOG.md`: `[Unreleased]` renamed to `## [x.y.z] - YYYY-MM-DD`, a fresh empty `[Unreleased]` added, comparison links updated
+- [ ] All tests pass (`cargo test`)
+- [ ] Changes committed to main branch
+- [ ] Git tag created and pushed
+
+After GitHub Actions completes:
+
+- [ ] Verify release is published (automatically un-drafted after all assets uploaded)
+- [ ] Verify all platform binaries built successfully
+- [ ] Verify all installer packages created (DEB, RPM, DMG, MSI)
+- [ ] Verify Docker image pushed to ghcr.io
+- [ ] Verify all four crates published to crates.io (`rustnet-monitor`, `rustnet-core`, `rustnet-capture`, `rustnet-host`) and docs.rs built
+- [ ] Review automatically extracted release notes
+- [ ] Verify Homebrew formula updated at https://github.com/domcyrus/homebrew-rustnet
+- [ ] Verify Chocolatey package updated at https://github.com/domcyrus/rustnet-chocolatey
+- [ ] Verify FreeBSD build at https://github.com/domcyrus/rustnet-bsd
+- [ ] Announce release (if applicable)
+
+## Maintenance: New Ubuntu and Fedora Releases
+
+This is an occasional task, not part of every release. When a new Ubuntu interim or LTS, or a new Fedora release, is published and we want RustNet packages to ship for it:
+
+1. **Ubuntu PPA**: add the new codename to the matrix in [`.github/workflows/ppa-release.yml`](.github/workflows/ppa-release.yml) (the `set-matrix` job's `releases=[...]` list and the `workflow_dispatch` choice options). Confirm the new series ships `rustc-1.88` (or whatever the current `rust-version` floor in `Cargo.toml` is). Reference: issue [#254](https://github.com/domcyrus/rustnet/issues/254) added Ubuntu 26.04 (Resolute) support.
+2. **Fedora COPR**: add the new chroot in the COPR project settings at [https://copr.fedorainfracloud.org/coprs/domcyrus/rustnet/edit/](https://copr.fedorainfracloud.org/coprs/domcyrus/rustnet/edit/). The chroot list is managed in the COPR UI, not in this repo.
+3. Trigger a `workflow_dispatch` of `Release to Ubuntu PPA` for the new codename to verify the build before relying on it from the next tagged release.
+
+Conversely, when an older Ubuntu series is no longer worth supporting (no `rustc-1.88` in archive, or end of life), remove it from the same two locations and update [INSTALL.md](INSTALL.md) and [debian/README.md](debian/README.md) to match.
+
+## Versioning
+
+RustNet follows [Semantic Versioning (SemVer)](https://semver.org/):
+
+- **MAJOR** version for incompatible API changes
+- **MINOR** version for backward-compatible functionality additions
+- **PATCH** version for backward-compatible bug fixes
+
+Examples:
+
+- `v0.1.0` → `v0.1.1` (bug fixes)
+- `v0.1.1` → `v0.2.0` (new features)
+- `v0.2.0` → `v1.0.0` (major changes, API stability)
diff --git a/ROADMAP.md b/ROADMAP.md
new file mode 100644
index 0000000..cb49d80
--- /dev/null
+++ b/ROADMAP.md
@@ -0,0 +1,270 @@
+# RustNet Roadmap
+
+This document outlines the planned features and improvements for RustNet.
+
+## Platform Support
+
+- [x] **macOS Support**: Full support including:
+ - BPF device access and permissions setup
+ - PKTAP (Packet Tap) headers for process identification from packet metadata
+ - Fallback to `lsof` system commands for process-socket associations
+ - DMG installation packages for Apple Silicon and Intel
+ - Homebrew installation support
+- [x] **Windows Support**: Full functionality working with:
+ - Npcap SDK and runtime integration
+ - MSI installation packages for 64-bit and 32-bit
+ - Process identification via Windows IP Helper API (GetExtendedTcpTable/GetExtendedUdpTable)
+- [x] **FreeBSD Support**: Full support including:
+ - Process identification via `sockstat` command parsing
+ - BPF device access and permissions setup
+ - Native libpcap packet capture
+ - Cross-compilation support from Linux
+- [ ] **FreeBSD Capsicum Full Sandbox** (`cap_enter()`): Replace per-FD `cap_rights_limit()` with full capability mode to prevent file access and data exfiltration. Requires:
+ - Switch from `sockstat` subprocess to `libprocstat(3)` library calls for process lookup (eliminates `fork()`/`execve()` dependency)
+ - Integrate `libcasper` for privileged sysctl access from inside capability mode (`kern.proc.filedesc` is blocked in `cap_enter()`)
+ - Architecture: pre-fork a Casper service before `cap_enter()`, communicate over socket pair at runtime
+ - Write FFI bindings for `libprocstat` and `libcasper` (no Rust crate exists)
+ - Link against `-lprocstat -lcasper -lcap_sysctl` (system libraries on FreeBSD 10+)
+- [ ] **Windows Sandbox Hardening**: Strengthen the current privilege-drop + Job Object setup with process mitigation policies (`SetProcessMitigationPolicy`), low-integrity execution, and evaluation of `CreateRestrictedToken` / AppContainer.
+- [ ] **macOS Seatbelt Hardening**: The current Seatbelt profile is allow-default with targeted denies (user homes, system credential stores, outbound TCP/UDP, `process-exec` except `lsof`). Tighten further:
+ - **Deny-by-default writes**: rustnet only writes its log/PCAP/JSONL output, so flip `file-write*` to deny-by-default with a small allowlist. This blocks root-level persistence (`/Library/LaunchDaemons`, `/Library/LaunchAgents`, `/private/etc` cron/launchd, etc.) that the current allow-default write policy leaves open. Needs on-host validation that the TUI's writes to the already-open tty and the `lsof` child still work.
+ - **More credential read denies**: system TCC database (`/Library/Application Support/com.apple.TCC`), Kerberos keytabs, `master.passwd`/`sudoers`, saved network/Wi-Fi configuration (`/Library/Preferences/SystemConfiguration`).
+ - **Eventual deny-by-default reads**: whitelist the dyld shared cache, system frameworks, `/dev/bpf*`, resolver/locale/timezone data, and the GeoIP paths. Strongest containment, but fragile across macOS releases — requires a multi-version on-host test pass before shipping.
+- [ ] **Linux Sandbox Hardening (capabilities + Landlock network)**: Landlock already enforces deny-by-default filesystem access on the post-sandbox worker threads, but two gaps remain when rustnet runs as root:
+ - **Drop all non-essential capabilities** (or clear the bounding set via `PR_CAPBSET_DROP`) before spawning the worker threads. Today only `CAP_NET_RAW`/`CAP_BPF`/`CAP_PERFMON` are dropped, so a root-launched process retains `CAP_DAC_OVERRIDE`, `CAP_SYS_ADMIN`, `CAP_SYS_MODULE`, etc. — which Landlock does not cover (non-filesystem/non-TCP abuse such as loading kernel modules). Running non-root with a `cap_net_raw` file capability already avoids this; the hardening is for the common `sudo rustnet` case.
+ - **UDP egress is not blocked**: Landlock ABI v4 only governs TCP `bind`/`connect`, so UDP exfiltration remains possible (accepted as low risk today, since filesystem reads are tightly contained). Revisit when a newer Landlock ABI adds UDP support. Note macOS Seatbelt already blocks both TCP and UDP.
+- [ ] **OpenBSD and NetBSD Support**: Future platforms to support
+- [x] **Linux Process Identification**: **Experimental eBPF Support Implemented** - Basic eBPF-based process identification now available with `--features ebpf`. Provides efficient kernel-level process-to-connection mapping with lower overhead than procfs. Currently has limitations (see eBPF Improvements section below).
+
+## eBPF Improvements (Linux)
+
+The experimental eBPF support provides efficient process identification but has several areas for improvement:
+
+### Current Limitations
+- **Process Names Limited to 16 Characters**: Uses kernel `comm` field, causing truncation (e.g., "Firefox" → "Socket Thread")
+- **Thread Names vs Process Names**: Shows thread command names instead of full executable names
+
+### Planned Improvements
+- **Hybrid eBPF + Procfs Approach**: Use eBPF for connection tracking, selectively lookup full process names via procfs for better accuracy
+- **Full Executable Path Resolution**: Investigate accessing full process executable path from eBPF programs
+- **Better Process-Thread Mapping**: Improve mapping from thread IDs to parent process information
+- **Enhanced BTF Support**: Better compatibility across different kernel versions and distributions
+- **Performance Optimizations**: Reduce eBPF map lookups and improve connection-to-process matching efficiency
+- **Switch from kprobes to fentry/fexit or kprobe.multi**: Today we attach 7 kprobes via `perf_event_open(2)`, which is gated by `kernel.perf_event_paranoid`. On Debian 13 the default is `=3` (an out-of-tree patch that predates CAP_PERFMON and only honors CAP_SYS_ADMIN), so even with `cap_bpf,cap_perfmon+ep` set via `setcap`, attach fails with `-EACCES` until the user lowers the sysctl globally (issue #255). Debian bug #994044 was archived in 2025 without a fix, so this is unlikely to change in Forky (Debian 14, ~2027) without renewed pressure. Ubuntu has a parallel patch at `=4` that *was* updated to honor CAP_PERFMON in late 2025 (Jammy 5.15.0-165+, Noble 6.8.0-91+, Plucky 6.14.0-37+, Questing 6.17.0-14+, Resolute 6.18.0-8+), so Ubuntu users already work out of the box — Debian is the laggard. Both `BPF_PROG_TYPE_TRACING` (fentry/fexit, kernel ≥ 5.5) and `kprobe.multi` (fprobe-based, kernel ≥ 5.18) attach via `BPF_LINK_CREATE` and never call `perf_event_open` — they're gated only by CAP_BPF + CAP_PERFMON, so they would work out of the box on Debian regardless of the broken patch. fentry also gives entry args + return value in a single program (cleaner than kprobe + kretprobe). Mainline / Fedora / Arch / RHEL all default to paranoid=2 and the upstream direction (CAP_PERFMON, fprobe, BTF trampolines) is toward more privilege separation, not less, so this conversion is also future-aligned. Trade-off: raises minimum supported kernel from current (kprobes work back to ~4.x) to 5.5+ for fentry or 5.18+ for kprobe.multi — need to decide whether to keep a kprobe fallback for older kernels.
+
+### Future Enhancements
+- **Real-time Process Updates**: Track process name changes and executable updates
+- **Container Support**: Better process identification within containerized environments
+- **Security Context**: Include process security attributes (capabilities, SELinux context, etc.)
+- **Cross-Namespace Attribution for Kubernetes**: The current procfs fallback reads `/proc/net/tcp` from the reader's network namespace, so under `hostNetwork: true` (as used by kubectl-rustnet) it never sees sockets owned by pods in their own netns. The kubernetes feature ships a scoped per-PID `/proc//net/{tcp,tcp6,udp,udp6}` walker that covers TCP+UDP for kubepods PIDs, but it ticks at the enrichment interval and so misses sub-tick ephemeral flows. The complete fix lives in the eBPF layer: kprobes/fentry are netns-agnostic and fire at `connect()`/`accept()` time, but the current socket-tracker map is being pruned more aggressively than userspace can consume. Plan: extend map retention (or switch to a ring buffer of close events that userspace drains opportunistically), debug the "Map Lookup Miss" path under Kubernetes traffic patterns, and verify cross-namespace coverage end-to-end in a kind cluster. This work also benefits ICMP and raw-socket attribution, which procfs cannot reach.
+
+## Features
+
+### Monitoring & Protocol Support
+
+- [x] **Real-time Network Monitoring**: Monitor active TCP, UDP, ICMP, and ARP connections
+- [x] **Connection States**: Comprehensive state tracking for:
+ - TCP states (ESTABLISHED, SYN_SENT, TIME_WAIT, CLOSED, etc.)
+ - QUIC states (QUIC_INITIAL, QUIC_HANDSHAKE, QUIC_CONNECTED, QUIC_DRAINING)
+ - DNS states (DNS_QUERY, DNS_RESPONSE)
+ - SSH states (BANNER, KEYEXCHANGE, AUTHENTICATION, ESTABLISHED)
+ - Activity states (UDP_ACTIVE, UDP_IDLE, UDP_STALE)
+- [x] **Deep Packet Inspection (DPI)**: Application protocol detection:
+ - HTTP with host information
+ - HTTPS/TLS with SNI (Server Name Indication)
+ - DNS queries and responses
+ - SSH connections with version detection, software identification, and state tracking
+ - QUIC protocol with CONNECTION_CLOSE frame detection and RFC 9000 compliance
+- [ ] **DPI Enhancements**: Improve deep packet inspection capabilities:
+ - Support more protocols (e.g. FTP, SMTP, IMAP, etc.)
+ - CDP/LLDP (network device discovery protocols)
+ - LACP (Link Aggregation Control Protocol)
+ - More accurate SNI detection for QUIC/HTTPS
+- [x] **Connection Lifecycle Management**: Smart protocol-aware timeouts with visual staleness indicators (yellow at 75%, red at 90%)
+- [x] **Process Identification**: Associate network connections with running processes (with experimental eBPF support on Linux)
+- [x] **Service Name Resolution**: Identify well-known services using port numbers
+- [x] **Cross-platform Support**: Works on Linux, macOS, Windows, and FreeBSD
+- [x] **DNS Reverse Lookup**: Add optional hostname resolution (toggle between IP and hostname display) - `--resolve-dns` flag with `d` key toggle
+- [ ] **IPv6 Support**: Full IPv6 connection tracking and display, including DNS resolution (needs testing)
+- [ ] **VLAN Tag Detection**: Parse 802.1Q VLAN tags from packet headers to identify VLAN configurations
+- [ ] **Passive Host Discovery**: Infer local network hosts from observed ARP requests/replies and other broadcast traffic without active scanning
+- [ ] **MAC Vendor Lookup (OUI)**: Resolve MAC addresses to hardware vendor names using a local OUI database (e.g. "Apple", "Intel", "Ubiquiti")
+
+### Filtering & Search
+
+- [x] **Advanced Filtering**: Real-time vim/fzf-style filtering with:
+ - Navigate while typing filters
+ - Fuzzy search across all connection fields including DPI data
+ - Keyword filters: `port:`, `src:`, `dst:`, `sni:`, `process:`, `sport:`, `dport:`, `ssh:`, `state:`
+ - State filtering for all protocol states
+ - Exact port matching by default (`port:22` matches only port 22)
+ - Regular expression support via `/pattern/` syntax on any filter value
+
+### Sorting & Display
+
+- [x] **Sorting**: Comprehensive table sorting with:
+ - Sort by all columns: Protocol, Local/Remote Address, State, Service, Application, Bandwidth (Down/Up), Process
+ - Intuitive left-to-right column cycling with `s` key
+ - Direction toggle with `S` (Shift+s) for ascending/descending
+ - Visual indicators: cyan/underlined active column, arrows showing direction
+ - Smart defaults: bandwidth descending (show hogs), text ascending (alphabetical)
+ - Bandwidth sorting: sorts by combined up+down bandwidth total
+ - Seamless integration with filtering
+
+### Performance & Architecture
+
+- [x] **Multi-threaded Processing**: Concurrent packet processing across multiple threads
+- [x] **Optional Logging**: Detailed logging with configurable log levels (disabled by default)
+
+### Packaging & Distribution
+
+- [x] **Package Distribution**: Pre-built packages available:
+ - [x] **macOS DMG packages**: Apple Silicon and Intel (via GitHub Actions release workflow)
+ - [x] **Windows MSI packages**: 64-bit and 32-bit (via cargo-wix)
+ - [x] **Linux DEB packages**: amd64, arm64, armhf (via cargo-deb)
+ - [x] **Linux RPM packages**: x86_64, aarch64 (via cargo-generate-rpm)
+ - [x] **Cargo crates.io**: Published as `rustnet-monitor` (version 0.10.0+)
+ - [x] **Docker images**: Available on GitHub Container Registry with eBPF support
+ - [x] **Homebrew formula**: Available in separate tap repository (domcyrus/rustnet)
+
+### Future Enhancements
+
+- [ ] **Internationalization (i18n)**: Support for multiple languages in the UI
+- [x] **Connection History**: Store and display historical connection data (toggle with `t` key, up to 5,000 archived connections)
+- [x] **PCAP Export**: Export packets to PCAP file with process attribution sidecar (`--pcap-export`)
+ - Standard PCAP format compatible with Wireshark/tcpdump
+ - Streaming JSONL sidecar with PID, process name, timestamps
+ - Python enrichment script to create annotated PCAPNG
+- [x] **Native Annotated PCAPNG Export**: Export a Wireshark-ready PCAPNG file with live best-effort RustNet packet comments (`--pcapng-export`)
+ - Per-packet comments include process/PID, direction, DPI/SNI, and GeoIP/ASN when available
+ - Uses true capture timestamps and bounded attribution retry
+- [ ] **Enhanced PCAP Metadata**: Richer process information in sidecar file
+ - Process executable full path (not just name)
+ - Command line arguments
+ - Working directory
+ - User/UID information
+ - Parent process information
+- [ ] **Configuration File**: Support for persistent configuration:
+ - Custom color themes and UI styling
+ - Default filters and sort preferences
+ - Default process grouping (start with `group: true` in config)
+ - Color mode preference (disable colors via config, complementing `--no-color` flag)
+ - Per-interface settings
+ - Keybinding customization
+- [ ] **Connection Alerts**: Notifications for new connections or suspicious activity
+- [x] **GeoIP Integration**: Geographical location of remote IPs
+- [x] **GeoIP City-Level Resolution**: Extend GeoIP to include city-level location data using GeoLite2-City database
+- [ ] **Protocol Statistics**: Summary view of protocol distribution
+- [ ] **Rate Limiting Detection**: Identify connections with unusual traffic patterns
+- [ ] **Bufferbloat Detection**: Measure latency under load to identify bufferbloat issues on the network
+- [ ] **PCAP Import/Replay**: Load a PCAP file (with optional JSON process attribution sidecar) and replay it in the TUI for offline analysis. Enables remote monitoring workflows: capture on a remote host with `--pcap-export`, transfer files, and replay locally with full process-attributed view
+- [ ] **Route Table Display**: Show the system routing table in a user-friendly view within the TUI
+- [ ] **Privacy/Redact Mode**: Obfuscate sensitive information (IPs, MACs, hostnames) in the TUI for safe screenshots and sharing. Include option to export connection details from the details view to a text file with privacy redaction applied
+
+## UI Improvements
+
+- [x] **Terminal User Interface**: TUI built with ratatui with adjustable column widths
+- [x] **Sortable Columns**: Keyboard-based sorting by all table columns
+- [x] **Keyboard Controls**: Comprehensive keyboard navigation (q, Ctrl+C, x, Tab, arrows, j/k, g/G, PageUp/Down, Enter, Esc, c, p, s, S, h, /, a, r, Space)
+- [x] **Connection Details View**: Detailed information about selected connections (Enter key)
+- [x] **Help Screen**: Toggle help screen with keyboard shortcuts (h key)
+- [x] **Clipboard Support**: Copy remote address to clipboard (c key)
+- [x] **Service/Port Toggle**: Toggle between service names and port numbers (p key)
+- [x] **Platform-Specific CLI Help**: Show only relevant options per platform (hide Linux sandbox options on macOS, hide PKTAP notes on Linux)
+- [x] **Connection Grouping**: Group connections by process with expandable tree view (press `a` to toggle, aggregated stats, Space/arrows to expand/collapse)
+- [x] **Reset View**: Reset all view settings (grouping, sort, filter) with `r` key
+- [ ] **Resizable Columns**: Dynamic column width adjustment
+- [ ] **ASCII Graphs**: Terminal-based graphs for bandwidth/packet visualization
+- [ ] **Mouse Support**: Click to select connections
+- [ ] **Split Pane View**: Show multiple views simultaneously
+
+## Architecture
+
+### Workspace Split
+
+Restructure the single crate into a Cargo workspace (same GitHub repo) with clear separation of concerns:
+
+- [x] **rustnet-monitor** (binary, bin name `rustnet`): CLI, TUI, app event
+ loop, sandboxing (Landlock/Seatbelt), and interface statistics -- the
+ user-facing application; process attribution is delegated to `rustnet-host`.
+ (Package stays `rustnet-monitor` because the `rustnet` crate name is taken on
+ crates.io; the installed binary is `rustnet`.)
+- [x] **rustnet-core** (library): Packet parsing, protocol types, DPI,
+ link-layer parsers, connection merging, and DNS/GeoIP/OUI lookups -- the
+ reusable, platform-independent, capture-independent analysis core. Lives at
+ `crates/rustnet-core`. (Named `rustnet-core` rather than `rustnet-net` to
+ avoid the redundant "net-net"; verified available on crates.io.)
+- [x] **rustnet-capture** (library): the libpcap/Npcap-based capture backend --
+ device selection, BPF filters, macOS PKTAP, TUN/TAP, and a raw-frame
+ `PacketReader`. Lives at `crates/rustnet-capture`. This is the **existing**
+ pcap code moved into its own crate (not a libpcap-free rewrite): the point of
+ the split is composability — a headless front-end (e.g. a Prometheus exporter)
+ can pair `rustnet-capture` + `rustnet-core` without the TUI, and a platform
+ wanting a bespoke capture path (e.g. the macOS pktap helper) can swap it out.
+ The macOS `DegradationReason` coupling was untangled by giving capture its own
+ `PktapUnavailable` enum, which the binary maps to its UI `DegradationReason`.
+- [x] **rustnet-host** (library): Per-connection process attribution behind one
+ `ProcessLookup` trait -- eBPF/procfs on Linux, PKTAP/lsof on macOS, the IP
+ Helper API on Windows, and `sockstat` on FreeBSD. Lives at `crates/rustnet-host`
+ and owns the eBPF build tooling (the `socket_tracker.bpf.c` program and bundled
+ `vmlinux.h`). The binary injects PKTAP availability via `report_pktap_degradation`,
+ so the crate needs no dependency on `rustnet-capture`.
+- [ ] **rustnet-helper** (binary): Minimal suid helper for macOS pktap privilege
+ separation (~100 lines, zero C deps — just `libc`). **Future work, not yet a
+ crate.** The root-gated pktap interface creation (`SIOCIFCREATE`) can only be
+ written and validated on real macOS hardware, so this is deferred until it can
+ be done for real rather than scaffolded. See "macOS Privilege Separation" below.
+
+Benefits:
+- Clean dependency boundaries (helper has zero C dependencies)
+- `rustnet-core` becomes independently useful as a Rust network analysis library
+- Compile times improve (parallel crate compilation)
+- `cargo install rustnet-monitor` continues to work unchanged
+
+**Status:** The workspace exists with `rustnet-monitor` (binary) depending on
+`rustnet-core`, `rustnet-capture`, and `rustnet-host`. The binary's `src/network`
+module re-exports `rustnet_core::network::*` and `rustnet_capture` (as `capture`)
+so existing `crate::network::*` paths, integration tests, and benches are
+unchanged. Net-only dependencies (`dns-lookup`, `ring`, `aes`, `flate2`,
+`maxminddb`, `pnet_datalink`) and the baked-in `oui.gz` / `services` assets live
+in `rustnet-core`; all pcap usage lives in `rustnet-capture`; and `procfs` /
+`libbpf-rs` plus the eBPF programs and `vmlinux.h` live in `rustnet-host`.
+`rustnet-core` also exposes a `ConnectionTracker` so headless tools can fold
+captured packets into a live, lifecycle-managed connection table without the
+TUI. Remaining work: the `rustnet-helper` macOS pktap suid helper (needs real
+hardware).
+
+### macOS Privilege Separation (pktap without root)
+
+Currently pktap requires root because the macOS kernel enforces a root check (`SIOCIFCREATE` ioctl) when creating the pktap pseudo-interface. This is independent of BPF device permissions (ChmodBPF). The goal is to run the main RustNet process as a regular user while only the minimal helper runs privileged.
+
+**Approach**: Small suid helper binary that:
+1. Opens `/dev/bpf*` and creates the pktap interface (requires root)
+2. Configures BPF device (bind interface, set buffer size, immediate mode)
+3. Locks the device with `BIOCLOCK` (prevents further configuration changes)
+4. Passes the BPF file descriptor to the unprivileged RustNet process via Unix socket (`SCM_RIGHTS`)
+5. Drops privileges and exits
+
+The main RustNet process reads packets directly from the received BPF fd using `read()` -- no libpcap needed on this path. The existing pktap header parser (`link_layer/pktap.rs`) already handles the packet format. BPF filter compilation is not needed since BPF filters are already incompatible with pktap.
+
+On Linux/Windows/FreeBSD, nothing changes -- libpcap is used as today, with the existing capability-based privilege model on Linux.
+
+Security properties:
+- Helper is tiny (~100 lines of Rust, no C code) -- minimal attack surface as root
+- `BIOCLOCK` prevents the unprivileged process from reconfiguring the capture device
+- Seatbelt sandbox can still be applied to the main process after fd handoff
+- Similar pattern to Wireshark's `dumpcap` but with a smaller privileged surface (no libpcap in the helper)
+
+## Development
+
+- [x] **Unit Tests**: Basic unit tests in 12+ source modules (DPI protocols, filtering, services, network capture, etc.)
+- [x] **Integration Tests**: Platform-specific integration tests for Linux and macOS (tests/integration_tests.rs)
+- [ ] **Comprehensive Test Coverage**: Expand test coverage across all modules
+- [x] **CI/CD Pipeline**: Automated builds and releases for all platforms (GitHub Actions)
+ - [x] **Release workflow**: Multi-platform builds with cross-compilation
+ - [x] **Docker workflow**: Automated Docker image builds
+ - [x] **Rust workflow**: Basic CI checks
+- [x] **Documentation**: Comprehensive README with usage guides, architecture overview, and troubleshooting
+- [x] **Packaging/Distribution**: Create packages for easy installation on Linux, macOS, and Windows
+ - DMG packages with code signing
+ - MSI packages with code signing for Windows
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..0d19489
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,333 @@
+English | 简体中文
+
+# Security
+
+RustNet processes untrusted network data, making defense-in-depth security critical. This document describes the security measures implemented.
+
+## Table of Contents
+
+- [Landlock Sandboxing (Linux)](#landlock-sandboxing-linux)
+- [Seatbelt Sandboxing (macOS)](#seatbelt-sandboxing-macos)
+- [FreeBSD Sandboxing](#freebsd-sandboxing)
+- [Privilege Drop and Job Object Sandboxing (Windows)](#privilege-drop-and-job-object-sandboxing-windows)
+- [Privilege Requirements](#privilege-requirements)
+- [Read-Only Operation](#read-only-operation)
+- [No External Communication](#no-external-communication)
+- [Log File Privacy](#log-file-privacy)
+- [eBPF Security](#ebpf-security)
+- [Threat Model](#threat-model)
+- [Supply Chain Security](#supply-chain-security)
+- [Audit and Compliance](#audit-and-compliance)
+- [Reporting Security Issues](#reporting-security-issues)
+
+## Landlock Sandboxing (Linux)
+
+On Linux 5.13+, RustNet uses [Landlock](https://landlock.io/) to restrict its own capabilities after initialization. This limits the damage if a vulnerability in packet parsing is exploited.
+
+### What Gets Restricted
+
+| Restriction | Kernel Version | Description |
+|-------------|----------------|-------------|
+| Filesystem | 5.13+ | Only `/proc` readable (for process identification) |
+| Network | 6.4+ | TCP bind/connect blocked (RustNet is passive) |
+| Capabilities | Any | `CAP_NET_RAW` dropped after pcap socket opened |
+| Capabilities | Any | `CAP_BPF`, `CAP_PERFMON` dropped after eBPF programs loaded |
+| Root uid | Any | When started as root (e.g. `sudo rustnet`), the process drops to the invoking user (`SUDO_UID`/`SUDO_GID`) or `nobody` after initialization |
+| Privileges | 3.5+ | `PR_SET_NO_NEW_PRIVS` set by RustNet itself — always, even with `--no-sandbox` — prevents privilege escalation via setuid binaries |
+
+### How It Works
+
+1. **Initialization phase**: RustNet loads eBPF programs, opens packet capture handles, and creates log files
+2. **Privilege lock**: `PR_SET_NO_NEW_PRIVS` is set (applied even when the sandbox is disabled)
+3. **Capability drop**: `CAP_NET_RAW`, `CAP_BPF`, and `CAP_PERFMON` are removed from the process
+4. **Root uid drop**: when running as root, the process switches to the invoking sudo user (or `nobody`) via `setresuid`/`setresgid`. Already-open capture sockets, eBPF programs, and log/export files keep working. This matters most on kernels without Landlock, where the uid drop is the main containment
+5. **Landlock**: Restricts filesystem and network access
+
+### Security Benefits
+
+If an attacker exploits a vulnerability in DPI/packet parsing:
+- Cannot read arbitrary files (credentials, configs, etc.)
+- Cannot write to filesystem (except configured log paths)
+- Cannot make outbound TCP connections (data exfiltration blocked)
+- Cannot bind TCP ports (reverse shell blocked)
+- Cannot create new raw sockets (capability dropped)
+- Cannot escalate privileges via setuid binaries (`PR_SET_NO_NEW_PRIVS`, set even with `--no-sandbox`)
+- Does not run as root: under `sudo rustnet` the process continues as the invoking user, so even on kernels without Landlock a compromise does not yield root
+
+### CLI Options
+
+```
+--no-sandbox Disable Landlock sandboxing, capability dropping, and the
+ root uid drop (PR_SET_NO_NEW_PRIVS is still set)
+--sandbox-strict Require full sandbox enforcement or exit
+--no-uid-drop Keep running as root instead of dropping to
+ SUDO_UID/SUDO_GID (or nobody) after initialization
+```
+
+Trade-off of the root uid drop: the procfs fallback for process attribution can
+then only inspect processes owned by the target user, and Kubernetes log
+directories under `/var/log/pods` may become unreadable. The eBPF fast path
+(the default) is unaffected. If you rely on procfs-only attribution (e.g. a
+build without eBPF) and need to attribute other users' processes, use
+`--no-uid-drop`.
+
+### Graceful Degradation
+
+- **Kernel < 5.13**: Sandboxing skipped, warning logged
+- **Kernel 5.13-6.3**: Filesystem restrictions only
+- **Kernel 6.4+**: Full filesystem + network restrictions
+- **Docker**: Landlock may be restricted; app continues normally
+
+## Seatbelt Sandboxing (macOS)
+
+On macOS 10.5+, RustNet uses [Seatbelt](https://theapplewiki.com/wiki/Dev:Seatbelt) (`sandbox_init_with_parameters`) to restrict its own capabilities after initialization. This limits the damage if a vulnerability in packet parsing is exploited.
+
+### What Gets Restricted
+
+| Restriction | Description |
+|-------------|-------------|
+| Outbound network | TCP/UDP outbound blocked; Unix sockets (Mach IPC) allowed |
+| Filesystem reads | User home directories blocked (`/Users`, `/var/root`); GeoIP paths explicitly allowed |
+| Filesystem writes | All user home directories blocked (`/Users`, `/var/root`) |
+| Filesystem writes | Only configured log, PCAP, and PCAPNG export paths writable |
+| Process execution | All binaries blocked except `/usr/sbin/lsof` |
+| Root uid | When started as root (e.g. `sudo rustnet`), the process drops to the invoking user (`SUDO_UID`/`SUDO_GID`) or `nobody` after initialization |
+
+### How It Works
+
+1. **Initialization phase**: RustNet opens packet capture handles (BPF/PKTAP) and creates log files
+2. **Pre-create**: PCAP sidecar (`.connections.jsonl`) and PCAPNG export files are created before the sandbox so their paths are already valid allow targets, and are handed over to the uid-drop target so they stay writable after the drop
+3. **Root uid drop**: when running as root, the process switches to the invoking sudo user (or `nobody`) via `setgid`/`setuid`. Already-open capture and log/export descriptors keep working
+4. **Sandbox application**: `sandbox_init_with_parameters` is called; already-open file descriptors survive unchanged, only future operations are restricted
+
+### Profile Strategy
+
+RustNet uses an **allow-default** SBPL profile with targeted denies. A deny-default profile would require explicitly whitelisting all system libraries, Mach ports, locale data, fonts, and other OS internals — fragile and error-prone. Allow-default with targeted denies covers the primary threats (credential theft, data exfiltration, shell escapes) without operational risk. Specific deny rules block file reads/writes under user home directories, outbound network connections, and execution of all binaries except `/usr/sbin/lsof`.
+
+### Output File Support
+
+`--json-log`, `--pcap-export`, and `--pcapng-export` paths are passed to the SBPL profile as runtime parameters (`JSON_LOG_PATH`, `PCAP_PATH`, `PCAP_JSONL_PATH`, `PCAPNG_PATH`). The profile grants an explicit `allow file-write*` rule on each path, which takes precedence over the broader `/Users` deny rule via SBPL specificity. Unused parameters default to `/dev/null`.
+
+All three flags work normally within the sandbox.
+
+### Security Benefits
+
+If an attacker exploits a vulnerability in DPI/packet parsing:
+- Cannot read SSH keys, AWS credentials, browser profiles, or other credential files under `/Users`
+- Cannot write to SSH keys, AWS credentials, browser profiles, or other credential files
+- Cannot make outbound TCP/UDP connections (data exfiltration blocked)
+- Cannot open new raw network sockets
+- Cannot execute binaries (no shell escapes via `/bin/sh`, `/usr/bin/curl`, etc.)
+- Does not run as root: under `sudo rustnet` the process continues as the invoking user
+
+### CLI Options
+
+```
+--no-sandbox Disable Seatbelt sandboxing and the root uid drop
+--sandbox-strict Require full sandbox enforcement or exit
+--no-uid-drop Keep running as root instead of dropping to
+ SUDO_UID/SUDO_GID (or nobody) after initialization
+```
+
+Trade-off of the root uid drop: the default PKTAP attribution path is
+unaffected (process metadata arrives in-band on the already-open capture fd),
+but the lsof fallback (active when PKTAP is unavailable, e.g. with an explicit
+`--interface`) then only sees the target user's processes. Use `--no-uid-drop`
+if you rely on lsof attribution for other users' processes.
+
+### Why BestEffort is Default
+
+`sandbox_init_with_parameters` is a private (undocumented) macOS API. It has been stable since macOS 10.5 and is used by Chromium, Firefox, and Safari for process sandboxing, but it could theoretically change without notice. BestEffort degrades gracefully if the API behaves unexpectedly rather than preventing the app from running. Use `--sandbox-strict` to require sandboxing or abort.
+
+### Clipboard Behavior
+
+Unlike Linux Landlock, clipboard copy (`c` key) works normally under Seatbelt. macOS clipboard uses NSPasteboard, which communicates via Mach IPC over Unix domain sockets — the SBPL profile explicitly allows `(network-outbound (remote unix-socket))`.
+
+On Linux, clipboard requires access to Wayland sockets (`/run/user/UID/wayland-0`) or X11 sockets (`/tmp/.X11-unix/`). Landlock's deny-default model blocks these because they are not in the write-path allowlist, so clipboard is unavailable when Landlock is active.
+
+## FreeBSD Sandboxing
+
+FreeBSD does not currently have sandboxing enabled. A full Capsicum sandbox using `cap_enter()` with `libcasper` for privileged process lookup is planned — see [ROADMAP.md](ROADMAP.md) for details.
+
+### Root Uid Drop
+
+Until Capsicum lands, the primary containment on FreeBSD is a root privilege drop: when started as root (e.g. `sudo rustnet`), the process drops to the invoking user (`SUDO_UID`/`SUDO_GID`) or `nobody` after the BPF capture devices are open, via `setresuid`/`setresgid`. Already-open capture and log/export descriptors keep working, and pre-created export files are handed over to the target user. Note that `doas` does not set `SUDO_UID`, so doas users get the `nobody` fallback.
+
+Trade-off: process attribution uses `sockstat`, which as a non-root user only sees the target user's sockets. Use `--no-uid-drop` if you need attribution for other users' processes.
+
+```
+--no-uid-drop Keep running as root instead of dropping to
+ SUDO_UID/SUDO_GID (or nobody) after initialization
+```
+
+## Privilege Drop and Job Object Sandboxing (Windows)
+
+On Windows, RustNet removes dangerous privileges from the process token and applies a Job Object to prevent child process creation after initialization.
+
+### What Gets Restricted
+
+| Restriction | Description |
+|-------------|-------------|
+| Privilege removal | SeDebugPrivilege, SeTakeOwnershipPrivilege, SeBackupPrivilege, SeRestorePrivilege, and other dangerous privileges permanently removed |
+| Child processes | Job Object blocks creation of child processes (reverse shell, exec-based exfiltration) |
+
+### How It Works
+
+1. **Initialization phase**: RustNet opens Npcap handles and creates log files
+2. **Privilege removal**: `AdjustTokenPrivileges` with `SE_PRIVILEGE_REMOVED` permanently strips dangerous privileges from the process token
+3. **Job Object**: A Job Object with `JOB_OBJECT_LIMIT_ACTIVE_PROCESS = 1` is applied, preventing any child process creation
+
+### Security Benefits
+
+If an attacker exploits a vulnerability in DPI/packet parsing:
+- Cannot debug other processes (SeDebugPrivilege removed)
+- Cannot take ownership of arbitrary files (SeTakeOwnershipPrivilege removed)
+- Cannot bypass ACLs to read files (SeBackupPrivilege removed)
+- Cannot spawn child processes (cmd.exe, powershell.exe, curl.exe — blocked by Job Object)
+- Cannot load kernel drivers (SeLoadDriverPrivilege removed)
+
+### Limitations
+
+Windows sandboxing is weaker than Linux/macOS/FreeBSD:
+- No filesystem restriction — Windows lacks a process-wide filesystem sandbox equivalent to Landlock or Seatbelt
+- No network restriction — blocking outbound would break Npcap packet capture
+- Privilege removal only affects privileges the elevated process held
+
+### CLI Options
+
+```
+--no-sandbox Disable privilege removal and job object
+--sandbox-strict Require full sandbox enforcement or exit
+```
+
+## Privilege Requirements
+
+RustNet requires privileged access for packet capture:
+
+| Platform | Requirement |
+|----------|-------------|
+| Linux | `CAP_NET_RAW` capability or root |
+| macOS | Root or BPF group membership (`access_bpf` group) |
+| Windows | Administrator (for Npcap) |
+| FreeBSD | Root or BPF device access |
+
+### Why Privileges Are Needed
+
+- **Raw socket access** - Intercept network traffic at low level (read-only, non-promiscuous mode)
+- **BPF device access** - Load packet filters into kernel
+- **eBPF programs** - Optional kernel probes for enhanced process tracking (Linux only)
+
+### Recommended: Capability-based Execution (Linux)
+
+Instead of running as root, grant only the required capabilities:
+
+```bash
+# Modern Linux (5.8+): packet capture + eBPF
+sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' $(which rustnet)
+
+# Packet capture only (no eBPF process detection)
+sudo setcap cap_net_raw+eip $(which rustnet)
+```
+
+Legacy pre-5.8 kernels required `CAP_SYS_ADMIN` for eBPF operations. RustNet
+does not grant this broad capability automatically; use `CAP_NET_RAW` only and
+let eBPF fall back to procfs unless you explicitly accept the extra risk.
+
+After sandbox application, `CAP_NET_RAW` and eBPF-loading capabilities are
+dropped - the process retains only the minimum privileges needed.
+
+## Read-Only Operation
+
+RustNet only monitors traffic; it does not:
+- Modify packets
+- Block connections
+- Inject traffic
+- Alter routing tables
+- Change firewall rules
+
+The packet capture is opened in non-promiscuous, read-only mode.
+
+## No External Communication
+
+RustNet operates entirely locally:
+- No telemetry or analytics
+- No network requests (except monitored traffic)
+- No cloud services or remote APIs
+- All data stays on your system
+
+## Log File Privacy
+
+Log files may contain sensitive information:
+- IP addresses and ports
+- Hostnames and SNI data
+- Process names and PIDs
+- DNS queries and responses
+
+**Best Practices:**
+- Disable logging by default (no `--log-level` flag)
+- Secure log directory permissions
+- Implement log rotation and retention policies
+- Review logs for sensitive data before sharing
+
+## eBPF Security
+
+When using eBPF for enhanced process detection (default on Linux):
+
+- Requires additional kernel capabilities (`CAP_BPF`, `CAP_PERFMON`)
+- eBPF programs are verified by kernel before loading
+- Limited to read-only operations (no packet modification)
+- Automatically falls back to procfs if eBPF fails
+
+## Threat Model
+
+**What RustNet protects against:**
+- Unauthorized users cannot capture packets without proper permissions
+- Capability-based permissions limit blast radius of compromise
+- Landlock (Linux) and Seatbelt (macOS) sandboxes contain potential exploitation
+
+**What RustNet does NOT protect against:**
+- Users with packet capture permissions can see all unencrypted traffic
+- Root/Administrator users can modify RustNet or capture packets directly
+- Physical access to the machine enables packet capture
+- Network-level attacks (RustNet is a monitoring tool, not a security appliance)
+
+### Sandboxing as Root
+
+Both Landlock (Linux) and Seatbelt (macOS) enforce restrictions even when RustNet runs as root (UID 0). Once applied, the sandbox cannot be reversed from within the process — on Linux, RustNet sets `PR_SET_NO_NEW_PRIVS` directly before applying any restrictions (Landlock requires and would set it as well), which is irreversible per-process and applied even with `--no-sandbox`.
+
+However, sandboxing does **not** protect against supply chain attacks. A compromised binary would simply not apply the sandbox. Root can also:
+- Pass `--no-sandbox` to skip sandboxing entirely (except `PR_SET_NO_NEW_PRIVS`)
+- Unload the Landlock LSM kernel module
+- Disable SIP on macOS (which controls sandbox enforcement)
+- Use `ptrace` to modify a running process
+
+For this reason, running with fine-grained capabilities (`setcap cap_net_raw=eip`) is strongly preferred over running as root.
+
+## Supply Chain Security
+
+RustNet takes the following measures to protect against supply chain attacks:
+
+- **Dependency lockfile**: `Cargo.lock` is committed to the repository, pinning all transitive dependency versions and recording source checksums. This prevents silent version upgrades.
+- **Security audit**: `cargo deny check` runs in CI on every push and pull request, checking dependencies against the RustSec Advisory Database and enforcing license, source, and wildcard-version policies (`deny.toml`). A scheduled daily workflow re-checks advisories against the committed `Cargo.lock`, so newly published advisories surface without requiring a push.
+- **CI action pinning**: All GitHub Actions are pinned by commit SHA (not tags), preventing tag-rewriting attacks on upstream actions.
+- **Conservative dependency policy**: New dependencies require justification and are reviewed for maintenance status and security track record (see `CONTRIBUTING.md`).
+- **Build-time integrity**: The Windows Npcap SDK download in `build.rs` is verified against a hardcoded SHA256 checksum.
+- **Code signing**: macOS releases are signed with an Apple Developer certificate and notarized.
+- **Checksum verification**: All packaging workflows (Homebrew, Chocolatey, AUR) calculate and double-verify SHA256 checksums before publishing.
+
+### Limitations
+
+- `cargo install rustnet` fetches the latest compatible versions from crates.io and does **not** use `Cargo.lock`. Users building from source should verify the source tarball checksum.
+- Build scripts (`build.rs`) and proc-macros execute arbitrary code at compile time. While all current dependencies are well-established crates, this is an inherent risk of the Rust build model.
+
+## Audit and Compliance
+
+For production environments:
+- **Audit logging** of who runs RustNet with packet capture privileges
+- **Network monitoring policies** and compliance with data protection regulations
+- **User access reviews** for privileged network access
+- **Automated capability management** via configuration management systems
+
+## Reporting Security Issues
+
+Please report security vulnerabilities via GitHub Issues or contact the maintainers directly.
diff --git a/SECURITY.zh-CN.md b/SECURITY.zh-CN.md
new file mode 100644
index 0000000..945ad23
--- /dev/null
+++ b/SECURITY.zh-CN.md
@@ -0,0 +1,328 @@
+English | 简体中文
+
+# 安全
+
+RustNet 处理不受信任的网络数据,因此纵深防御至关重要。本文档描述了已实现的安全措施。
+
+## 目录
+
+- [Landlock 沙箱(Linux)](#landlock-sandboxing-linux)
+- [Seatbelt 沙箱(macOS)](#seatbelt-sandboxing-macos)
+- [FreeBSD 沙箱](#freebsd-sandboxing)
+- [权限剥离与 Job Object 沙箱(Windows)](#privilege-drop-and-job-object-sandboxing-windows)
+- [权限需求](#privilege-requirements)
+- [只读操作](#read-only-operation)
+- [不主动对外通信](#no-external-communication)
+- [日志文件隐私](#log-file-privacy)
+- [eBPF 安全](#ebpf-security)
+- [威胁模型](#threat-model)
+- [供应链安全](#supply-chain-security)
+- [审计与合规](#audit-and-compliance)
+- [报告安全问题](#reporting-security-issues)
+
+## Landlock 沙箱(Linux)
+
+在 Linux 5.13+ 上,RustNet 使用 [Landlock](https://landlock.io/) 在初始化后限制自身的 Linux capabilities。这样即使包解析存在漏洞被利用,也能限制损害范围。
+
+### 受限制的内容
+
+| 限制项 | 内核版本 | 描述 |
+|--------|----------|------|
+| 文件系统 | 5.13+ | 仅 `/proc` 可读(用于进程识别) |
+| 网络 | 6.4+ | 禁止 TCP bind/connect(RustNet 为被动模式) |
+| Linux capabilities | 任意 | pcap socket 打开后丢弃 `CAP_NET_RAW` |
+| Linux capabilities | 任意 | eBPF 程序加载后丢弃 `CAP_BPF`、`CAP_PERFMON`、`CAP_SYS_ADMIN` |
+| root uid | 任意 | 以 root 启动时(如 `sudo rustnet`),初始化完成后降权到调用用户(`SUDO_UID`/`SUDO_GID`)或 `nobody` |
+| 特权 | 3.5+ | `PR_SET_NO_NEW_PRIVS` 由 RustNet 自身设置——始终生效,即使使用 `--no-sandbox`——防止通过 setuid 二进制文件提升特权 |
+
+### 工作原理
+
+1. **初始化阶段**:RustNet 加载 eBPF 程序、打开包捕获句柄、创建日志文件
+2. **特权锁定**:设置 `PR_SET_NO_NEW_PRIVS`(即使禁用沙箱也会应用)
+3. **Linux capabilities 剥离**:移除 `CAP_NET_RAW`、`CAP_BPF`、`CAP_PERFMON` 和 `CAP_SYS_ADMIN`
+4. **root uid 降权**:以 root 运行时,通过 `setresuid`/`setresgid` 切换到调用 sudo 的用户(或 `nobody`)。已打开的捕获 socket、eBPF 程序和日志/导出文件继续有效。在没有 Landlock 的内核上,这是主要的隔离手段
+5. **Landlock**:限制文件系统和网络访问
+
+### 安全收益
+
+如果攻击者利用 DPI/包解析中的漏洞:
+- 无法读取任意文件(凭据、配置等)
+- 无法写入文件系统(除配置的日志路径外)
+- 无法建立出站 TCP 连接(阻止数据外泄)
+- 无法绑定 TCP 端口(阻止反向 shell)
+- 无法创建新的 raw socket(Linux capabilities 已剥离)
+- 无法通过 setuid 二进制文件提升特权(`PR_SET_NO_NEW_PRIVS`,即使使用 `--no-sandbox` 也会设置)
+- 不以 root 运行:使用 `sudo rustnet` 时进程会切换为调用用户,即使在没有 Landlock 的内核上,攻击者也无法获得 root
+
+### CLI 选项
+
+```
+--no-sandbox 禁用 Landlock 沙箱、Linux capabilities 剥离和 root uid 降权
+ (仍会设置 PR_SET_NO_NEW_PRIVS)
+--sandbox-strict 要求完整沙箱强制生效,否则退出
+--no-uid-drop 初始化后保持 root 运行,
+ 不降权到 SUDO_UID/SUDO_GID(或 nobody)
+```
+
+root uid 降权的权衡:降权后,procfs 回退路径的进程归属只能检查目标用户拥有的进程,
+`/var/log/pods` 下的 Kubernetes 日志目录也可能不可读。eBPF 快速路径(默认)不受影响。
+如果依赖纯 procfs 归属(如未启用 eBPF 的构建)且需要归属其他用户的进程,请使用
+`--no-uid-drop`。
+
+### 优雅降级
+
+- **Kernel < 5.13**:跳过沙箱,记录警告
+- **Kernel 5.13-6.3**:仅文件系统限制
+- **Kernel 6.4+**:完整的文件系统 + 网络限制
+- **Docker**:Landlock 可能受限;应用正常运行
+
+## Seatbelt 沙箱(macOS)
+
+在 macOS 10.5+ 上,RustNet 使用 [Seatbelt](https://theapplewiki.com/wiki/Dev:Seatbelt)(`sandbox_init_with_parameters`)在初始化后限制自身能力。这样即使包解析存在漏洞被利用,也能限制损害范围。
+
+### 受限制的内容
+
+| 限制项 | 描述 |
+|--------|------|
+| 出站网络 | TCP/UDP 出站被阻止;Unix socket(Mach IPC)允许 |
+| 文件系统读取 | 禁止读取用户主目录(`/Users`、`/var/root`);GeoIP 路径显式允许 |
+| 文件系统写入 | 禁止写入所有用户主目录(`/Users`、`/var/root`) |
+| 文件系统写入 | 仅配置的日志、JSON log、PCAP 和 PCAPNG 导出路径可写 |
+| 进程执行 | 除 `/usr/sbin/lsof` 外,禁止执行所有二进制文件 |
+| root uid | 以 root 启动时(如 `sudo rustnet`),初始化完成后降权到调用用户(`SUDO_UID`/`SUDO_GID`)或 `nobody` |
+
+### 工作原理
+
+1. **初始化阶段**:RustNet 打开包捕获句柄(BPF/PKTAP)并创建日志文件
+2. **预创建**:PCAP sidecar 文件(`.connections.jsonl`)和 PCAPNG 输出文件在沙箱应用前创建,因此其路径已经是有效的允许目标;同时移交给降权目标用户,确保降权后仍可写入
+3. **root uid 降权**:以 root 运行时,通过 `setgid`/`setuid` 切换到调用 sudo 的用户(或 `nobody`)。已打开的捕获和日志/导出文件描述符继续有效
+4. **沙箱应用**:调用 `sandbox_init_with_parameters`;已打开的文件描述符保持不变,仅限制未来的操作
+
+### 配置文件策略
+
+RustNet 使用 **默认允许** 的 SBPL 配置文件配合针对性拒绝。拒绝默认的配置文件需要显式将所有系统库、Mach 端口、区域设置数据、字体和其他 OS 内部组件加入白名单——脆弱且容易出错。默认允许配合针对性拒绝覆盖了主要威胁(凭据窃取、数据外泄、shell 逃逸),同时避免操作风险。具体的拒绝规则阻止对用户主目录下的文件读/写、出站网络连接,以及除 `/usr/sbin/lsof` 外所有二进制文件的执行。
+
+### 输出文件支持
+
+`--json-log`、`--pcap-export` 和 `--pcapng-export` 路径通过运行时参数(`JSON_LOG_PATH`、`PCAP_PATH`、`PCAP_JSONL_PATH`、`PCAPNG_PATH`)传递给 SBPL 配置文件。配置文件为每个路径授予显式的 `allow file-write*` 规则,该规则通过 SBPL 的特异性优先于更宽泛的 `/Users` 拒绝规则。未使用的参数默认为 `/dev/null`。
+
+三个标志在沙箱内均可正常工作。
+
+### 安全收益
+
+如果攻击者利用 DPI/包解析中的漏洞:
+- 无法读取 `/Users` 下的 SSH 密钥、AWS 凭据、浏览器配置文件或其他凭据文件
+- 无法写入 `/Users` 下的 SSH 密钥、AWS 凭据、浏览器配置文件或其他凭据文件
+- 无法建立出站 TCP/UDP 连接(阻止数据外泄)
+- 无法打开新的 raw network socket
+- 无法执行二进制文件(不能通过 `/bin/sh`、`/usr/bin/curl` 等逃逸 shell)
+- 不以 root 运行:使用 `sudo rustnet` 时进程会切换为调用用户
+
+### CLI 选项
+
+```
+--no-sandbox 禁用 Seatbelt 沙箱和 root uid 降权
+--sandbox-strict 要求完整沙箱强制生效,否则退出
+--no-uid-drop 初始化后保持 root 运行,
+ 不降权到 SUDO_UID/SUDO_GID(或 nobody)
+```
+
+root uid 降权的权衡:默认的 PKTAP 归属路径不受影响(进程元数据随已打开的捕获
+fd 带内到达),但 lsof 回退路径(PKTAP 不可用时启用,例如显式指定 `--interface`)
+降权后只能看到目标用户的进程。如果依赖 lsof 归属其他用户的进程,请使用
+`--no-uid-drop`。
+
+### 为什么默认使用 BestEffort
+
+`sandbox_init_with_parameters` 是 macOS 的私有(未公开)API。自 macOS 10.5 以来一直保持稳定,Chromium、Firefox 和 Safari 都使用它进行进程沙箱,但理论上可能在没有通知的情况下发生变化。BestEffort 在 API 行为异常时优雅降级,而不是阻止应用运行。使用 `--sandbox-strict` 可要求沙箱生效,否则中止。
+
+### 剪贴板行为
+
+与 Linux Landlock 不同,在 Seatbelt 下剪贴板复制(`c` 键)正常工作。macOS 剪贴板使用 NSPasteboard,通过 Mach IPC 在 Unix domain socket 上通信——SBPL 配置文件显式允许 `(network-outbound (remote unix-socket))`。
+
+在 Linux 上,剪贴板需要访问 Wayland socket(`/run/user/UID/wayland-0`)或 X11 socket(`/tmp/.X11-unix/`)。Landlock 的拒绝默认模型会阻止这些,因为它们不在写路径的允许列表中,因此当 Landlock 激活时剪贴板不可用。
+
+## FreeBSD 沙箱
+
+FreeBSD 当前未启用沙箱。计划使用 `cap_enter()` 配合 `libcasper` 实现完整的 Capsicum 沙箱,用于特权进程查找——详见 [ROADMAP.md](ROADMAP.md)。
+
+### root uid 降权
+
+在 Capsicum 落地之前,FreeBSD 上的主要隔离手段是 root 降权:以 root 启动时(如 `sudo rustnet`),BPF 捕获设备打开后,进程通过 `setresuid`/`setresgid` 降权到调用用户(`SUDO_UID`/`SUDO_GID`)或 `nobody`。已打开的捕获和日志/导出文件描述符继续有效,预创建的导出文件会移交给目标用户。注意 `doas` 不设置 `SUDO_UID`,因此 doas 用户会回退到 `nobody`。
+
+权衡:进程归属使用 `sockstat`,非 root 用户只能看到目标用户的 socket。如需归属其他用户的进程,请使用 `--no-uid-drop`。
+
+```
+--no-uid-drop 初始化后保持 root 运行,
+ 不降权到 SUDO_UID/SUDO_GID(或 nobody)
+```
+
+## 权限剥离与 Job Object 沙箱(Windows)
+
+在 Windows 上,RustNet 在初始化后从进程令牌中移除危险特权,并应用 Job Object 阻止子进程创建。
+
+### 受限制的内容
+
+| 限制项 | 描述 |
+|--------|------|
+| 特权移除 | 永久移除 SeDebugPrivilege、SeTakeOwnershipPrivilege、SeBackupPrivilege、SeRestorePrivilege 等危险特权 |
+| 子进程 | Job Object 阻止创建子进程(反向 shell、基于 exec 的数据外泄) |
+
+### 工作原理
+
+1. **初始化阶段**:RustNet 打开 Npcap 句柄并创建日志文件
+2. **特权移除**:`AdjustTokenPrivileges` 配合 `SE_PRIVILEGE_REMOVED` 永久从进程令牌中剥离危险特权
+3. **Job Object**:应用 `JOB_OBJECT_LIMIT_ACTIVE_PROCESS = 1` 的 Job Object,阻止任何子进程创建
+
+### 安全收益
+
+如果攻击者利用 DPI/包解析中的漏洞:
+- 无法调试其他进程(SeDebugPrivilege 已移除)
+- 无法取得任意文件的所有权(SeTakeOwnershipPrivilege 已移除)
+- 无法通过 ACL 绕过读取文件(SeBackupPrivilege 已移除)
+- 无法生成子进程(cmd.exe、powershell.exe、curl.exe —— 被 Job Object 阻止)
+- 无法加载内核驱动(SeLoadDriverPrivilege 已移除)
+
+### 局限性
+
+Windows 沙箱弱于 Linux/macOS/FreeBSD:
+- 无文件系统限制 —— Windows 缺少与 Landlock 或 Seatbelt 等效的进程级文件系统沙箱
+- 无网络限制 —— 阻止出站会中断 Npcap 包捕获
+- 特权移除仅影响提升进程已拥有的特权
+
+### CLI 选项
+
+```
+--no-sandbox 禁用特权移除和 Job Object
+--sandbox-strict 要求完整沙箱强制生效,否则退出
+```
+
+## 权限需求
+
+RustNet 需要特权访问来捕获网络数据包:
+
+| 平台 | 需求 |
+|------|------|
+| Linux | `CAP_NET_RAW` 这项 Linux capability 或 root |
+| macOS | Root 或 BPF 组成员(`access_bpf` 组) |
+| Windows | Administrator(用于 Npcap) |
+| FreeBSD | Root 或 BPF 设备访问 |
+
+### 为什么需要特权
+
+- **Raw socket 访问** —— 在低层拦截网络流量(只读、非混杂模式)
+- **BPF 设备访问** —— 将包过滤器加载到内核
+- **eBPF 程序** —— 可选的内核探针,用于增强进程追踪(仅限 Linux)
+
+### 推荐:基于 Linux capabilities 的执行(Linux)
+
+与其以 root 运行,不如仅授予所需的 Linux capabilities:
+
+```bash
+# 现代 Linux(5.8+):包捕获 + eBPF
+sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' $(which rustnet)
+
+# 仅包捕获(eBPF 会回退到 procfs)
+sudo setcap cap_net_raw+eip $(which rustnet)
+```
+
+旧版 pre-5.8 内核需要宽泛的 `CAP_SYS_ADMIN` 才能执行 eBPF 操作。RustNet
+的安装包不会自动授予该 capability;除非你明确接受该风险,否则请只授予
+`CAP_NET_RAW` 并使用 procfs 回退。沙箱应用后,`CAP_NET_RAW` 和 eBPF
+加载相关 capabilities 会被丢弃——进程仅保留所需的最小特权。
+
+## 只读操作
+
+RustNet 仅监控流量,不会:
+- 修改数据包
+- 阻断连接
+- 注入流量
+- 更改路由表
+- 更改防火墙规则
+
+包捕获以非混杂、只读模式打开。
+
+## 不主动对外通信
+
+RustNet 完全在本地运行:
+- 无遥测或分析
+- 无网络请求(除监控的流量外)
+- 无云服务或远程 API
+- 所有数据保留在你的系统上
+
+## 日志文件隐私
+
+日志文件可能包含敏感信息:
+- IP 地址和端口
+- 主机名和 SNI 数据
+- 进程名和 PID
+- DNS 查询和响应
+
+**最佳实践:**
+- 默认禁用日志记录(不使用 `--log-level` 标志)
+- 保护日志目录权限
+- 实施日志轮转和保留策略
+- 分享前检查日志中的敏感数据
+
+## eBPF 安全
+
+使用 eBPF 进行增强型进程检测时(Linux 默认):
+
+- 现代内核需要额外的 Linux capabilities(`CAP_BPF`、`CAP_PERFMON`)
+- eBPF 程序在加载前由内核验证
+- 仅限只读操作(不修改数据包)
+- 如果 eBPF 失败,自动回退到 procfs
+
+## 威胁模型
+
+**RustNet 防护的内容:**
+- 未经授权的用户无法在没有适当权限的情况下捕获数据包
+- 基于 Linux capabilities 的权限限制了被入侵后的影响范围
+- Landlock(Linux)和 Seatbelt(macOS)沙箱限制潜在的漏洞利用
+
+**RustNet 不防护的内容:**
+- 拥有包捕获权限的用户可以看到所有未加密的流量
+- Root/Administrator 用户可以直接修改 RustNet 或捕获数据包
+- 对机器的物理访问可以捕获数据包
+- 网络级攻击(RustNet 是监控工具,不是安全设备)
+
+### 以 Root 身份运行时的沙箱
+
+Landlock(Linux)和 Seatbelt(macOS)即使在 RustNet 以 root(UID 0)运行时也会强制执行限制。沙箱一旦应用就无法从进程内部撤销——在 Linux 上,RustNet 会在应用任何限制之前直接设置 `PR_SET_NO_NEW_PRIVS`(Landlock 也需要并会同样设置它),该设置对每个进程不可逆,并且即使使用 `--no-sandbox` 也会应用。
+
+然而,沙箱**不能**防护供应链攻击。被入侵的二进制文件可以直接不应用沙箱。Root 也可以:
+- 传递 `--no-sandbox` 完全跳过沙箱(`PR_SET_NO_NEW_PRIVS` 除外)
+- 卸载 Landlock LSM 内核模块
+- 在 macOS 上禁用 SIP(控制沙箱强制执行)
+- 使用 `ptrace` 修改运行中的进程
+
+因此,强烈建议使用细粒度 Linux capabilities(`setcap cap_net_raw=eip`)运行,而不是以 root 运行。
+
+## 供应链安全
+
+RustNet 采取以下措施防护供应链攻击:
+
+- **依赖锁文件**:`Cargo.lock` 已提交到仓库,固定所有传递依赖版本并记录源校验和。这防止静默版本升级。
+- **安全审计**:`cargo deny check` 在每次 push 和 pull request 时于 CI 中运行,对照 RustSec Advisory Database 检查依赖,并强制执行许可证、来源和通配符版本策略(`deny.toml`)。一个每日定时工作流会针对已提交的 `Cargo.lock` 重新检查安全公告,因此新发布的公告无需 push 即可被发现。
+- **CI action 固定**:所有 GitHub Actions 均通过 commit SHA(而非标签)固定,防止对上游 action 的标签重写攻击。
+- **保守的依赖策略**:新依赖需要说明理由,并审查其维护状态和安全记录(参见 [CONTRIBUTING.zh-CN.md](CONTRIBUTING.zh-CN.md))。
+- **构建时完整性**:Windows Npcap SDK 下载在 `build.rs` 中对照硬编码的 SHA256 校验和进行验证。
+- **代码签名**:macOS 发布版本使用 Apple Developer 证书签名并进行公证。
+- **校验和验证**:所有打包工作流(Homebrew、Chocolatey、AUR)在发布前计算并双重验证 SHA256 校验和。
+
+### 局限性
+
+- `cargo install rustnet` 从 crates.io 获取最新兼容版本,并**不**使用 `Cargo.lock`。从源码构建的用户应验证源 tarball 校验和。
+- 构建脚本(`build.rs`)和 proc-macros 在编译时执行任意代码。虽然所有当前依赖都是久经考验的 crate,但这是 Rust 构建模型的固有风险。
+
+## 审计与合规
+
+对于生产环境:
+- 审计记录谁以包捕获权限运行 RustNet
+- 网络监控策略和数据保护法规合规
+- 对特权网络访问进行用户访问审查
+- 通过配置管理系统实现自动化 Linux capabilities 管理
+
+## 报告安全问题
+
+请通过 GitHub Issues 报告安全漏洞,或直接与维护者联系。
diff --git a/USAGE.md b/USAGE.md
new file mode 100644
index 0000000..9cc0307
--- /dev/null
+++ b/USAGE.md
@@ -0,0 +1,1189 @@
+English | 简体中文
+
+# Usage Guide
+
+This guide covers detailed usage of RustNet, including command-line options, keyboard controls, filtering, sorting, and understanding connection lifecycle.
+
+## Table of Contents
+
+- [Running RustNet](#running-rustnet)
+- [Command-line Options](#command-line-options)
+- [Keyboard Controls](#keyboard-controls)
+- [Mouse Controls](#mouse-controls)
+- [Filtering](#filtering)
+- [Sorting](#sorting)
+- [Process Grouping](#process-grouping)
+- [Network Statistics Panel](#network-statistics-panel)
+- [Interface Statistics](#interface-statistics)
+- [Connection Lifecycle & Visual Indicators](#connection-lifecycle--visual-indicators)
+- [Logging](#logging)
+
+## Running RustNet
+
+Packet capture requires elevated privileges on most systems. See [INSTALL.md](INSTALL.md) for detailed permission setup instructions.
+
+**Quick start:**
+
+```bash
+# Run with sudo (works on all platforms)
+sudo rustnet
+
+# Or grant capabilities to run without sudo (see INSTALL.md for details)
+# Linux example (modern kernel 5.8+):
+sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' /path/to/rustnet
+rustnet
+```
+
+**Basic usage examples:**
+
+```bash
+# Run with default settings
+# macOS: Uses PKTAP for process metadata
+# Linux/Other: Auto-detects active interface
+rustnet
+
+# Specify network interface
+rustnet -i eth0
+rustnet --interface wlan0
+
+# Linux: Monitor all interfaces simultaneously
+rustnet -i any
+
+# Filter out localhost connections (already filtered by default)
+rustnet --no-localhost
+
+# Show localhost connections (override default filtering)
+rustnet --show-localhost
+
+# Set UI refresh interval (in milliseconds)
+rustnet -r 500
+rustnet --refresh-interval 2000
+
+# Disable deep packet inspection
+rustnet --no-dpi
+
+# Restore the original full-color palette
+rustnet --theme classic
+
+# Disable reverse DNS lookups (enabled by default)
+rustnet --no-resolve-dns
+
+# Enable logging with specific level (options: error, warn, info, debug, trace)
+rustnet -l debug
+rustnet --log-level info
+
+# View help and all options
+rustnet --help
+```
+
+## Command-line Options
+
+```
+Usage: rustnet [OPTIONS]
+
+Options:
+ -i, --interface Network interface to monitor
+ --no-localhost Filter out localhost connections (default: filtered)
+ --show-localhost Show localhost connections (overrides default filtering)
+ -r, --refresh-interval UI refresh interval in milliseconds [default: 1000]
+ --no-dpi Disable deep packet inspection
+ --no-resolve-dns Disable reverse DNS lookups (enabled by default)
+ --show-ptr-lookups Show PTR lookup connections (hidden by default)
+ -l, --log-level Set the log level (if not provided, no logging will be enabled)
+ --json-log Enable JSON logging of connection events to specified file
+ --pcap-export Export captured packets to PCAP file for Wireshark analysis
+ --pcapng-export Export captured packets to annotated PCAPNG file for Wireshark analysis
+ --no-color Disable all colors in the UI (also respects NO_COLOR env var)
+ --theme Color theme preset: "muted" (single accent, color reserved
+ for signals, default) or "classic" (original full-color palette)
+ --geoip-country Path to GeoLite2-Country.mmdb (auto-discovered if not specified)
+ --geoip-asn Path to GeoLite2-ASN.mmdb (auto-discovered if not specified)
+ --geoip-city Path to GeoLite2-City.mmdb (auto-discovered if not specified)
+ --no-geoip Disable GeoIP lookups entirely
+ -f, --bpf-filter BPF filter expression for packet capture
+ --no-sandbox Disable Landlock sandboxing (Linux only)
+ --sandbox-strict Require full sandbox enforcement or exit (Linux only)
+ --no-uid-drop Keep running as root instead of dropping to
+ SUDO_UID/SUDO_GID (or nobody) after initialization (Linux, macOS, and FreeBSD)
+ -h, --help Print help
+ -V, --version Print version
+```
+
+Builds compiled with the optional `kubernetes` feature (including the official Docker image) additionally expose `--kubernetes `. See [`--kubernetes`](#--kubernetes-mode-optional-feature) below.
+
+### Option Details
+
+#### `-i, --interface `
+
+Specify which network interface to monitor.
+
+**Default behavior (no `-i` flag):**
+- **macOS**: Automatically uses PKTAP for enhanced process metadata (requires sudo)
+- **Linux/Other**: Auto-detects the first available non-loopback interface
+
+**Examples:**
+```bash
+# Default: Auto-detect interface (PKTAP on macOS)
+rustnet
+
+# Linux: Monitor all interfaces using the special "any" pseudo-interface
+rustnet -i any
+
+# Monitor specific interfaces
+rustnet -i eth0 # Monitor Ethernet interface
+rustnet -i wlan0 # Monitor WiFi interface
+rustnet -i en0 # Monitor macOS primary interface
+
+# Monitor VPN and tunnel interfaces (TUN/TAP support)
+rustnet -i utun0 # macOS VPN tunnel (TUN, Layer 3)
+rustnet -i tun0 # Linux/BSD VPN tunnel (TUN, Layer 3)
+rustnet -i tap0 # TAP interface (Layer 2, includes Ethernet)
+```
+
+**TUN/TAP Interface Support:**
+
+RustNet fully supports monitoring VPN and virtual network interfaces:
+
+- **TUN interfaces** (Layer 3): Carry IP packets directly without Ethernet headers
+ - Common on VPNs: WireGuard, OpenVPN (tun mode), Tailscale
+ - Examples: `utun0-utun9` (macOS), `tun0-tun9` (Linux/BSD)
+
+- **TAP interfaces** (Layer 2): Include full Ethernet frames
+ - Used by: OpenVPN (tap mode), QEMU/KVM virtual networks, Docker
+ - Examples: `tap0-tap9` (Linux/BSD)
+
+RustNet automatically detects TUN/TAP interfaces and adjusts packet parsing accordingly. The interface type is displayed in the UI status area.
+
+**Platform-specific notes:**
+- **macOS**: Without `-i`, PKTAP is used automatically for better process detection. Use `-i ` to monitor a specific interface instead
+- **Linux**: Use `-i any` to capture on all interfaces simultaneously (not available on other platforms)
+- **TUN/TAP**: Fully supported on all platforms - RustNet detects interface type by name and adjusts parsing
+- **All platforms**: If you specify a non-existent interface, an error will show available interfaces
+
+**Finding your interfaces:**
+- Linux: `ip link show` or `ifconfig`
+- macOS: `ifconfig` or `networksetup -listallhardwareports`
+- Windows: `ipconfig /all`
+
+#### `--no-localhost` / `--show-localhost`
+
+Control whether localhost (127.0.0.1/::1) connections are displayed.
+
+- **Default**: Localhost connections are filtered out (`--no-localhost`)
+- **Override**: Use `--show-localhost` to see localhost connections
+
+This is useful for reducing noise in the connection list, as most users don't need to monitor local IPC connections.
+
+#### `-r, --refresh-interval `
+
+Set the UI refresh rate in milliseconds. Lower values provide more responsive updates but increase CPU usage.
+
+**Recommendations:**
+- **Default (1000ms)**: Good balance for most users
+- **High-traffic networks (2000ms)**: Reduce CPU usage on busy networks
+- **Real-time monitoring (500ms)**: More responsive updates for quick analysis
+- **Low-end systems (2000-3000ms)**: Reduce load on resource-constrained machines
+
+#### `--no-dpi`
+
+Disable Deep Packet Inspection (DPI). This reduces CPU usage by 20-40% on high-traffic networks but disables:
+- HTTP host detection
+- HTTPS/TLS SNI extraction
+- DNS query/response detection
+- SSH version identification
+- QUIC protocol detection
+
+Useful for performance-constrained environments or when application-level details aren't needed.
+
+#### `--no-resolve-dns` / `--show-ptr-lookups`
+
+Reverse DNS lookups are **enabled by default**: IP addresses are resolved to hostnames in the background and shown in the connection list (toggle with the `d` key) and in the Details tab.
+
+- **`--no-resolve-dns`**: Disable reverse DNS resolution entirely. The connection list shows IP addresses only and no PTR queries are issued.
+- **`--show-ptr-lookups`**: PTR lookup traffic is hidden by default. Use this flag to show the DNS PTR queries generated by the resolver.
+
+**Note**: Resolved hostnames are also included in JSON logs (`destination_hostname`, `source_hostname` fields).
+
+#### `--theme `
+
+Select the color theme preset:
+
+- **`muted`** (default): A restrained palette with one cyan accent. Addresses
+ keep calm colors (remote = blue, local = cyan); everything else uses color
+ only for *signals* — transitional connection states, staleness (yellow/red
+ rows), and live bandwidth.
+- **`classic`**: The original full-color palette from earlier releases, with a
+ distinct color per column.
+
+```bash
+# Bring back the original full-color look
+rustnet --theme classic
+```
+
+Related: `--no-color` disables all colors entirely (also honors the `NO_COLOR`
+environment variable).
+
+#### `-f, --bpf-filter `
+
+Apply a BPF (Berkeley Packet Filter) expression to filter packets at capture time. This is more efficient than application-level filtering as packets are filtered in the kernel before reaching RustNet.
+
+**Common filter expressions:**
+
+```bash
+# Filter by port (matches source OR destination)
+rustnet --bpf-filter "port 443"
+rustnet --bpf-filter "port 80 or port 8080"
+
+# Filter by destination port specifically
+rustnet --bpf-filter "dst port 443"
+rustnet --bpf-filter "tcp dst port 80"
+
+# Filter by source port specifically
+rustnet --bpf-filter "src port 443"
+
+# Filter by host
+rustnet --bpf-filter "host 192.168.1.1"
+rustnet --bpf-filter "net 10.0.0.0/8"
+
+# Filter by protocol
+rustnet --bpf-filter "tcp"
+rustnet --bpf-filter "udp port 53"
+
+# Combine filters
+rustnet --bpf-filter "tcp port 443 and host github.com"
+
+# Exclude traffic
+rustnet --bpf-filter "not port 22"
+```
+
+**Notes:**
+- BPF filter syntax follows the pcap-filter(7) format. Invalid filters will cause RustNet to exit with an error. Use `man pcap-filter` for complete syntax documentation.
+- **macOS limitation:** BPF filters are incompatible with PKTAP (linktype 149). When you specify a BPF filter on macOS, RustNet automatically falls back to regular interface capture. This means process identification uses `lsof` instead of PKTAP's direct process metadata, which may be slightly less accurate for short-lived connections.
+
+#### `-l, --log-level `
+
+Enable logging with the specified level. Logging is **disabled by default**.
+
+**Available levels:**
+- `error` - Only errors (minimal logging)
+- `warn` - Warnings and errors
+- `info` - General information (recommended for normal debugging)
+- `debug` - Detailed debugging information
+- `trace` - Very verbose output (includes packet-level details)
+
+Log files are created in the `logs/` directory with timestamp: `rustnet_YYYY-MM-DD_HH-MM-SS.log`
+
+#### `--kubernetes ` (optional feature)
+
+Attribute connections to their owning Kubernetes pod and container. This flag only exists in builds compiled with the `kubernetes` cargo feature: the official Docker image (`ghcr.io/domcyrus/rustnet`) ships with it enabled, while native installs (cargo, Homebrew, deb/rpm) leave it off.
+
+**Modes:**
+
+- `auto` (default): Enable attribution only when RustNet itself is running inside a pod
+- `on`: Always enable (e.g. when running directly on a node)
+- `off`: Disable attribution
+
+When active, RustNet maps each connection's PID to its pod UID and container ID via cgroups (`/proc//cgroup`) and resolves human-readable pod and container names from the kubelet log directories (`/var/log/containers`, `/var/log/pods`). This is runtime-agnostic and needs no CRI socket or kubelet credentials. Pod-owned sockets are attributed even when RustNet runs with `hostNetwork: true`, because the per-PID socket tables are network-namespace aware.
+
+Attribution is surfaced in:
+
+- The **Details tab**, as a "Kubernetes" section showing pod name, namespace, pod UID, container name, and container ID
+- **JSON logs** (`--json-log`) and the `--pcap-export` sidecar JSONL, as a `kubernetes` object per connection event
+- **PCAPNG packet comments** (`--pcapng-export`), as `pod=`, `ns=`, `pod_uid=`, `container=`, and `container_id=` fields
+- The `pod:`, `ns:`, and `container:` [filter keywords](#keyword-filters)
+
+**Running on a cluster:** the easiest way to use this is the [kubectl-rustnet](https://github.com/domcyrus/kubectl-rustnet) plugin (`kubectl krew install rustnet`). It launches RustNet as an ephemeral debug pod on a node using the official image, mounts the kubelet log directories read-only for name resolution, and cleans up the pod on exit. Since the plugin runs RustNet inside a pod, the default `auto` mode enables attribution without any flags.
+
+```bash
+# On a Kubernetes cluster: run as an ephemeral debug pod via the plugin
+kubectl rustnet --node worker-3
+
+# Native build with the feature enabled
+cargo build --release --features kubernetes
+
+# Force attribution on outside a pod (e.g. directly on a node)
+rustnet --kubernetes on
+```
+
+## Keyboard Controls
+
+### Navigation
+
+- `↑` or `k` - Navigate up in connection list
+- `↓` or `j` - Navigate down in connection list
+- `g` - Jump to first connection (vim-style)
+- `G` (Shift+g) - Jump to last connection (vim-style)
+- `PageUp` or `Ctrl+B` - Move up by one page
+- `PageDown` or `Ctrl+F` - Move down by one page
+
+### Views and Tabs
+
+- `Tab` or `]` - Next tab
+- `Shift+Tab` or `[` - Previous tab
+- `1` / `2` / `3` / `4` / `5` - Jump directly to Overview / Details / Interfaces / Graph / Help
+- `Enter` - View detailed information about selected connection
+- `Esc` - Go back to previous view or clear active filter
+- `h` - Toggle help screen
+
+### Actions
+
+- `c` - Copy remote address to clipboard
+- `p` - Toggle between service names and port numbers
+- `d` - Toggle between hostnames and IP addresses (disabled by `--no-resolve-dns`)
+- `/` - Enter filter mode (vim-style search with real-time results)
+- `x` - Clear all connections and reset statistics (press twice to confirm)
+- `t` - Toggle display of historic (closed) connections
+- `i` - Toggle the System info sidebar
+- `r` - Reset view to defaults (clears grouping, sort, filter, and historic)
+
+### Process Grouping
+
+- `a` - Toggle process grouping mode (aggregate connections by process)
+- `Space` - Expand/collapse selected process group
+- `←` or `h` - Collapse selected group
+- `→` or `l` - Expand selected group
+
+### Sorting
+
+- `s` - Cycle through sort columns (left-to-right order)
+- `S` (Shift+s) - Toggle sort direction (ascending/descending)
+
+### Exit
+
+- `q` - Quit the application (press twice to confirm)
+- `Ctrl+C` - Quit immediately
+
+## Mouse Controls
+
+RustNet has full mouse support. Mouse capture is enabled automatically — all interactions described below work out of the box.
+
+### Overview Tab
+
+| Action | Effect |
+|--------|--------|
+| **Click** on a connection row | Select that connection |
+| **Double-click** a connection row | Open the Details tab for that connection |
+| **Scroll wheel** over the connection list | Navigate up/down through connections |
+| **Click** on a tab name | Switch to that tab |
+
+### Grouped View (press `a` to enable)
+
+| Action | Effect |
+|--------|--------|
+| **Click** on a group header (`▸`/`▾`) | Select the group |
+| **Double-click** a group header | Expand or collapse the process group |
+| **Click** on a connection within an expanded group | Select that connection |
+| **Double-click** a connection within an expanded group | Open the Details tab for that connection |
+| **Scroll wheel** | Navigate through groups and connections |
+
+### Details Tab
+
+| Action | Effect |
+|--------|--------|
+| **Click** on any field line | Copy the field value to the system clipboard |
+
+Clicking a field copies just the value (not the label). For example, clicking the "Remote Address: 142.250.80.46:443" line copies `142.250.80.46:443` to your clipboard. A confirmation message appears in the status bar for 3 seconds.
+
+Both the "Connection Information" and "Traffic Statistics" panels support click-to-copy.
+
+## Filtering
+
+Press `/` to enter filter mode. Type to filter connections in real-time, navigate with arrow keys while typing.
+
+### Basic Search
+
+Simply type any text to search across all connection fields:
+
+```
+/google # Find connections containing "google"
+/firefox # Find Firefox connections
+/192.168 # Find connections with IP starting with 192.168
+```
+
+### Keyword Filters
+
+Use keyword filters for targeted searches:
+
+| Keyword | Aliases | Description | Example |
+|---------|---------|-------------|---------|
+| `port:` | | Exact port match; use `/pattern/` for regex | `port:22` matches only 22; `port:/22/` matches 22, 220, 5522 |
+| `sport:` | `srcport:`, `source-port:` | Source port (exact or regex) | `sport:80` matches only source port 80 |
+| `dport:` | `dstport:`, `dest-port:`, `destination-port:` | Destination port (exact or regex) | `dport:443` matches only destination port 443 |
+| `src:` | `source:` | Source IPs/hostnames | `src:192.168` matches 192.168.x.x |
+| `dst:` | `dest:`, `destination:` | Destinations | `dst:github.com` matches github.com |
+| `process:` | `proc:` | Process names | `process:ssh` matches ssh, sshd |
+| `sni:` | `host:`, `hostname:` | SNI hostnames (HTTPS) | `sni:api` matches api.example.com |
+| `service:` | `svc:` | Service names | `service:https` matches HTTPS service |
+| `app:` | `application:` | Detected application protocol | `app:ssh` matches SSH connections |
+| `state:` | | Protocol states | `state:established` matches established connections |
+| `proto:` | `protocol:` | Protocol type | `proto:tcp` matches TCP connections |
+| `pod:` | | Kubernetes pod name or UID * | `pod:nginx` matches nginx-86644db9cc-mf5lx |
+| `ns:` | `namespace:` | Kubernetes pod namespace * | `ns:kube-system` matches pods in kube-system |
+| `container:` | `cont:` | Kubernetes container name or ID * | `container:nginx` matches the nginx container |
+
+\* Requires a build with the `kubernetes` feature and active pod attribution. See [`--kubernetes`](#--kubernetes-mode-optional-feature).
+
+### State Filtering
+
+Filter connections by their current protocol state (case-insensitive):
+
+⚠️ **Note:** State tracking accuracy varies by protocol. TCP states are most reliable, while UDP, QUIC, and other protocol states are derived from packet inspection and may not always reflect the true connection state.
+
+**Examples:**
+```
+state:syn_recv # Show half-open connections (useful for detecting SYN floods)
+state:established # Show only established connections
+state:fin_wait # Show connections in closing states
+state:quic_handshake # Show QUIC connections during handshake
+state:dns_query # Show DNS query connections
+state:udp_active # Show active UDP connections
+```
+
+**Available states:**
+
+| Protocol | States |
+|----------|--------|
+| **TCP** | `SYN_SENT`, `SYN_RECV`, `ESTABLISHED`, `FIN_WAIT1`, `FIN_WAIT2`, `TIME_WAIT`, `CLOSE_WAIT`, `LAST_ACK`, `CLOSING`, `CLOSED` |
+| **QUIC** | `QUIC_INITIAL`, `QUIC_HANDSHAKE`, `QUIC_CONNECTED`, `QUIC_DRAINING`, `QUIC_CLOSED` ⚠️ *Note: May be incomplete due to encrypted handshakes* |
+| **UDP** | `UDP_ACTIVE`, `UDP_IDLE`, `UDP_STALE` |
+| **DNS** | `DNS_QUERY`, `DNS_RESPONSE` |
+| **SSH** | `BANNER`, `KEYEXCHANGE`, `AUTHENTICATION`, `ESTABLISHED` ⚠️ *Note: Based on packet inspection* |
+| **Other** | `ECHO_REQUEST`, `ECHO_REPLY`, `ARP_REQUEST`, `ARP_REPLY` |
+
+### Regex Filters
+
+Wrap any filter value in `/pattern/` to use a regular expression (case-insensitive). Regexes use standard syntax supported by the `regex-lite` crate.
+
+```
+/192\.168\.[0-9]+/ # General regex across all fields
+port:/22/ # Ports containing "22" (22, 220, 2200, 5522 …)
+sni:/.*github\..*/ # SNI matching github.com, api.github.com, etc.
+process:/chrom(e|ium)/ # Chrome or Chromium
+```
+
+> **Port matching**: `port:443` is an **exact** match (only port 443). Use `port:/443/` if you want substring/regex behaviour.
+
+### Combining Filters
+
+Combine multiple filters with spaces (implicit AND):
+
+```
+sport:80 process:nginx # Nginx connections from port 80
+dport:443 sni:google.com # HTTPS connections to Google
+sport:443 state:syn_recv # Half-open connections to port 443 (SYN flood detection)
+proto:tcp state:established # All established TCP connections
+process:firefox state:quic_connected # Active QUIC connections from Firefox
+dport:22 app:openssh # SSH connections using OpenSSH
+state:established app:ssh # Established SSH connections
+```
+
+### Clearing Filters
+
+Press `Esc` to clear the active filter and return to the full connection list.
+
+## Sorting
+
+RustNet provides powerful table sorting to help you analyze network connections. Press `s` to cycle through sortable columns in left-to-right visual order, and press `S` (Shift+s) to toggle between ascending and descending order.
+
+### Quick Start
+
+**Find bandwidth hogs (combined up+down traffic):**
+```
+Press 's' repeatedly until you see: Bandwidth Total ↓
+The connections with highest total bandwidth appear at the top
+```
+
+**Sort by process name:**
+```
+Press 's' repeatedly until you see: Process ↑
+Connections are sorted alphabetically by process name
+```
+
+### Sortable Columns
+
+Press `s` to cycle through columns in left-to-right order:
+
+| Column | Default Direction | Description |
+|--------|-------------------|-------------|
+| **Process** | ↑ Ascending | Sort by process name alphabetically |
+| **Remote Address** | ↑ Ascending | Sort by remote IP:port |
+| **Local Address** | ↑ Ascending | Sort by local IP:port (useful for multi-interface systems) |
+| **Location** | ↑ Ascending | Sort by country code (requires GeoIP database) |
+| **Service** | ↑ Ascending | Sort by service name or port number |
+| **Application** | ↑ Ascending | Sort by detected application protocol (HTTP, DNS, etc.), with TCP/UDP as tie-break |
+| **State** | ↑ Ascending | Sort by connection state (ESTABLISHED, etc.) |
+| **Bandwidth (Rx/Tx)** | ↓ Descending | Sort by **combined up+down** bandwidth (highest first by default) |
+
+Columns hidden at narrow terminal widths stay in the cycle — the active sort is always named in the table's section title.
+
+### Sort Indicators
+
+The active sort column is highlighted with:
+- **Cyan color** and **underline** styling
+- **Arrow symbol** (↑ or ↓) showing sort direction
+- **Table title** showing current sort state
+
+**Visual indicators:**
+```
+Active column header appears in cyan with underline:
+Process │ Remote ↑ │ Local │ Service │ App │ ...
+ ^^^^^^^^
+ (cyan, underlined, with arrow)
+
+Section title shows current sort:
+▎ Active Connections · 42 shown · sort Remote Addr ↑
+```
+
+### Sort Behavior
+
+**Press `s` (lowercase) - Cycle Columns:**
+- Moves to the next column in left-to-right visual order
+- **Resets to default direction** for that column
+- Bandwidth column defaults to descending (↓) to show highest values first
+- Text columns default to ascending (↑) for alphabetical order
+
+**Press `S` (Shift+s) - Toggle Direction:**
+- **Stays on current column**
+- Flips between ascending (↑) and descending (↓)
+- Useful for reversing sort order (e.g., finding smallest bandwidth users)
+
+**Press `s` multiple times to return to default:**
+- Cycling through all columns returns to the default chronological sort (by connection creation time)
+- No sort indicator is shown when in default mode
+
+### Sorting with Filtering
+
+Sorting works seamlessly with filtering:
+1. **Filter first**: Press `/` and enter your filter criteria
+2. **Then sort**: Press `s` to sort the filtered results
+3. **The sort persists**: Changing the filter keeps your sort order active
+
+Example workflow:
+```
+1. Press '/' and type 'firefox' to filter Firefox connections
+2. Press 's' until you see "Bandwidth Total ↓"
+3. Now viewing Firefox connections sorted by total bandwidth (up+down combined)
+```
+
+### Examples
+
+**Find which process is using the most bandwidth:**
+```
+1. Press 's' until "Bandwidth Total ↓" appears
+2. Top connection shows the highest total bandwidth (up+down combined)
+3. Look at the "Process" column to see which application
+```
+
+**Sort connections by remote destination:**
+```
+1. Press 's' until "Remote Address ↑" appears
+2. Connections are grouped by remote IP address
+3. Press 'S' to reverse order if needed
+```
+
+**Find idle connections (lowest bandwidth):**
+```
+1. Press 's' to cycle to "Bandwidth Total ↓"
+2. Press 'S' to toggle to "Bandwidth Total ↑" (ascending)
+3. Connections with lowest total bandwidth appear first
+```
+
+**Sort by application protocol:**
+```
+1. Press 's' until "Application ↑" appears
+2. All HTTPS connections group together, DNS queries together, etc.
+3. Useful for finding all connections of a specific type
+```
+
+## Process Grouping
+
+RustNet can group connections by process name, providing an aggregated view that makes it easier to see which applications are using your network.
+
+### Enabling Process Grouping
+
+Press `a` to toggle process grouping mode. When enabled:
+- Connections are grouped by process name (sorted alphabetically)
+- Each group shows aggregated statistics
+- Groups can be expanded/collapsed to show individual connections
+
+Press `a` again to return to the flat (ungrouped) connection list.
+
+### Grouped View Display
+
+When grouping is enabled, the connection list shows process groups:
+
+```
+▸ firefox (12) TCP:10 UDP:2 12.5K/1.2K
+▾ chrome (8) TCP:8 UDP:0 45.2K/5.1K
+ ├─ 4101 142.250.80.78:443 192.168.1.10:54321 ESTABLISHED 1.2K/0.3K
+ ├─ 4101 142.250.80.78:443 192.168.1.10:54322 ESTABLISHED 0.8K/0.1K
+ └─ 4102 8.8.8.8:53 192.168.1.10:54323 UDP_ACTIVE 0.2K/0.1K
+▸ systemd-resolved (3) TCP:0 UDP:3 0.2K/0.1K
+▸ (5) TCP:2 UDP:3 0.5K/0.2K
+```
+
+**Group header format:**
+- `▸` / `▾` - Collapsed/expanded indicator
+- Process name and connection count
+- Protocol breakdown (TCP/UDP counts)
+- Total bandwidth (rx/tx, in the Bandwidth column)
+
+**Expanded connections:**
+- Tree-style prefixes (`├─` / `└─`) with the PID (the group header carries the process name)
+- The same columns as the flat view (addresses, state, application, bandwidth)
+
+### Expanding and Collapsing Groups
+
+| Key | Action |
+|-----|--------|
+| `Space` | Toggle expand/collapse on selected group |
+| `→` or `l` | Expand selected group |
+| `←` or `h` | Collapse selected group |
+
+### Navigation in Grouped View
+
+Navigation works the same as in flat view:
+- `↑`/`k` and `↓`/`j` move through visible rows (groups and expanded connections)
+- `g` jumps to the first row
+- `G` jumps to the last row
+- `Enter` on a connection opens the Details view
+
+### Unknown Processes
+
+Connections without process information are grouped into a single `` group. This typically includes:
+- Short-lived connections that closed before process lookup completed
+- System-level connections on some platforms
+- Connections from restricted processes
+
+### Filtering with Grouping
+
+Filtering works seamlessly with grouping:
+1. Press `/` and enter your filter
+2. Only groups containing matching connections are shown
+3. Expand groups to see which connections matched
+
+### Sorting in Grouped View
+
+When grouping is enabled:
+- Groups are sorted alphabetically by process name (A-Z)
+- The sort column indicator shows how connections within groups are sorted
+- Press `s` to change how connections are sorted within expanded groups
+
+### Reset View
+
+Press `r` to reset all view settings at once:
+- Disables process grouping
+- Clears any active filter
+- Resets sort to default (chronological order)
+
+## Network Statistics Panel
+
+The Network Statistics panel appears on the right side of the interface, below the Traffic panel. It provides real-time TCP connection quality metrics derived directly from packet capture analysis, making it platform-independent across Linux, macOS, Windows, and FreeBSD.
+
+### Available Metrics
+
+**TCP Retransmits**
+Detects when a TCP segment is retransmitted due to packet loss or timeout. RustNet identifies retransmissions by analyzing TCP sequence numbers: when a packet arrives with a sequence number lower than expected, it indicates the original packet was lost and is being resent.
+
+**Out-of-Order Packets**
+Tracks inbound TCP packets that arrive out of sequence, typically caused by network congestion or multiple routing paths. These packets eventually arrive but in the wrong order, requiring the receiver to buffer and reorder them.
+
+**Fast Retransmits**
+Identifies TCP fast retransmit events triggered by receiving three duplicate acknowledgments (RFC 2581). This mechanism allows TCP to detect and recover from packet loss more quickly than waiting for a timeout, improving connection performance.
+
+### Statistics Display Format
+
+The panel shows both **active** and **total** counts for each metric:
+
+```
+TCP Retransmits: 5 / 142 total
+Out-of-Order: 2 / 89 total
+Fast Retransmits: 1 / 23 total
+Active TCP Flows: 18
+```
+
+- **Active count** (left number): Sum of events from currently tracked connections. This number goes up and down as connections are established and cleaned up.
+- **Total count** (right number): Cumulative count since RustNet started. This number only increases and provides historical context.
+- **Active TCP Flows**: Number of active TCP connections with analytics data.
+
+### Per-Connection Statistics
+
+When viewing connection details (press `Enter` on a connection), TCP analytics are shown for that specific connection:
+
+```
+TCP Retransmits: 3
+Out-of-Order: 1
+Fast Retransmits: 0
+```
+
+These counters are tracked independently for each connection, allowing you to identify problematic connections experiencing packet loss or network issues.
+
+### Use Cases
+
+**Network Quality Monitoring**
+A sudden increase in retransmissions or out-of-order packets indicates network congestion, packet loss, or routing issues.
+
+**Connection Troubleshooting**
+High retransmit counts on specific connections can identify:
+- Unreliable network paths to certain destinations
+- Bandwidth-constrained links
+- Faulty network hardware or drivers
+
+**Performance Analysis**
+Fast retransmit frequency indicates how well TCP is recovering from packet loss without waiting for timeouts.
+
+### Technical Notes
+
+- Statistics are derived from TCP sequence number analysis without requiring packet timestamps
+- Analysis works on both outbound and inbound packets
+- SYN and FIN flags are properly accounted for in sequence number tracking (each consumes 1 sequence number)
+- Only TCP connections show analytics; UDP, ICMP, and other protocols do not have these metrics
+
+## Interface Statistics
+
+RustNet provides real-time network interface statistics across all supported platforms (Linux, macOS, FreeBSD, Windows). Interface stats are displayed in two locations:
+
+### Accessing Interface Statistics
+
+**Overview Tab (Main Screen):**
+- Interface stats appear in the right panel below Network Stats
+- Shows up to 3 active interfaces with current rates
+- Displays: `InterfaceName: X KB/s ↓ / Y KB/s ↑`
+- Shows cumulative totals: `Errors (Total): N Drops (Total): M`
+
+**Interfaces Tab (Detailed View):**
+- Press `i` to toggle the Interface Statistics view
+- Shows a detailed table of all network interfaces
+- Displays comprehensive metrics for each interface
+
+### Statistics Displayed
+
+| Metric | Description | Notes |
+|--------|-------------|-------|
+| **RX Rate** | Current receive rate (bytes/sec) | Calculated from recent activity |
+| **TX Rate** | Current transmit rate (bytes/sec) | Calculated from recent activity |
+| **RX Packets** | Total packets received | Cumulative since boot/interface up |
+| **TX Packets** | Total packets transmitted | Cumulative since boot/interface up |
+| **RX Err** | Receive errors | Cumulative total (not recent) |
+| **TX Err** | Transmit errors | Cumulative total (not recent) |
+| **RX Drop** | Dropped incoming packets | Cumulative total (not recent) |
+| **TX Drop** | Dropped outgoing packets | Cumulative total (not recent) |
+| **Collisions** | Network collisions | Platform-dependent availability |
+
+**Important**: Error and drop counters are **cumulative totals** since the system booted or the interface came up, not recent activity. These help identify long-term interface reliability but won't show immediate issues.
+
+### Platform-Specific Behavior
+
+**All Platforms:**
+- All counters (bytes, packets, errors, drops) are cumulative from boot/interface up
+- Rates (bytes/sec) are calculated from snapshots taken every 2 seconds
+- Loopback interface is included for monitoring local traffic
+
+**Windows:**
+- Filters out virtual/filter adapters to show only physical interfaces:
+ - Excludes: `-Npcap`, `-WFP`, `-QoS`, `-Native`, `-Virtual`, `-Packet` variants
+ - Excludes: `Lightweight Filter`, `MAC Layer` interfaces
+ - Excludes: Disconnected "Local Area Connection" adapters
+- Uses LUID-based deduplication to prevent duplicate interface entries
+- Collisions: Always 0 (not available on modern Windows interfaces)
+
+**macOS:**
+- Includes data validation to detect corrupt counters on virtual interfaces
+- TX Drops: Always 0 (limited availability on macOS)
+- Sanitizes error/drop counters if values appear corrupted (>2^31 or errors>packets)
+
+**FreeBSD:**
+- TX Drops: Always 0 (not typically available on FreeBSD)
+- Uses BSD getifaddrs API with AF_LINK filtering
+
+**Linux:**
+- Reads statistics from `/sys/class/net/{interface}/statistics`
+- All counters typically available and reliable
+
+### Interpreting the Statistics
+
+**Healthy Interface:**
+```
+Ethernet: 2.40 KB/s ↓ / 1.96 KB/s ↑
+ Errors (Total): 0 Drops (Total): 0
+```
+Zero or very low error/drop counts indicate a reliable network connection.
+
+**Problematic Interface:**
+```
+WiFi: 150 KB/s ↓ / 45 KB/s ↑
+ Errors (Total): 1089 Drops (Total): 2178
+```
+High error/drop counts may indicate:
+- Signal interference (WiFi)
+- Cable issues (Ethernet)
+- Network congestion
+- Driver or hardware problems
+
+**Note**: Since error/drop counters are cumulative, evaluate them relative to total packets. A few errors out of millions of packets is normal; thousands of errors with low packet counts indicates problems.
+
+### Interface Filtering
+
+**Which Interfaces Are Shown:**
+- Interfaces must be operationally "up" OR have traffic statistics
+- Loopback interface is included (useful for monitoring local connections)
+- Virtual/filter adapters are excluded on Windows (they mirror physical interfaces)
+
+**Overview Tab Filtering:**
+- Windows: Shows all active interfaces (NPF device path detected automatically)
+- macOS/Linux: Shows interfaces with recent traffic (`rx_bytes > 0 || tx_bytes > 0 || rx_packets > 0 || tx_packets > 0`)
+- Special interfaces (`any`, `pktap`): Shows all interfaces with any activity
+
+**Interfaces Tab:**
+- Shows all detected interfaces that pass the platform-specific filters
+- Sorts to show the currently captured interface first (highlighted)
+- Other interfaces appear in alphabetical order
+
+### Use Cases
+
+**Bandwidth Monitoring:**
+Monitor real-time bandwidth usage across all network interfaces to identify:
+- Which interface is carrying the most traffic
+- Bandwidth distribution across WiFi vs Ethernet
+- Local traffic volume (loopback interface)
+
+**Reliability Analysis:**
+Check cumulative error and drop counters to:
+- Identify unreliable network interfaces
+- Detect hardware or driver issues
+- Compare interface quality over time
+
+**Multi-Interface Systems:**
+On systems with multiple network interfaces:
+- Compare performance across interfaces
+- Monitor VPN tunnel statistics
+- Track interface failover behavior
+
+## Connection Lifecycle & Visual Indicators
+
+RustNet uses intelligent timeout management to automatically clean up inactive connections while providing visual warnings before removal.
+
+### Visual Staleness Indicators
+
+Connections change color based on how close they are to being cleaned up:
+
+| Color | Meaning | Staleness |
+|-------|---------|-----------|
+| **White** (default) | Active connection | < 75% of timeout |
+| **Yellow** | Stale - approaching timeout | 75-90% of timeout |
+| **Red** | Critical - will be removed soon | > 90% of timeout |
+
+**Example**: An HTTP connection with a 10-minute timeout will:
+- Stay **white** for the first 7.5 minutes
+- Turn **yellow** from 7.5 to 9 minutes (warning)
+- Turn **red** after 9 minutes (critical)
+- Be removed at 10 minutes
+
+This gives you advance warning when a connection is about to disappear from the list.
+
+### Smart Protocol-Aware Timeouts
+
+RustNet adjusts connection timeouts based on the protocol and detected application:
+
+#### TCP Connections
+- **HTTP/HTTPS** (detected via DPI): **10 minutes** - supports HTTP keep-alive
+- **SSH** (detected via DPI): **30 minutes** - accommodates long interactive sessions
+- **Active established** (< 1 min idle): **10 minutes**
+- **Idle established** (> 1 min idle): **5 minutes**
+- **TIME_WAIT**: 30 seconds - standard TCP timeout
+- **CLOSED**: 5 seconds - rapid cleanup
+- **SYN_SENT, FIN_WAIT, etc.**: 30-60 seconds
+
+#### UDP Connections
+- **SSH over UDP**: **30 minutes** - long-lived sessions
+- **DNS**: **30 seconds** - short-lived queries
+- **Regular UDP**: **60 seconds** - standard timeout
+
+#### QUIC Connections (Detected State)
+- **Connected**: **3 minutes** default (or uses idle timeout from transport parameters if available)
+- **With CONNECTION_CLOSE frame**: 1-10 seconds (based on close type)
+- **Initial/Handshaking**: 60 seconds - allow connection establishment
+- **Draining**: 10 seconds - RFC 9000 draining period
+
+### Activity-Based Adjustment
+
+Connections showing recent packet activity get longer timeouts:
+- **Last packet < 60 seconds ago**: Uses "active" timeout (longer)
+- **Last packet > 60 seconds ago**: Uses "idle" timeout (shorter)
+
+This ensures active connections stay visible while idle connections are cleaned up more quickly.
+
+### Why Connections Disappear
+
+A connection is removed when:
+1. **No packets received** for the duration of its timeout period
+2. The connection enters a **closed state** (TCP CLOSED, QUIC CLOSED)
+3. **Explicit close frames** detected (QUIC CONNECTION_CLOSE)
+
+**Note**: Rate indicators (bandwidth display) show *decaying* traffic based on recent activity. A connection may show declining bandwidth (yellow bars) but remain in the list until it exceeds its idle timeout. This is intentional - the visual decay gives you time to see the connection winding down before it's removed.
+
+### Historic Connections
+
+By default, connections disappear from the list once they time out or close. Press `t` to toggle **historic connections** mode, which keeps closed connections visible alongside active ones.
+
+**How it works:**
+
+When a connection is cleaned up, it is archived into a historic connections pool (up to 5,000 entries; oldest are evicted first). Pressing `t` toggles their visibility:
+
+- **Active connections** display normally with standard color indicators
+- **Historic connections** appear in **dim gray** to clearly distinguish them from active connections
+- The table title changes to **"Active + Historic Connections"** when historic mode is on
+
+**Details view:**
+
+Selecting a historic connection and pressing `Enter` shows the usual connection details, plus a **Status** field displaying how long ago the connection was closed (e.g., "Closed (5m ago)").
+
+**Stats panel:**
+
+When historic connections are present, the stats panel shows a separate **"Historic: N"** count below the total active connections.
+
+**Grouped view:**
+
+In process grouping mode (`a`), group headers show the historic connection count separately from the active count when historic mode is enabled.
+
+**Graph tab:**
+
+The graph tab always shows only active connections, even when historic mode is on.
+
+**Resetting:**
+
+- Press `r` to reset all view settings, which also hides historic connections
+- Press `x` twice to clear all connections, which also clears the historic pool
+
+## Logging
+
+Logging is **disabled by default**. When enabled with the `--log-level` option, RustNet creates timestamped log files in the `logs/` directory. Each session generates a new log file with the format `rustnet_YYYY-MM-DD_HH-MM-SS.log`.
+
+### Log File Contents
+
+Log files contain:
+- Application startup and shutdown events
+- Network interface information
+- Packet capture statistics
+- Connection state changes
+- Error diagnostics
+- DPI detection results (at debug/trace levels)
+- Performance metrics (at trace level)
+
+### Enabling Logging
+
+Use the `--log-level` option to enable logging:
+
+```bash
+# Info-level logging (recommended for general use)
+sudo rustnet --log-level info
+
+# Debug-level logging (detailed troubleshooting)
+sudo rustnet --log-level debug
+
+# Trace-level logging (very verbose, includes packet-level details)
+sudo rustnet --log-level trace
+
+# Error-only logging (minimal logging)
+sudo rustnet --log-level error
+```
+
+### Log Levels Explained
+
+| Level | What Gets Logged | Use Case |
+|-------|------------------|----------|
+| `error` | Only errors and critical issues | Production monitoring |
+| `warn` | Warnings and errors | Normal operation with warnings |
+| `info` | General information, startup/shutdown | Standard debugging |
+| `debug` | Detailed debugging information | Troubleshooting issues |
+| `trace` | Packet-level details, very verbose | Deep debugging |
+
+### Managing Log Files
+
+**Log cleanup script:**
+
+The `scripts/clear_old_logs.sh` script is provided for log cleanup:
+
+```bash
+# Remove logs older than 7 days
+./scripts/clear_old_logs.sh
+
+# Customize retention period by editing the script
+```
+
+**Manual cleanup:**
+
+```bash
+# Remove all logs
+rm -rf logs/
+
+# Remove logs older than 7 days (Linux/macOS)
+find logs/ -name "rustnet_*.log" -mtime +7 -delete
+
+# View log file size
+du -sh logs/
+```
+
+### Log File Privacy
+
+⚠️ **Warning**: Log files may contain sensitive information:
+- IP addresses and ports
+- Hostnames and SNI data (HTTPS)
+- DNS queries and responses
+- Process names and PIDs
+- Packet contents (at trace level)
+
+**Best practices:**
+- Only enable logging when needed for debugging
+- Secure log directory permissions: `chmod 700 logs/`
+- Review logs for sensitive data before sharing
+- Implement log rotation and retention policies
+- Delete logs when no longer needed
+
+### Troubleshooting with Logs
+
+When reporting issues:
+1. Enable debug logging: `rustnet --log-level debug`
+2. Reproduce the issue
+3. Find the latest log file in `logs/`
+4. Review for errors or unexpected behavior
+5. Redact sensitive information before sharing
+
+For performance issues, trace-level logging provides the most detail but generates large log files quickly.
+
+### JSON Logging
+
+The `--json-log` option enables structured JSON logging of connection events to a file. Each line is a separate JSON object (JSONL format).
+
+```bash
+# Enable JSON logging
+sudo rustnet --json-log /tmp/connections.json
+
+# Combine with other options
+sudo rustnet -i eth0 --json-log ~/network-events.json
+```
+
+**Event types:**
+- `new_connection` - Logged when a new connection is first detected
+- `connection_closed` - Logged when a connection is cleaned up after becoming inactive
+
+**JSON fields:**
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `timestamp` | string | RFC3339 UTC timestamp |
+| `event` | string | Event type (`new_connection` or `connection_closed`) |
+| `protocol` | string | Protocol (TCP, UDP, etc.) |
+| `source_ip` | string | Local IP address |
+| `source_port` | number | Local port number |
+| `destination_ip` | string | Remote IP address |
+| `destination_port` | number | Remote port number |
+| `pid` | number | Process ID (if available) |
+| `process_name` | string | Process name (if available) |
+| `service_name` | string | Service name from port lookup (if available) |
+| `direction` | string | Connection direction (`outgoing` or `incoming`), TCP only when handshake observed |
+| `dpi_protocol` | string | Detected application protocol (if DPI enabled) |
+| `dpi_domain` | string | Extracted domain/hostname (if available) |
+| `bytes_sent` | number | Total bytes sent (connection_closed only) |
+| `bytes_received` | number | Total bytes received (connection_closed only) |
+| `duration_secs` | number | Connection duration in seconds (connection_closed only) |
+
+**Example output:**
+
+```json
+{"timestamp":"2025-01-15T10:30:00Z","event":"new_connection","protocol":"TCP","source_ip":"192.168.1.100","source_port":54321,"destination_ip":"93.184.216.34","destination_port":443,"pid":1234,"process_name":"curl","service_name":"https","direction":"outgoing","dpi_protocol":"HTTPS","dpi_domain":"example.com"}
+{"timestamp":"2025-01-15T10:30:05Z","event":"connection_closed","protocol":"TCP","source_ip":"192.168.1.100","source_port":54321,"destination_ip":"93.184.216.34","destination_port":443,"pid":1234,"process_name":"curl","service_name":"https","direction":"outgoing","bytes_sent":1024,"bytes_received":4096,"duration_secs":5}
+```
+
+**Processing JSON logs:**
+
+```bash
+# Pretty-print latest events
+tail -f /tmp/connections.json | jq .
+
+# Filter by process
+cat /tmp/connections.json | jq 'select(.process_name == "firefox")'
+
+# Count connections by destination
+cat /tmp/connections.json | jq -s 'group_by(.destination_ip) | map({ip: .[0].destination_ip, count: length})'
+```
+
+### PCAP Export
+
+The `--pcap-export` option captures raw packets to a standard PCAP file for analysis in Wireshark, tcpdump, or other tools.
+
+```bash
+# Export all captured packets
+sudo rustnet -i eth0 --pcap-export capture.pcap
+
+# Combine with BPF filter
+sudo rustnet -i eth0 --bpf-filter "tcp port 443" --pcap-export https.pcap
+```
+
+**Output files:**
+
+| File | Description |
+|------|-------------|
+| `capture.pcap` | Raw packet data in standard PCAP format |
+| `capture.pcap.connections.jsonl` | Streaming connection metadata with process info |
+
+**Sidecar JSONL format** (one JSON object per line, written as connections close):
+
+```json
+{"timestamp":"2026-01-17T10:30:00Z","protocol":"TCP","local_addr":"192.168.1.100:54321","remote_addr":"142.250.80.46:443","pid":1234,"process_name":"firefox","first_seen":"...","last_seen":"...","bytes_sent":1024,"bytes_received":8192,"state":"ESTABLISHED"}
+```
+
+| Field | Description |
+|-------|-------------|
+| `timestamp` | When the connection record was written |
+| `protocol` | TCP, UDP, ICMP, etc. |
+| `local_addr` / `remote_addr` | Connection endpoints |
+| `pid` / `process_name` | Process info (if identified) |
+| `first_seen` / `last_seen` | Connection timestamps |
+| `bytes_sent` / `bytes_received` | Traffic totals |
+| `state` | Final connection state |
+
+#### Enriching PCAP with Process Information
+
+Standard PCAP files don't include process information. Use the included `scripts/pcap_enrich.py` script to correlate packets with processes:
+
+```bash
+# Install scapy (required)
+pip install scapy
+
+# Show packets with process info
+python scripts/pcap_enrich.py capture.pcap
+
+# Output as TSV for further processing
+python scripts/pcap_enrich.py capture.pcap --format tsv > report.tsv
+
+# Create annotated PCAPNG with process comments (requires Wireshark's editcap)
+python scripts/pcap_enrich.py capture.pcap -o annotated.pcapng
+```
+
+The annotated PCAPNG embeds process information as packet comments, visible in Wireshark's packet details.
+
+#### Native Annotated PCAPNG Export
+
+The `--pcapng-export` option writes a PCAPNG file directly with RustNet packet comments. This avoids the Python enrichment step when you want to open the capture in Wireshark immediately:
+
+```bash
+sudo rustnet -i eth0 --pcapng-export capture.pcapng
+```
+
+Packet comments are live best-effort annotations. RustNet waits briefly for process and GeoIP enrichment, then writes the packet even if attribution is still unavailable; packets may still have DPI/SNI, direction, or GeoIP comments without `process=`/`pid=` fields. Under heavy load, packets dropped before the processor stage or dropped by the bounded PCAPNG export queue will not appear in the PCAPNG, so a simultaneous `--pcap-export` file can contain packets missing from the PCAPNG. Packet blocks may be written out of capture order, but they keep their true capture timestamps and Wireshark can sort/display them by time.
+
+Use `--pcap-export` plus `capture.pcap.connections.jsonl` when cleanup-time metadata completeness matters more than a single annotated file.
+
+**Manual correlation:**
+
+```bash
+# View packets
+wireshark capture.pcap
+
+# View process mappings
+cat capture.pcap.connections.jsonl | jq -r '[.protocol, .local_addr, .remote_addr, .pid, .process_name] | @tsv'
+
+# Filter in Wireshark by connection tuple
+# ip.addr == 142.250.80.46 && tcp.port == 443
+```
diff --git a/USAGE.zh-CN.md b/USAGE.zh-CN.md
new file mode 100644
index 0000000..5c3633f
--- /dev/null
+++ b/USAGE.zh-CN.md
@@ -0,0 +1,1147 @@
+English | 简体中文
+
+# 使用指南
+
+本文档涵盖 RustNet 的详细使用说明,包括命令行选项、键盘控制、过滤、排序以及理解连接生命周期。
+
+## 目录
+
+- [运行 RustNet](#running-rustnet)
+- [命令行选项](#command-line-options)
+- [键盘控制](#keyboard-controls)
+- [鼠标控制](#mouse-controls)
+- [过滤](#filtering)
+- [排序](#sorting)
+- [进程分组](#process-grouping)
+- [网络统计面板](#network-statistics-panel)
+- [接口统计](#interface-statistics)
+- [连接生命周期与视觉指示器](#connection-lifecycle--visual-indicators)
+- [日志](#logging)
+
+## 运行 RustNet
+
+在大多数系统上,数据包捕获需要提升的特权。详细的权限配置说明参见 [INSTALL.zh-CN.md](INSTALL.zh-CN.md)。
+
+**快速开始:**
+
+```bash
+# 使用 sudo 运行(适用于所有平台)
+sudo rustnet
+
+# 或授予 Linux capabilities 以无需 sudo 运行(详情参见 INSTALL.md)
+# Linux 示例(现代内核 5.8+):
+sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' /path/to/rustnet
+rustnet
+```
+
+**基本使用示例:**
+
+```bash
+# 使用默认设置运行
+# macOS:使用 PKTAP 获取进程元数据
+# Linux/其他:自动检测活动接口
+rustnet
+
+# 指定网络接口
+rustnet -i eth0
+rustnet --interface wlan0
+
+# Linux:同时监控所有接口
+rustnet -i any
+
+# 过滤掉 localhost 连接(默认已过滤)
+rustnet --no-localhost
+
+# 显示 localhost 连接(覆盖默认过滤)
+rustnet --show-localhost
+
+# 设置 UI 刷新间隔(毫秒)
+rustnet -r 500
+rustnet --refresh-interval 2000
+
+# 禁用深度包检测
+rustnet --no-dpi
+
+# 恢复原始全彩调色板
+rustnet --theme classic
+
+# 禁用反向 DNS 查找(默认启用)
+rustnet --no-resolve-dns
+
+# 使用指定级别启用日志(选项:error、warn、info、debug、trace)
+rustnet -l debug
+rustnet --log-level info
+
+# 查看帮助和所有选项
+rustnet --help
+```
+
+## 命令行选项
+
+```
+Usage: rustnet [OPTIONS]
+
+Options:
+ -i, --interface 要监控的网络接口
+ --no-localhost 过滤掉 localhost 连接(默认:已过滤)
+ --show-localhost 显示 localhost 连接(覆盖默认过滤)
+ -r, --refresh-interval UI 刷新间隔,单位为毫秒 [默认:1000]
+ --no-dpi 禁用深度包检测
+ --no-resolve-dns 禁用反向 DNS 查找(默认启用)
+ --show-ptr-lookups 显示 PTR 查找连接(默认隐藏)
+ -l, --log-level 设置日志级别(如果未提供,则不启用日志)
+ --json-log 将连接事件以 JSON 格式记录到指定文件
+ --pcap-export 将捕获的数据包导出到 PCAP 文件供 Wireshark 分析
+ --pcapng-export 将捕获的数据包导出为带注释的 PCAPNG 文件供 Wireshark 分析
+ --no-color 禁用 UI 中的所有颜色(同时尊重 NO_COLOR 环境变量)
+ --theme 颜色主题预设:"muted"(单一强调色,颜色仅用于信号,默认)
+ 或 "classic"(原始全彩调色板)
+ --geoip-country GeoLite2-Country.mmdb 的路径(未指定时自动发现)
+ --geoip-asn GeoLite2-ASN.mmdb 的路径(未指定时自动发现)
+ --geoip-city GeoLite2-City.mmdb 的路径(未指定时自动发现)
+ --no-geoip 完全禁用 GeoIP 查询
+ -f, --bpf-filter 用于数据包捕获的 BPF 过滤器表达式
+ --no-sandbox 禁用 Landlock 沙箱(仅限 Linux)
+ --sandbox-strict 要求完整沙箱强制执行,否则退出(仅限 Linux)
+ --no-uid-drop 初始化后保持 root 运行,不降权到
+ SUDO_UID/SUDO_GID(或 nobody)(仅限 Linux、macOS 和 FreeBSD)
+ -h, --help 打印帮助
+ -V, --version 打印版本
+```
+
+### 选项详情
+
+#### `-i, --interface `
+
+指定要监控的网络接口。
+
+**默认行为(无 `-i` 标志):**
+- **macOS**:自动使用 PKTAP 获取增强型进程元数据(需要 sudo)
+- **Linux/其他**:自动检测第一个可用的非回环接口
+
+**示例:**
+```bash
+# 默认:自动检测接口(macOS 上使用 PKTAP)
+rustnet
+
+# Linux:使用特殊的 "any" 伪接口监控所有接口
+rustnet -i any
+
+# 监控特定接口
+rustnet -i eth0 # 监控以太网接口
+rustnet -i wlan0 # 监控 WiFi 接口
+rustnet -i en0 # 监控 macOS 主接口
+
+# 监控 VPN 和隧道接口(TUN/TAP 支持)
+rustnet -i utun0 # macOS VPN 隧道(TUN,Layer 3)
+rustnet -i tun0 # Linux/BSD VPN 隧道(TUN,Layer 3)
+rustnet -i tap0 # TAP 接口(Layer 2,包含 Ethernet)
+```
+
+**TUN/TAP 接口支持:**
+
+RustNet 完全支持监控 VPN 和虚拟网络接口:
+
+- **TUN 接口**(Layer 3):直接承载 IP 数据包,不含 Ethernet 头部
+ - VPN 常见:WireGuard、OpenVPN(tun 模式)、Tailscale
+ - 示例:`utun0-utun9`(macOS)、`tun0-tun9`(Linux/BSD)
+
+- **TAP 接口**(Layer 2):包含完整的 Ethernet 帧
+ - 用于:OpenVPN(tap 模式)、QEMU/KVM 虚拟网络、Docker
+ - 示例:`tap0-tap9`(Linux/BSD)
+
+RustNet 自动检测 TUN/TAP 接口并相应调整数据包解析。接口类型显示在 UI 状态区域。
+
+**平台特定说明:**
+- **macOS**:不使用 `-i` 时自动使用 PKTAP 以获得更好的进程检测。使用 `-i ` 来监控特定接口
+- **Linux**:使用 `-i any` 同时在所有接口上捕获(其他平台不可用)
+- **TUN/TAP**:所有平台均完全支持 —— RustNet 通过名称检测接口类型并调整解析
+- **所有平台**:如果指定了不存在的接口,会显示错误并列出可用接口
+
+**查找你的接口:**
+- Linux:`ip link show` 或 `ifconfig`
+- macOS:`ifconfig` 或 `networksetup -listallhardwareports`
+- Windows:`ipconfig /all`
+
+#### `--no-localhost` / `--show-localhost`
+
+控制是否显示 localhost(127.0.0.1/::1)连接。
+
+- **默认**:过滤掉 localhost 连接(`--no-localhost`)
+- **覆盖**:使用 `--show-localhost` 查看 localhost 连接
+
+这对于减少连接列表中的噪音很有用,因为大多数用户不需要监控本地 IPC 连接。
+
+#### `-r, --refresh-interval `
+
+以毫秒为单位设置 UI 刷新率。较低的值提供更灵敏的更新,但会增加 CPU 使用率。
+
+**建议:**
+- **默认(1000ms)**:大多数用户的良好平衡
+- **高流量网络(2000ms)**:在繁忙网络上降低 CPU 使用率
+- **实时监控(500ms)**:更灵敏的更新,适合快速分析
+- **低端系统(2000-3000ms)**:降低资源受限机器上的负载
+
+#### `--no-dpi`
+
+禁用深度包检测(DPI)。这在高流量网络上可降低 20-40% 的 CPU 使用率,但会禁用:
+- HTTP 主机检测
+- HTTPS/TLS SNI 提取
+- DNS 查询/响应检测
+- SSH 版本识别
+- QUIC 协议检测
+
+适用于性能受限的环境或不需要应用层详细信息时。
+
+#### `--no-resolve-dns` / `--show-ptr-lookups`
+
+反向 DNS 查找**默认启用**:IP 地址在后台解析为主机名,并显示在连接列表中(按 `d` 键切换)和详情标签页中。
+
+- **`--no-resolve-dns`**:完全禁用反向 DNS 解析。连接列表仅显示 IP 地址,不发起 PTR 查询。
+- **`--show-ptr-lookups`**:PTR 查找流量默认隐藏。使用此标志显示解析器生成的 DNS PTR 查询。
+
+**注意**:解析后的主机名也包含在 JSON 日志中(`destination_hostname`、`source_hostname` 字段)。
+
+#### `--theme `
+
+选择颜色主题预设:
+
+- **`muted`**(默认):克制的调色板,只有一个青色强调色。地址保留柔和的颜色
+ (远程 = 蓝色,本地 = 青色);其他颜色仅用于*信号* —— 连接状态变化、
+ 过期状态(黄色/红色行)以及实时带宽。
+- **`classic`**:早期版本的原始全彩调色板,每列一种颜色。
+
+```bash
+# 恢复原始全彩外观
+rustnet --theme classic
+```
+
+相关:`--no-color` 完全禁用所有颜色(同时尊重 `NO_COLOR` 环境变量)。
+
+#### `-f, --bpf-filter `
+
+应用 BPF(Berkeley Packet Filter)表达式在捕获时过滤数据包。这比应用层过滤更高效,因为数据包在到达 RustNet 之前在内核中被过滤。
+
+**常见过滤器表达式:**
+
+```bash
+# 按端口过滤(匹配源 OR 目的)
+rustnet --bpf-filter "port 443"
+rustnet --bpf-filter "port 80 or port 8080"
+
+# 按目的端口过滤
+rustnet --bpf-filter "dst port 443"
+rustnet --bpf-filter "tcp dst port 80"
+
+# 按源端口过滤
+rustnet --bpf-filter "src port 443"
+
+# 按主机过滤
+rustnet --bpf-filter "host 192.168.1.1"
+rustnet --bpf-filter "net 10.0.0.0/8"
+
+# 按协议过滤
+rustnet --bpf-filter "tcp"
+rustnet --bpf-filter "udp port 53"
+
+# 组合过滤器
+rustnet --bpf-filter "tcp port 443 and host github.com"
+
+# 排除流量
+rustnet --bpf-filter "not port 22"
+```
+
+**注意:**
+- BPF 过滤器语法遵循 pcap-filter(7) 格式。无效过滤器会导致 RustNet 退出并报错。使用 `man pcap-filter` 查看完整语法文档。
+- **macOS 限制:** BPF 过滤器与 PKTAP(linktype 149)不兼容。在 macOS 上指定 BPF 过滤器时,RustNet 自动回退到常规接口捕获。这意味着进程识别使用 `lsof` 而非 PKTAP 的直接进程元数据,对于短寿命连接可能略不准确。
+
+#### `-l, --log-level `
+
+使用指定级别启用日志。**默认禁用**日志。
+
+**可用级别:**
+- `error` —— 仅错误(最小日志)
+- `warn` —— 警告和错误
+- `info` —— 一般信息(建议用于常规调试)
+- `debug` —— 详细的调试信息
+- `trace` —— 非常详细的输出(包含数据包级详情)
+
+日志文件创建于 `logs/` 目录,带时间戳:`rustnet_YYYY-MM-DD_HH-MM-SS.log`
+
+## 键盘控制
+
+### 导航
+
+- `↑` 或 `k` —— 在连接列表中向上导航
+- `↓` 或 `j` —— 在连接列表中向下导航
+- `g` —— 跳转到第一个连接(vim 风格)
+- `G`(Shift+g)—— 跳转到最后一个连接(vim 风格)
+- `PageUp` 或 `Ctrl+B` —— 向上翻一页
+- `PageDown` 或 `Ctrl+F` —— 向下翻一页
+
+### 视图与标签页
+
+- `Tab` 或 `]` —— 下一个标签页
+- `Shift+Tab` 或 `[` —— 上一个标签页
+- `1` / `2` / `3` / `4` / `5` —— 直接跳转到 概览 / 详情 / 接口 / 图表 / 帮助
+- `Enter` —— 查看所选连接的详细信息
+- `Esc` —— 返回上一个视图或清除活动过滤器
+- `h` —— 切换帮助屏幕
+
+### 操作
+
+- `c` —— 将远程地址复制到剪贴板
+- `p` —— 在服务名和端口号之间切换
+- `d` —— 在主机名和 IP 地址之间切换(由 `--no-resolve-dns` 禁用)
+- `/` —— 进入过滤模式(vim 风格搜索,实时结果)
+- `x` —— 清除所有连接并重置统计(按两次确认)
+- `t` —— 切换历史(已关闭)连接的显示
+- `i` —— 切换 System 信息侧边栏
+- `r` —— 将视图重置为默认值(清除分组、排序、过滤和历史)
+
+### 进程分组
+
+- `a` —— 切换进程分组模式(按进程聚合连接)
+- `Space` —— 展开/折叠所选进程分组
+- `←` 或 `h` —— 折叠所选分组
+- `→` 或 `l` —— 展开所选分组
+
+### 排序
+
+- `s` —— 在可排序列之间循环切换(从左到右顺序)
+- `S`(Shift+s)—— 切换排序方向(升序/降序)
+
+### 退出
+
+- `q` —— 退出应用(按两次确认)
+- `Ctrl+C` —— 立即退出
+
+## 鼠标控制
+
+RustNet 具有完整的鼠标支持。鼠标捕获自动启用 —— 以下描述的所有交互均可开箱即用。
+
+### 概览标签页
+
+| 操作 | 效果 |
+|------|------|
+| **单击** 连接行 | 选择该连接 |
+| **双击** 连接行 | 打开该连接的详情标签页 |
+| **滚轮** 在连接列表上 | 在连接中上下导航 |
+| **单击** 标签页名称 | 切换到该标签页 |
+
+### 分组视图(按 `a` 启用)
+
+| 操作 | 效果 |
+|------|------|
+| **单击** 分组头部(`▸`/`▾`) | 选择该分组 |
+| **双击** 分组头部 | 展开或折叠进程分组 |
+| **单击** 展开分组内的连接 | 选择该连接 |
+| **双击** 展开分组内的连接 | 打开该连接的详情标签页 |
+| **滚轮** | 在分组和连接中导航 |
+
+### 详情标签页
+
+| 操作 | 效果 |
+|------|------|
+| **单击** 任意字段行 | 将字段值复制到系统剪贴板 |
+
+单击字段仅复制值(不包括标签)。例如,单击 "Remote Address: 142.250.80.46:443" 行会将 `142.250.80.46:443` 复制到剪贴板。状态栏会显示 3 秒的确认消息。
+
+"Connection Information" 和 "Traffic Statistics" 两个面板都支持点击复制。
+
+## 过滤
+
+按 `/` 进入过滤模式。输入以实时过滤连接,输入时可用方向键导航。
+
+### 基本搜索
+
+直接输入任意文本即可在所有连接字段中搜索:
+
+```
+/google # 查找包含 "google" 的连接
+/firefox # 查找 Firefox 连接
+/192.168 # 查找 IP 以 192.168 开头的连接
+```
+
+### 关键字过滤器
+
+使用关键字过滤器进行定向搜索:
+
+| 关键字 | 别名 | 描述 | 示例 |
+|---------|---------|-------------|---------|
+| `port:` | | 精确端口匹配;使用 `/pattern/` 进行正则 | `port:22` 仅匹配 22;`port:/22/` 匹配 22、220、5522 |
+| `sport:` | `srcport:`、`source-port:` | 源端口(精确或正则) | `sport:80` 仅匹配源端口 80 |
+| `dport:` | `dstport:`、`dest-port:`、`destination-port:` | 目的端口(精确或正则) | `dport:443` 仅匹配目的端口 443 |
+| `src:` | `source:` | 源 IP/主机名 | `src:192.168` 匹配 192.168.x.x |
+| `dst:` | `dest:`、`destination:` | 目的地址 | `dst:github.com` 匹配 github.com |
+| `process:` | `proc:` | 进程名 | `process:ssh` 匹配 ssh、sshd |
+| `sni:` | `host:`、`hostname:` | SNI 主机名(HTTPS) | `sni:api` 匹配 api.example.com |
+| `service:` | `svc:` | 服务名 | `service:https` 匹配 HTTPS 服务 |
+| `app:` | `application:` | 检测到的应用协议 | `app:ssh` 匹配 SSH 连接 |
+| `state:` | | 协议状态 | `state:established` 匹配已建立的连接 |
+| `proto:` | `protocol:` | 协议类型 | `proto:tcp` 匹配 TCP 连接 |
+
+### 状态过滤
+
+按当前协议状态过滤连接(不区分大小写):
+
+⚠️ **注意:** 状态追踪的准确性因协议而异。TCP 状态最可靠,而 UDP、QUIC 和其他协议的状态来自数据包检测,可能并不总是反映真实的连接状态。
+
+**示例:**
+```
+state:syn_recv # 显示半开连接(可用于检测 SYN flood)
+state:established # 仅显示已建立的连接
+state:fin_wait # 显示处于关闭状态的连接
+state:quic_handshake # 显示握手期间的 QUIC 连接
+state:dns_query # 显示 DNS 查询连接
+state:udp_active # 显示活跃的 UDP 连接
+```
+
+**可用状态:**
+
+| 协议 | 状态 |
+|----------|--------|
+| **TCP** | `SYN_SENT`、`SYN_RECV`、`ESTABLISHED`、`FIN_WAIT1`、`FIN_WAIT2`、`TIME_WAIT`、`CLOSE_WAIT`、`LAST_ACK`、`CLOSING`、`CLOSED` |
+| **QUIC** | `QUIC_INITIAL`、`QUIC_HANDSHAKE`、`QUIC_CONNECTED`、`QUIC_DRAINING`、`QUIC_CLOSED` ⚠️ *注意:可能不完整,因为握手已加密* |
+| **UDP** | `UDP_ACTIVE`、`UDP_IDLE`、`UDP_STALE` |
+| **DNS** | `DNS_QUERY`、`DNS_RESPONSE` |
+| **SSH** | `BANNER`、`KEYEXCHANGE`、`AUTHENTICATION`、`ESTABLISHED` ⚠️ *注意:基于数据包检测* |
+| **Other** | `ECHO_REQUEST`、`ECHO_REPLY`、`ARP_REQUEST`、`ARP_REPLY` |
+
+### 正则过滤器
+
+将任何过滤值包裹在 `/pattern/` 中以使用正则表达式(不区分大小写)。正则使用 `regex-lite` crate 支持的标准语法。
+
+```
+/192\.168\.[0-9]+/ # 在所有字段中通用正则
+port:/22/ # 端口包含 "22"(22、220、2200、5522 …)
+sni:/.*github\..*/ # SNI 匹配 github.com、api.github.com 等
+process:/chrom(e|ium)/ # Chrome 或 Chromium
+```
+
+> **端口匹配**:`port:443` 是**精确**匹配(仅端口 443)。如需子串/正则行为,请使用 `port:/443/`。
+
+### 组合过滤器
+
+用空格组合多个过滤器(隐式 AND):
+
+```
+sport:80 process:nginx # Nginx 从 80 端口发出的连接
+dport:443 sni:google.com # 到 Google 的 HTTPS 连接
+sport:443 state:syn_recv # 到 443 端口的半开连接(SYN flood 检测)
+proto:tcp state:established # 所有已建立的 TCP 连接
+process:firefox state:quic_connected # Firefox 的活跃 QUIC 连接
+dport:22 app:openssh # 使用 OpenSSH 的 SSH 连接
+state:established app:ssh # 已建立的 SSH 连接
+```
+
+### 清除过滤器
+
+按 `Esc` 清除活动过滤器并返回完整连接列表。
+
+## 排序
+
+RustNet 提供强大的表格排序功能来帮助你分析网络连接。按 `s` 按从左到右的视觉顺序在可排序列之间循环切换,按 `S`(Shift+s)在升序和降序之间切换。
+
+### 快速开始
+
+**找出带宽大户(上下行合计流量):**
+```
+反复按 's' 直到看到:Bandwidth Total ↓
+总带宽最高的连接显示在顶部
+```
+
+**按进程名排序:**
+```
+反复按 's' 直到看到:Process ↑
+连接按进程名字母顺序排序
+```
+
+### 可排序列
+
+按 `s` 按从左到右顺序在列之间循环切换:
+
+| 列 | 默认方向 | 描述 |
+|--------|-------------------|-------------|
+| **Process** | ↑ 升序 | 按进程名字母顺序排序 |
+| **Remote Address** | ↑ 升序 | 按远程 IP:port 排序 |
+| **Local Address** | ↑ 升序 | 按本地 IP:port 排序(适用于多接口系统) |
+| **Location** | ↑ 升序 | 按国家代码排序(需要 GeoIP 数据库) |
+| **Service** | ↑ 升序 | 按服务名或端口号排序 |
+| **Application** | ↑ 升序 | 按检测到的应用协议排序(HTTP、DNS 等),以 TCP/UDP 作为同序比较 |
+| **State** | ↑ 升序 | 按连接状态排序(ESTABLISHED 等) |
+| **Bandwidth (Rx/Tx)** | ↓ 降序 | 按**上下行合计**带宽排序(默认最高优先) |
+
+在窄终端下被隐藏的列仍留在循环中 —— 当前排序列始终显示在表格的区段标题中。
+
+### 排序指示器
+
+活动排序列通过以下方式高亮:
+- **青色**和**下划线**样式
+- 显示排序方向的**箭头符号**(↑ 或 ↓)
+- 显示当前排序状态的**表格标题**
+
+**视觉指示器:**
+```
+活动列头部以青色和下划线显示:
+Process │ Remote ↑ │ Local │ Service │ App │ ...
+ ^^^^^^^^
+ (青色、下划线、带箭头)
+
+区段标题显示当前排序:
+▎ Active Connections · 42 shown · sort Remote Addr ↑
+```
+
+### 排序行为
+
+**按 `s`(小写)—— 循环列:**
+- 移动到从左到右视觉顺序的下一列
+- **重置为该列的默认方向**
+- 带宽列默认降序(↓)以优先显示最高值
+- 文本列默认升序(↑)以按字母顺序排列
+
+**按 `S`(Shift+s)—— 切换方向:**
+- **保持在当前列**
+- 在升序(↑)和降序(↓)之间切换
+- 可用于反转排序顺序(例如找出带宽最小的用户)
+
+**多次按 `s` 返回默认:**
+- 循环浏览所有列后返回默认按时间排序(按连接创建时间)
+- 默认模式下不显示排序指示器
+
+### 过滤时排序
+
+排序与过滤无缝配合:
+1. **先过滤**:按 `/` 输入过滤条件
+2. **再排序**:按 `s` 对过滤结果排序
+3. **排序持续**:更改过滤器时保持排序顺序活动
+
+示例工作流:
+```
+1. 按 '/' 并输入 'firefox' 过滤 Firefox 连接
+2. 按 's' 直到看到 "Bandwidth Total ↓"
+3. 现在查看按总带宽(上下行合计)排序的 Firefox 连接
+```
+
+### 示例
+
+**找出哪个进程使用最多带宽:**
+```
+1. 按 's' 直到出现 "Bandwidth Total ↓"
+2. 顶部连接显示最高总带宽(上下行合计)
+3. 查看 "Process" 列以确定是哪个应用
+```
+
+**按远程目的地排序连接:**
+```
+1. 按 's' 直到出现 "Remote Address ↑"
+2. 连接按远程 IP 地址分组
+3. 如需,按 'S' 反转顺序
+```
+
+**找出空闲连接(最低带宽):**
+```
+1. 按 's' 循环到 "Bandwidth Total ↓"
+2. 按 'S' 切换到 "Bandwidth Total ↑"(升序)
+3. 总带宽最低的连接显示在最前面
+```
+
+**按应用协议排序:**
+```
+1. 按 's' 直到出现 "Application / Host ↑"
+2. 所有 HTTPS 连接分组在一起,DNS 查询分组在一起,等等
+3. 适合查找特定类型的所有连接
+```
+
+## 进程分组
+
+RustNet 可以按进程名分组连接,提供聚合视图,让你更容易看到哪些应用正在使用你的网络。
+
+### 启用进程分组
+
+按 `a` 切换进程分组模式。启用时:
+- 连接按进程名分组(按字母顺序排序)
+- 每个分组显示聚合统计
+- 分组可以展开/折叠以显示单个连接
+
+再次按 `a` 返回扁平(未分组)连接列表。
+
+### 分组视图显示
+
+启用分组时,连接列表显示进程分组:
+
+```
+▸ firefox (12) TCP:10 UDP:2 12.5K/1.2K
+▾ chrome (8) TCP:8 UDP:0 45.2K/5.1K
+ ├─ 4101 142.250.80.78:443 192.168.1.10:54321 ESTABLISHED 1.2K/0.3K
+ ├─ 4101 142.250.80.78:443 192.168.1.10:54322 ESTABLISHED 0.8K/0.1K
+ └─ 4102 8.8.8.8:53 192.168.1.10:54323 UDP_ACTIVE 0.2K/0.1K
+▸ systemd-resolved (3) TCP:0 UDP:3 0.2K/0.1K
+▸ (5) TCP:2 UDP:3 0.5K/0.2K
+```
+
+**分组头部格式:**
+- `▸` / `▾` —— 折叠/展开指示器
+- 进程名和连接数
+- 协议细分(TCP/UDP 计数)
+- 总带宽(rx/tx,位于 Bandwidth 列)
+
+**展开的连接:**
+- 树形前缀(`├─` / `└─`)加 PID(进程名由上方的分组头部承载)
+- 单个连接详情(协议、地址、状态、应用)
+
+### 展开和折叠分组
+
+| 按键 | 操作 |
+|-----|--------|
+| `Space` | 切换所选分组的展开/折叠 |
+| `→` 或 `l` | 展开所选分组 |
+| `←` 或 `h` | 折叠所选分组 |
+
+### 分组视图中的导航
+
+导航方式与扁平视图相同:
+- `↑`/`k` 和 `↓`/`j` 在可见行(分组和展开的连接)之间移动
+- `g` 跳转到第一行
+- `G` 跳转到最后一行
+- `Enter` 在连接上打开详情视图
+
+### 未知进程
+
+没有进程信息的连接被分组到单个 `` 分组中。这通常包括:
+- 在进程查找完成前就已关闭的短寿命连接
+- 某些平台上的系统级连接
+- 来自受限进程的连接
+
+### 分组时过滤
+
+过滤与分组无缝配合:
+1. 按 `/` 输入你的过滤条件
+2. 仅显示包含匹配连接的分组
+3. 展开分组以查看哪些连接匹配
+
+### 分组时排序
+
+启用分组时:
+- 分组按进程名字母顺序排序(A-Z)
+- 排序列指示器显示分组内连接的排序方式
+- 按 `s` 更改展开分组内连接的排序方式
+
+### 重置视图
+
+按 `r` 一次性重置所有视图设置:
+- 禁用进程分组
+- 清除任何活动过滤器
+- 将排序重置为默认(按时间顺序)
+
+## 网络统计面板
+
+网络统计面板显示在界面右侧,位于流量面板下方。它提供直接从数据包捕获分析中得出的实时 TCP 连接质量指标,使其在 Linux、macOS、Windows 和 FreeBSD 上跨平台一致。
+
+### 可用指标
+
+**TCP 重传**
+检测由于数据包丢失或超时而重新传输的 TCP 段。RustNet 通过分析 TCP 序列号来识别重传:当到达的数据包序列号低于预期时,表示原始数据包已丢失并正在重发。
+
+**乱序包**
+追踪到达顺序错误的入站 TCP 数据包,通常由网络拥塞或多条路由路径导致。这些数据包最终会到达,但顺序错误,需要接收方缓冲并重新排序。
+
+**快速重传**
+识别由收到三个重复确认(RFC 2581)触发的 TCP 快速重传事件。这种机制允许 TCP 比等待超时更快地从数据包丢失中恢复,从而提高连接性能。
+
+### 统计显示格式
+
+面板为每个指标显示**活动**和**总计**计数:
+
+```
+TCP Retransmits: 5 / 142 total
+Out-of-Order: 2 / 89 total
+Fast Retransmits: 1 / 23 total
+Active TCP Flows: 18
+```
+
+- **活动计数**(左侧数字):当前追踪连接的各事件总和。这个数字随着连接的建立和清理而上下波动。
+- **总计计数**(右侧数字):自 RustNet 启动以来的累积计数。这个数字只增不减,提供历史背景。
+- **Active TCP Flows**:具有分析数据的活跃 TCP 连接数。
+
+### 逐连接统计
+
+查看连接详情时(在连接上按 `Enter`),显示该特定连接的 TCP 分析:
+
+```
+TCP Retransmits: 3
+Out-of-Order: 1
+Fast Retransmits: 0
+```
+
+这些计数器独立追踪每个连接,允许你识别遇到数据包丢失或网络问题的有问题连接。
+
+### 使用场景
+
+**网络质量监控**
+重传或乱序包的突然增加表明网络拥塞、数据包丢失或路由问题。
+
+**连接故障排查**
+特定连接上的高重传计数可以识别:
+- 到某些目的地的不可靠网络路径
+- 带宽受限的链路
+- 故障的网络硬件或驱动
+
+**性能分析**
+快速重传频率表明 TCP 在不等待超时的情况下从数据包丢失中恢复的情况。
+
+### 技术说明
+
+- 统计源自 TCP 序列号分析,不需要数据包时间戳
+- 分析适用于出站和入站数据包
+- SYN 和 FIN 标志在序列号追踪中被正确计算(每个消耗 1 个序列号)
+- 仅 TCP 连接显示分析指标;UDP、ICMP 和其他协议没有这些指标
+
+## 接口统计
+
+RustNet 在所有支持的平台上(Linux、macOS、FreeBSD、Windows)提供实时网络接口统计。接口统计显示在两个位置:
+
+### 访问接口统计
+
+**概览标签页(主屏幕):**
+- 接口统计出现在右侧面板,位于网络统计下方
+- 显示最多 3 个活跃接口及当前速率
+- 显示:`InterfaceName: X KB/s ↓ / Y KB/s ↑`
+- 显示累计总数:`Errors (Total): N Drops (Total): M`
+
+**接口标签页(详细视图):**
+- 按 `i` 切换接口统计视图
+- 显示所有网络接口的详细表格
+- 显示每个接口的综合指标
+
+### 显示的统计
+
+| 指标 | 描述 | 说明 |
+|--------|-------------|-------|
+| **RX Rate** | 当前接收速率(字节/秒) | 根据近期活动计算 |
+| **TX Rate** | 当前发送速率(字节/秒) | 根据近期活动计算 |
+| **RX Packets** | 接收的总数据包数 | 自启动/接口上线以来累计 |
+| **TX Packets** | 发送的总数据包数 | 自启动/接口上线以来累计 |
+| **RX Err** | 接收错误 | 累计总数(非近期) |
+| **TX Err** | 发送错误 | 累计总数(非近期) |
+| **RX Drop** | 丢弃的入站数据包 | 累计总数(非近期) |
+| **TX Drop** | 丢弃的出站数据包 | 累计总数(非近期) |
+| **Collisions** | 网络冲突 | 平台相关的可用性 |
+
+**重要**:错误和丢弃计数器是**自系统启动或接口上线以来的累计总数**,不是近期活动。这些有助于识别长期接口可靠性,但不会显示即时问题。
+
+### 平台特定行为
+
+**所有平台:**
+- 所有计数器(字节、数据包、错误、丢弃)自启动/接口上线以来累计
+- 速率(字节/秒)根据每 2 秒采集的快照计算
+- 包含回环接口用于监控本地流量
+
+**Windows:**
+- 过滤掉虚拟/过滤适配器,仅显示物理接口:
+ - 排除:`-Npcap`、`-WFP`、`-QoS`、`-Native`、`-Virtual`、`-Packet` 变体
+ - 排除:`Lightweight Filter`、`MAC Layer` 接口
+ - 排除:已断开的 "Local Area Connection" 适配器
+- 使用基于 LUID 的去重防止重复的接口条目
+- Collisions:始终为 0(现代 Windows 接口上不可用)
+
+**macOS:**
+- 包含数据验证以检测虚拟接口上的损坏计数器
+- TX Drops:始终为 0(macOS 上可用性有限)
+- 如果错误/丢弃计数器值看起来损坏(>2^31 或 errors>packets),则进行清理
+
+**FreeBSD:**
+- TX Drops:始终为 0(FreeBSD 上通常不可用)
+- 使用 BSD getifaddrs API 配合 AF_LINK 过滤
+
+**Linux:**
+- 从 `/sys/class/net/{interface}/statistics` 读取统计
+- 所有计数器通常可用且可靠
+
+### 解读统计
+
+**健康的接口:**
+```
+Ethernet: 2.40 KB/s ↓ / 1.96 KB/s ↑
+ Errors (Total): 0 Drops (Total): 0
+```
+零或极低的错误/丢弃计数表明可靠的网络连接。
+
+**有问题的接口:**
+```
+WiFi: 150 KB/s ↓ / 45 KB/s ↑
+ Errors (Total): 1089 Drops (Total): 2178
+```
+高错误/丢弃计数可能表明:
+- 信号干扰(WiFi)
+- 线缆问题(Ethernet)
+- 网络拥塞
+- 驱动或硬件问题
+
+**注意**:由于错误/丢弃计数器是累计的,请相对于总数据包数来评估它们。数百万数据包中有少量错误是正常的;数据包数低但有数千错误则表明有问题。
+
+### 接口过滤
+
+**显示哪些接口:**
+- 接口必须在操作上 "up" OR 拥有流量统计
+- 包含回环接口(用于监控本地连接)
+- Windows 上排除虚拟/过滤适配器(它们镜像物理接口)
+
+**概览标签页过滤:**
+- Windows:显示所有活跃接口(NPF 设备路径自动检测)
+- macOS/Linux:显示有近期流量的接口(`rx_bytes > 0 || tx_bytes > 0 || rx_packets > 0 || tx_packets > 0`)
+- 特殊接口(`any`、`pktap`):显示有任何活动的所有接口
+
+**接口标签页:**
+- 显示通过平台特定过滤的所有检测到的接口
+- 排序将当前捕获的接口排在最前面(高亮)
+- 其他接口按字母顺序出现
+
+### 使用场景
+
+**带宽监控:**
+监控所有网络接口的实时带宽使用情况以识别:
+- 哪个接口承载最多流量
+- WiFi 与 Ethernet 之间的带宽分布
+- 本地流量体积(回环接口)
+
+**可靠性分析:**
+检查累计错误和丢弃计数器以:
+- 识别不可靠的网络接口
+- 检测硬件或驱动问题
+- 随时间比较接口质量
+
+**多接口系统:**
+在具有多个网络接口的系统上:
+- 跨接口比较性能
+- 监控 VPN 隧道统计
+- 追踪接口故障转移行为
+
+## 连接生命周期与视觉指示器
+
+RustNet 使用智能超时管理自动清理不活跃的连接,同时在移除前提供视觉警告。
+
+### 视觉陈旧度指示器
+
+连接根据距离被清理的接近程度改变颜色:
+
+| 颜色 | 含义 | 陈旧度 |
+|-------|---------|-----------|
+| **白色**(默认) | 活跃连接 | < 75% 的超时时间 |
+| **黄色** | 陈旧 - 接近超时 | 75-90% 的超时时间 |
+| **红色** | 严重 - 即将被移除 | > 90% 的超时时间 |
+
+**示例**:一条超时为 10 分钟的 HTTP 连接会:
+- 前 7.5 分钟保持**白色**
+- 7.5 到 9 分钟变为**黄色**(警告)
+- 9 分钟后变为**红色**(严重)
+- 10 分钟时被移除
+
+这让你在连接即将从列表中消失前得到预警。
+
+### 智能协议感知超时
+
+RustNet 根据协议和检测到的应用调整连接超时:
+
+#### TCP 连接
+- **HTTP/HTTPS**(通过 DPI 检测):**10 分钟** —— 支持 HTTP keep-alive
+- **SSH**(通过 DPI 检测):**30 分钟** —— 适应长交互会话
+- **活跃已建立**(< 1 分钟空闲):**10 分钟**
+- **空闲已建立**(> 1 分钟空闲):**5 分钟**
+- **TIME_WAIT**:30 秒 —— 标准 TCP 超时
+- **CLOSED**:5 秒 —— 快速清理
+- **SYN_SENT、FIN_WAIT 等**:30-60 秒
+
+#### UDP 连接
+- **基于 UDP 的 SSH**:**30 分钟** —— 长会话
+- **DNS**:**30 秒** —— 短查询
+- **普通 UDP**:**60 秒** —— 标准超时
+
+#### QUIC 连接(检测到的状态)
+- **已连接**:**3 分钟** 默认(或当可用时使用来自传输参数的 idle timeout)
+- **带 CONNECTION_CLOSE 帧**:1-10 秒(基于关闭类型)
+- **Initial/Handshaking**:60 秒 —— 允许连接建立
+- **Draining**:10 秒 —— RFC 9000 draining 周期
+
+### 基于活动的调整
+
+显示近期数据包活动的连接获得更长的超时:
+- **最近数据包 < 60 秒前**:使用"活跃"超时(更长)
+- **最近数据包 > 60 秒前**:使用"空闲"超时(更短)
+
+这确保活跃连接保持可见,而空闲连接更快被清理。
+
+### 连接为什么消失
+
+连接在以下情况下被移除:
+1. **在超时期间内未收到数据包**
+2. 连接进入**关闭状态**(TCP CLOSED、QUIC CLOSED)
+3. 检测到**显式关闭帧**(QUIC CONNECTION_CLOSE)
+
+**注意**:速率指示器(带宽显示)基于近期活动显示*衰减*的流量。连接可能显示带宽下降(黄色条),但在超过空闲超时前仍保留在列表中。这是有意设计的 —— 视觉衰减让你在连接被移除前有时间看到它逐渐结束。
+
+### 历史连接
+
+默认情况下,连接在超时或关闭后从列表中消失。按 `t` 切换**历史连接**模式,使已关闭的连接与活跃连接一起保持可见。
+
+**工作原理:**
+
+当连接被清理时,它被归档到历史连接池中(最多 5,000 条;最旧的先被逐出)。按 `t` 切换它们的可见性:
+
+- **活跃连接**以标准颜色指示器正常显示
+- **历史连接**以**暗灰色**显示,以清楚区分于活跃连接
+- 启用历史模式时,表格标题变为 **"Active + Historic Connections"**
+
+**详情视图:**
+
+选择历史连接并按 `Enter` 显示通常的连接详情,外加一个**Status**字段显示连接关闭多久前(例如 "Closed (5m ago)")。
+
+**统计面板:**
+
+当存在历史连接时,统计面板在总活跃连接数下方显示一个单独的 **"Historic: N"** 计数。
+
+**分组视图:**
+
+在进程分组模式(`a`)中,当启用历史模式时,分组头部将历史连接数与活跃计数分开显示。
+
+**图表标签页:**
+
+图表标签页始终只显示活跃连接,即使历史模式开启。
+
+**重置:**
+
+- 按 `r` 重置所有视图设置,这也会隐藏历史连接
+- 按 `x` 两次清除所有连接,这也会清除历史池
+
+## 日志
+
+日志**默认禁用**。使用 `--log-level` 选项启用时,RustNet 在 `logs/` 目录中创建带时间戳的日志文件。每个会话生成一个新日志文件,格式为 `rustnet_YYYY-MM-DD_HH-MM-SS.log`。
+
+### 日志文件内容
+
+日志文件包含:
+- 应用启动和关闭事件
+- 网络接口信息
+- 数据包捕获统计
+- 连接状态变更
+- 错误诊断
+- DPI 检测结果(debug/trace 级别)
+- 性能指标(trace 级别)
+
+### 启用日志
+
+使用 `--log-level` 选项启用日志:
+
+```bash
+# Info 级别日志(建议常规使用)
+sudo rustnet --log-level info
+
+# Debug 级别日志(详细故障排查)
+sudo rustnet --log-level debug
+
+# Trace 级别日志(非常详细,包含数据包级详情)
+sudo rustnet --log-level trace
+
+# 仅 Error 级别日志(最小日志)
+sudo rustnet --log-level error
+```
+
+### 日志级别说明
+
+| 级别 | 记录内容 | 使用场景 |
+|-------|------------------|----------|
+| `error` | 仅错误和关键问题 | 生产监控 |
+| `warn` | 警告和错误 | 带警告的正常运行 |
+| `info` | 一般信息、启动/关闭 | 标准调试 |
+| `debug` | 详细的调试信息 | 故障排查 |
+| `trace` | 数据包级详情,非常详细 | 深度调试 |
+
+### 管理日志文件
+
+**日志清理脚本:**
+
+提供了 `scripts/clear_old_logs.sh` 脚本用于日志清理:
+
+```bash
+# 删除 7 天前的日志
+./scripts/clear_old_logs.sh
+
+# 通过编辑脚本自定义保留期
+```
+
+**手动清理:**
+
+```bash
+# 删除所有日志
+rm -rf logs/
+
+# 删除 7 天前的日志(Linux/macOS)
+find logs/ -name "rustnet_*.log" -mtime +7 -delete
+
+# 查看日志文件大小
+du -sh logs/
+```
+
+### 日志文件隐私
+
+⚠️ **警告**:日志文件可能包含敏感信息:
+- IP 地址和端口
+- 主机名和 SNI 数据(HTTPS)
+- DNS 查询和响应
+- 进程名和 PID
+- 数据包内容(trace 级别)
+
+**最佳实践:**
+- 仅在需要调试时启用日志
+- 保护日志目录权限:`chmod 700 logs/`
+- 分享前检查日志中的敏感数据
+- 实施日志轮转和保留策略
+- 不再需要时删除日志
+
+### 使用日志故障排查
+
+报告问题时:
+1. 启用 debug 日志:`rustnet --log-level debug`
+2. 重现问题
+3. 在 `logs/` 中找到最新的日志文件
+4. 查看错误或异常行为
+5. 分享前脱敏敏感信息
+
+对于性能问题,trace 级别日志提供最详细的细节,但会快速生成大量日志文件。
+
+### JSON 日志
+
+`--json-log` 选项启用将连接事件以结构化 JSON 格式记录到文件。每行是一个独立的 JSON 对象(JSONL 格式)。
+
+```bash
+# 启用 JSON 日志
+sudo rustnet --json-log /tmp/connections.json
+
+# 与其他选项组合
+sudo rustnet -i eth0 --json-log ~/network-events.json
+```
+
+**事件类型:**
+- `new_connection` —— 首次检测到新连接时记录
+- `connection_closed` —— 连接在变为不活跃后被清理时记录
+
+**JSON 字段:**
+
+| 字段 | 类型 | 描述 |
+|-------|------|-------------|
+| `timestamp` | string | RFC3339 UTC 时间戳 |
+| `event` | string | 事件类型(`new_connection` 或 `connection_closed`) |
+| `protocol` | string | 协议(TCP、UDP 等) |
+| `source_ip` | string | 本地 IP 地址 |
+| `source_port` | number | 本地端口号 |
+| `destination_ip` | string | 远程 IP 地址 |
+| `destination_port` | number | 远程端口号 |
+| `pid` | number | 进程 ID(如果可用) |
+| `process_name` | string | 进程名(如果可用) |
+| `service_name` | string | 端口查找的服务名(如果可用) |
+| `direction` | string | 连接方向(`outgoing` 或 `incoming`),仅当观察到 TCP 握手时 |
+| `dpi_protocol` | string | 检测到的应用协议(如果启用 DPI) |
+| `dpi_domain` | string | 提取的域名/主机名(如果可用) |
+| `bytes_sent` | number | 发送的总字节数(仅 connection_closed) |
+| `bytes_received` | number | 接收的总字节数(仅 connection_closed) |
+| `duration_secs` | number | 连接持续时间,单位为秒(仅 connection_closed) |
+
+**示例输出:**
+
+```json
+{"timestamp":"2025-01-15T10:30:00Z","event":"new_connection","protocol":"TCP","source_ip":"192.168.1.100","source_port":54321,"destination_ip":"93.184.216.34","destination_port":443,"pid":1234,"process_name":"curl","service_name":"https","direction":"outgoing","dpi_protocol":"HTTPS","dpi_domain":"example.com"}
+{"timestamp":"2025-01-15T10:30:05Z","event":"connection_closed","protocol":"TCP","source_ip":"192.168.1.100","source_port":54321,"destination_ip":"93.184.216.34","destination_port":443,"pid":1234,"process_name":"curl","service_name":"https","direction":"outgoing","bytes_sent":1024,"bytes_received":4096,"duration_secs":5}
+```
+
+**处理 JSON 日志:**
+
+```bash
+# 漂亮打印最新事件
+tail -f /tmp/connections.json | jq .
+
+# 按进程过滤
+cat /tmp/connections.json | jq 'select(.process_name == "firefox")'
+
+# 按目的地统计连接数
+cat /tmp/connections.json | jq -s 'group_by(.destination_ip) | map({ip: .[0].destination_ip, count: length})'
+```
+
+### PCAP 导出
+
+`--pcap-export` 选项将原始数据包捕获到标准 PCAP 文件,供 Wireshark、tcpdump 或其他工具分析。
+
+```bash
+# 导出所有捕获的数据包
+sudo rustnet -i eth0 --pcap-export capture.pcap
+
+# 与 BPF 过滤器组合
+sudo rustnet -i eth0 --bpf-filter "tcp port 443" --pcap-export https.pcap
+```
+
+**输出文件:**
+
+| 文件 | 描述 |
+|------|-------------|
+| `capture.pcap` | 标准 PCAP 格式的原始数据包 |
+| `capture.pcap.connections.jsonl` | 流式连接元数据,带进程信息 |
+
+**Sidecar JSONL 格式**(每行一个 JSON 对象,连接关闭时写入):
+
+```json
+{"timestamp":"2026-01-17T10:30:00Z","protocol":"TCP","local_addr":"192.168.1.100:54321","remote_addr":"142.250.80.46:443","pid":1234,"process_name":"firefox","first_seen":"...","last_seen":"...","bytes_sent":1024,"bytes_received":8192,"state":"ESTABLISHED"}
+```
+
+| 字段 | 描述 |
+|-------|-------------|
+| `timestamp` | 连接记录写入时间 |
+| `protocol` | TCP、UDP、ICMP 等 |
+| `local_addr` / `remote_addr` | 连接端点 |
+| `pid` / `process_name` | 进程信息(如果已识别) |
+| `first_seen` / `last_seen` | 连接时间戳 |
+| `bytes_sent` / `bytes_received` | 流量总计 |
+| `state` | 最终连接状态 |
+
+#### 用进程信息富化 PCAP
+
+标准 PCAP 文件不包含进程信息。使用附带的 `scripts/pcap_enrich.py` 脚本将数据包与进程关联:
+
+```bash
+# 安装 scapy(必需)
+pip install scapy
+
+# 显示带进程信息的数据包
+python scripts/pcap_enrich.py capture.pcap
+
+# 输出为 TSV 以便进一步处理
+python scripts/pcap_enrich.py capture.pcap --format tsv > report.tsv
+
+# 创建带进程注释的注释 PCAPNG(需要 Wireshark 的 editcap)
+python scripts/pcap_enrich.py capture.pcap -o annotated.pcapng
+```
+
+注释 PCAPNG 将进程信息嵌入为数据包注释,在 Wireshark 的数据包详情中可见。
+
+#### 原生带注释 PCAPNG 导出
+
+`--pcapng-export` 选项会直接写出带 RustNet 数据包注释的 PCAPNG 文件。想立即在 Wireshark 中打开捕获文件时,可以省去 Python 富化步骤:
+
+```bash
+sudo rustnet -i eth0 --pcapng-export capture.pcapng
+```
+
+数据包注释是实时的 best-effort 标注。RustNet 会短暂等待进程和 GeoIP 补全,然后即使归因仍不可用也会写出数据包;因此有些注释可能只有 DPI/SNI、方向或 GeoIP 字段,而没有 `process=`/`pid=`。高负载下,在处理器阶段之前丢弃的数据包或被有界 PCAPNG 导出队列丢弃的数据包不会出现在 PCAPNG 中,所以同时生成的 `--pcap-export` 文件可能包含 PCAPNG 中缺失的数据包。Enhanced Packet Block 可能不是按捕获时间顺序写入,但保留真实捕获时间戳,Wireshark 可以按时间排序/显示。
+
+当清理阶段的元数据完整性比单个带注释文件更重要时,请使用 `--pcap-export` 加 `capture.pcap.connections.jsonl`。
+
+**手动关联:**
+
+```bash
+# 查看数据包
+wireshark capture.pcap
+
+# 查看进程映射
+cat capture.pcap.connections.jsonl | jq -r '[.protocol, .local_addr, .remote_addr, .pid, .process_name] | @tsv'
+
+# 在 Wireshark 中按连接元组过滤
+# ip.addr == 142.250.80.46 && tcp.port == 443
+```
diff --git a/assets/rustnet.gif b/assets/rustnet.gif
new file mode 100644
index 0000000..be5f1cc
Binary files /dev/null and b/assets/rustnet.gif differ
diff --git a/assets/rustnet.svg b/assets/rustnet.svg
new file mode 100644
index 0000000..f22b8f7
--- /dev/null
+++ b/assets/rustnet.svg
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ RustNet
+
+
diff --git a/assets/screenshots/details.png b/assets/screenshots/details.png
new file mode 100644
index 0000000..5132df6
Binary files /dev/null and b/assets/screenshots/details.png differ
diff --git a/assets/screenshots/filter.png b/assets/screenshots/filter.png
new file mode 100644
index 0000000..44e8383
Binary files /dev/null and b/assets/screenshots/filter.png differ
diff --git a/assets/screenshots/graph.png b/assets/screenshots/graph.png
new file mode 100644
index 0000000..00e4703
Binary files /dev/null and b/assets/screenshots/graph.png differ
diff --git a/assets/screenshots/grouping.png b/assets/screenshots/grouping.png
new file mode 100644
index 0000000..4395b47
Binary files /dev/null and b/assets/screenshots/grouping.png differ
diff --git a/assets/screenshots/interfaces.png b/assets/screenshots/interfaces.png
new file mode 100644
index 0000000..5bff2bf
Binary files /dev/null and b/assets/screenshots/interfaces.png differ
diff --git a/assets/screenshots/overview.png b/assets/screenshots/overview.png
new file mode 100644
index 0000000..96a62fb
Binary files /dev/null and b/assets/screenshots/overview.png differ
diff --git a/benches/connection_merge.rs b/benches/connection_merge.rs
new file mode 100644
index 0000000..97d68f6
--- /dev/null
+++ b/benches/connection_merge.rs
@@ -0,0 +1,113 @@
+use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
+use rustnet_monitor::network::merge::merge_packet_into_connection;
+use rustnet_monitor::network::parser::{TcpFlags, TcpHeaderInfo};
+use rustnet_monitor::network::types::*;
+use std::net::{IpAddr, Ipv4Addr, SocketAddr};
+use std::time::SystemTime;
+
+/// Create a Connection with a RateTracker filled to `n_samples` entries.
+fn make_connection_with_samples(n_samples: usize) -> Connection {
+ let local = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 100)), 54321);
+ let remote = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(93, 184, 216, 34)), 443);
+ let mut conn = Connection::new(
+ Protocol::Tcp,
+ local,
+ remote,
+ ProtocolState::Tcp(TcpState::Established),
+ );
+
+ // Simulate rate tracker updates to fill it with samples
+ for _ in 0..n_samples {
+ conn.bytes_sent += 100;
+ conn.bytes_received += 200;
+ conn.rate_tracker
+ .update(conn.bytes_sent, conn.bytes_received);
+ conn.packets_sent += 1;
+ conn.packets_received += 1;
+ }
+ conn
+}
+
+/// Create a minimal ParsedPacket for merge benchmarking.
+fn make_parsed_packet() -> rustnet_monitor::network::parser::ParsedPacket {
+ rustnet_monitor::network::parser::ParsedPacket {
+ protocol: Protocol::Tcp,
+ local_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 100)), 54321),
+ remote_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(93, 184, 216, 34)), 443),
+ tcp_header: Some(TcpHeaderInfo {
+ seq: 1000,
+ ack: 2000,
+ window: 65535,
+ flags: TcpFlags {
+ syn: false,
+ ack: true,
+ fin: false,
+ rst: false,
+ psh: true,
+ urg: false,
+ },
+ payload_len: 1400,
+ }),
+ protocol_state: ProtocolState::Tcp(TcpState::Established),
+ is_outgoing: true,
+ packet_len: 1400,
+ dpi_result: None,
+ process_name: None,
+ process_id: None,
+ }
+}
+
+fn bench_merge(c: &mut Criterion) {
+ let parsed = make_parsed_packet();
+ let now = SystemTime::now();
+
+ let mut group = c.benchmark_group("merge_packet");
+
+ for n_samples in [0, 100, 1000, 5000, 10000] {
+ // Benchmark the new in-place merge (no clone)
+ group.bench_with_input(
+ BenchmarkId::new("in_place_merge", n_samples),
+ &n_samples,
+ |b, &n| {
+ let mut conn = make_connection_with_samples(n);
+ b.iter(|| merge_packet_into_connection(&mut conn, &parsed, now));
+ },
+ );
+
+ // Keep clone benchmark for comparison with baseline
+ let conn = make_connection_with_samples(n_samples);
+ group.bench_with_input(
+ BenchmarkId::new("clone_only", n_samples),
+ &conn,
+ |b, conn| {
+ b.iter(|| conn.clone());
+ },
+ );
+ }
+
+ group.finish();
+}
+
+/// Compare the old String key construction (kept as a baseline) against the
+/// compact `ConnectionKey` the tracker uses now: build + hash, no allocation.
+fn bench_connection_key_format(c: &mut Criterion) {
+ use std::hash::BuildHasher;
+
+ let local = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 100)), 54321);
+ let remote = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(93, 184, 216, 34)), 443);
+
+ // Baseline: what every parsed packet used to allocate.
+ c.bench_function("connection_key_format_string", |b| {
+ b.iter(|| format!("TCP:{}-TCP:{}", local, remote));
+ });
+
+ // Now: construct the Copy key and hash it with the tracker's FxHash.
+ let parsed = make_parsed_packet();
+ let hasher = rustc_hash::FxBuildHasher;
+ c.bench_function("connection_key_struct_fxhash", |b| {
+ b.iter(|| hasher.hash_one(parsed.connection_key()));
+ });
+}
+
+criterion_group!(benches, bench_merge, bench_connection_key_format);
+criterion_main!(benches);
diff --git a/benches/packet_parsing.rs b/benches/packet_parsing.rs
new file mode 100644
index 0000000..169bab5
--- /dev/null
+++ b/benches/packet_parsing.rs
@@ -0,0 +1,93 @@
+use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
+use rustnet_monitor::network::parser::PacketParser;
+use std::path::Path;
+
+/// Load all raw packet bytes from the capture.pcap file.
+fn load_packets_from_pcap(path: &str) -> Vec> {
+ let mut cap = pcap::Capture::from_file(path).expect("failed to open pcap file");
+ let mut packets = Vec::new();
+ while let Ok(packet) = cap.next_packet() {
+ packets.push(packet.data.to_vec());
+ }
+ packets
+}
+
+fn bench_parse_packet(c: &mut Criterion) {
+ if !Path::new("capture.pcap").exists() {
+ if std::env::var_os("CI").is_some()
+ || std::env::var_os("RUSTNET_REQUIRE_BENCH_PCAP").is_some()
+ {
+ panic!("capture.pcap fixture is required for packet_parsing benchmark");
+ }
+ eprintln!(
+ "Skipping packet_parsing benchmark: capture.pcap not found \
+ (set RUSTNET_REQUIRE_BENCH_PCAP=1 to require it)"
+ );
+ return;
+ }
+
+ let packets = load_packets_from_pcap("capture.pcap");
+ assert!(!packets.is_empty(), "capture.pcap must contain packets");
+
+ // Configure parser with Linux SLL linktype (DLT 113) matching capture.pcap
+ let parser = PacketParser::new().with_linktype(113);
+
+ let mut group = c.benchmark_group("parse_packet");
+ group.throughput(Throughput::Elements(packets.len() as u64));
+
+ group.bench_function("all_packets", |b| {
+ b.iter(|| {
+ let mut parsed_count = 0u32;
+ for pkt in &packets {
+ if parser.parse_packet(pkt).is_some() {
+ parsed_count += 1;
+ }
+ }
+ parsed_count
+ });
+ });
+
+ group.finish();
+
+ // Per-packet benchmark (average cost of a single parse)
+ let mut single_group = c.benchmark_group("parse_single_packet");
+ single_group.throughput(Throughput::Elements(1));
+
+ // Pick a few representative packets (first TCP, first UDP if available)
+ let mut tcp_packet = None;
+ let mut udp_packet = None;
+ for pkt in &packets {
+ let result = parser.parse_packet(pkt);
+ if let Some(ref parsed) = result {
+ match parsed.protocol {
+ rustnet_monitor::network::types::Protocol::Tcp if tcp_packet.is_none() => {
+ tcp_packet = Some(pkt.clone());
+ }
+ rustnet_monitor::network::types::Protocol::Udp if udp_packet.is_none() => {
+ udp_packet = Some(pkt.clone());
+ }
+ _ => {}
+ }
+ }
+ if tcp_packet.is_some() && udp_packet.is_some() {
+ break;
+ }
+ }
+
+ if let Some(ref pkt) = tcp_packet {
+ single_group.bench_with_input(BenchmarkId::new("tcp", pkt.len()), pkt, |b, pkt| {
+ b.iter(|| parser.parse_packet(pkt));
+ });
+ }
+
+ if let Some(ref pkt) = udp_packet {
+ single_group.bench_with_input(BenchmarkId::new("udp", pkt.len()), pkt, |b, pkt| {
+ b.iter(|| parser.parse_packet(pkt));
+ });
+ }
+
+ single_group.finish();
+}
+
+criterion_group!(benches, bench_parse_packet);
+criterion_main!(benches);
diff --git a/benches/rate_tracker.rs b/benches/rate_tracker.rs
new file mode 100644
index 0000000..4918210
--- /dev/null
+++ b/benches/rate_tracker.rs
@@ -0,0 +1,221 @@
+use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
+use rustnet_monitor::network::types::*;
+use std::net::{IpAddr, Ipv4Addr, SocketAddr};
+
+/// Create a Connection with a RateTracker filled to `n_samples` entries.
+fn make_connection_with_samples(n_samples: usize) -> Connection {
+ let local = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 100)), 54321);
+ let remote = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(93, 184, 216, 34)), 443);
+ let mut conn = Connection::new(
+ Protocol::Tcp,
+ local,
+ remote,
+ ProtocolState::Tcp(TcpState::Established),
+ );
+
+ for i in 0..n_samples {
+ conn.bytes_sent += 100;
+ conn.bytes_received += 200;
+ conn.rate_tracker
+ .update(conn.bytes_sent, conn.bytes_received);
+ // Sprinkle in some pruning to keep the tracker realistic
+ if i % 500 == 0 {
+ conn.rate_tracker.prune();
+ }
+ }
+ conn
+}
+
+/// Benchmark the per-packet `update()` call on RateTracker.
+/// This is the hot path — called for every packet received.
+/// The Arc change adds an `Arc::make_mut` atomic check here.
+fn bench_rate_update(c: &mut Criterion) {
+ let mut group = c.benchmark_group("rate_tracker_update");
+
+ for n_samples in [0, 100, 1000, 5000] {
+ // Unique owner: simulates the normal packet-processing path where
+ // no snapshot clone is holding a shared reference.
+ group.bench_with_input(
+ BenchmarkId::new("unique_owner", n_samples),
+ &n_samples,
+ |b, &n| {
+ let mut conn = make_connection_with_samples(n);
+ let mut bytes_sent = conn.bytes_sent;
+ let mut bytes_recv = conn.bytes_received;
+ b.iter(|| {
+ bytes_sent += 100;
+ bytes_recv += 200;
+ conn.rate_tracker.update(bytes_sent, bytes_recv);
+ });
+ },
+ );
+
+ // Shared owner: simulates the case right after a full clone, where
+ // two Arcs point to the same VecDeque while the snapshot is alive
+ // (in the app the UI holds it for a full refresh interval). The
+ // first `update()` then triggers a full VecDeque copy via
+ // Arc::make_mut. The snapshot is threaded through the routine and
+ // returned so it stays alive during the update and its drop isn't
+ // measured.
+ group.bench_with_input(
+ BenchmarkId::new("after_snapshot_clone", n_samples),
+ &n_samples,
+ |b, &n| {
+ b.iter_batched(
+ || {
+ let conn = make_connection_with_samples(n);
+ let snapshot = conn.clone(); // shared Arc, kept alive
+ (conn, snapshot)
+ },
+ |(mut conn, snapshot)| {
+ conn.bytes_sent += 100;
+ conn.bytes_received += 200;
+ conn.rate_tracker
+ .update(conn.bytes_sent, conn.bytes_received);
+ (conn, snapshot)
+ },
+ criterion::BatchSize::SmallInput,
+ );
+ },
+ );
+
+ // Detached snapshot: what the snapshot thread actually does now —
+ // snapshot_clone() drops the sample buffer, so the live tracker
+ // stays unique owner and the next update takes the fast path even
+ // while the snapshot is alive.
+ group.bench_with_input(
+ BenchmarkId::new("after_snapshot_clone_detached", n_samples),
+ &n_samples,
+ |b, &n| {
+ b.iter_batched(
+ || {
+ let conn = make_connection_with_samples(n);
+ let snapshot = conn.snapshot_clone(); // no shared samples
+ (conn, snapshot)
+ },
+ |(mut conn, snapshot)| {
+ conn.bytes_sent += 100;
+ conn.bytes_received += 200;
+ conn.rate_tracker
+ .update(conn.bytes_sent, conn.bytes_received);
+ (conn, snapshot)
+ },
+ criterion::BatchSize::SmallInput,
+ );
+ },
+ );
+ }
+
+ group.finish();
+}
+
+/// Benchmark `refresh_rates()` (prune + rate calculation + smoothing).
+/// Called once per second per connection from the refresh loop.
+fn bench_refresh_rates(c: &mut Criterion) {
+ let mut group = c.benchmark_group("refresh_rates");
+
+ // 20000 = the sample cap: with O(1) window totals the curve must stay
+ // flat instead of growing with the sample count.
+ for n_samples in [0, 100, 1000, 5000, 20000] {
+ group.bench_with_input(
+ BenchmarkId::new("unique_owner", n_samples),
+ &n_samples,
+ |b, &n| {
+ let mut conn = make_connection_with_samples(n);
+ b.iter(|| {
+ conn.refresh_rates();
+ });
+ },
+ );
+ }
+
+ group.finish();
+}
+
+/// Benchmark Connection::clone() to measure the impact of Arc
+/// vs a plain VecDeque. With Arc, clone is O(1) for the samples field
+/// (just a refcount bump). Without Arc, it's O(n_samples).
+fn bench_connection_clone(c: &mut Criterion) {
+ let mut group = c.benchmark_group("connection_clone");
+
+ for n_samples in [0, 100, 1000, 5000, 10000] {
+ let conn = make_connection_with_samples(n_samples);
+ group.bench_with_input(BenchmarkId::new("clone", n_samples), &conn, |b, conn| {
+ b.iter(|| conn.clone());
+ });
+ }
+
+ group.finish();
+}
+
+/// Benchmark the snapshot-then-mutate cycle that happens in practice:
+/// 1. Clone N connections for a UI snapshot
+/// 2. Then update each original connection with a new packet
+///
+/// This measures the real-world cost: cheap clone (Arc refcount) followed
+/// by a CoW deep-copy on first mutation.
+fn bench_snapshot_then_update(c: &mut Criterion) {
+ let mut group = c.benchmark_group("snapshot_then_update");
+
+ for n_conns in [100, 1000, 5000] {
+ let connections: Vec = (0..n_conns)
+ .map(|_| make_connection_with_samples(100))
+ .collect();
+
+ group.bench_with_input(
+ BenchmarkId::new("clone_all_then_update_all", n_conns),
+ &connections,
+ |b, connections| {
+ b.iter_batched(
+ || connections.clone(),
+ |mut conns| {
+ // Step 1: snapshot clone (simulates UI snapshot)
+ let _snapshot: Vec = conns.to_vec();
+ // Step 2: mutate originals (simulates incoming packets)
+ for conn in &mut conns {
+ conn.bytes_sent += 100;
+ conn.bytes_received += 200;
+ conn.rate_tracker
+ .update(conn.bytes_sent, conn.bytes_received);
+ }
+ },
+ criterion::BatchSize::LargeInput,
+ );
+ },
+ );
+
+ // Same cycle but with snapshot_clone(): the snapshot detaches from
+ // the sample buffers, so step 2 never pays the CoW deep copy.
+ group.bench_with_input(
+ BenchmarkId::new("snapshot_clone_all_then_update_all", n_conns),
+ &connections,
+ |b, connections| {
+ b.iter_batched(
+ || connections.clone(),
+ |mut conns| {
+ let _snapshot: Vec =
+ conns.iter().map(|c| c.snapshot_clone()).collect();
+ for conn in &mut conns {
+ conn.bytes_sent += 100;
+ conn.bytes_received += 200;
+ conn.rate_tracker
+ .update(conn.bytes_sent, conn.bytes_received);
+ }
+ },
+ criterion::BatchSize::LargeInput,
+ );
+ },
+ );
+ }
+
+ group.finish();
+}
+
+criterion_group!(
+ benches,
+ bench_rate_update,
+ bench_refresh_rates,
+ bench_connection_clone,
+ bench_snapshot_then_update,
+);
+criterion_main!(benches);
diff --git a/benches/snapshot.rs b/benches/snapshot.rs
new file mode 100644
index 0000000..058c9bf
--- /dev/null
+++ b/benches/snapshot.rs
@@ -0,0 +1,93 @@
+use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
+use dashmap::DashMap;
+use rustnet_monitor::network::types::*;
+use std::net::{IpAddr, Ipv4Addr, SocketAddr};
+
+/// Populate a DashMap with `n` connections, each with a small number of rate samples.
+fn populate_connections(n: usize) -> DashMap {
+ let map = DashMap::new();
+ for i in 0..n {
+ let port = (i % 65000) as u16 + 1;
+ let octet3 = ((i / 256) % 256) as u8;
+ let octet4 = (i % 256) as u8;
+ let local = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 100)), port);
+ let remote = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(10, 0, octet3, octet4)), 443);
+ let key = format!("TCP:{}-TCP:{}", local, remote);
+ let mut conn = Connection::new(
+ Protocol::Tcp,
+ local,
+ remote,
+ ProtocolState::Tcp(TcpState::Established),
+ );
+ // Add a few rate samples to simulate a real connection
+ conn.bytes_sent = 5000;
+ conn.bytes_received = 15000;
+ conn.packets_sent = 10;
+ conn.packets_received = 30;
+ conn.rate_tracker
+ .update(conn.bytes_sent, conn.bytes_received);
+ map.insert(key, conn);
+ }
+ map
+}
+
+fn bench_snapshot(c: &mut Criterion) {
+ let mut group = c.benchmark_group("snapshot");
+
+ for n_conns in [100, 1000, 5000, 10000, 50000] {
+ let connections = populate_connections(n_conns);
+
+ // Benchmark: iterate + clone + collect (mirrors start_snapshot_provider)
+ group.bench_with_input(
+ BenchmarkId::new("clone_and_collect", n_conns),
+ &connections,
+ |b, connections| {
+ b.iter(|| {
+ let snapshot: Vec = connections
+ .iter()
+ .map(|entry| entry.value().clone())
+ .collect();
+ snapshot
+ });
+ },
+ );
+
+ // Benchmark: snapshot_clone (drops rate samples) + collect — what
+ // start_snapshot_provider actually does now, leaving the live
+ // connections as unique owners of their sample buffers.
+ group.bench_with_input(
+ BenchmarkId::new("snapshot_clone_and_collect", n_conns),
+ &connections,
+ |b, connections| {
+ b.iter(|| {
+ let snapshot: Vec = connections
+ .iter()
+ .map(|entry| entry.value().snapshot_clone())
+ .collect();
+ snapshot
+ });
+ },
+ );
+
+ // Benchmark: clone + collect + sort by created_at
+ group.bench_with_input(
+ BenchmarkId::new("clone_collect_sort", n_conns),
+ &connections,
+ |b, connections| {
+ b.iter(|| {
+ let mut snapshot: Vec = connections
+ .iter()
+ .map(|entry| entry.value().clone())
+ .collect();
+ snapshot.sort_by_key(|a| a.created_at);
+ snapshot
+ });
+ },
+ );
+ }
+
+ group.finish();
+}
+
+criterion_group!(benches, bench_snapshot);
+criterion_main!(benches);
diff --git a/benches/tracker_ingest.rs b/benches/tracker_ingest.rs
new file mode 100644
index 0000000..e6bfcbb
--- /dev/null
+++ b/benches/tracker_ingest.rs
@@ -0,0 +1,101 @@
+use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
+use rustnet_monitor::network::parser::{ParsedPacket, TcpFlags, TcpHeaderInfo};
+use rustnet_monitor::network::tracker::ConnectionTracker;
+use rustnet_monitor::network::types::*;
+use std::net::{IpAddr, Ipv4Addr, SocketAddr};
+use std::time::SystemTime;
+
+/// Build a synthetic TCP data packet for one of `n_connections` flows.
+///
+/// Flows are distinguished by client port so the workload exercises the
+/// tracker's key hashing and DashMap sharding the same way live traffic does.
+fn make_packet(flow: u16, seq: u32) -> ParsedPacket {
+ let local_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 100)), 10000 + flow);
+ let remote_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(93, 184, 216, 34)), 443);
+ ParsedPacket {
+ protocol: Protocol::Tcp,
+ local_addr,
+ remote_addr,
+ tcp_header: Some(TcpHeaderInfo {
+ seq,
+ ack: seq.wrapping_add(1),
+ window: 65535,
+ flags: TcpFlags {
+ syn: false,
+ ack: true,
+ fin: false,
+ rst: false,
+ psh: true,
+ urg: false,
+ },
+ payload_len: 1400,
+ }),
+ protocol_state: ProtocolState::Tcp(TcpState::Established),
+ is_outgoing: true,
+ packet_len: 1400,
+ dpi_result: None,
+ process_name: None,
+ process_id: None,
+ }
+}
+
+/// Interleaved packet stream: `flows` connections sending `packets_per_flow`
+/// packets round-robin, mimicking concurrent flows rather than one flow at a
+/// time.
+fn make_workload(flows: u16, packets_per_flow: u32) -> Vec {
+ let mut packets = Vec::with_capacity(flows as usize * packets_per_flow as usize);
+ for round in 0..packets_per_flow {
+ for flow in 0..flows {
+ packets.push(make_packet(flow, round * 1460));
+ }
+ }
+ packets
+}
+
+/// The canonical per-packet ingest cost: parse results folded into a fresh
+/// tracker (mix of connection creation and in-place merge). This is the
+/// before/after number for connection-key, timestamp, and limit-check work
+/// on the packet path.
+fn bench_tracker_ingest(c: &mut Criterion) {
+ let now = SystemTime::now();
+
+ let mut group = c.benchmark_group("tracker_ingest");
+ for (flows, per_flow) in [(100u16, 100u32), (1000, 50)] {
+ let packets = make_workload(flows, per_flow);
+ group.throughput(Throughput::Elements(packets.len() as u64));
+ group.bench_with_input(
+ BenchmarkId::new("fresh_tracker", format!("{flows}x{per_flow}")),
+ &packets,
+ |b, packets| {
+ b.iter(|| {
+ let tracker = ConnectionTracker::new();
+ for p in packets {
+ tracker.ingest_at(p, now);
+ }
+ tracker
+ });
+ },
+ );
+ }
+ group.finish();
+
+ // Steady state: every packet updates an existing connection (no creation).
+ let mut group = c.benchmark_group("tracker_ingest_steady");
+ let packets = make_workload(1000, 1);
+ let tracker = ConnectionTracker::new();
+ for p in &packets {
+ tracker.ingest_at(p, now);
+ }
+ group.throughput(Throughput::Elements(packets.len() as u64));
+ group.bench_function("existing_connections_1000", |b| {
+ b.iter(|| {
+ for p in &packets {
+ tracker.ingest_at(p, now);
+ }
+ });
+ });
+ group.finish();
+}
+
+criterion_group!(benches, bench_tracker_ingest);
+criterion_main!(benches);
diff --git a/build.rs b/build.rs
new file mode 100644
index 0000000..e5702bb
--- /dev/null
+++ b/build.rs
@@ -0,0 +1,180 @@
+use anyhow::Result;
+use std::{env, fs::File, path::PathBuf};
+
+fn main() -> Result<()> {
+ // Generate shell completions and manpage
+ generate_assets()?;
+
+ // Add library search paths for cross-compilation
+ setup_cross_compilation_libs();
+
+ // eBPF program compilation now lives in the rustnet-host crate's build.rs.
+
+ #[cfg(target_os = "windows")]
+ download_windows_npcap_sdk()?;
+
+ println!("cargo:rerun-if-changed=src/cli.rs");
+
+ Ok(())
+}
+
+include!("src/cli.rs");
+
+fn setup_cross_compilation_libs() {
+ let target = env::var("TARGET").unwrap_or_default();
+ let host = env::var("HOST").unwrap_or_default();
+
+ // Only apply hard-coded multiarch lib paths when actually cross-compiling.
+ // On native builds (e.g. Homebrew on Linux arm64) these paths would shadow
+ // package-manager-provided libraries and break linkage.
+ if host == target {
+ return;
+ }
+
+ match target.as_str() {
+ "aarch64-unknown-linux-gnu" => {
+ println!("cargo:rustc-link-search=native=/usr/lib/aarch64-linux-gnu");
+ println!("cargo:rustc-link-lib=elf");
+ println!("cargo:rustc-link-lib=z");
+ }
+ "armv7-unknown-linux-gnueabihf" => {
+ println!("cargo:rustc-link-search=native=/usr/lib/arm-linux-gnueabihf");
+ println!("cargo:rustc-link-lib=elf");
+ println!("cargo:rustc-link-lib=z");
+ }
+ "x86_64-unknown-freebsd" => {
+ // FreeBSD uses libpcap from base system (in /usr/lib)
+ // When cross-compiling, the sysroot should provide these
+ println!("cargo:rustc-link-lib=pcap");
+ }
+ _ => {
+ // For other targets, including native builds, let pkg-config handle it
+ }
+ }
+}
+
+fn generate_assets() -> Result<()> {
+ use clap::ValueEnum;
+ use clap_complete::Shell;
+ use clap_mangen::Man;
+
+ let mut cmd = build_cli();
+
+ // build into `RUSTNET_ASSET_DIR` with a fallback to `OUT_DIR`
+ let asset_dir: PathBuf = env::var_os("RUSTNET_ASSET_DIR")
+ .or_else(|| env::var_os("OUT_DIR"))
+ .ok_or_else(|| anyhow::anyhow!("OUT_DIR is unset"))?
+ .into();
+
+ // completion
+ for &shell in Shell::value_variants() {
+ clap_complete::generate_to(shell, &mut cmd, "rustnet", &asset_dir)?;
+ }
+
+ // manpage
+ let mut manpage_out = File::create(asset_dir.join("rustnet.1"))?;
+ let manpage = Man::new(cmd);
+ manpage.render(&mut manpage_out)?;
+
+ Ok(())
+}
+
+#[cfg(target_os = "windows")]
+fn download_windows_npcap_sdk() -> Result<()> {
+ use sha2::{Digest, Sha256};
+ use std::{
+ fs,
+ io::{self, Write},
+ };
+
+ println!("cargo:rerun-if-changed=build.rs");
+
+ // get npcap SDK
+ const NPCAP_SDK: &str = "npcap-sdk-1.15.zip";
+ const NPCAP_SDK_SHA256: &str =
+ "52c7b9fb4abee3ad9fe739bb545c3efe77b731c8e127122bdf328eafdae3ed4f";
+
+ let npcap_sdk_download_url = format!("https://npcap.com/dist/{NPCAP_SDK}");
+ let cache_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?).join("target");
+ let npcap_sdk_cache_path = cache_dir.join(NPCAP_SDK);
+
+ let npcap_zip = match fs::read(&npcap_sdk_cache_path) {
+ // use cached (verify checksum)
+ Ok(zip_data) => {
+ eprintln!("Found cached npcap SDK");
+ verify_npcap_checksum(&zip_data)?;
+ zip_data
+ }
+ // download SDK
+ Err(_) => {
+ eprintln!("Downloading npcap SDK");
+
+ // download
+ let mut zip_data = vec![];
+ let _res = http_req::request::get(npcap_sdk_download_url, &mut zip_data)?;
+
+ // verify checksum before caching
+ verify_npcap_checksum(&zip_data)?;
+
+ // write cache
+ fs::create_dir_all(cache_dir)?;
+ let mut cache = fs::File::create(npcap_sdk_cache_path)?;
+ cache.write_all(&zip_data)?;
+
+ zip_data
+ }
+ };
+
+ fn verify_npcap_checksum(data: &[u8]) -> Result<()> {
+ let hash = Sha256::digest(data);
+ let actual = hash.iter().map(|b| format!("{b:02x}")).collect::();
+ if actual != NPCAP_SDK_SHA256 {
+ anyhow::bail!(
+ "Npcap SDK checksum mismatch!\n Expected: {}\n Actual: {}\n\
+ The downloaded file may be corrupted or tampered with.",
+ NPCAP_SDK_SHA256,
+ actual
+ );
+ }
+ eprintln!("Npcap SDK checksum verified: {actual}");
+ Ok(())
+ }
+
+ // extract libraries based on target architecture
+ let target = env::var("TARGET").unwrap_or_else(|_| "unknown".to_string());
+ let (packet_lib_path, wpcap_lib_path) = if target.contains("aarch64") {
+ ("Lib/ARM64/Packet.lib", "Lib/ARM64/wpcap.lib")
+ } else if target.contains("x86_64") {
+ ("Lib/x64/Packet.lib", "Lib/x64/wpcap.lib")
+ } else if target.contains("i686") || target.contains("i586") {
+ ("Lib/Packet.lib", "Lib/wpcap.lib")
+ } else {
+ panic!("Unsupported target: {}", target)
+ };
+
+ let mut archive = zip::ZipArchive::new(io::Cursor::new(npcap_zip))?;
+
+ // Extract Packet.lib
+ let mut packet_lib = archive.by_name(packet_lib_path)?;
+ let lib_dir = PathBuf::from(env::var("OUT_DIR")?).join("npcap_sdk");
+ fs::create_dir_all(&lib_dir)?;
+ let packet_lib_dest = lib_dir.join("Packet.lib");
+ let mut packet_file = fs::File::create(packet_lib_dest)?;
+ io::copy(&mut packet_lib, &mut packet_file)?;
+ drop(packet_lib);
+
+ // Extract wpcap.lib
+ let mut wpcap_lib = archive.by_name(wpcap_lib_path)?;
+ let wpcap_lib_dest = lib_dir.join("wpcap.lib");
+ let mut wpcap_file = fs::File::create(wpcap_lib_dest)?;
+ io::copy(&mut wpcap_lib, &mut wpcap_file)?;
+
+ println!(
+ "cargo:rustc-link-search=native={}",
+ lib_dir
+ .to_str()
+ .ok_or_else(|| anyhow::anyhow!("{lib_dir:?} is not valid UTF-8"))?
+ );
+
+ Ok(())
+}
diff --git a/crates/rustnet-capture/Cargo.toml b/crates/rustnet-capture/Cargo.toml
new file mode 100644
index 0000000..43f4f76
--- /dev/null
+++ b/crates/rustnet-capture/Cargo.toml
@@ -0,0 +1,27 @@
+[package]
+name = "rustnet-capture"
+version.workspace = true
+authors.workspace = true
+edition.workspace = true
+rust-version.workspace = true
+description = "libpcap/Npcap-based packet-capture backend for rustnet: device selection, BPF filters, macOS PKTAP, TUN/TAP, and a raw-frame reader"
+repository.workspace = true
+homepage.workspace = true
+documentation = "https://docs.rs/rustnet-capture"
+readme = "README.md"
+license.workspace = true
+keywords = ["network", "packet", "capture", "pcap", "bpf"]
+categories = ["network-programming"]
+
+[lib]
+name = "rustnet_capture"
+path = "src/lib.rs"
+
+# This crate yields raw frames + DLT only; parsing is rustnet-core's job. It
+# deliberately does NOT depend on rustnet-core, so a consumer wanting just a
+# capture source isn't forced to compile the full analysis stack (ring, aes,
+# maxminddb, ...). Pair it with rustnet-core at the application layer.
+[dependencies]
+anyhow.workspace = true
+log.workspace = true
+pcap = "2.4.0"
diff --git a/crates/rustnet-capture/README.md b/crates/rustnet-capture/README.md
new file mode 100644
index 0000000..bd5d5ce
--- /dev/null
+++ b/crates/rustnet-capture/README.md
@@ -0,0 +1,32 @@
+# rustnet-capture
+
+The packet-capture backend of [RustNet](https://github.com/domcyrus/rustnet),
+built on `libpcap` / `Npcap` via the [`pcap`](https://crates.io/crates/pcap)
+crate.
+
+This crate owns all of RustNet's pcap-based capture:
+
+- network device selection (with sensible defaults that skip virtual/loopback
+ adapters),
+- BPF-filter setup,
+- the macOS **PKTAP** fast path that attaches process metadata to packets,
+- TUN/TAP interface handling,
+- and a simple [`PacketReader`] that yields raw link-layer frames plus the
+ libpcap data-link type (DLT).
+
+## Why a separate crate?
+
+It is intentionally decoupled from the analysis core (`rustnet-core`) and the
+`rustnet` application so you can compose them differently:
+
+- pair `rustnet-capture` + `rustnet-core` to build a **headless** tool (e.g. a
+ Prometheus exporter) with no terminal UI;
+- or swap `rustnet-capture` out for a **bespoke capture path** (for example a
+ root-free macOS pktap helper) while still using `rustnet-core` for parsing.
+
+Capture produces bytes; turning those bytes into connections, DPI results, and
+GeoIP/DNS lookups is `rustnet-core`'s job.
+
+## License
+
+Apache-2.0
diff --git a/crates/rustnet-capture/src/lib.rs b/crates/rustnet-capture/src/lib.rs
new file mode 100644
index 0000000..975c48f
--- /dev/null
+++ b/crates/rustnet-capture/src/lib.rs
@@ -0,0 +1,607 @@
+//! # rustnet-capture
+//!
+//! Packet-capture backend for [RustNet](https://github.com/domcyrus/rustnet),
+//! built on `libpcap` / `Npcap` via the [`pcap`] crate. This crate owns all of
+//! RustNet's pcap-based capture: device selection, BPF-filter setup, the macOS
+//! PKTAP fast path for process metadata, TUN/TAP handling, and a simple
+//! [`PacketReader`] that yields raw link-layer frames.
+//!
+//! It is deliberately separate from the analysis core (`rustnet-core`) and the
+//! `rustnet` application so that alternative front-ends — e.g. a headless
+//! Prometheus exporter — can pair capture with `rustnet-core` without pulling
+//! in the TUI, and so that platforms wanting a bespoke capture path (e.g. a
+//! root-free macOS pktap helper) can swap this crate out entirely.
+//!
+//! Capture yields raw bytes plus the libpcap data-link type (DLT); parsing
+//! those bytes is `rustnet-core`'s job.
+use anyhow::{Result, anyhow};
+use pcap::{Active, Capture, Device, Error as PcapError};
+use std::time::{Duration, SystemTime, UNIX_EPOCH};
+
+/// Why the macOS PKTAP fast path could not be used during capture setup.
+///
+/// PKTAP attaches process metadata to captured packets, but it requires root,
+/// a usable BPF device, the default interface, and no BPF filter. When any of
+/// those preconditions fail, capture records the reason here so the application
+/// can surface it (the `rustnet` binary maps these to its UI-level
+/// `DegradationReason`). Kept capture-native so this crate has no dependency on
+/// the application's process-attribution types.
+#[cfg(target_os = "macos")]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum PktapUnavailable {
+ /// Could not open the BPF device (typically a permission issue).
+ NoBpfDeviceAccess,
+ /// PKTAP device creation failed — almost always missing root privileges.
+ MissingRootPrivileges,
+ /// A specific interface was requested; PKTAP only works on the default path.
+ InterfaceSpecified,
+ /// A BPF filter was supplied, which is incompatible with PKTAP.
+ BpfFilterIncompatible,
+}
+
+/// Stores why PKTAP is not available on macOS (set during capture setup).
+#[cfg(target_os = "macos")]
+pub static PKTAP_DEGRADATION_REASON: std::sync::OnceLock =
+ std::sync::OnceLock::new();
+
+/// Packet capture configuration
+#[derive(Debug, Clone)]
+pub struct CaptureConfig {
+ /// Network interface name (None for default)
+ pub interface: Option,
+ /// Snapshot length (bytes to capture per packet)
+ pub snaplen: i32,
+ /// Buffer size for packet capture
+ pub buffer_size: i32,
+ /// Read timeout in milliseconds
+ pub timeout_ms: i32,
+ /// BPF filter string
+ pub filter: Option,
+}
+
+impl Default for CaptureConfig {
+ fn default() -> Self {
+ Self {
+ interface: None,
+ snaplen: 1514, // Limit packet size to keep more in buffer
+ buffer_size: 20_000_000, // 20MB buffer
+ timeout_ms: 150, // 150ms timeout for UI responsiveness
+ filter: None, // Start without filter to ensure we see packets
+ }
+ }
+}
+
+/// Find the best active network device
+fn find_best_device() -> Result {
+ let devices = Device::list().map_err(|e| {
+ anyhow!(
+ "Failed to list network devices: {}. This may indicate insufficient privileges.",
+ e
+ )
+ })?;
+
+ log::info!(
+ "Scanning {} devices for best active interface...",
+ devices.len()
+ );
+
+ // Log all devices for debugging
+ for d in &devices {
+ let has_valid_ip = d.addresses.iter().any(|addr| match &addr.addr {
+ std::net::IpAddr::V4(v4) => {
+ !v4.is_link_local() && !v4.is_loopback() && !v4.is_unspecified()
+ }
+ std::net::IpAddr::V6(v6) => {
+ !v6.is_loopback() && !v6.is_multicast() && !v6.is_unspecified()
+ }
+ });
+
+ log::debug!(
+ " Device: {} [up: {}, running: {}, has_ip: {}]",
+ d.name,
+ d.flags.is_up(),
+ d.flags.is_running(),
+ has_valid_ip
+ );
+ }
+
+ if devices.is_empty() {
+ return Err(anyhow!("No network devices found"));
+ }
+
+ // Find the best active device
+ let suitable_device = devices
+ .iter()
+ // First priority: up, running, has a valid IP address, and NOT virtual
+ .find(|d| {
+ // Check if it's a virtual/problematic interface
+ let desc_lower = d
+ .desc
+ .as_ref()
+ .map(|s| s.to_lowercase())
+ .unwrap_or_default();
+ let is_virtual = desc_lower.contains("hyper-v")
+ || desc_lower.contains("vmware")
+ || desc_lower.contains("virtualbox");
+
+ !d.name.starts_with("lo")
+ // Note: 'any' is excluded here because it's not a real interface
+ // Users can still specify '-i any' explicitly on Linux
+ && d.name != "any"
+ && !is_virtual // Skip virtual adapters in first priority too
+ && d.flags.is_up()
+ && d.flags.is_running()
+ && d.addresses.iter().any(|addr| {
+ match &addr.addr {
+ std::net::IpAddr::V4(v4) => {
+ !v4.is_link_local() && !v4.is_loopback() && !v4.is_unspecified()
+ }
+ std::net::IpAddr::V6(_v6) => false, // Skip IPv6 for now
+ }
+ })
+ })
+ // Second priority: common active interface names
+ .or_else(|| {
+ devices.iter().find(|d| {
+ (d.name == "en0" || d.name == "en1" || d.name.starts_with("eth"))
+ && d.flags.is_up()
+ && d.addresses.iter().any(|addr| addr.addr.is_ipv4())
+ })
+ })
+ // Third priority: any up interface with valid addresses (excluding problematic ones)
+ .or_else(|| {
+ devices.iter().find(|d| {
+ // Check if it's a virtual/problematic interface
+ let desc_lower = d
+ .desc
+ .as_ref()
+ .map(|s| s.to_lowercase())
+ .unwrap_or_default();
+ let is_virtual = desc_lower.contains("hyper-v")
+ || desc_lower.contains("virtual")
+ || desc_lower.contains("vmware")
+ || desc_lower.contains("virtualbox")
+ || desc_lower.contains("loopback");
+
+ !d.name.starts_with("lo") &&
+ !d.name.starts_with("ap") && // Skip Apple's ap interfaces
+ !d.name.starts_with("awdl") && // Skip Apple Wireless Direct
+ !d.name.starts_with("llw") && // Skip Low latency WLAN
+ !d.name.starts_with("bridge") && // Skip bridges
+ // TUN/TAP interfaces now supported - removed utun/tun/tap exclusion
+ !d.name.starts_with("vmnet") && // Skip VM interfaces
+ // Note: 'any' is excluded here because it's not a real interface
+ // Users can still specify '-i any' explicitly on Linux
+ d.name != "any" &&
+ !is_virtual && // Skip virtual adapters
+ d.flags.is_up() &&
+ !d.addresses.is_empty()
+ })
+ })
+ .cloned();
+
+ match suitable_device {
+ Some(device) => {
+ log::info!(
+ "Selected active device: {} ({} addresses)",
+ device.name,
+ device.addresses.len()
+ );
+ for addr in &device.addresses {
+ log::debug!(" Address: {}", addr.addr);
+ }
+ Ok(device)
+ }
+ None => {
+ log::error!("No suitable active network device found!");
+ log::error!("Try specifying an interface manually with -i flag");
+ Err(anyhow!(
+ "No active network interface found. Use -i to specify one manually."
+ ))
+ }
+ }
+}
+
+/// Setup packet capture with the given configuration
+pub fn setup_packet_capture(config: CaptureConfig) -> Result<(Capture, String, i32)> {
+ // Try PKTAP first on macOS for process metadata, but only when:
+ // - No interface is explicitly specified
+ // - No BPF filter is specified (BPF filters don't work with PKTAP's linktype 149)
+ #[cfg(target_os = "macos")]
+ if config.interface.is_none() && config.filter.is_none() {
+ log::info!("Attempting to use PKTAP for process metadata on macOS");
+
+ match Capture::from_device("pktap") {
+ Ok(pktap_builder) => {
+ let pktap_cap = pktap_builder
+ .promisc(false) // PKTAP doesn't use promiscuous mode
+ .snaplen(config.snaplen)
+ .buffer_size(config.buffer_size)
+ .timeout(config.timeout_ms)
+ .immediate_mode(true)
+ .want_pktap(true)
+ .open();
+
+ match pktap_cap {
+ Ok(mut cap) => {
+ // Try to set direction for better performance (optional)
+ if let Err(e) = cap.direction(pcap::Direction::InOut) {
+ log::debug!("Could not set PKTAP direction: {}", e);
+ }
+
+ let linktype = cap.get_datalink();
+ log::info!(
+ "✓ PKTAP enabled successfully, linktype: {} ({})",
+ linktype.0,
+ if linktype.0 == 149 {
+ "Apple PKTAP"
+ } else {
+ "Unknown"
+ }
+ );
+
+ // Apply BPF filter if specified
+ if let Some(filter) = &config.filter {
+ log::info!("Applying BPF filter to PKTAP: {}", filter);
+ cap.filter(filter, true)?;
+ }
+
+ log::info!("PKTAP capture ready - process metadata will be available");
+ return Ok((cap, "pktap".to_string(), linktype.0));
+ }
+ Err(e) => {
+ log::warn!("Failed to open PKTAP capture: {}", e);
+ log::info!(
+ "PKTAP requires root privileges - run with 'sudo' for process metadata support"
+ );
+ log::info!(
+ "Falling back to regular capture (process detection will use lsof)"
+ );
+ // Store degradation reason - failed to open (permission issue)
+ let _ = PKTAP_DEGRADATION_REASON.set(PktapUnavailable::NoBpfDeviceAccess);
+ }
+ }
+ }
+ Err(e) => {
+ log::warn!("Failed to create PKTAP device: {}", e);
+ log::info!(
+ "PKTAP requires root privileges - run with 'sudo' for process metadata support"
+ );
+ log::info!("Falling back to regular capture (process detection will use lsof)");
+ // Store degradation reason - failed to create device (permission issue)
+ let _ = PKTAP_DEGRADATION_REASON.set(PktapUnavailable::MissingRootPrivileges);
+ }
+ }
+ }
+
+ // Track PKTAP degradation reasons for macOS
+ #[cfg(target_os = "macos")]
+ {
+ if config.interface.is_some() {
+ // Specific interface requested - PKTAP can't be used
+ let _ = PKTAP_DEGRADATION_REASON.set(PktapUnavailable::InterfaceSpecified);
+ }
+ if config.filter.is_some() {
+ log::warn!(
+ "BPF filter specified - using regular capture instead of PKTAP (BPF filters don't work with PKTAP)"
+ );
+ // Store degradation reason - BPF filter incompatible
+ let _ = PKTAP_DEGRADATION_REASON.set(PktapUnavailable::BpfFilterIncompatible);
+ }
+ }
+
+ // Fallback to regular capture (original code)
+ log::info!("Setting up regular packet capture");
+ let device = find_capture_device(&config.interface)?;
+
+ // Check if this is a TUN/TAP interface (for the log line below). This is a
+ // capture-side device concern, so we match the names here rather than pull
+ // all of `rustnet-core` into this crate just to label a log message. The
+ // actual TUN/TAP frame parsing still lives in `rustnet-core`.
+ let is_tun = device.name.starts_with("tun") || device.name.starts_with("utun");
+ let is_tap = device.name.starts_with("tap");
+ let is_tunnel = is_tun || is_tap;
+ let tunnel_type = if is_tun {
+ "TUN (Layer 3)"
+ } else if is_tap {
+ "TAP (Layer 2)"
+ } else {
+ "N/A"
+ };
+
+ log::info!(
+ "Setting up capture on device: {} ({}){}",
+ device.name,
+ device.desc.as_deref().unwrap_or("no description"),
+ if is_tunnel {
+ format!(" [Tunnel: {}]", tunnel_type)
+ } else {
+ String::new()
+ }
+ );
+
+ let device_name = device.name.clone();
+
+ // Create capture handle with promiscuous mode disabled
+ // We use non-promiscuous mode (read-only packet capture) which only requires CAP_NET_RAW
+ let cap = Capture::from_device(device)?
+ .promisc(false)
+ .snaplen(config.snaplen)
+ .buffer_size(config.buffer_size)
+ .timeout(config.timeout_ms)
+ .immediate_mode(true); // Parse packets ASAP
+
+ // Open the capture
+ let mut cap = cap.open()?;
+
+ // Apply BPF filter if specified
+ if let Some(filter) = &config.filter {
+ log::info!("Applying BPF filter: {}", filter);
+ cap.filter(filter, true)?;
+ }
+
+ // Note: We're not setting non-blocking mode as we're using timeout instead
+ let linktype = cap.get_datalink();
+
+ Ok((cap, device_name, linktype.0))
+}
+
+/// Validate that the specified interface exists (if provided)
+/// This is useful for failing fast before starting capture threads
+pub fn validate_interface(interface_name: &Option) -> Result<()> {
+ if let Some(name) = interface_name {
+ // This will return an error if the interface doesn't exist
+ find_capture_device(&Some(name.clone()))?;
+ }
+ Ok(())
+}
+
+/// Find a capture device by name or return the default
+fn find_capture_device(interface_name: &Option) -> Result {
+ match interface_name {
+ Some(name) => {
+ log::info!("Looking for interface: {}", name);
+
+ // Special handling for 'any' interface
+ if name == "any" {
+ #[cfg(not(target_os = "linux"))]
+ {
+ return Err(anyhow!(
+ "The 'any' interface is only supported on Linux.\n\
+ On your platform, please specify a specific interface with -i .\n\
+ Run without -i to auto-detect the default interface."
+ ));
+ }
+
+ #[cfg(target_os = "linux")]
+ {
+ log::info!("Using 'any' pseudo-interface to capture on all interfaces");
+ }
+ }
+
+ // List all devices
+ let devices = Device::list()?;
+
+ // Find exact match first
+ if let Some(device) = devices.iter().find(|d| d.name == *name) {
+ return Ok(device.clone());
+ }
+
+ // Try case-insensitive match
+ let name_lower = name.to_lowercase();
+ if let Some(device) = devices.iter().find(|d| d.name.to_lowercase() == name_lower) {
+ return Ok(device.clone());
+ }
+
+ // List available interfaces for error message
+ let available: Vec = devices.iter().map(|d| d.name.clone()).collect();
+
+ Err(anyhow!(
+ "Interface '{}' not found. Available interfaces: {}",
+ name,
+ available.join(", ")
+ ))
+ }
+ None => {
+ log::info!("No interface specified, using default");
+
+ // Resolve active interface via OS routing table by creating a connectionless UDP socket
+ if let Some(active_ip) = std::net::UdpSocket::bind("0.0.0.0:0")
+ .and_then(|s| {
+ let _ = s.connect("8.8.8.8:53");
+ s.local_addr()
+ })
+ .ok()
+ .map(|addr| addr.ip())
+ {
+ log::info!("Found active routed IP: {}", active_ip);
+ if let Ok(devices) = Device::list()
+ && let Some(device) = devices
+ .into_iter()
+ .find(|d| d.addresses.iter().any(|a| a.addr == active_ip))
+ {
+ log::info!("Selected interface {} based on active route", device.name);
+ return Ok(device);
+ }
+ }
+ log::info!("Fallback: using libpcap default device logic");
+
+ // Try to get default device
+ match Device::lookup() {
+ Ok(Some(device)) => {
+ log::info!(
+ "Found default device: {} ({})",
+ device.name,
+ device.desc.as_deref().unwrap_or("no description")
+ );
+
+ // Check if the default device is actually active
+ let has_valid_ip = device.addresses.iter().any(|addr| {
+ match &addr.addr {
+ std::net::IpAddr::V4(v4) => {
+ !v4.is_link_local() && !v4.is_loopback() && !v4.is_unspecified()
+ }
+ std::net::IpAddr::V6(_v6) => false, // Skip IPv6 for now
+ }
+ });
+
+ // Check if it's a problematic interface type
+ // Note: 'any' is excluded on non-Linux platforms where it doesn't work
+ let is_problematic = device.name.starts_with("ap")
+ || device.name.starts_with("awdl")
+ || device.name.starts_with("llw")
+ || device.name.starts_with("bridge")
+ // TUN/TAP interfaces now supported - removed utun/tun/tap check
+ || device.name.starts_with("vmnet")
+ || (device.name == "any" && !cfg!(target_os = "linux"))
+ || device.flags.is_loopback();
+
+ if device.flags.is_up()
+ && device.flags.is_running()
+ && has_valid_ip
+ && !is_problematic
+ {
+ log::info!("Default device appears active, using it");
+ Ok(device)
+ } else {
+ log::warn!(
+ "Default device '{}' is not suitable (up: {}, running: {}, has_ip: {}, problematic: {})",
+ device.name,
+ device.flags.is_up(),
+ device.flags.is_running(),
+ has_valid_ip,
+ is_problematic
+ );
+ log::info!("Looking for a better interface...");
+
+ // Fall through to the device selection logic below
+ find_best_device()
+ }
+ }
+ Ok(None) => {
+ log::info!("No default device found");
+ find_best_device()
+ }
+ Err(e) => Err(e.into()),
+ }
+ }
+ }
+}
+
+/// Simple packet reader that handles timeouts gracefully
+pub struct PacketReader {
+ capture: Capture,
+}
+
+/// A captured link-layer frame with the timestamp reported by libpcap/Npcap.
+#[derive(Debug, Clone)]
+pub struct CapturedPacket {
+ pub data: Vec,
+ pub timestamp: SystemTime,
+ pub original_len: u32,
+}
+
+impl PacketReader {
+ pub fn new(capture: Capture) -> Self {
+ Self { capture }
+ }
+
+ /// Read next packet, returning None on timeout.
+ pub fn next_packet(&mut self) -> Result> {
+ match self.capture.next_packet() {
+ Ok(packet) => {
+ let ts = packet.header.ts;
+ Ok(Some(CapturedPacket {
+ data: packet.data.to_vec(),
+ timestamp: timeval_to_system_time(ts.tv_sec, ts.tv_usec),
+ original_len: packet.header.len,
+ }))
+ }
+ Err(PcapError::TimeoutExpired) => Ok(None),
+ Err(e) => Err(e.into()),
+ }
+ }
+
+ /// Get capture statistics
+ pub fn stats(&mut self) -> Result {
+ let stats = self.capture.stats()?;
+ let capture_stats = CaptureStats {
+ received: stats.received,
+ dropped: stats.dropped,
+ if_dropped: stats.if_dropped,
+ };
+
+ // Log dropped packets if any occurred
+ if capture_stats.total_dropped() > 0 {
+ log::debug!(
+ "Total {} packets dropped (kernel: {}, interface: {})",
+ capture_stats.total_dropped(),
+ capture_stats.dropped,
+ capture_stats.if_dropped
+ );
+ }
+
+ Ok(capture_stats)
+ }
+}
+
+fn timeval_to_system_time(secs: S, usecs: U) -> SystemTime
+where
+ S: Into,
+ U: Into,
+{
+ let secs = secs.into();
+ let usecs = usecs.into().clamp(0, 999_999);
+ if secs < 0 {
+ UNIX_EPOCH
+ } else {
+ UNIX_EPOCH + Duration::from_secs(secs as u64) + Duration::from_micros(usecs as u64)
+ }
+}
+
+/// Packet capture statistics
+#[derive(Debug, Clone, Default)]
+pub struct CaptureStats {
+ pub received: u32,
+ pub dropped: u32,
+ /// Interface-level dropped packets (platform-specific)
+ pub if_dropped: u32,
+}
+
+impl CaptureStats {
+ /// Get total packets dropped (both kernel and interface level)
+ pub fn total_dropped(&self) -> u32 {
+ self.dropped.saturating_add(self.if_dropped)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_default_config() {
+ let config = CaptureConfig::default();
+ assert_eq!(config.snaplen, 1514);
+ assert!(config.filter.is_none()); // Default starts without filter
+ }
+
+ #[test]
+ fn test_udp_routing_resolution_can_execute() {
+ // Sanity-check test to ensure the OS handles UDP metric routing cleanly.
+ // It's perfectly fine if this fails in hermetic CI environments without outbound routes.
+ if let Ok(socket) = std::net::UdpSocket::bind("0.0.0.0:0")
+ && socket.connect("8.8.8.8:53").is_ok()
+ && let Ok(addr) = socket.local_addr()
+ {
+ assert!(
+ !addr.ip().is_loopback(),
+ "Active routed IP should not be loopback"
+ );
+ assert!(
+ !addr.ip().is_unspecified(),
+ "Active routed IP should not be unspecified"
+ );
+ }
+ }
+}
diff --git a/crates/rustnet-core/Cargo.toml b/crates/rustnet-core/Cargo.toml
new file mode 100644
index 0000000..55c87a8
--- /dev/null
+++ b/crates/rustnet-core/Cargo.toml
@@ -0,0 +1,36 @@
+[package]
+name = "rustnet-core"
+version.workspace = true
+authors.workspace = true
+edition.workspace = true
+rust-version.workspace = true
+description = "Network analysis library: packet parsing, protocol types, deep packet inspection, link-layer parsers, connection merging, and DNS/GeoIP/OUI lookups — the reusable core of rustnet"
+repository.workspace = true
+homepage.workspace = true
+documentation = "https://docs.rs/rustnet-core"
+readme = "README.md"
+license.workspace = true
+keywords = ["network", "packet", "dpi", "parser", "geoip"]
+categories = ["network-programming", "parser-implementations"]
+
+[lib]
+name = "rustnet_core"
+path = "src/lib.rs"
+
+[dependencies]
+anyhow.workspace = true
+log.workspace = true
+dashmap = "6.2"
+rustc-hash = "2"
+crossbeam = "0.8"
+dns-lookup = "3.0"
+pnet_datalink = "0.35"
+maxminddb = "0.29"
+flate2 = "1"
+ring = "0.17"
+aes = "0.9"
+
+[features]
+# Adds the Kubernetes pod/container metadata (`K8sInfo`) to `Connection`.
+# Resolution itself lives in the rustnet binary; no extra dependencies.
+kubernetes = []
diff --git a/crates/rustnet-core/README.md b/crates/rustnet-core/README.md
new file mode 100644
index 0000000..28607bf
--- /dev/null
+++ b/crates/rustnet-core/README.md
@@ -0,0 +1,38 @@
+# rustnet-core
+
+The reusable network-analysis core of [RustNet](https://github.com/domcyrus/rustnet).
+
+`rustnet-core` is the platform-independent, capture-independent layer that
+turns bytes into meaningful network information. It has **no dependency on
+libpcap, raw sockets, or OS process tables** — it operates on byte slices and
+parsed structures, so it can be embedded in any tool that already has packets
+to analyze.
+
+## Features
+
+- **Packet parsing** for Ethernet, Linux SLL/SLL2, PKTAP, raw IP, and TUN/TAP
+ link layers, plus IPv4/IPv6, TCP, UDP, ICMP, and IGMP.
+- **Deep packet inspection** for HTTP, HTTPS/TLS (SNI extraction), DNS, SSH,
+ QUIC, NTP, mDNS, LLMNR, DHCP, SNMP, SSDP, NetBIOS, and more.
+- **Connection merging** — fold parsed packets into long-lived connection
+ state with protocol-aware lifecycle tracking and TCP analytics
+ (retransmissions, out-of-order, fast-retransmit).
+- **GeoIP** lookups against MaxMind GeoLite2 databases.
+- **Reverse DNS** with background async resolution and caching.
+- **OUI vendor** and **service-name** resolution from baked-in datasets (no
+ runtime files required — the data is embedded at compile time).
+
+## Layout
+
+Everything lives under the [`network`] module and is re-exported at the crate
+root, so both `rustnet_core::network::types` and `rustnet_core::types` resolve
+to the same place.
+
+## Status
+
+The public API is currently `0.x` and may change between minor versions while
+the workspace split stabilizes.
+
+## License
+
+Apache-2.0
diff --git a/crates/rustnet-core/assets/oui.gz b/crates/rustnet-core/assets/oui.gz
new file mode 100644
index 0000000..0e014f5
Binary files /dev/null and b/crates/rustnet-core/assets/oui.gz differ
diff --git a/crates/rustnet-core/assets/services b/crates/rustnet-core/assets/services
new file mode 100644
index 0000000..50ad944
--- /dev/null
+++ b/crates/rustnet-core/assets/services
@@ -0,0 +1,11607 @@
+# /etc/services:
+# $Id: services,v 1.49 2017/08/18 12:43:23 ovasik Exp $
+#
+# Network services, Internet style
+# IANA services version: last updated 2021-01-19
+#
+# Note that it is presently the policy of IANA to assign a single well-known
+# port number for both TCP and UDP; hence, most entries here have two entries
+# even if the protocol doesn't support UDP operations.
+# Updated from RFC 1700, ``Assigned Numbers'' (October 1994). Not all ports
+# are included, only the more common ones.
+#
+# The latest IANA port assignments can be gotten from
+# http://www.iana.org/assignments/port-numbers
+# The Well Known Ports are those from 0 through 1023.
+# The Registered Ports are those from 1024 through 49151
+# The Dynamic and/or Private Ports are those from 49152 through 65535
+#
+# Each line describes one service, and is of the form:
+#
+# service-name port/protocol [aliases ...] [# comment]
+
+tcpmux 1/tcp # TCP port service multiplexer
+tcpmux 1/udp # TCP port service multiplexer
+rje 5/tcp # Remote Job Entry
+rje 5/udp # Remote Job Entry
+echo 7/tcp
+echo 7/udp
+discard 9/tcp sink null
+discard 9/udp sink null
+systat 11/tcp users
+systat 11/udp users
+daytime 13/tcp
+daytime 13/udp
+qotd 17/tcp quote
+qotd 17/udp quote
+chargen 19/tcp ttytst source
+chargen 19/udp ttytst source
+ftp-data 20/tcp
+ftp-data 20/udp
+# 21 is registered to ftp, but also used by fsp
+ftp 21/tcp
+ftp 21/udp fsp fspd
+ssh 22/tcp # The Secure Shell (SSH) Protocol
+ssh 22/udp # The Secure Shell (SSH) Protocol
+telnet 23/tcp
+telnet 23/udp
+# 24 - private mail system
+lmtp 24/tcp # LMTP Mail Delivery
+lmtp 24/udp # LMTP Mail Delivery
+smtp 25/tcp mail
+smtp 25/udp mail
+time 37/tcp timserver
+time 37/udp timserver
+#rap 38/tcp # Route Access Protocol
+#rap 38/udp # Route Access Protocol
+rlp 39/tcp resource # resource location
+rlp 39/udp resource # resource location
+nameserver 42/tcp name # IEN 116
+nameserver 42/udp name # IEN 116
+nicname 43/tcp whois
+nicname 43/udp whois
+tacacs 49/tcp # Login Host Protocol (TACACS)
+tacacs 49/udp # Login Host Protocol (TACACS)
+re-mail-ck 50/tcp # Remote Mail Checking Protocol
+re-mail-ck 50/udp # Remote Mail Checking Protocol
+domain 53/tcp # name-domain server
+domain 53/udp
+whois++ 63/tcp whoispp
+whois++ 63/udp whoispp
+bootps 67/tcp # BOOTP server
+bootps 67/udp
+bootpc 68/tcp dhcpc # BOOTP client
+bootpc 68/udp dhcpc
+tftp 69/tcp
+tftp 69/udp
+gopher 70/tcp # Internet Gopher
+gopher 70/udp
+netrjs-1 71/tcp # Remote Job Service
+netrjs-1 71/udp # Remote Job Service
+netrjs-2 72/tcp # Remote Job Service
+netrjs-2 72/udp # Remote Job Service
+netrjs-3 73/tcp # Remote Job Service
+netrjs-3 73/udp # Remote Job Service
+netrjs-4 74/tcp # Remote Job Service
+netrjs-4 74/udp # Remote Job Service
+finger 79/tcp
+finger 79/udp
+http 80/tcp www www-http # WorldWideWeb HTTP
+http 80/udp www www-http # HyperText Transfer Protocol
+http 80/sctp # HyperText Transfer Protocol
+kerberos 88/tcp kerberos5 krb5 # Kerberos v5
+kerberos 88/udp kerberos5 krb5 # Kerberos v5
+supdup 95/tcp
+supdup 95/udp
+hostname 101/tcp hostnames # usually from sri-nic
+hostname 101/udp hostnames # usually from sri-nic
+iso-tsap 102/tcp tsap # part of ISODE.
+csnet-ns 105/tcp cso # also used by CSO name server
+csnet-ns 105/udp cso
+# unfortunately the poppassd (Eudora) uses a port which has already
+# been assigned to a different service. We list the poppassd as an
+# alias here. This should work for programs asking for this service.
+# (due to a bug in inetd the 3com-tsmux line is disabled)
+#3com-tsmux 106/tcp poppassd
+#3com-tsmux 106/udp poppassd
+rtelnet 107/tcp # Remote Telnet
+rtelnet 107/udp
+pop2 109/tcp pop-2 postoffice # POP version 2
+pop2 109/udp pop-2
+pop3 110/tcp pop-3 # POP version 3
+pop3 110/udp pop-3
+sunrpc 111/tcp portmapper rpcbind # RPC 4.0 portmapper TCP
+sunrpc 111/udp portmapper rpcbind # RPC 4.0 portmapper UDP
+auth 113/tcp authentication tap ident
+auth 113/udp authentication tap ident
+sftp 115/tcp
+sftp 115/udp
+uucp-path 117/tcp
+uucp-path 117/udp
+nntp 119/tcp readnews untp # USENET News Transfer Protocol
+nntp 119/udp readnews untp # USENET News Transfer Protocol
+ntp 123/tcp
+ntp 123/udp # Network Time Protocol
+netbios-ns 137/tcp # NETBIOS Name Service
+netbios-ns 137/udp
+netbios-dgm 138/tcp # NETBIOS Datagram Service
+netbios-dgm 138/udp
+netbios-ssn 139/tcp # NETBIOS session service
+netbios-ssn 139/udp
+imap 143/tcp imap2 # Interim Mail Access Proto v2
+imap 143/udp imap2
+snmp 161/tcp # Simple Net Mgmt Proto
+snmp 161/udp # Simple Net Mgmt Proto
+snmptrap 162/tcp # SNMPTRAP
+snmptrap 162/udp snmp-trap # Traps for SNMP
+cmip-man 163/tcp # ISO mgmt over IP (CMOT)
+cmip-man 163/udp
+cmip-agent 164/tcp
+cmip-agent 164/udp
+mailq 174/tcp # MAILQ
+mailq 174/udp # MAILQ
+xdmcp 177/tcp # X Display Mgr. Control Proto
+xdmcp 177/udp
+nextstep 178/tcp NeXTStep NextStep # NeXTStep window
+nextstep 178/udp NeXTStep NextStep # server
+bgp 179/tcp # Border Gateway Proto.
+bgp 179/udp
+bgp 179/sctp
+prospero 191/tcp # Cliff Neuman's Prospero
+prospero 191/udp
+irc 194/tcp # Internet Relay Chat
+irc 194/udp
+smux 199/tcp # SNMP Unix Multiplexer
+smux 199/udp
+at-rtmp 201/tcp # AppleTalk routing
+at-rtmp 201/udp
+at-nbp 202/tcp # AppleTalk name binding
+at-nbp 202/udp
+at-echo 204/tcp # AppleTalk echo
+at-echo 204/udp
+at-zis 206/tcp # AppleTalk zone information
+at-zis 206/udp
+qmtp 209/tcp # Quick Mail Transfer Protocol
+qmtp 209/udp # Quick Mail Transfer Protocol
+z39.50 210/tcp z3950 z39-50 wais # NISO Z39.50 database
+z39.50 210/udp z3950 z39-50 wais
+ipx 213/tcp # IPX
+ipx 213/udp
+imap3 220/tcp # Interactive Mail Access
+imap3 220/udp # Protocol v3
+link 245/tcp ttylink
+link 245/udp ttylink
+gist 270/udp # Q-mode encapsulation for GIST messages
+fatserv 347/tcp # Fatmen Server
+fatserv 347/udp # Fatmen Server
+rsvp_tunnel 363/tcp rsvp-tunnel
+rsvp_tunnel 363/udp rsvp-tunnel
+odmr 366/tcp # odmr required by fetchmail
+odmr 366/udp # odmr required by fetchmail
+rpc2portmap 369/tcp
+rpc2portmap 369/udp # Coda portmapper
+codaauth2 370/tcp
+codaauth2 370/udp # Coda authentication server
+ulistproc 372/tcp ulistserv # UNIX Listserv
+ulistproc 372/udp ulistserv
+ldap 389/tcp
+ldap 389/udp
+osb-sd 400/tcp # Oracle Secure Backup
+osb-sd 400/udp # Oracle Secure Backup
+svrloc 427/tcp # Server Location
+svrloc 427/udp # Server Location
+mobileip-agent 434/tcp
+mobileip-agent 434/udp
+mobilip-mn 435/tcp
+mobilip-mn 435/udp
+https 443/tcp # http protocol over TLS/SSL
+https 443/udp # http protocol over TLS/SSL
+https 443/sctp # http protocol over TLS/SSL
+snpp 444/tcp # Simple Network Paging Protocol
+snpp 444/udp # Simple Network Paging Protocol
+microsoft-ds 445/tcp
+microsoft-ds 445/udp
+kpasswd 464/tcp kpwd # Kerberos "passwd"
+kpasswd 464/udp kpwd # Kerberos "passwd"
+photuris 468/tcp
+photuris 468/udp
+saft 487/tcp # Simple Asynchronous File Transfer
+saft 487/udp # Simple Asynchronous File Transfer
+gss-http 488/tcp
+gss-http 488/udp
+pim-rp-disc 496/tcp
+pim-rp-disc 496/udp
+isakmp 500/tcp
+isakmp 500/udp
+gdomap 538/tcp # GNUstep distributed objects
+gdomap 538/udp # GNUstep distributed objects
+iiop 535/tcp
+iiop 535/udp
+dhcpv6-client 546/tcp
+dhcpv6-client 546/udp
+dhcpv6-server 547/tcp
+dhcpv6-server 547/udp
+rtsp 554/tcp # Real Time Stream Control Protocol
+rtsp 554/udp # Real Time Stream Control Protocol
+nntps 563/tcp # NNTP over SSL
+nntps 563/udp # NNTP over SSL
+whoami 565/tcp
+whoami 565/udp
+submission 587/tcp msa # mail message submission
+submission 587/udp msa # mail message submission
+npmp-local 610/tcp dqs313_qmaster # npmp-local / DQS
+npmp-local 610/udp dqs313_qmaster # npmp-local / DQS
+npmp-gui 611/tcp dqs313_execd # npmp-gui / DQS
+npmp-gui 611/udp dqs313_execd # npmp-gui / DQS
+hmmp-ind 612/tcp dqs313_intercell # HMMP Indication / DQS
+hmmp-ind 612/udp dqs313_intercell # HMMP Indication / DQS
+ipp 631/tcp # Internet Printing Protocol
+ipp 631/udp # Internet Printing Protocol
+ldaps 636/tcp # LDAP over SSL
+ldaps 636/udp # LDAP over SSL
+acap 674/tcp
+acap 674/udp
+ha-cluster 694/tcp # Heartbeat HA-cluster
+ha-cluster 694/udp # Heartbeat HA-cluster
+kerberos-adm 749/tcp # Kerberos `kadmin' (v5)
+kerberos-adm 749/udp # kerberos administration
+kerberos-iv 750/udp kerberos4 kerberos-sec kdc loadav
+kerberos-iv 750/tcp kerberos4 kerberos-sec kdc rfile
+webster 765/tcp # Network dictionary
+webster 765/udp
+phonebook 767/tcp # Network phonebook
+phonebook 767/udp
+rsync 873/tcp # rsync
+rsync 873/udp # rsync
+#rquotad unreserved in IANA!
+rquotad 875/tcp # rquota daemon
+#rquotad unreserved in IANA!
+rquotad 875/udp # rquota daemon
+telnets 992/tcp
+telnets 992/udp
+imaps 993/tcp # IMAP over SSL
+imaps 993/udp # IMAP over SSL
+pop3s 995/tcp # POP-3 over SSL
+pop3s 995/udp # POP-3 over SSL
+
+#
+# UNIX specific services
+#
+exec 512/tcp
+biff 512/udp comsat
+login 513/tcp
+who 513/udp whod
+shell 514/tcp cmd # no passwords used
+syslog 514/udp
+printer 515/tcp spooler # line printer spooler
+printer 515/udp spooler # line printer spooler
+talk 517/udp
+ntalk 518/udp
+utime 519/tcp unixtime
+utime 519/udp unixtime
+efs 520/tcp
+router 520/udp route routed # RIP
+ripng 521/tcp
+ripng 521/udp
+timed 525/tcp timeserver
+timed 525/udp timeserver
+tempo 526/tcp newdate
+courier 530/tcp rpc
+conference 531/tcp chat
+netnews 532/tcp
+netwall 533/udp # -for emergency broadcasts
+#nmsp 537/tcp # Networked Media Streaming Protocol
+#nmsp 537/udp # Networked Media Streaming Protocol
+uucp 540/tcp uucpd # uucp daemon
+klogin 543/tcp # Kerberized `rlogin' (v5)
+kshell 544/tcp krcmd # Kerberized `rsh' (v5)
+afpovertcp 548/tcp # AFP over TCP
+afpovertcp 548/udp # AFP over TCP
+remotefs 556/tcp rfs_server rfs # Brunhoff remote filesystem
+
+## FileMaker Inc
+# http-alt 591/tcp # FileMaker, Inc. - HTTP Alternate (see Port 80)
+# http-alt 591/udp # FileMaker, Inc. - HTTP Alternate (see Port 80)
+#epp 700/tcp # Extensible Provisioning Protocol
+#epp 700/udp # Extensible Provisioning Protocol
+
+#
+# From ``PORT NUMBERS'':
+#
+#>REGISTERED PORT NUMBERS
+#>
+#>The Registered Ports are listed by the IANA and on most systems can be
+#>used by ordinary user processes or programs executed by ordinary
+#>users.
+#>
+#>Ports are used in the TCP [RFC793] to name the ends of logical
+#>connections which carry long term conversations. For the purpose of
+#>providing services to unknown callers, a service contact port is
+#>defined. This list specifies the port used by the server process as
+#>its contact port.
+#>
+#>The IANA registers uses of these ports as a convienence to the
+#>community.
+#
+socks 1080/tcp # socks proxy server
+socks 1080/udp # socks proxy server
+
+# Port 1236 is registered as `bvcontrol', but is also used by the
+# Gracilis Packeten remote config server. The official name is listed as
+# the primary name, with the unregistered name as an alias.
+bvcontrol 1236/tcp rmtcfg # Daniel J. Walsh, Gracilis Packeten remote config server
+bvcontrol 1236/udp # Daniel J. Walsh
+
+h323hostcallsc 1300/tcp # H.323 Secure Call Control
+h323hostcallsc 1300/udp # H.323 Secure Call Control
+ms-sql-s 1433/tcp # Microsoft-SQL-Server
+ms-sql-s 1433/udp # Microsoft-SQL-Server
+ms-sql-m 1434/tcp # Microsoft-SQL-Monitor
+ms-sql-m 1434/udp # Microsoft-SQL-Monitor
+#csdmbase 1467/tcp # CSDMBASE
+#csdmbase 1467/udp # CSDMBASE
+#csdm 1468/tcp # CSDM
+#csdm 1468/udp # CSDM
+ica 1494/tcp # Citrix ICA Client
+ica 1494/udp # Citrix ICA Client
+wins 1512/tcp # Microsoft's Windows Internet Name Service
+wins 1512/udp # Microsoft's Windows Internet Name Service
+#ricardo-lm 1522/tcp # Ricardo North America License Manager
+#ricardo-lm 1522/udp # Ricardo North America License Manager
+ingreslock 1524/tcp
+ingreslock 1524/udp
+prospero-np 1525/tcp orasrv # Prospero non-privileged/oracle
+prospero-np 1525/udp orasrv
+datametrics 1645/tcp old-radius sightline # datametrics / old radius entry
+datametrics 1645/udp old-radius sightline # datametrics / old radius entry
+sa-msg-port 1646/tcp old-radacct # sa-msg-port / old radacct entry
+sa-msg-port 1646/udp old-radacct # sa-msg-port / old radacct entry
+kermit 1649/tcp
+kermit 1649/udp
+l2tp 1701/tcp l2f
+l2tp 1701/udp l2f
+h323gatedisc 1718/tcp
+h323gatedisc 1718/udp
+h323gatestat 1719/tcp
+h323gatestat 1719/udp
+h323hostcall 1720/tcp
+h323hostcall 1720/udp
+h323hostcall 1720/sctp # H.323 Call Control
+tftp-mcast 1758/tcp
+tftp-mcast 1758/udp
+mtftp 1759/udp spss-lm
+vdab 1775/tcp # data interchange between visual processing containers
+hello 1789/tcp
+hello 1789/udp
+radius 1812/tcp # Radius
+radius 1812/udp # Radius
+radius-acct 1813/tcp radacct # Radius Accounting
+radius-acct 1813/udp radacct # Radius Accounting
+mtp 1911/tcp #
+mtp 1911/udp #
+hsrp 1985/tcp # Cisco Hot Standby Router Protocol
+hsrp 1985/udp # Cisco Hot Standby Router Protocol
+licensedaemon 1986/tcp
+licensedaemon 1986/udp
+gdp-port 1997/tcp # Cisco Gateway Discovery Protocol
+gdp-port 1997/udp # Cisco Gateway Discovery Protocol
+sieve-filter 2000/tcp cisco-sccp # Sieve Mail Filter Daemon
+sieve-filter 2000/udp cisco-sccp # Sieve Mail Filter Daemon
+#raid-cd 2006/udp # raid
+nfs 2049/tcp nfsd shilp # Network File System
+nfs 2049/udp nfsd shilp # Network File System
+nfs 2049/sctp nfsd shilp # Network File System
+zephyr-srv 2102/tcp # Zephyr server
+zephyr-srv 2102/udp # Zephyr server
+zephyr-clt 2103/tcp # Zephyr serv-hm connection
+zephyr-clt 2103/udp # Zephyr serv-hm connection
+zephyr-hm 2104/tcp # Zephyr hostmanager
+zephyr-hm 2104/udp # Zephyr hostmanager
+#nvd 2184/tcp # NVD User
+#nvd 2184/udp # NVD User
+dali 2378/udp # DALI lighting control
+cvspserver 2401/tcp # CVS client/server operations
+cvspserver 2401/udp # CVS client/server operations
+venus 2430/tcp # codacon port
+venus 2430/udp # Venus callback/wbc interface
+venus-se 2431/tcp # tcp side effects
+venus-se 2431/udp # udp sftp side effect
+codasrv 2432/tcp # not used
+codasrv 2432/udp # server port
+codasrv-se 2433/tcp # tcp side effects
+codasrv-se 2433/udp # udp sftp side effectQ
+#unicontrol 2437/tcp # UniControl
+#unicontrol 2437/udp # UniControl
+
+
+# Ports numbered 2600 through 2606 are used by the zebra package without
+# being registred. The primary names are the registered names, and the
+# unregistered names used by zebra are listed as aliases.
+hpstgmgr 2600/tcp zebrasrv # HPSTGMGR
+hpstgmgr 2600/udp # HPSTGMGR
+discp-client 2601/tcp zebra # discp client
+discp-client 2601/udp # discp client
+discp-server 2602/tcp ripd # discp server
+discp-server 2602/udp # discp server
+servicemeter 2603/tcp ripngd # Service Meter
+servicemeter 2603/udp # Service Meter
+nsc-ccs 2604/tcp ospfd # NSC CCS
+nsc-ccs 2604/udp # NSC CCS
+nsc-posa 2605/tcp bgpd # NSC POSA
+nsc-posa 2605/udp # NSC POSA
+netmon 2606/tcp ospf6d # Dell Netmon
+netmon 2606/udp # Dell Netmon
+dict 2628/tcp # RFC 2229
+dict 2628/udp # RFC 2229
+corbaloc 2809/tcp # CORBA naming service locator
+#nmsigport 2817/tcp # NMSig Port
+#nmsigport 2817/udp # NMSig Port
+icpv2 3130/tcp # Internet Cache Protocol V2 (Squid)
+icpv2 3130/udp # Internet Cache Protocol V2 (Squid)
+#ceph 3300/tcp # Ceph monitor
+mysql 3306/tcp # MySQL
+mysql 3306/udp # MySQL
+trnsprntproxy 3346/tcp # Trnsprnt Proxy
+trnsprntproxy 3346/udp # Trnsprnt Proxy
+pxe 4011/udp altserviceboot # PXE server
+minirem 4120/tcp # MiniRem Remote Telemetry and Control
+aws-wsp 4195/tcp # AWS protocol for cloud remoting solution
+aws-wsp 4195/udp # AWS protocol for cloud remoting solution
+aws-wsp 4195/sctp # AWS protocol for cloud remoting solution
+aws-wsp 4195/dccp # AWS protocol for cloud remoting solution
+fud 4201/udp # Cyrus IMAP FUD Daemon
+opentelemetry 4317/tcp # OpenTelemetry Protocol
+rwhois 4321/tcp # Remote Who Is
+rwhois 4321/udp # Remote Who Is
+getty-focus 4332/tcp # Getty Images FOCUS service
+krb524 4444/tcp nv-video # Kerberos 5 to 4 ticket xlator
+krb524 4444/udp nv-video # Kerberos 5 to 4 ticket xlator
+ntske 4460/tcp # Network Time Security Key Establishment
+sixid 4606/tcp # Secure ID to IP registration and lookup
+dots-signal 4646/tcp # DOTS Signal Channel Protocol.
+dots-signal 4646/udp # DOTS Signal Channel Protocol.
+xcap-portal 4888/tcp # xcap code analysis portal public user access
+xcap-control 4889/tcp # xcap code analysis portal cluster control and administration
+burp 4971/tcp # BackUp and Restore Program
+rfe 5002/tcp # Radio Free Ethernet
+rfe 5002/udp # Actually uses UDP only
+cfengine 5308/tcp # CFengine
+cfengine 5308/udp # CFengine
+
+coap 5683/tcp # Constrained Application Protocol (CoAP)
+coaps 5684/tcp # Constrained Application Protocol (CoAP)
+cvsup 5999/tcp CVSup # CVSup file transfer/John Polstra/FreeBSD
+cvsup 5999/udp CVSup # CVSup file transfer/John Polstra/FreeBSD
+x11 6000/tcp X # the X Window System
+heliosd 6440/tcp # heliosd daemon
+checkmk-agent 6556/tcp # Checkmk Monitoring Agent
+babel-dtls 6699/udp # Babel Routing Protocol over DTLS
+split-ping 6924/tcp # Ping with RX/TX latency/loss split
+split-ping 6924/udp # Ping with RX/TX latency/loss split
+afs3-fileserver 7000/tcp # file server itself
+afs3-fileserver 7000/udp # file server itself
+afs3-callback 7001/tcp # callbacks to cache managers
+afs3-callback 7001/udp # callbacks to cache managers
+afs3-prserver 7002/tcp # users & groups database
+afs3-prserver 7002/udp # users & groups database
+afs3-vlserver 7003/tcp # volume location database
+afs3-vlserver 7003/udp # volume location database
+afs3-kaserver 7004/tcp # AFS/Kerberos authentication service
+afs3-kaserver 7004/udp # AFS/Kerberos authentication service
+afs3-volser 7005/tcp # volume managment server
+afs3-volser 7005/udp # volume managment server
+afs3-errors 7006/tcp # error interpretation service
+afs3-errors 7006/udp # error interpretation service
+afs3-bos 7007/tcp # basic overseer process
+afs3-bos 7007/udp # basic overseer process
+afs3-update 7008/tcp # server-to-server updater
+afs3-update 7008/udp # server-to-server updater
+afs3-rmtsys 7009/tcp # remote cache manager service
+afs3-rmtsys 7009/udp # remote cache manager service
+loreji-panel 7026/tcp # Loreji Webhosting Panel
+iba-cfg 7072/tcp # iba Device Configuration Protocol
+iba-cfg-disc 7072/udp # iba Device Configuration Protocol
+asa-gateways 7234/tcp # Traffic forwarding for Okta cloud infra
+ipluminary 7420/udp # Multichannel real-time lighting control
+rome 7663/tcp # Proprietary immutable distributed data storage
+rome 7663/udp # Proprietary immutable distributed data storage
+p2pevolvenet 8004/tcp # Opensource Evolv Enterprise Platform P2P Network Node Connection Prot
+warppipe 8007/tcp # I/O oriented cluster computing software
+warppipe 8007/udp # I/O oriented cluster computing software
+nvme-disc 8009/tcp # NVMe over Fabrics Discovery Service
+cfg-cloud 8015/tcp # Configuration Cloud Service
+ads-s 8016/tcp # Beckhoff Automation Device Specification
+arca-api 8023/tcp # ARCATrust vault API
+arca-api 8023/udp # ARCATrust vault API
+papachi-p2p-srv 8027/tcp # peer tracker and data relay service
+papachi-p2p-srv 8027/udp # peer tracker and data relay service
+enguity-xccetp 8041/tcp # Xcorpeon ASIC Carrier Ethernet Transport
+enguity-xccetp 8041/udp # Xcorpeon ASIC Carrier Ethernet Transport
+websnp 8084/tcp # Snarl Network Protocol over HTTP
+skynetflow 8111/udp # Skynetflow network services
+aruba-papi 8211/udp # Aruba Networks AP management
+espeasy-p2p 8266/udp # ESPeasy peer-2-peer communication
+semi-grpc 8710/tcp # gRPC for SEMI Standards implementations
+core-of-source 8767/tcp # Online mobile multiplayer game
+sandpolis 8768/tcp # Sandpolis Server
+oktaauthenticat 8769/tcp # Okta MultiPlatform Access Mgmt for Cloud Svcs
+pfcp 8805/udp # Destination Port number for PFCP
+hes-clip 8807/udp # HES-CLIP Interoperability protocol
+3gpp-monp 8809/udp # MCPTT Off-Network Protocol (MONP)
+dpp 8908/tcp # WFA Device Provisioning Protocol
+
+d-star 9011/udp # D-Star Routing digital voice+data for amateur radio
+cisco-aqos 9081/udp # Required for Adaptive Quality of Service
+hexxorecore 9111/tcp # Multiple Purpose, Distributed Message Bus
+hexxorecore 9111/udp # Multiple Purpose, Distributed Message Bus
+sapms 9310/tcp # SAP Message Server
+gnmi-gnoi 9339/tcp # gRPC Network Mgmt/Operations Interface
+p4runtime 9559/tcp # P4Runtime gRPC Service
+x510 9877/tcp # The X.510 wrapper protocol
+visweather 9979/tcp # Valley Information Systems Weather station data
+amanda 10080/tcp # amanda backup services
+amanda 10080/udp # amanda backup services
+cimple 10125/tcp # HotLink CIMple REST API
+cirrossp 10443/tcp # CirrosSP Workstation Communication
+xcompute 11235/tcp # numerical systems messaging
+xcompute 11235/sctp # numerical systems messaging
+pgpkeyserver 11371/tcp hkp # PGP/GPG public keyserver
+pgpkeyserver 11371/udp hkp # PGP/GPG public keyserver
+asgcypresstcps 11489/tcp # ASG Cypress Secure Only
+h323callsigalt 11720/tcp 323callsigalt # H323 Call Signal Alternate
+h323callsigalt 11720/udp 323callsigalt # H323 Call Signal Alternate
+tibsd 11971/tcp # TiBS Service
+bprd 13720/tcp # BPRD (VERITAS NetBackup)
+bprd 13720/udp # BPRD (VERITAS NetBackup)
+bpdbm 13721/tcp # BPDBM (VERITAS NetBackup)
+bpdbm 13721/udp # BPDBM (VERITAS NetBackup)
+bpjava-msvc 13722/tcp # BP Java MSVC Protocol
+bpjava-msvc 13722/udp # BP Java MSVC Protocol
+vnetd 13724/tcp # Veritas Network Utility
+vnetd 13724/udp # Veritas Network Utility
+bpcd 13782/tcp # VERITAS NetBackup
+bpcd 13782/udp # VERITAS NetBackup
+vopied 13783/tcp # VOPIED Protocol
+vopied 13783/udp # VOPIED Protocol
+
+heythings 18516/udp # HeyThings Device communicate service
+faircom-db 19790/tcp # FairCom Database
+trinket-agent 21212/tcp # Distributed artificial intelligence
+cohesity-agent 21213/tcp # Cohesity backup agents
+
+
+
+# This port is registered as wnn6, but also used under the unregistered name
+# "wnn4" by the FreeWnn package.
+wnn6 22273/tcp wnn4
+wnn6 22273/udp wnn4
+
+showcockpit-net 22333/tcp # ShowCockpit Networking
+showcockpit-net 22333/udp # ShowCockpit Networking
+vrmg-ip 24323/tcp # Verimag mobile class protocol over TCP
+
+
+quake 26000/tcp
+quake 26000/udp
+wnn6-ds 26208/tcp
+wnn6-ds 26208/udp
+flex-lmadmin 27010/tcp # A protocol for managing license services
+mongodb 27017/tcp # Mongo database system
+gruber-cashreg 28010/tcp # Gruber cash registry protocol
+saltd-licensing 29000/tcp # Siemens Licensing Server
+gs-realtime 30400/tcp # GroundStar RealTime System
+wg-endpt-comms 33000/tcp # WatchGuard Endpoint Communications
+
+traceroute 33434/tcp
+traceroute 33434/udp
+
+mtrace 33435/udp # IP Multicast Traceroute
+
+digilent-adept 33890/tcp # Adept IP protocol
+3gpp-w1ap 37472/sctp # W1 signalling transport
+ng-control 38412/sctp # NG Control Plane (3GPP)
+xn-control 38422/sctp # Xn Control Plane (3GPP)
+e1-interface 38462/sctp # E1 signalling transport (3GPP)
+f1-control 38472/sctp # F1 Control Plane (3GPP)
+hmip-routing 43438/udp # HmIP LAN Routing
+acronis-backup 44445/tcp # Acronis Backup Gateway service port
+juka 48048/tcp # Juliar Programming Language Protocol
+inspider 49150/tcp # InSpider System
+
+
+
+
+#
+# Datagram Delivery Protocol services
+#
+rtmp 1/ddp # Routing Table Maintenance Protocol
+nbp 2/ddp # Name Binding Protocol
+echo 4/ddp # AppleTalk Echo Protocol
+zip 6/ddp # Zone Information Protocol
+
+#
+# Kerberos (Project Athena/MIT) services
+# Note that these are for Kerberos v4, and are unregistered/unofficial. Sites
+# running v4 should uncomment these and comment out the v5 entries above.
+#
+kerberos_master 751/udp pump # Kerberos authentication
+kerberos_master 751/tcp pump # Kerberos authentication
+passwd_server 752/udp qrh # Kerberos passwd server
+krbupdate 760/tcp kreg ns # Kerberos registration
+kpop 1109/tcp # Pop with Kerberos
+knetd 2053/tcp lot105-ds-upd # Kerberos de-multiplexor
+
+#
+# Kerberos 5 services, also not registered with IANA
+#
+krb5_prop 754/tcp tell # Kerberos slave propagation
+eklogin 2105/tcp minipay # Kerberos encrypted rlogin
+
+#
+# Unregistered but necessary(?) (for NetBSD) services
+#
+supfilesrv 871/tcp # SUP server
+supfiledbg 1127/tcp kwdb-commn # SUP debugging
+
+#
+# Unregistered but useful/necessary other services
+#
+netstat 15/tcp # (was once asssigned, no more)
+poppassd 106/tcp # Eudora
+poppassd 106/udp # Eudora
+omirr 808/tcp omirrd # online mirror
+omirr 808/udp omirrd # online mirror
+swat 901/tcp smpnameres # Samba Web Administration Tool
+rndc 953/tcp # rndc control sockets (BIND 9)
+rndc 953/udp # rndc control sockets (BIND 9)
+skkserv 1178/tcp sgi-storman # SKK Japanese input method
+xtel 1313/tcp bmc_patroldb bmc-patroldb # french minitel
+support 1529/tcp prmsd gnatsd coauthor # GNATS, cygnus bug tracker
+cfinger 2003/tcp brutus # GNU Finger
+ninstall 2150/tcp dynamic3d # ninstall service
+ninstall 2150/udp dynamic3d # ninstall service
+afbackup 2988/tcp hippad # Afbackup system
+afbackup 2988/udp hippad # Afbackup system
+squid 3128/tcp ndl-aas # squid web proxy
+prsvp 3455/tcp # RSVP Port
+prsvp 3455/udp # RSVP Port
+distcc 3632/tcp # distcc
+svn 3690/tcp # Subversion
+svn 3690/udp # Subversion
+postgres 5432/tcp postgresql # POSTGRES
+postgres 5432/udp postgresql # POSTGRES
+fax 4557/tcp # FAX transmission service (old)
+hylafax 4559/tcp # HylaFAX client-server protocol (new)
+sgi-dgl 5232/tcp csedaemon # SGI Distributed Graphics
+sgi-dgl 5232/udp
+llmnr 5355/tcp hostmon # LLMNR
+llmnr 5355/udp hostmon # LLMNR
+canna 5680/tcp auriga-router
+x11-ssh-offset 6010/tcp # SSH X11 forwarding offset
+xfs 7100/tcp font-service # X font server
+tircproxy 7666/tcp # Tircproxy
+# IANA claims 8008 for http-alt
+webcache 8080/tcp http-alt # WWW caching service
+webcache 8080/udp http-alt # WWW caching service
+tproxy 8081/tcp sunproxyadmin # Transparent Proxy
+tproxy 8081/udp sunproxyadmin # Transparent Proxy
+jetdirect 9100/tcp laserjet hplj hp-pdl-datastr pdl-datastream
+mandelspawn 9359/udp mandelbrot # network mandelbrot
+kamanda 10081/tcp famdc # amanda backup services (Kerberos)
+kamanda 10081/udp famdc # amanda backup services (Kerberos)
+amandaidx 10082/tcp # amanda backup services
+amidxtape 10083/tcp # amanda backup services
+isdnlog 20011/tcp # isdn logging system
+isdnlog 20011/udp # isdn logging system
+wnn4_Kr 22305/tcp cis # used by the kWnn package
+wnn4_Cn 22289/tcp # used by the cWnn package
+wnn4_Tw 22321/tcp # used by the tWnn package
+binkp 24554/tcp # Binkley
+binkp 24554/udp # Binkley
+sdtvwcam 24666/tcp # Service used by SmarDTV to communicate between a CAM and second screen application
+canditv 24676/tcp # Canditv Message Service
+canditv 24676/udp # Canditv Message Service
+asp 27374/tcp # Address Search Protocol
+asp 27374/udp # Address Search Protocol
+tfido 60177/tcp # Ifmail
+tfido 60177/udp # Ifmail
+fido 60179/tcp # Ifmail
+fido 60179/udp # Ifmail
+
+
+# Updated additional list from IANA with all missing services 10/04/2015
+#spr-itunes 0/tcp spl-itunes # Shirt Pocket netTunes - no port allocated
+compressnet 2/tcp # Management Utility
+compressnet 2/udp # Management Utility
+#compressnet 3/tcp # Compression Process
+#compressnet 3/udp # Compression Process
+discard 9/sctp # Discard
+discard 9/dccp # Discard SC:DISC
+ftp-data 20/sctp # FTP
+ftp 21/sctp # FTP
+ssh 22/sctp # SSH
+nsw-fe 27/tcp # NSW User System FE
+nsw-fe 27/udp # NSW User System FE
+msg-icp 29/tcp # MSG ICP
+msg-icp 29/udp # MSG ICP
+msg-auth 31/tcp # MSG Authentication
+msg-auth 31/udp # MSG Authentication
+dsp 33/tcp # Display Support Protocol
+dsp 33/udp # Display Support Protocol
+graphics 41/tcp # Graphics
+graphics 41/udp # Graphics
+mpm-flags 44/tcp # MPM FLAGS Protocol
+mpm-flags 44/udp # MPM FLAGS Protocol
+mpm 45/tcp # Message Processing Module [recv]
+mpm 45/udp # Message Processing Module [recv]
+mpm-snd 46/tcp # MPM [default send]
+mpm-snd 46/udp # MPM [default send]
+ni-ftp 47/tcp # NI FTP
+ni-ftp 47/udp # NI FTP
+auditd 48/tcp # Digital Audit Daemon
+auditd 48/udp # Digital Audit Daemon
+la-maint 51/tcp # IMP Logical Address Maintenance
+la-maint 51/udp # IMP Logical Address Maintenance
+xns-time 52/tcp # XNS Time Protocol
+xns-time 52/udp # XNS Time Protocol
+xns-ch 54/tcp # XNS Clearinghouse
+xns-ch 54/udp # XNS Clearinghouse
+isi-gl 55/tcp # ISI Graphics Language
+isi-gl 55/udp # ISI Graphics Language
+xns-auth 56/tcp # XNS Authentication
+xns-auth 56/udp # XNS Authentication
+xns-mail 58/tcp # XNS Mail
+xns-mail 58/udp # XNS Mail
+ni-mail 61/tcp # NI MAIL
+ni-mail 61/udp # NI MAIL
+acas 62/tcp # ACA Services
+acas 62/udp # ACA Services
+covia 64/tcp # Communications Integrator (CI)
+covia 64/udp # Communications Integrator (CI)
+tacacs-ds 65/tcp # TACACS-Database Service
+tacacs-ds 65/udp # TACACS-Database Service
+sql*net 66/tcp # Oracle SQL*NET
+sql*net 66/udp # Oracle SQL*NET
+deos 76/tcp # Distributed External Object Store
+deos 76/udp # Distributed External Object Store
+vettcp 78/tcp # vettcp
+vettcp 78/udp # vettcp
+xfer 82/tcp # XFER Utility
+xfer 82/udp # XFER Utility
+ctf 84/tcp # Common Trace Facility
+ctf 84/udp # Common Trace Facility
+mfcobol 86/tcp # Micro Focus Cobol
+mfcobol 86/udp # Micro Focus Cobol
+su-mit-tg 89/tcp # SU/MIT Telnet Gateway
+su-mit-tg 89/udp # SU/MIT Telnet Gateway
+dnsix 90/tcp # DNSIX Securit Attribute Token Map
+dnsix 90/udp # DNSIX Securit Attribute Token Map
+mit-dov 91/tcp # MIT Dover Spooler
+mit-dov 91/udp # MIT Dover Spooler
+#npp 92/tcp # Network Printing Protocol
+#npp 92/udp # Network Printing Protocol
+dcp 93/tcp # Device Control Protocol
+dcp 93/udp # Device Control Protocol
+objcall 94/tcp # Tivoli Object Dispatcher
+objcall 94/udp # Tivoli Object Dispatcher
+dixie 96/tcp # DIXIE Protocol Specification
+dixie 96/udp # DIXIE Protocol Specification
+swift-rvf 97/tcp # Swift Remote Virtural File Protocol
+swift-rvf 97/udp # Swift Remote Virtural File Protocol
+tacnews 98/tcp # TAC News
+tacnews 98/udp # TAC News
+metagram 99/tcp # Metagram Relay
+metagram 99/udp # Metagram Relay
+newacct 100/tcp # [unauthorized use]
+iso-tsap 102/udp # ISO-TSAP Class 0
+gppitnp 103/tcp # Genesis Point-to-Point Trans Net
+gppitnp 103/udp # Genesis Point-to-Point Trans Net
+acr-nema 104/tcp # ACR-NEMA Digital Imag. & Comm. 300
+acr-nema 104/udp # ACR-NEMA Digital Imag. & Comm. 300
+snagas 108/tcp # SNA Gateway Access Server
+snagas 108/udp # SNA Gateway Access Server
+mcidas 112/tcp # McIDAS Data Transmission Protocol
+mcidas 112/udp # McIDAS Data Transmission Protocol
+ansanotify 116/tcp # ANSA REX Notify
+ansanotify 116/udp # ANSA REX Notify
+sqlserv 118/tcp # SQL Services
+sqlserv 118/udp # SQL Services
+cfdptkt 120/tcp # CFDPTKT
+cfdptkt 120/udp # CFDPTKT
+erpc 121/tcp # Encore Expedited Remote Pro.Call
+erpc 121/udp # Encore Expedited Remote Pro.Call
+smakynet 122/tcp # SMAKYNET
+smakynet 122/udp # SMAKYNET
+ansatrader 124/tcp # ANSA REX Trader
+ansatrader 124/udp # ANSA REX Trader
+locus-map 125/tcp # Locus PC-Interface Net Map Ser
+locus-map 125/udp # Locus PC-Interface Net Map Ser
+nxedit 126/tcp # NXEdit
+nxedit 126/udp # NXEdit
+locus-con 127/tcp # Locus PC-Interface Conn Server
+locus-con 127/udp # Locus PC-Interface Conn Server
+gss-xlicen 128/tcp # GSS X License Verification
+gss-xlicen 128/udp # GSS X License Verification
+pwdgen 129/tcp # Password Generator Protocol
+pwdgen 129/udp # Password Generator Protocol
+cisco-fna 130/tcp # cisco FNATIVE
+cisco-fna 130/udp # cisco FNATIVE
+cisco-tna 131/tcp # cisco TNATIVE
+cisco-tna 131/udp # cisco TNATIVE
+cisco-sys 132/tcp # cisco SYSMAINT
+cisco-sys 132/udp # cisco SYSMAINT
+statsrv 133/tcp # Statistics Service
+statsrv 133/udp # Statistics Service
+ingres-net 134/tcp # INGRES-NET Service
+ingres-net 134/udp # INGRES-NET Service
+epmap 135/tcp # DCE endpoint resolution
+epmap 135/udp # DCE endpoint resolution
+profile 136/tcp # PROFILE Naming System
+profile 136/udp # PROFILE Naming System
+emfis-data 140/tcp # EMFIS Data Service
+emfis-data 140/udp # EMFIS Data Service
+emfis-cntl 141/tcp # EMFIS Control Service
+emfis-cntl 141/udp # EMFIS Control Service
+bl-idm 142/tcp # Britton-Lee IDM
+bl-idm 142/udp # Britton-Lee IDM
+uaac 145/tcp # UAAC Protocol
+uaac 145/udp # UAAC Protocol
+iso-tp0 146/tcp # ISO-IP0
+iso-tp0 146/udp # ISO-IP0
+iso-ip 147/tcp # ISO-IP
+iso-ip 147/udp # ISO-IP
+jargon 148/tcp # Jargon
+jargon 148/udp # Jargon
+aed-512 149/tcp # AED 512 Emulation Service
+aed-512 149/udp # AED 512 Emulation Service
+sql-net 150/tcp # SQL-NET
+sql-net 150/udp # SQL-NET
+hems 151/tcp # HEMS
+hems 151/udp # HEMS
+bftp 152/tcp # Background File Transfer Program
+bftp 152/udp # Background File Transfer Program
+sgmp 153/tcp # SGMP
+sgmp 153/udp # SGMP
+netsc-prod 154/tcp # NETSC
+netsc-prod 154/udp # NETSC
+netsc-dev 155/tcp # NETSC
+netsc-dev 155/udp # NETSC
+sqlsrv 156/tcp # SQL Service
+sqlsrv 156/udp # SQL Service
+knet-cmp 157/tcp # KNET/VM Command/Message Protocol
+knet-cmp 157/udp # KNET/VM Command/Message Protocol
+pcmail-srv 158/tcp # PCMail Server
+pcmail-srv 158/udp # PCMail Server
+nss-routing 159/tcp # NSS-Routing
+nss-routing 159/udp # NSS-Routing
+sgmp-traps 160/tcp # SGMP-TRAPS
+sgmp-traps 160/udp # SGMP-TRAPS
+xns-courier 165/tcp # Xerox
+xns-courier 165/udp # Xerox
+s-net 166/tcp # Sirius Systems
+s-net 166/udp # Sirius Systems
+namp 167/tcp # NAMP
+namp 167/udp # NAMP
+rsvd 168/tcp # RSVD
+rsvd 168/udp # RSVD
+send 169/tcp # SEND
+send 169/udp # SEND
+print-srv 170/tcp # Network PostScript
+print-srv 170/udp # Network PostScript
+multiplex 171/tcp # Network Innovations Multiplex
+multiplex 171/udp # Network Innovations Multiplex
+cl/1 172/tcp cl-1 # Network Innovations CL/1
+cl/1 172/udp cl-1 # Network Innovations CL/1
+xyplex-mux 173/tcp # Xyplex
+xyplex-mux 173/udp # Xyplex
+vmnet 175/tcp # VMNET
+vmnet 175/udp # VMNET
+genrad-mux 176/tcp # GENRAD-MUX
+genrad-mux 176/udp # GENRAD-MUX
+ris 180/tcp # Intergraph
+ris 180/udp # Intergraph
+unify 181/tcp # Unify
+unify 181/udp # Unify
+audit 182/tcp # Unisys Audit SITP
+audit 182/udp # Unisys Audit SITP
+ocbinder 183/tcp # OCBinder
+ocbinder 183/udp # OCBinder
+ocserver 184/tcp # OCServer
+ocserver 184/udp # OCServer
+remote-kis 185/tcp # Remote-KIS
+remote-kis 185/udp # Remote-KIS
+kis 186/tcp # KIS Protocol
+kis 186/udp # KIS Protocol
+aci 187/tcp # Application Communication Interface
+aci 187/udp # Application Communication Interface
+mumps 188/tcp # Plus Five's MUMPS
+mumps 188/udp # Plus Five's MUMPS
+qft 189/tcp # Queued File Transport
+qft 189/udp # Queued File Transport
+gacp 190/tcp # Gateway Access Control Protocol
+gacp 190/udp # Gateway Access Control Protocol
+osu-nms 192/tcp # OSU Network Monitoring System
+osu-nms 192/udp # OSU Network Monitoring System
+srmp 193/tcp # Spider Remote Monitoring Protocol
+srmp 193/udp # Spider Remote Monitoring Protocol
+dn6-nlm-aud 195/tcp # DNSIX Network Level Module Audit
+dn6-nlm-aud 195/udp # DNSIX Network Level Module Audit
+dn6-smm-red 196/tcp # DNSIX Session Mgt Module Audit Redir
+dn6-smm-red 196/udp # DNSIX Session Mgt Module Audit Redir
+dls-mon 198/tcp # Directory Location Service Monitor
+dls-mon 198/udp # Directory Location Service Monitor
+src 200/tcp # IBM System Resource Controller
+src 200/udp # IBM System Resource Controller
+at-3 203/tcp # AppleTalk Unused
+at-3 203/udp # AppleTalk Unused
+at-5 205/tcp # AppleTalk Unused
+at-5 205/udp # AppleTalk Unused
+at-7 207/tcp # AppleTalk Unused
+at-7 207/udp # AppleTalk Unused
+at-8 208/tcp # AppleTalk Unused
+at-8 208/udp # AppleTalk Unused
+914c/g 211/tcp 914c-g # Texas Instruments 914C/G Terminal
+914c/g 211/udp 914c-g # Texas Instruments 914C/G Terminal
+anet 212/tcp # ATEXSSTR
+anet 212/udp # ATEXSSTR
+vmpwscs 214/tcp # VM PWSCS
+vmpwscs 214/udp # VM PWSCS
+softpc 215/tcp # Insignia Solutions
+softpc 215/udp # Insignia Solutions
+CAIlic 216/tcp # Computer Associates Int'l License Server
+CAIlic 216/udp # Computer Associates Int'l License Server
+dbase 217/tcp # dBASE Unix
+dbase 217/udp # dBASE Unix
+mpp 218/tcp # Netix Message Posting Protocol
+mpp 218/udp # Netix Message Posting Protocol
+uarps 219/tcp # Unisys ARPs
+uarps 219/udp # Unisys ARPs
+fln-spx 221/tcp # Berkeley rlogind with SPX auth
+fln-spx 221/udp # Berkeley rlogind with SPX auth
+rsh-spx 222/tcp # Berkeley rshd with SPX auth
+rsh-spx 222/udp # Berkeley rshd with SPX auth
+cdc 223/tcp # Certificate Distribution Center
+cdc 223/udp # Certificate Distribution Center
+masqdialer 224/tcp # masqdialer
+masqdialer 224/udp # masqdialer
+direct 242/tcp # Direct
+direct 242/udp # Direct
+sur-meas 243/tcp # Survey Measurement
+sur-meas 243/udp # Survey Measurement
+inbusiness 244/tcp # inbusiness
+inbusiness 244/udp # inbusiness
+dsp3270 246/tcp # Display Systems Protocol
+dsp3270 246/udp # Display Systems Protocol
+subntbcst_tftp 247/tcp subntbcst-tftp # SUBNTBCST_TFTP
+subntbcst_tftp 247/udp subntbcst-tftp # SUBNTBCST_TFTP
+bhfhs 248/tcp # bhfhs
+bhfhs 248/udp # bhfhs
+set 257/tcp # Secure Electronic Transaction
+set 257/udp # Secure Electronic Transaction
+esro-gen 259/tcp # Efficient Short Remote Operations
+esro-gen 259/udp # Efficient Short Remote Operations
+openport 260/tcp # Openport
+openport 260/udp # Openport
+nsiiops 261/tcp # IIOP Name Service over TLS/SSL
+nsiiops 261/udp # IIOP Name Service over TLS/SSL
+arcisdms 262/tcp # Arcisdms
+arcisdms 262/udp # Arcisdms
+hdap 263/tcp # HDAP
+hdap 263/udp # HDAP
+bgmp 264/tcp # BGMP
+bgmp 264/udp # BGMP
+x-bone-ctl 265/tcp # X-Bone CTL
+x-bone-ctl 265/udp # X-Bone CTL
+sst 266/tcp # SCSI on ST
+sst 266/udp # SCSI on ST
+td-service 267/tcp # Tobit David Service Layer
+td-service 267/udp # Tobit David Service Layer
+td-replica 268/tcp # Tobit David Replica
+td-replica 268/udp # Tobit David Replica
+manet 269/tcp # MANET Protocols
+manet 269/udp # MANET Protocols [RFC5498]
+http-mgmt 280/tcp # http-mgmt
+http-mgmt 280/udp # http-mgmt
+personal-link 281/tcp # Personal Link
+personal-link 281/udp # Personal Link
+cableport-ax 282/tcp # Cable Port A/X
+cableport-ax 282/udp # Cable Port A/X
+rescap 283/tcp # rescap
+rescap 283/udp # rescap
+corerjd 284/tcp # corerjd
+corerjd 284/udp # corerjd
+k-block 287/tcp # K-BLOCK
+k-block 287/udp # K-BLOCK
+novastorbakcup 308/tcp # Novastor Backup
+novastorbakcup 308/udp # Novastor Backup
+entrusttime 309/tcp # EntrustTime
+entrusttime 309/udp # EntrustTime
+bhmds 310/tcp # bhmds
+bhmds 310/udp # bhmds
+asip-webadmin 311/tcp # AppleShare IP WebAdmin
+asip-webadmin 311/udp # AppleShare IP WebAdmin
+vslmp 312/tcp # VSLMP
+vslmp 312/udp # VSLMP
+magenta-logic 313/tcp # Magenta Logic
+magenta-logic 313/udp # Magenta Logic
+opalis-robot 314/tcp # Opalis Robot
+opalis-robot 314/udp # Opalis Robot
+dpsi 315/tcp # DPSI
+dpsi 315/udp # DPSI
+decauth 316/tcp # decAuth
+decauth 316/udp # decAuth
+zannet 317/tcp # Zannet
+zannet 317/udp # Zannet
+pkix-timestamp 318/tcp # PKIX TimeStamp
+pkix-timestamp 318/udp # PKIX TimeStamp
+ptp-event 319/tcp # PTP Event
+ptp-event 319/udp # PTP Event
+ptp-general 320/tcp # PTP General
+ptp-general 320/udp # PTP General
+rtsps 322/tcp # RTSPS
+rtsps 322/udp # RTSPS
+rpki-rtr 323/tcp # Resource PKI to Router
+rpki-rtr-tls 324/tcp # Resource PKI to Router
+texar 333/tcp # Texar Security Port
+texar 333/udp # Texar Security Port
+pdap 344/tcp # Prospero Data Access Protocol
+pdap 344/udp # Prospero Data Access Protocol
+pawserv 345/tcp # Perf Analysis Workbench
+pawserv 345/udp # Perf Analysis Workbench
+zserv 346/tcp # Zebra server
+zserv 346/udp # Zebra server
+csi-sgwp 348/tcp # Cabletron Management Protocol
+csi-sgwp 348/udp # Cabletron Management Protocol
+matip-type-a 350/tcp # MATIP Type A
+matip-type-a 350/udp # MATIP Type A
+matip-type-b 351/tcp bhoetty # MATIP Type B / bhoetty (added 5/21/97)
+matip-type-b 351/udp bhoetty # MATIP Type B / bhoetty
+dtag-ste-sb 352/tcp bhoedap4 # DTAG (assigned long ago) / bhoedap4
+dtag-ste-sb 352/udp bhoedap4 # DTAG / bhoedap4
+ndsauth 353/tcp # NDSAUTH
+ndsauth 353/udp # NDSAUTH
+bh611 354/tcp # bh611
+bh611 354/udp # bh611
+datex-asn 355/tcp # DATEX-ASN
+datex-asn 355/udp # DATEX-ASN
+cloanto-net-1 356/tcp # Cloanto Net 1
+cloanto-net-1 356/udp # Cloanto Net 1
+bhevent 357/tcp # bhevent
+bhevent 357/udp # bhevent
+shrinkwrap 358/tcp # Shrinkwrap
+shrinkwrap 358/udp # Shrinkwrap
+nsrmp 359/tcp # Network Security Risk Management Protocol
+nsrmp 359/udp # Network Security Risk Management Protocol
+scoi2odialog 360/tcp # scoi2odialog
+scoi2odialog 360/udp # scoi2odialog
+semantix 361/tcp # Semantix
+semantix 361/udp # Semantix
+srssend 362/tcp # SRS Send
+srssend 362/udp # SRS Send
+aurora-cmgr 364/tcp # Aurora CMGR
+aurora-cmgr 364/udp # Aurora CMGR
+dtk 365/tcp # DTK
+dtk 365/udp # DTK
+mortgageware 367/tcp # MortgageWare
+mortgageware 367/udp # MortgageWare
+qbikgdp 368/tcp # QbikGDP
+qbikgdp 368/udp # QbikGDP
+clearcase 371/tcp # Clearcase
+clearcase 371/udp # Clearcase
+legent-1 373/tcp # Legent Corporation
+legent-1 373/udp # Legent Corporation
+legent-2 374/tcp # Legent Corporation
+legent-2 374/udp # Legent Corporation
+hassle 375/tcp # Hassle
+hassle 375/udp # Hassle
+nip 376/tcp # Amiga Envoy Network Inquiry Proto
+nip 376/udp # Amiga Envoy Network Inquiry Proto
+tnETOS 377/tcp # NEC Corporation
+tnETOS 377/udp # NEC Corporation
+dsETOS 378/tcp # NEC Corporation
+dsETOS 378/udp # NEC Corporation
+is99c 379/tcp # TIA/EIA/IS-99 modem client
+is99c 379/udp # TIA/EIA/IS-99 modem client
+is99s 380/tcp # TIA/EIA/IS-99 modem server
+is99s 380/udp # TIA/EIA/IS-99 modem server
+hp-collector 381/tcp # hp performance data collector
+hp-collector 381/udp # hp performance data collector
+hp-managed-node 382/tcp # hp performance data managed node
+hp-managed-node 382/udp # hp performance data managed node
+hp-alarm-mgr 383/tcp # hp performance data alarm manager
+hp-alarm-mgr 383/udp # hp performance data alarm manager
+arns 384/tcp # A Remote Network Server System
+arns 384/udp # A Remote Network Server System
+ibm-app 385/tcp # IBM Application
+ibm-app 385/udp # IBM Application
+asa 386/tcp # ASA Message Router Object Def.
+asa 386/udp # ASA Message Router Object Def.
+aurp 387/tcp # Appletalk Update-Based Routing Pro.
+aurp 387/udp # Appletalk Update-Based Routing Pro.
+unidata-ldm 388/tcp # Unidata LDM
+unidata-ldm 388/udp # Unidata LDM
+uis 390/tcp # UIS
+uis 390/udp # UIS
+synotics-relay 391/tcp # SynOptics SNMP Relay Port
+synotics-relay 391/udp # SynOptics SNMP Relay Port
+synotics-broker 392/tcp # SynOptics Port Broker Port
+synotics-broker 392/udp # SynOptics Port Broker Port
+meta5 393/tcp # Meta5
+meta5 393/udp # Meta5
+embl-ndt 394/tcp # EMBL Nucleic Data Transfer
+embl-ndt 394/udp # EMBL Nucleic Data Transfer
+netcp 395/tcp # NETscout Control Protocol
+netcp 395/udp # NETscout Control Protocol
+netware-ip 396/tcp # Novell Netware over IP
+netware-ip 396/udp # Novell Netware over IP
+mptn 397/tcp # Multi Protocol Trans. Net.
+mptn 397/udp # Multi Protocol Trans. Net.
+kryptolan 398/tcp # Kryptolan
+kryptolan 398/udp # Kryptolan
+iso-tsap-c2 399/tcp # ISO Transport Class 2 Non-Control over TCP
+iso-tsap-c2 399/udp # ISO Transport Class 2 Non-Control over UDP
+ups 401/tcp # Uninterruptible Power Supply
+ups 401/udp # Uninterruptible Power Supply
+genie 402/tcp # Genie Protocol
+genie 402/udp # Genie Protocol
+decap 403/tcp # decap
+decap 403/udp # decap
+nced 404/tcp # nced
+nced 404/udp # nced
+ncld 405/tcp # ncld
+ncld 405/udp # ncld
+imsp 406/tcp # Interactive Mail Support Protocol
+imsp 406/udp # Interactive Mail Support Protocol
+timbuktu 407/tcp # Timbuktu
+timbuktu 407/udp # Timbuktu
+prm-sm 408/tcp # Prospero Resource Manager Sys. Man.
+prm-sm 408/udp # Prospero Resource Manager Sys. Man.
+prm-nm 409/tcp # Prospero Resource Manager Node Man.
+prm-nm 409/udp # Prospero Resource Manager Node Man.
+decladebug 410/tcp # DECLadebug Remote Debug Protocol
+decladebug 410/udp # DECLadebug Remote Debug Protocol
+rmt 411/tcp # Remote MT Protocol
+rmt 411/udp # Remote MT Protocol
+synoptics-trap 412/tcp # Trap Convention Port
+synoptics-trap 412/udp # Trap Convention Port
+smsp 413/tcp # Storage Management Services Protocol
+smsp 413/udp # Storage Management Services Protocol
+infoseek 414/tcp # InfoSeek
+infoseek 414/udp # InfoSeek
+bnet 415/tcp # BNet
+bnet 415/udp # BNet
+silverplatter 416/tcp # Silverplatter
+silverplatter 416/udp # Silverplatter
+onmux 417/tcp # Onmux
+onmux 417/udp # Onmux
+hyper-g 418/tcp # Hyper-G
+hyper-g 418/udp # Hyper-G
+ariel1 419/tcp # Ariel 1
+ariel1 419/udp # Ariel 1
+smpte 420/tcp # SMPTE
+smpte 420/udp # SMPTE
+ariel2 421/tcp # Ariel 2
+ariel2 421/udp # Ariel 2
+ariel3 422/tcp # Ariel 3
+ariel3 422/udp # Ariel 3
+opc-job-start 423/tcp # IBM Operations Planning and Control Start
+opc-job-start 423/udp # IBM Operations Planning and Control Start
+opc-job-track 424/tcp # IBM Operations Planning and Control Track
+opc-job-track 424/udp # IBM Operations Planning and Control Track
+icad-el 425/tcp # ICAD
+icad-el 425/udp # ICAD
+smartsdp 426/tcp # smartsdp
+smartsdp 426/udp # smartsdp
+ocs_cmu 428/tcp ocs-cmu # OCS_CMU
+ocs_cmu 428/udp ocs-cmu # OCS_CMU
+ocs_amu 429/tcp ocs-amu # OCS_AMU
+ocs_amu 429/udp ocs-amu # OCS_AMU
+utmpsd 430/tcp # UTMPSD
+utmpsd 430/udp # UTMPSD
+utmpcd 431/tcp # UTMPCD
+utmpcd 431/udp # UTMPCD
+iasd 432/tcp # IASD
+iasd 432/udp # IASD
+nnsp 433/tcp # NNSP
+nnsp 433/udp # NNSP
+dna-cml 436/tcp # DNA-CML
+dna-cml 436/udp # DNA-CML
+comscm 437/tcp # comscm
+comscm 437/udp # comscm
+dsfgw 438/tcp # dsfgw
+dsfgw 438/udp # dsfgw
+dasp 439/tcp # dasp Thomas Obermair
+dasp 439/udp # dasp tommy&inlab.m.eunet.de
+sgcp 440/tcp # sgcp
+sgcp 440/udp # sgcp
+decvms-sysmgt 441/tcp # decvms-sysmgt
+decvms-sysmgt 441/udp # decvms-sysmgt
+cvc_hostd 442/tcp cvc-hostd # cvc_hostd
+cvc_hostd 442/udp cvc-hostd # cvc_hostd
+ddm-rdb 446/tcp # DDM-Remote Relational Database Access
+ddm-rdb 446/udp # DDM-Remote Relational Database Access
+ddm-dfm 447/tcp # DDM-Distributed File Management
+ddm-dfm 447/udp # DDM-Distributed File Management
+ddm-ssl 448/tcp # DDM-Remote DB Access Using Secure Sockets
+ddm-ssl 448/udp # DDM-Remote DB Access Using Secure Sockets
+as-servermap 449/tcp # AS Server Mapper
+as-servermap 449/udp # AS Server Mapper
+tserver 450/tcp # Computer Supported Telecomunication Applications
+tserver 450/udp # Computer Supported Telecomunication Applications
+sfs-smp-net 451/tcp # Cray Network Semaphore server
+sfs-smp-net 451/udp # Cray Network Semaphore server
+sfs-config 452/tcp # Cray SFS config server
+sfs-config 452/udp # Cray SFS config server
+macon-tcp 456/tcp # macon-tcp
+macon-udp 456/udp # macon-udp
+scohelp 457/tcp # scohelp
+scohelp 457/udp # scohelp
+appleqtc 458/tcp # apple quick time
+appleqtc 458/udp # apple quick time
+ampr-rcmd 459/tcp # ampr-rcmd
+ampr-rcmd 459/udp # ampr-rcmd
+skronk 460/tcp # skronk
+skronk 460/udp # skronk
+datasurfsrv 461/tcp # DataRampSrv
+datasurfsrv 461/udp # DataRampSrv
+datasurfsrvsec 462/tcp # DataRampSrvSec
+datasurfsrvsec 462/udp # DataRampSrvSec
+alpes 463/tcp # alpes
+alpes 463/udp # alpes
+urd 465/tcp smtps # URL Rendesvous Directory for SSM / SMTP over SSL (TLS)
+igmpv3lite 465/udp # IGMP over UDP for SSM
+digital-vrc 466/tcp # digital-vrc
+digital-vrc 466/udp # digital-vrc
+mylex-mapd 467/tcp # mylex-mapd
+mylex-mapd 467/udp # mylex-mapd
+rcp 469/tcp # Radio Control Protocol
+rcp 469/udp # Radio Control Protocol
+scx-proxy 470/tcp # scx-proxy
+scx-proxy 470/udp # scx-proxy
+mondex 471/tcp # Mondex
+mondex 471/udp # Mondex
+ljk-login 472/tcp # ljk-login
+ljk-login 472/udp # ljk-login
+hybrid-pop 473/tcp # hybrid-pop
+hybrid-pop 473/udp # hybrid-pop
+tn-tl-w1 474/tcp # tn-tl-w1
+tn-tl-w2 474/udp # tn-tl-w2
+tcpnethaspsrv 475/tcp # tcpnethaspsrv
+tcpnethaspsrv 475/udp # tcpnethaspsrv
+tn-tl-fd1 476/tcp # tn-tl-fd1
+tn-tl-fd1 476/udp # tn-tl-fd1
+ss7ns 477/tcp # ss7ns
+ss7ns 477/udp # ss7ns
+spsc 478/tcp # spsc
+spsc 478/udp # spsc
+iafserver 479/tcp # iafserver
+iafserver 479/udp # iafserver
+iafdbase 480/tcp # iafdbase
+iafdbase 480/udp # iafdbase
+ph 481/tcp # Ph service
+ph 481/udp # Ph service
+bgs-nsi 482/tcp # bgs-nsi
+bgs-nsi 482/udp # bgs-nsi
+ulpnet 483/tcp # ulpnet
+ulpnet 483/udp # ulpnet
+integra-sme 484/tcp # Integra Software Management Environment
+integra-sme 484/udp # Integra Software Management Environment
+powerburst 485/tcp # Air Soft Power Burst
+powerburst 485/udp # Air Soft Power Burst
+avian 486/tcp # avian
+avian 486/udp # avian
+nest-protocol 489/tcp # nest-protocol
+nest-protocol 489/udp # nest-protocol
+micom-pfs 490/tcp # micom-pfs
+micom-pfs 490/udp # micom-pfs
+go-login 491/tcp # go-login
+go-login 491/udp # go-login
+ticf-1 492/tcp # Transport Independent Convergence for FNA
+ticf-1 492/udp # Transport Independent Convergence for FNA
+ticf-2 493/tcp # Transport Independent Convergence for FNA
+ticf-2 493/udp # Transport Independent Convergence for FNA
+pov-ray 494/tcp # POV-Ray
+pov-ray 494/udp # POV-Ray
+intecourier 495/tcp # intecourier
+intecourier 495/udp # intecourier
+retrospect 497/tcp # Retrospect backup
+retrospect 497/udp # Retrospect backup
+siam 498/tcp # siam
+siam 498/udp # siam
+iso-ill 499/tcp # ISO ILL Protocol
+iso-ill 499/udp # ISO ILL Protocol
+stmf 501/tcp # STMF
+stmf 501/udp # STMF
+mbap 502/tcp # Modbus Application Protocol
+mbap 502/udp # Modbus Application Protocol
+intrinsa 503/tcp # Intrinsa
+intrinsa 503/udp # Intrinsa
+citadel 504/tcp # citadel
+citadel 504/udp # citadel
+mailbox-lm 505/tcp # mailbox-lm
+mailbox-lm 505/udp # mailbox-lm
+ohimsrv 506/tcp # ohimsrv
+ohimsrv 506/udp # ohimsrv
+crs 507/tcp # crs
+crs 507/udp # crs
+xvttp 508/tcp # xvttp
+xvttp 508/udp # xvttp
+snare 509/tcp # snare
+snare 509/udp # snare
+fcp 510/tcp # FirstClass Protocol
+fcp 510/udp # FirstClass Protocol
+passgo 511/tcp # PassGo
+passgo 511/udp # PassGo
+videotex 516/tcp # videotex
+videotex 516/udp # videotex
+talk 517/tcp # like tenex link, but across
+ntalk 518/tcp #
+ulp 522/tcp # ULP
+ulp 522/udp # ULP
+ibm-db2 523/tcp # IBM-DB2
+ibm-db2 523/udp # IBM-DB2
+ncp 524/tcp # NCP
+ncp 524/udp # NCP
+tempo 526/udp # newdate
+stx 527/tcp # Stock IXChange
+stx 527/udp # Stock IXChange
+custix 528/tcp # Customer IXChange
+custix 528/udp # Customer IXChange
+irc-serv 529/tcp # IRC-SERV
+irc-serv 529/udp # IRC-SERV
+courier 530/udp # rpc
+conference 531/udp # chat
+netnews 532/udp # readnews
+netwall 533/tcp # for emergency broadcasts
+windream 534/tcp # windream Admin
+windream 534/udp # windream Admin
+opalis-rdv 536/tcp # opalis-rdv
+opalis-rdv 536/udp # opalis-rdv
+apertus-ldp 539/tcp # Apertus Technologies Load Determination
+apertus-ldp 539/udp # Apertus Technologies Load Determination
+uucp 540/udp # uucpd
+uucp-rlogin 541/tcp # uucp-rlogin
+uucp-rlogin 541/udp # uucp-rlogin
+commerce 542/tcp # commerce
+commerce 542/udp # commerce
+klogin 543/udp #
+kshell 544/udp # krcmd
+appleqtcsrvr 545/tcp # appleqtcsrvr
+appleqtcsrvr 545/udp # appleqtcsrvr
+idfp 549/tcp # IDFP
+idfp 549/udp # IDFP
+new-rwho 550/tcp # new-who
+new-rwho 550/udp # new-who
+cybercash 551/tcp # cybercash
+cybercash 551/udp # cybercash
+devshr-nts 552/tcp # DeviceShare
+devshr-nts 552/udp # DeviceShare
+pirp 553/tcp # pirp
+pirp 553/udp # pirp
+dsf 555/tcp #
+dsf 555/udp #
+remotefs 556/udp # rfs server
+openvms-sysipc 557/tcp # openvms-sysipc
+openvms-sysipc 557/udp # openvms-sysipc
+sdnskmp 558/tcp # SDNSKMP
+sdnskmp 558/udp # SDNSKMP
+teedtap 559/tcp # TEEDTAP
+teedtap 559/udp # TEEDTAP
+rmonitor 560/tcp # rmonitord
+rmonitor 560/udp # rmonitord
+monitor 561/tcp #
+monitor 561/udp #
+chshell 562/tcp # chcmd
+chshell 562/udp # chcmd
+9pfs 564/tcp # plan 9 file service
+9pfs 564/udp # plan 9 file service
+streettalk 566/tcp # streettalk
+streettalk 566/udp # streettalk
+banyan-rpc 567/tcp # banyan-rpc
+banyan-rpc 567/udp # banyan-rpc
+ms-shuttle 568/tcp # microsoft shuttle
+ms-shuttle 568/udp # microsoft shuttle
+ms-rome 569/tcp # microsoft rome
+ms-rome 569/udp # microsoft rome
+#meter 570/tcp # demon
+#meter 570/udp # demon
+#meter 571/tcp # udemon
+#meter 571/udp # udemon
+sonar 572/tcp # sonar
+sonar 572/udp # sonar
+banyan-vip 573/tcp # banyan-vip
+banyan-vip 573/udp # banyan-vip
+ftp-agent 574/tcp # FTP Software Agent System
+ftp-agent 574/udp # FTP Software Agent System
+vemmi 575/tcp # VEMMI
+vemmi 575/udp # VEMMI
+ipcd 576/tcp # ipcd
+ipcd 576/udp # ipcd
+vnas 577/tcp # vnas
+vnas 577/udp # vnas
+ipdd 578/tcp # ipdd
+ipdd 578/udp # ipdd
+decbsrv 579/tcp # decbsrv
+decbsrv 579/udp # decbsrv
+sntp-heartbeat 580/tcp # SNTP HEARTBEAT
+sntp-heartbeat 580/udp # SNTP HEARTBEAT
+bdp 581/tcp # Bundle Discovery Protocol
+bdp 581/udp # Bundle Discovery Protocol
+scc-security 582/tcp # SCC Security
+scc-security 582/udp # SCC Security
+philips-vc 583/tcp # Philips Video-Conferencing
+philips-vc 583/udp # Philips Video-Conferencing
+keyserver 584/tcp # Key Server
+keyserver 584/udp # Key Server
+password-chg 586/tcp # Password Change
+password-chg 586/udp # Password Change
+cal 588/tcp # CAL
+cal 588/udp # CAL
+eyelink 589/tcp # EyeLink
+eyelink 589/udp # EyeLink
+tns-cml 590/tcp # TNS CML
+tns-cml 590/udp # TNS CML
+eudora-set 592/tcp # Eudora Set
+eudora-set 592/udp # Eudora Set
+http-rpc-epmap 593/tcp # HTTP RPC Ep Map
+http-rpc-epmap 593/udp # HTTP RPC Ep Map
+tpip 594/tcp # TPIP
+tpip 594/udp # TPIP
+cab-protocol 595/tcp # CAB Protocol
+cab-protocol 595/udp # CAB Protocol
+smsd 596/tcp # SMSD
+smsd 596/udp # SMSD
+ptcnameservice 597/tcp # PTC Name Service
+ptcnameservice 597/udp # PTC Name Service
+sco-websrvrmg3 598/tcp # SCO Web Server Manager 3
+sco-websrvrmg3 598/udp # SCO Web Server Manager 3
+acp 599/tcp # Aeolon Core Protocol
+acp 599/udp # Aeolon Core Protocol
+ipcserver 600/tcp # Sun IPC server
+ipcserver 600/udp # Sun IPC server
+syslog-conn 601/tcp # Reliable Syslog Service
+syslog-conn 601/udp # Reliable Syslog Service
+xmlrpc-beep 602/tcp # XML-RPC over BEEP
+xmlrpc-beep 602/udp # XML-RPC over BEEP
+idxp 603/tcp # IDXP
+idxp 603/udp # IDXP
+tunnel 604/tcp # TUNNEL
+tunnel 604/udp # TUNNEL
+soap-beep 605/tcp # SOAP over BEEP
+soap-beep 605/udp # SOAP over BEEP
+urm 606/tcp # Cray Unified Resource Manager
+urm 606/udp # Cray Unified Resource Manager
+nqs 607/tcp # nqs
+nqs 607/udp # nqs
+sift-uft 608/tcp # Sender-Initiated/Unsolicited File Transfer
+sift-uft 608/udp # Sender-Initiated/Unsolicited File Transfer
+npmp-trap 609/tcp # npmp-trap
+npmp-trap 609/udp # npmp-trap
+hmmp-op 613/tcp # HMMP Operation
+hmmp-op 613/udp # HMMP Operation
+sshell 614/tcp # SSLshell
+sshell 614/udp # SSLshell
+sco-inetmgr 615/tcp # Internet Configuration Manager
+sco-inetmgr 615/udp # Internet Configuration Manager
+sco-sysmgr 616/tcp gii # SCO System Administration Server
+sco-sysmgr 616/udp # SCO System Administration Server
+sco-dtmgr 617/tcp # SCO Desktop Administration Server
+sco-dtmgr 617/udp # SCO Desktop Administration Server
+dei-icda 618/tcp # DEI-ICDA
+dei-icda 618/udp # DEI-ICDA
+compaq-evm 619/tcp # Compaq EVM
+compaq-evm 619/udp # Compaq EVM
+sco-websrvrmgr 620/tcp # SCO WebServer Manager
+sco-websrvrmgr 620/udp # SCO WebServer Manager
+escp-ip 621/tcp # ESCP
+escp-ip 621/udp # ESCP
+collaborator 622/tcp # Collaborator
+collaborator 622/udp # Collaborator
+oob-ws-http 623/tcp # DMTF out-of-band web services management protocol
+asf-rmcp 623/udp # ASF Remote Management and Control Protocol
+cryptoadmin 624/tcp # Crypto Admin
+cryptoadmin 624/udp # Crypto Admin
+dec_dlm 625/tcp dec-dlm # DEC DLM
+dec_dlm 625/udp dec-dlm # DEC DLM
+asia 626/tcp # ASIA
+asia 626/udp # ASIA
+passgo-tivoli 627/tcp # PassGo Tivoli
+passgo-tivoli 627/udp # PassGo Tivoli
+qmqp 628/tcp # QMQP
+qmqp 628/udp # QMQP
+3com-amp3 629/tcp # 3Com AMP3
+3com-amp3 629/udp # 3Com AMP3
+rda 630/tcp # RDA
+rda 630/udp # RDA
+bmpp 632/tcp # bmpp
+bmpp 632/udp # bmpp
+servstat 633/tcp # Service Status update (Sterling Software)
+servstat 633/udp # Service Status update (Sterling Software)
+ginad 634/tcp # ginad
+ginad 634/udp # ginad
+rlzdbase 635/tcp # RLZ DBase
+rlzdbase 635/udp # RLZ DBase
+lanserver 637/tcp # lanserver
+lanserver 637/udp # lanserver
+mcns-sec 638/tcp # mcns-sec
+mcns-sec 638/udp # mcns-sec
+msdp 639/tcp # MSDP
+msdp 639/udp # MSDP
+entrust-sps 640/tcp # entrust-sps
+entrust-sps 640/udp # entrust-sps
+repcmd 641/tcp # repcmd
+repcmd 641/udp # repcmd
+esro-emsdp 642/tcp # ESRO-EMSDP V1.3
+esro-emsdp 642/udp # ESRO-EMSDP V1.3
+sanity 643/tcp # SANity
+sanity 643/udp # SANity
+dwr 644/tcp # dwr
+dwr 644/udp # dwr
+pssc 645/tcp # PSSC
+pssc 645/udp # PSSC
+ldp 646/tcp # LDP
+ldp 646/udp # LDP
+dhcp-failover 647/tcp # DHCP Failover
+dhcp-failover 647/udp # DHCP Failover
+rrp 648/tcp # Registry Registrar Protocol (RRP)
+rrp 648/udp # Registry Registrar Protocol (RRP)
+cadview-3d 649/tcp # Cadview-3d - streaming 3d models over the internet
+cadview-3d 649/udp # Cadview-3d - streaming 3d models over the internet
+obex 650/tcp # OBEX
+obex 650/udp # OBEX
+ieee-mms 651/tcp # IEEE MMS
+ieee-mms 651/udp # IEEE MMS
+hello-port 652/tcp # HELLO_PORT
+hello-port 652/udp # HELLO_PORT
+repscmd 653/tcp # RepCmd
+repscmd 653/udp # RepCmd
+aodv 654/tcp # AODV
+aodv 654/udp # AODV
+tinc 655/tcp # TINC
+tinc 655/udp # TINC
+spmp 656/tcp # SPMP
+spmp 656/udp # SPMP
+rmc 657/tcp # RMC
+rmc 657/udp # RMC
+tenfold 658/tcp # TenFold
+tenfold 658/udp # TenFold
+mac-srvr-admin 660/tcp # MacOS Server Admin
+mac-srvr-admin 660/udp # MacOS Server Admin
+hap 661/tcp # HAP
+hap 661/udp # HAP
+pftp 662/tcp # PFTP
+pftp 662/udp # PFTP
+purenoise 663/tcp # PureNoise
+purenoise 663/udp # PureNoise
+oob-ws-https 664/tcp # DMTF out-of-band secure web services management protocol
+asf-secure-rmcp 664/udp # ASF Secure Remote Management and Control Protocol
+sun-dr 665/tcp # Sun DR
+sun-dr 665/udp # Sun DR
+mdqs 666/tcp doom # doom Id Software
+mdqs 666/udp doom # doom Id Software
+disclose 667/tcp # campaign contribution disclosures - SDR Technologies
+disclose 667/udp # campaign contribution disclosures - SDR Technologies
+mecomm 668/tcp # MeComm
+mecomm 668/udp # MeComm
+meregister 669/tcp # MeRegister
+meregister 669/udp # MeRegister
+vacdsm-sws 670/tcp # VACDSM-SWS
+vacdsm-sws 670/udp # VACDSM-SWS
+vacdsm-app 671/tcp # VACDSM-APP
+vacdsm-app 671/udp # VACDSM-APP
+vpps-qua 672/tcp # VPPS-QUA
+vpps-qua 672/udp # VPPS-QUA
+cimplex 673/tcp # CIMPLEX
+cimplex 673/udp # CIMPLEX
+dctp 675/tcp # DCTP
+dctp 675/udp # DCTP
+vpps-via 676/tcp # VPPS Via
+vpps-via 676/udp # VPPS Via
+vpp 677/tcp # Virtual Presence Protocol
+vpp 677/udp # Virtual Presence Protocol
+ggf-ncp 678/tcp # GNU Generation Foundation NCP
+ggf-ncp 678/udp # GNU Generation Foundation NCP
+mrm 679/tcp # MRM
+mrm 679/udp # MRM
+entrust-aaas 680/tcp # entrust-aaas
+entrust-aaas 680/udp # entrust-aaas
+entrust-aams 681/tcp # entrust-aams
+entrust-aams 681/udp # entrust-aams
+xfr 682/tcp # XFR
+xfr 682/udp # XFR
+corba-iiop 683/tcp # CORBA IIOP
+corba-iiop 683/udp # CORBA IIOP
+corba-iiop-ssl 684/tcp # CORBA IIOP SSL
+corba-iiop-ssl 684/udp # CORBA IIOP SSL
+mdc-portmapper 685/tcp # MDC Port Mapper
+mdc-portmapper 685/udp # MDC Port Mapper
+hcp-wismar 686/tcp # Hardware Control Protocol Wismar
+hcp-wismar 686/udp # Hardware Control Protocol Wismar
+asipregistry 687/tcp # asipregistry
+asipregistry 687/udp # asipregistry
+realm-rusd 688/tcp # ApplianceWare managment protocol
+realm-rusd 688/udp # ApplianceWare managment protocol
+nmap 689/tcp # NMAP
+nmap 689/udp # NMAP
+vatp 690/tcp # Velneo Application Transfer Protocol
+vatp 690/udp # Velneo Application Transfer Protocol
+msexch-routing 691/tcp # MS Exchange Routing
+msexch-routing 691/udp # MS Exchange Routing
+hyperwave-isp 692/tcp # Hyperwave-ISP
+hyperwave-isp 692/udp # Hyperwave-ISP
+connendp 693/tcp # almanid Connection Endpoint
+connendp 693/udp # almanid Connection Endpoint
+ieee-mms-ssl 695/tcp # IEEE-MMS-SSL
+ieee-mms-ssl 695/udp # IEEE-MMS-SSL
+rushd 696/tcp # RUSHD
+rushd 696/udp # RUSHD
+uuidgen 697/tcp # UUIDGEN
+uuidgen 697/udp # UUIDGEN
+olsr 698/tcp # OLSR
+olsr 698/udp # OLSR
+accessnetwork 699/tcp # Access Network
+accessnetwork 699/udp # Access Network
+#epp 700/tcp # Extensible Provisioning Protocol
+#epp 700/udp # Extensible Provisioning Protocol
+lmp 701/tcp # Link Management Protocol (LMP)
+lmp 701/udp # Link Management Protocol (LMP)
+iris-beep 702/tcp # IRIS over BEEP
+iris-beep 702/udp # IRIS over BEEP
+elcsd 704/tcp # errlog copy/server daemon
+elcsd 704/udp # errlog copy/server daemon
+agentx 705/tcp # AgentX
+agentx 705/udp # AgentX
+silc 706/tcp # SILC
+silc 706/udp # SILC
+borland-dsj 707/tcp # Borland DSJ
+borland-dsj 707/udp # Borland DSJ
+entrust-kmsh 709/tcp # Entrust Key Management Service Handler
+entrust-kmsh 709/udp # Entrust Key Management Service Handler
+entrust-ash 710/tcp # Entrust Administration Service Handler
+entrust-ash 710/udp # Entrust Administration Service Handler
+cisco-tdp 711/tcp # Cisco TDP
+cisco-tdp 711/udp # Cisco TDP
+tbrpf 712/tcp # TBRPF
+tbrpf 712/udp # TBRPF
+iris-xpc 713/tcp # IRIS over XPC
+iris-xpc 713/udp # IRIS over XPC
+iris-xpcs 714/tcp # IRIS over XPCS
+iris-xpcs 714/udp # IRIS over XPCS
+iris-lwz 715/tcp # IRIS-LWZ
+iris-lwz 715/udp # IRIS-LWZ
+pana 716/udp # PANA Messages
+netviewdm1 729/tcp # IBM NetView DM/6000 Server/Client
+netviewdm1 729/udp # IBM NetView DM/6000 Server/Client
+netviewdm2 730/tcp # IBM NetView DM/6000 send/tcp
+netviewdm2 730/udp # IBM NetView DM/6000 send/tcp
+netviewdm3 731/tcp # IBM NetView DM/6000 receive/tcp
+netviewdm3 731/udp # IBM NetView DM/6000 receive/tcp
+netgw 741/tcp # netGW
+netgw 741/udp # netGW
+netrcs 742/tcp # Network based Rev. Cont. Sys.
+netrcs 742/udp # Network based Rev. Cont. Sys.
+flexlm 744/tcp # Flexible License Manager
+flexlm 744/udp # Flexible License Manager
+fujitsu-dev 747/tcp # Fujitsu Device Control
+fujitsu-dev 747/udp # Fujitsu Device Control
+ris-cm 748/tcp # Russell Info Sci Calendar Manager
+ris-cm 748/udp # Russell Info Sci Calendar Manager
+qrh 752/tcp #
+rrh 753/tcp #
+rrh 753/udp #
+tell 754/udp # send
+nlogin 758/tcp #
+nlogin 758/udp #
+con 759/tcp #
+con 759/udp #
+ns 760/udp #
+rxe 761/tcp #
+rxe 761/udp #
+quotad 762/tcp #
+quotad 762/udp #
+cycleserv 763/tcp #
+cycleserv 763/udp #
+omserv 764/tcp #
+omserv 764/udp #
+vid 769/tcp #
+vid 769/udp #
+cadlock 770/tcp #
+cadlock 770/udp #
+rtip 771/tcp #
+rtip 771/udp #
+cycleserv2 772/tcp #
+cycleserv2 772/udp #
+submit 773/tcp #
+notify 773/udp #
+rpasswd 774/tcp #
+acmaint_dbd 774/udp acmaint-dbd #
+entomb 775/tcp #
+acmaint_transd 775/udp acmaint-transd #
+wpages 776/tcp #
+wpages 776/udp #
+multiling-http 777/tcp # Multiling HTTP
+multiling-http 777/udp # Multiling HTTP
+wpgs 780/tcp #
+wpgs 780/udp #
+mdbs_daemon 800/tcp mdbs-daemon #
+mdbs_daemon 800/udp mdbs-daemon #
+device 801/tcp #
+device 801/udp #
+mbap-s 802/tcp # Modbus Application Protocol Secure
+mbap-s 802/udp # Modbus Application Protocol Secure
+fcp-udp 810/tcp # FCP
+fcp-udp 810/udp # FCP Datagram
+itm-mcell-s 828/tcp # itm-mcell-s
+itm-mcell-s 828/udp # itm-mcell-s
+pkix-3-ca-ra 829/tcp # PKIX-3 CA/RA
+pkix-3-ca-ra 829/udp # PKIX-3 CA/RA
+netconf-ssh 830/tcp # NETCONF over SSH
+netconf-ssh 830/udp # NETCONF over SSH
+netconf-beep 831/tcp # NETCONF over BEEP
+netconf-beep 831/udp # NETCONF over BEEP
+netconfsoaphttp 832/tcp # NETCONF for SOAP over HTTPS
+netconfsoaphttp 832/udp # NETCONF for SOAP over HTTPS
+netconfsoapbeep 833/tcp # NETCONF for SOAP over BEEP
+netconfsoapbeep 833/udp # NETCONF for SOAP over BEEP
+dhcp-failover2 847/tcp # dhcp-failover 2
+dhcp-failover2 847/udp # dhcp-failover 2
+gdoi 848/tcp # GDOI
+gdoi 848/udp # GDOI
+domain-s 853/tcp # DNS query-response protocol
+domain-s 853/udp # DNS query-response protocol
+iscsi 860/tcp # iSCSI
+iscsi 860/udp # iSCSI
+owamp-control 861/tcp # OWAMP-Control
+owamp-control 861/udp # OWAMP-Control
+twamp-control 862/tcp # Two-way Active Measurement Protocol (TWAMP) Control
+twamp-control 862/udp # Two-way Active Measurement Protocol (TWAMP) Control
+iclcnet-locate 886/tcp # ICL coNETion locate server
+iclcnet-locate 886/udp # ICL coNETion locate server
+iclcnet_svinfo 887/tcp iclcnet-svinfo # ICL coNETion server info
+iclcnet_svinfo 887/udp iclcnet-svinfo # ICL coNETion server info
+#accessbuilder 888/tcp # AccessBuilder
+#accessbuilder 888/udp # AccessBuilder
+cddbp 888/tcp # CD Database Protocol
+omginitialrefs 900/tcp # OMG Initial Refs
+omginitialrefs 900/udp # OMG Initial Refs
+smpnameres 901/udp # SMPNAMERES
+ideafarm-door 902/tcp # self documenting Telnet Door
+ideafarm-door 902/udp # self documenting Door: send 0x00 for info
+ideafarm-panic 903/tcp # self documenting Telnet Panic Door
+ideafarm-panic 903/udp # self documenting Panic Door: send 0x00 for info
+kink 910/tcp # Kerberized Internet Negotiation of Keys (KINK)
+kink 910/udp # Kerberized Internet Negotiation of Keys (KINK)
+xact-backup 911/tcp # xact-backup
+xact-backup 911/udp # xact-backup
+apex-mesh 912/tcp # APEX relay-relay service
+apex-mesh 912/udp # APEX relay-relay service
+apex-edge 913/tcp # APEX endpoint-relay service
+apex-edge 913/udp # APEX endpoint-relay service
+ftps-data 989/tcp # ftp protocol, data, over TLS/SSL
+ftps-data 989/udp # ftp protocol, data, over TLS/SSL
+ftps 990/tcp # ftp protocol, control, over TLS/SSL
+ftps 990/udp # ftp protocol, control, over TLS/SSL
+nas 991/tcp # Netnews Administration System
+nas 991/udp # Netnews Administration System
+vsinet 996/tcp # vsinet
+vsinet 996/udp # vsinet
+maitrd 997/tcp #
+maitrd 997/udp #
+busboy 998/tcp #
+puparp 998/udp #
+garcon 999/tcp #
+applix 999/udp # Applix ac
+#puprouter 999/tcp #
+#puprouter 999/udp #
+cadlock2 1000/tcp #
+cadlock2 1000/udp #
+surf 1010/tcp # surf
+surf 1010/udp # surf
+exp1 1021/tcp # RFC3692-style Experiment 1 (*) [RFC4727]
+exp1 1021/udp # RFC3692-style Experiment 1 (*) [RFC4727]
+exp1 1021/sctp # RFC3692-style Experiment 1 (*) [RFC4727]
+exp1 1021/dccp # RFC3692-style Experiment 1 (*) [RFC4727]
+exp2 1022/tcp # RFC3692-style Experiment 2 (*) [RFC4727]
+exp2 1022/udp # RFC3692-style Experiment 2 (*) [RFC4727]
+exp2 1022/sctp # RFC3692-style Experiment 2 (*) [RFC4727]
+exp2 1022/dccp # RFC3692-style Experiment 2 (*) [RFC4727]
+blackjack 1025/tcp # network blackjack
+blackjack 1025/udp # network blackjack
+cap 1026/tcp # Calendar Access Protocol
+cap 1026/udp # Calendar Access Protocol
+6a44 1027/udp # IPv6 Behind NAT44 CPEs
+solid-mux 1029/tcp # Solid Mux Server
+solid-mux 1029/udp # Solid Mux Server
+netinfo-local 1033/tcp # local netinfo port
+netinfo-local 1033/udp # local netinfo port
+activesync 1034/tcp # ActiveSync Notifications
+activesync 1034/udp # ActiveSync Notifications
+mxxrlogin 1035/tcp # MX-XR RPC
+mxxrlogin 1035/udp # MX-XR RPC
+nsstp 1036/tcp # Nebula Secure Segment Transfer Protocol
+nsstp 1036/udp # Nebula Secure Segment Transfer Protocol
+ams 1037/tcp # AMS
+ams 1037/udp # AMS
+mtqp 1038/tcp # Message Tracking Query Protocol
+mtqp 1038/udp # Message Tracking Query Protocol
+sbl 1039/tcp # Streamlined Blackhole
+sbl 1039/udp # Streamlined Blackhole
+netarx 1040/tcp # Netarx Netcare
+netarx 1040/udp # Netarx Netcare
+danf-ak2 1041/tcp # AK2 Product
+danf-ak2 1041/udp # AK2 Product
+afrog 1042/tcp # Subnet Roaming
+afrog 1042/udp # Subnet Roaming
+boinc-client 1043/tcp # BOINC Client Control
+boinc-client 1043/udp # BOINC Client Control
+dcutility 1044/tcp # Dev Consortium Utility
+dcutility 1044/udp # Dev Consortium Utility
+fpitp 1045/tcp # Fingerprint Image Transfer Protocol
+fpitp 1045/udp # Fingerprint Image Transfer Protocol
+wfremotertm 1046/tcp # WebFilter Remote Monitor
+wfremotertm 1046/udp # WebFilter Remote Monitor
+neod1 1047/tcp # Sun's NEO Object Request Broker
+neod1 1047/udp # Sun's NEO Object Request Broker
+neod2 1048/tcp # Sun's NEO Object Request Broker
+neod2 1048/udp # Sun's NEO Object Request Broker
+td-postman 1049/tcp # Tobit David Postman VPMN
+td-postman 1049/udp # Tobit David Postman VPMN
+cma 1050/tcp # CORBA Management Agent
+cma 1050/udp # CORBA Management Agent
+optima-vnet 1051/tcp # Optima VNET
+optima-vnet 1051/udp # Optima VNET
+ddt 1052/tcp # Dynamic DNS Tools
+ddt 1052/udp # Dynamic DNS Tools
+remote-as 1053/tcp # Remote Assistant (RA)
+remote-as 1053/udp # Remote Assistant (RA)
+brvread 1054/tcp # BRVREAD
+brvread 1054/udp # BRVREAD
+ansyslmd 1055/tcp # ANSYS - License Manager
+ansyslmd 1055/udp # ANSYS - License Manager
+vfo 1056/tcp # VFO
+vfo 1056/udp # VFO
+startron 1057/tcp # STARTRON
+startron 1057/udp # STARTRON
+nim 1058/tcp # nim
+nim 1058/udp # nim
+nimreg 1059/tcp # nimreg
+nimreg 1059/udp # nimreg
+polestar 1060/tcp # POLESTAR
+polestar 1060/udp # POLESTAR
+kiosk 1061/tcp # KIOSK
+kiosk 1061/udp # KIOSK
+veracity 1062/tcp # Veracity
+veracity 1062/udp # Veracity
+kyoceranetdev 1063/tcp # KyoceraNetDev
+kyoceranetdev 1063/udp # KyoceraNetDev
+jstel 1064/tcp # JSTEL
+jstel 1064/udp # JSTEL
+syscomlan 1065/tcp # SYSCOMLAN
+syscomlan 1065/udp # SYSCOMLAN
+fpo-fns 1066/tcp # FPO-FNS
+fpo-fns 1066/udp # FPO-FNS
+instl_boots 1067/tcp instl-boots # Installation Bootstrap Proto. Serv.
+instl_boots 1067/udp instl-boots # Installation Bootstrap Proto. Serv.
+instl_bootc 1068/tcp instl-bootc # Installation Bootstrap Proto. Cli.
+instl_bootc 1068/udp instl-bootc # Installation Bootstrap Proto. Cli.
+cognex-insight 1069/tcp # COGNEX-INSIGHT
+cognex-insight 1069/udp # COGNEX-INSIGHT
+gmrupdateserv 1070/tcp # GMRUpdateSERV
+gmrupdateserv 1070/udp # GMRUpdateSERV
+bsquare-voip 1071/tcp # BSQUARE-VOIP
+bsquare-voip 1071/udp # BSQUARE-VOIP
+cardax 1072/tcp # CARDAX
+cardax 1072/udp # CARDAX
+bridgecontrol 1073/tcp # Bridge Control
+bridgecontrol 1073/udp # Bridge Control
+warmspotMgmt 1074/tcp # Warmspot Management Protocol
+warmspotMgmt 1074/udp # Warmspot Management Protocol
+rdrmshc 1075/tcp # RDRMSHC
+rdrmshc 1075/udp # RDRMSHC
+dab-sti-c 1076/tcp # DAB STI-C
+dab-sti-c 1076/udp # DAB STI-C
+imgames 1077/tcp # IMGames
+imgames 1077/udp # IMGames
+avocent-proxy 1078/tcp # Avocent Proxy Protocol
+avocent-proxy 1078/udp # Avocent Proxy Protocol
+asprovatalk 1079/tcp # ASPROVATalk
+asprovatalk 1079/udp # ASPROVATalk
+pvuniwien 1081/tcp # PVUNIWIEN
+pvuniwien 1081/udp # PVUNIWIEN
+amt-esd-prot 1082/tcp # AMT-ESD-PROT
+amt-esd-prot 1082/udp # AMT-ESD-PROT
+ansoft-lm-1 1083/tcp # Anasoft License Manager
+ansoft-lm-1 1083/udp # Anasoft License Manager
+ansoft-lm-2 1084/tcp # Anasoft License Manager
+ansoft-lm-2 1084/udp # Anasoft License Manager
+webobjects 1085/tcp # Web Objects
+webobjects 1085/udp # Web Objects
+cplscrambler-lg 1086/tcp # CPL Scrambler Logging
+cplscrambler-lg 1086/udp # CPL Scrambler Logging
+cplscrambler-in 1087/tcp # CPL Scrambler Internal
+cplscrambler-in 1087/udp # CPL Scrambler Internal
+cplscrambler-al 1088/tcp # CPL Scrambler Alarm Log
+cplscrambler-al 1088/udp # CPL Scrambler Alarm Log
+ff-annunc 1089/tcp # FF Annunciation
+ff-annunc 1089/udp # FF Annunciation
+ff-fms 1090/tcp # FF Fieldbus Message Specification
+ff-fms 1090/udp # FF Fieldbus Message Specification
+ff-sm 1091/tcp # FF System Management
+ff-sm 1091/udp # FF System Management
+obrpd 1092/tcp # Open Business Reporting Protocol
+obrpd 1092/udp # Open Business Reporting Protocol
+proofd 1093/tcp # PROOFD
+proofd 1093/udp # PROOFD
+rootd 1094/tcp # ROOTD
+rootd 1094/udp # ROOTD
+nicelink 1095/tcp # NICELink
+nicelink 1095/udp # NICELink
+cnrprotocol 1096/tcp # Common Name Resolution Protocol
+cnrprotocol 1096/udp # Common Name Resolution Protocol
+sunclustermgr 1097/tcp # Sun Cluster Manager
+sunclustermgr 1097/udp # Sun Cluster Manager
+rmiactivation 1098/tcp # RMI Activation
+rmiactivation 1098/udp # RMI Activation
+rmiregistry 1099/tcp # RMI Registry
+rmiregistry 1099/udp # RMI Registry
+mctp 1100/tcp # MCTP
+mctp 1100/udp # MCTP
+pt2-discover 1101/tcp # PT2-DISCOVER
+pt2-discover 1101/udp # PT2-DISCOVER
+adobeserver-1 1102/tcp # ADOBE SERVER 1
+adobeserver-1 1102/udp # ADOBE SERVER 1
+adobeserver-2 1103/tcp # ADOBE SERVER 2
+adobeserver-2 1103/udp # ADOBE SERVER 2
+xrl 1104/tcp # XRL
+xrl 1104/udp # XRL
+ftranhc 1105/tcp # FTRANHC
+ftranhc 1105/udp # FTRANHC
+isoipsigport-1 1106/tcp # ISOIPSIGPORT-1
+isoipsigport-1 1106/udp # ISOIPSIGPORT-1
+isoipsigport-2 1107/tcp # ISOIPSIGPORT-2
+isoipsigport-2 1107/udp # ISOIPSIGPORT-2
+ratio-adp 1108/tcp # ratio-adp
+ratio-adp 1108/udp # ratio-adp
+webadmstart 1110/tcp # Start web admin server
+nfsd-keepalive 1110/udp # Client status info
+lmsocialserver 1111/tcp # LM Social Server
+lmsocialserver 1111/udp # LM Social Server
+icp 1112/tcp # Intelligent Communication Protocol
+icp 1112/udp # Intelligent Communication Protocol
+ltp-deepspace 1113/tcp # Licklider Transmission Protocol
+ltp-deepspace 1113/udp # Licklider Transmission Protocol
+ltp-deepspace 1113/dccp # Licklider Transmission Protocol
+mini-sql 1114/tcp # Mini SQL
+mini-sql 1114/udp # Mini SQL
+ardus-trns 1115/tcp # ARDUS Transfer
+ardus-trns 1115/udp # ARDUS Transfer
+ardus-cntl 1116/tcp # ARDUS Control
+ardus-cntl 1116/udp # ARDUS Control
+ardus-mtrns 1117/tcp # ARDUS Multicast Transfer
+ardus-mtrns 1117/udp # ARDUS Multicast Transfer
+sacred 1118/tcp # SACRED
+sacred 1118/udp # SACRED
+bnetgame 1119/tcp # Battle.net Chat/Game Protocol
+bnetgame 1119/udp # Battle.net Chat/Game Protocol
+bnetfile 1120/tcp # Battle.net File Transfer Protocol
+bnetfile 1120/udp # Battle.net File Transfer Protocol
+rmpp 1121/tcp # Datalode RMPP
+rmpp 1121/udp # Datalode RMPP
+availant-mgr 1122/tcp # availant-mgr
+availant-mgr 1122/udp # availant-mgr
+murray 1123/tcp # Murray
+murray 1123/udp # Murray
+hpvmmcontrol 1124/tcp # HP VMM Control
+hpvmmcontrol 1124/udp # HP VMM Control
+hpvmmagent 1125/tcp # HP VMM Agent
+hpvmmagent 1125/udp # HP VMM Agent
+hpvmmdata 1126/tcp # HP VMM Agent
+hpvmmdata 1126/udp # HP VMM Agent
+kwdb-commn 1127/udp # KWDB Remote Communication
+saphostctrl 1128/tcp # SAPHostControl over SOAP/HTTP
+saphostctrl 1128/udp # SAPHostControl over SOAP/HTTP
+saphostctrls 1129/tcp # SAPHostControl over SOAP/HTTPS
+saphostctrls 1129/udp # SAPHostControl over SOAP/HTTPS
+casp 1130/tcp # CAC App Service Protocol
+casp 1130/udp # CAC App Service Protocol
+caspssl 1131/tcp # CAC App Service Protocol Encripted
+caspssl 1131/udp # CAC App Service Protocol Encripted
+kvm-via-ip 1132/tcp # KVM-via-IP Management Service
+kvm-via-ip 1132/udp # KVM-via-IP Management Service
+dfn 1133/tcp # Data Flow Network
+dfn 1133/udp # Data Flow Network
+aplx 1134/tcp # MicroAPL APLX
+aplx 1134/udp # MicroAPL APLX
+omnivision 1135/tcp # OmniVision Communication Service
+omnivision 1135/udp # OmniVision Communication Service
+hhb-gateway 1136/tcp # HHB Gateway Control
+hhb-gateway 1136/udp # HHB Gateway Control
+trim 1137/tcp # TRIM Workgroup Service
+trim 1137/udp # TRIM Workgroup Service
+encrypted_admin 1138/tcp encrypted-admin # encrypted admin requests
+encrypted_admin 1138/udp encrypted-admin # encrypted admin requests
+evm 1139/tcp # Enterprise Virtual Manager
+evm 1139/udp # Enterprise Virtual Manager
+autonoc 1140/tcp # AutoNOC Network Operations Protocol
+autonoc 1140/udp # AutoNOC Network Operations Protocol
+mxomss 1141/tcp # User Message Service
+mxomss 1141/udp # User Message Service
+edtools 1142/tcp # User Discovery Service
+edtools 1142/udp # User Discovery Service
+imyx 1143/tcp # Infomatryx Exchange
+imyx 1143/udp # Infomatryx Exchange
+fuscript 1144/tcp # Fusion Script
+fuscript 1144/udp # Fusion Script
+x9-icue 1145/tcp # X9 iCue Show Control
+x9-icue 1145/udp # X9 iCue Show Control
+audit-transfer 1146/tcp # audit transfer
+audit-transfer 1146/udp # audit transfer
+capioverlan 1147/tcp # CAPIoverLAN
+capioverlan 1147/udp # CAPIoverLAN
+elfiq-repl 1148/tcp # Elfiq Replication Service
+elfiq-repl 1148/udp # Elfiq Replication Service
+bvtsonar 1149/tcp # BlueView Sonar Service
+bvtsonar 1149/udp # BlueView Sonar Service
+blaze 1150/tcp # Blaze File Server
+blaze 1150/udp # Blaze File Server
+unizensus 1151/tcp # Unizensus Login Server
+unizensus 1151/udp # Unizensus Login Server
+winpoplanmess 1152/tcp # Winpopup LAN Messenger
+winpoplanmess 1152/udp # Winpopup LAN Messenger
+c1222-acse 1153/tcp # ANSI C12.22 Port
+c1222-acse 1153/udp # ANSI C12.22 Port
+resacommunity 1154/tcp # Community Service
+resacommunity 1154/udp # Community Service
+nfa 1155/tcp # Network File Access
+nfa 1155/udp # Network File Access
+iascontrol-oms 1156/tcp # iasControl OMS
+iascontrol-oms 1156/udp # iasControl OMS
+iascontrol 1157/tcp # Oracle iASControl
+iascontrol 1157/udp # Oracle iASControl
+dbcontrol-oms 1158/tcp # dbControl OMS
+dbcontrol-oms 1158/udp # dbControl OMS
+oracle-oms 1159/tcp # Oracle OMS
+oracle-oms 1159/udp # Oracle OMS
+olsv 1160/tcp # DB Lite Mult-User Server
+olsv 1160/udp # DB Lite Mult-User Server
+health-polling 1161/tcp # Health Polling
+health-polling 1161/udp # Health Polling
+health-trap 1162/tcp # Health Trap
+health-trap 1162/udp # Health Trap
+sddp 1163/tcp # SmartDialer Data Protocol
+sddp 1163/udp # SmartDialer Data Protocol
+qsm-proxy 1164/tcp # QSM Proxy Service
+qsm-proxy 1164/udp # QSM Proxy Service
+qsm-gui 1165/tcp # QSM GUI Service
+qsm-gui 1165/udp # QSM GUI Service
+qsm-remote 1166/tcp # QSM RemoteExec
+qsm-remote 1166/udp # QSM RemoteExec
+cisco-ipsla 1167/tcp # Cisco IP SLAs Control Protocol
+cisco-ipsla 1167/udp # Cisco IP SLAs Control Protocol
+cisco-ipsla 1167/sctp # Cisco IP SLAs Control Protocol
+vchat 1168/tcp # VChat Conference Service
+vchat 1168/udp # VChat Conference Service
+tripwire 1169/tcp # TRIPWIRE
+tripwire 1169/udp # TRIPWIRE
+atc-lm 1170/tcp # AT+C License Manager
+atc-lm 1170/udp # AT+C License Manager
+atc-appserver 1171/tcp # AT+C FmiApplicationServer
+atc-appserver 1171/udp # AT+C FmiApplicationServer
+dnap 1172/tcp # DNA Protocol
+dnap 1172/udp # DNA Protocol
+d-cinema-rrp 1173/tcp # D-Cinema Request-Response
+d-cinema-rrp 1173/udp # D-Cinema Request-Response
+fnet-remote-ui 1174/tcp # FlashNet Remote Admin
+fnet-remote-ui 1174/udp # FlashNet Remote Admin
+dossier 1175/tcp # Dossier Server
+dossier 1175/udp # Dossier Server
+indigo-server 1176/tcp # Indigo Home Server
+indigo-server 1176/udp # Indigo Home Server
+dkmessenger 1177/tcp # DKMessenger Protocol
+dkmessenger 1177/udp # DKMessenger Protocol
+sgi-storman 1178/udp # SGI Storage Manager
+b2n 1179/tcp # Backup To Neighbor
+b2n 1179/udp # Backup To Neighbor
+mc-client 1180/tcp # Millicent Client Proxy
+mc-client 1180/udp # Millicent Client Proxy
+3comnetman 1181/tcp # 3Com Net Management
+3comnetman 1181/udp # 3Com Net Management
+accelenet 1182/tcp # AcceleNet Control
+accelenet-data 1182/udp # AcceleNet Data
+llsurfup-http 1183/tcp # LL Surfup HTTP
+llsurfup-http 1183/udp # LL Surfup HTTP
+llsurfup-https 1184/tcp # LL Surfup HTTPS
+llsurfup-https 1184/udp # LL Surfup HTTPS
+catchpole 1185/tcp # Catchpole port
+catchpole 1185/udp # Catchpole port
+mysql-cluster 1186/tcp # MySQL Cluster Manager
+mysql-cluster 1186/udp # MySQL Cluster Manager
+alias 1187/tcp # Alias Service
+alias 1187/udp # Alias Service
+hp-webadmin 1188/tcp # HP Web Admin
+hp-webadmin 1188/udp # HP Web Admin
+unet 1189/tcp # Unet Connection
+unet 1189/udp # Unet Connection
+commlinx-avl 1190/tcp # CommLinx GPS / AVL System
+commlinx-avl 1190/udp # CommLinx GPS / AVL System
+gpfs 1191/tcp # General Parallel File System
+gpfs 1191/udp # General Parallel File System
+caids-sensor 1192/tcp # caids sensors channel
+caids-sensor 1192/udp # caids sensors channel
+fiveacross 1193/tcp # Five Across Server
+fiveacross 1193/udp # Five Across Server
+openvpn 1194/tcp # OpenVPN
+openvpn 1194/udp # OpenVPN
+rsf-1 1195/tcp # RSF-1 clustering
+rsf-1 1195/udp # RSF-1 clustering
+netmagic 1196/tcp # Network Magic
+netmagic 1196/udp # Network Magic
+carrius-rshell 1197/tcp # Carrius Remote Access
+carrius-rshell 1197/udp # Carrius Remote Access
+cajo-discovery 1198/tcp # cajo reference discovery
+cajo-discovery 1198/udp # cajo reference discovery
+dmidi 1199/tcp # DMIDI
+dmidi 1199/udp # DMIDI
+scol 1200/tcp # SCOL
+scol 1200/udp # SCOL
+nucleus-sand 1201/tcp # Nucleus Sand Database Server
+nucleus-sand 1201/udp # Nucleus Sand Database Server
+caiccipc 1202/tcp # caiccipc
+caiccipc 1202/udp # caiccipc
+ssslic-mgr 1203/tcp # License Validation
+ssslic-mgr 1203/udp # License Validation
+ssslog-mgr 1204/tcp # Log Request Listener
+ssslog-mgr 1204/udp # Log Request Listener
+accord-mgc 1205/tcp # Accord-MGC
+accord-mgc 1205/udp # Accord-MGC
+anthony-data 1206/tcp # Anthony Data
+anthony-data 1206/udp # Anthony Data
+metasage 1207/tcp # MetaSage
+metasage 1207/udp # MetaSage
+seagull-ais 1208/tcp # SEAGULL AIS
+seagull-ais 1208/udp # SEAGULL AIS
+ipcd3 1209/tcp # IPCD3
+ipcd3 1209/udp # IPCD3
+eoss 1210/tcp # EOSS
+eoss 1210/udp # EOSS
+groove-dpp 1211/tcp # Groove DPP
+groove-dpp 1211/udp # Groove DPP
+lupa 1212/tcp # lupa
+lupa 1212/udp # lupa
+mpc-lifenet 1213/tcp # MPC LIFENET
+mpc-lifenet 1213/udp # MPC LIFENET
+kazaa 1214/tcp # KAZAA
+kazaa 1214/udp # KAZAA
+scanstat-1 1215/tcp # scanSTAT 1.0
+scanstat-1 1215/udp # scanSTAT 1.0
+etebac5 1216/tcp # ETEBAC 5
+etebac5 1216/udp # ETEBAC 5
+hpss-ndapi 1217/tcp # HPSS NonDCE Gateway
+hpss-ndapi 1217/udp # HPSS NonDCE Gateway
+aeroflight-ads 1218/tcp # AeroFlight-ADs
+aeroflight-ads 1218/udp # AeroFlight-ADs
+aeroflight-ret 1219/tcp # AeroFlight-Ret
+aeroflight-ret 1219/udp # AeroFlight-Ret
+qt-serveradmin 1220/tcp # QT SERVER ADMIN
+qt-serveradmin 1220/udp # QT SERVER ADMIN
+sweetware-apps 1221/tcp # SweetWARE Apps
+sweetware-apps 1221/udp # SweetWARE Apps
+nerv 1222/tcp # SNI R&D network
+nerv 1222/udp # SNI R&D network
+tgp 1223/tcp # TrulyGlobal Protocol
+tgp 1223/udp # TrulyGlobal Protocol
+vpnz 1224/tcp # VPNz
+vpnz 1224/udp # VPNz
+slinkysearch 1225/tcp # SLINKYSEARCH
+slinkysearch 1225/udp # SLINKYSEARCH
+stgxfws 1226/tcp # STGXFWS
+stgxfws 1226/udp # STGXFWS
+dns2go 1227/tcp # DNS2Go
+dns2go 1227/udp # DNS2Go
+florence 1228/tcp # FLORENCE
+florence 1228/udp # FLORENCE
+zented 1229/tcp # ZENworks Tiered Electronic Distribution
+zented 1229/udp # ZENworks Tiered Electronic Distribution
+periscope 1230/tcp # Periscope
+periscope 1230/udp # Periscope
+menandmice-lpm 1231/tcp # menandmice-lpm
+menandmice-lpm 1231/udp # menandmice-lpm
+first-defense 1232/tcp # Remote systems monitoring
+first-defense 1232/udp # Remote systems monitoring
+univ-appserver 1233/tcp # Universal App Server
+univ-appserver 1233/udp # Universal App Server
+search-agent 1234/tcp # Infoseek Search Agent
+search-agent 1234/udp # Infoseek Search Agent
+mosaicsyssvc1 1235/tcp # mosaicsyssvc1
+mosaicsyssvc1 1235/udp # mosaicsyssvc1
+tsdos390 1237/tcp # tsdos390
+tsdos390 1237/udp # tsdos390
+hacl-qs 1238/tcp # hacl-qs
+hacl-qs 1238/udp # hacl-qs
+nmsd 1239/tcp # NMSD
+nmsd 1239/udp # NMSD
+instantia 1240/tcp # Instantia
+instantia 1240/udp # Instantia
+nessus 1241/tcp # nessus
+nessus 1241/udp # nessus
+nmasoverip 1242/tcp # NMAS over IP
+nmasoverip 1242/udp # NMAS over IP
+serialgateway 1243/tcp # SerialGateway
+serialgateway 1243/udp # SerialGateway
+isbconference1 1244/tcp # isbconference1
+isbconference1 1244/udp # isbconference1
+isbconference2 1245/tcp # isbconference2
+isbconference2 1245/udp # isbconference2
+payrouter 1246/tcp # payrouter
+payrouter 1246/udp # payrouter
+visionpyramid 1247/tcp # VisionPyramid
+visionpyramid 1247/udp # VisionPyramid
+hermes 1248/tcp # hermes
+hermes 1248/udp # hermes
+mesavistaco 1249/tcp # Mesa Vista Co
+mesavistaco 1249/udp # Mesa Vista Co
+swldy-sias 1250/tcp # swldy-sias
+swldy-sias 1250/udp # swldy-sias
+servergraph 1251/tcp # servergraph
+servergraph 1251/udp # servergraph
+bspne-pcc 1252/tcp # bspne-pcc
+bspne-pcc 1252/udp # bspne-pcc
+q55-pcc 1253/tcp # q55-pcc
+q55-pcc 1253/udp # q55-pcc
+de-noc 1254/tcp # de-noc
+de-noc 1254/udp # de-noc
+de-cache-query 1255/tcp # de-cache-query
+de-cache-query 1255/udp # de-cache-query
+de-server 1256/tcp # de-server
+de-server 1256/udp # de-server
+shockwave2 1257/tcp # Shockwave 2
+shockwave2 1257/udp # Shockwave 2
+opennl 1258/tcp # Open Network Library
+opennl 1258/udp # Open Network Library
+opennl-voice 1259/tcp # Open Network Library Voice
+opennl-voice 1259/udp # Open Network Library Voice
+ibm-ssd 1260/tcp # ibm-ssd
+ibm-ssd 1260/udp # ibm-ssd
+mpshrsv 1261/tcp # mpshrsv
+mpshrsv 1261/udp # mpshrsv
+qnts-orb 1262/tcp # QNTS-ORB
+qnts-orb 1262/udp # QNTS-ORB
+dka 1263/tcp # dka
+dka 1263/udp # dka
+prat 1264/tcp # PRAT
+prat 1264/udp # PRAT
+dssiapi 1265/tcp # DSSIAPI
+dssiapi 1265/udp # DSSIAPI
+dellpwrappks 1266/tcp # DELLPWRAPPKS
+dellpwrappks 1266/udp # DELLPWRAPPKS
+epc 1267/tcp # eTrust Policy Compliance
+epc 1267/udp # eTrust Policy Compliance
+propel-msgsys 1268/tcp # PROPEL-MSGSYS
+propel-msgsys 1268/udp # PROPEL-MSGSYS
+watilapp 1269/tcp # WATiLaPP
+watilapp 1269/udp # WATiLaPP
+opsmgr 1270/tcp # Microsoft Operations Manager
+opsmgr 1270/udp # Microsoft Operations Manager
+excw 1271/tcp # eXcW
+excw 1271/udp # eXcW
+cspmlockmgr 1272/tcp # CSPMLockMgr
+cspmlockmgr 1272/udp # CSPMLockMgr
+emc-gateway 1273/tcp # EMC-Gateway
+emc-gateway 1273/udp # EMC-Gateway
+t1distproc 1274/tcp # t1distproc
+t1distproc 1274/udp # t1distproc
+ivcollector 1275/tcp # ivcollector
+ivcollector 1275/udp # ivcollector
+miva-mqs 1277/tcp # mqs
+miva-mqs 1277/udp # mqs
+dellwebadmin-1 1278/tcp # Dell Web Admin 1
+dellwebadmin-1 1278/udp # Dell Web Admin 1
+dellwebadmin-2 1279/tcp # Dell Web Admin 2
+dellwebadmin-2 1279/udp # Dell Web Admin 2
+pictrography 1280/tcp # Pictrography
+pictrography 1280/udp # Pictrography
+healthd 1281/tcp # healthd
+healthd 1281/udp # healthd
+emperion 1282/tcp # Emperion
+emperion 1282/udp # Emperion
+productinfo 1283/tcp # Product Information
+productinfo 1283/udp # Product Information
+iee-qfx 1284/tcp # IEE-QFX
+iee-qfx 1284/udp # IEE-QFX
+neoiface 1285/tcp # neoiface
+neoiface 1285/udp # neoiface
+netuitive 1286/tcp # netuitive
+netuitive 1286/udp # netuitive
+routematch 1287/tcp # RouteMatch Com
+routematch 1287/udp # RouteMatch Com
+navbuddy 1288/tcp # NavBuddy
+navbuddy 1288/udp # NavBuddy
+jwalkserver 1289/tcp # JWalkServer
+jwalkserver 1289/udp # JWalkServer
+winjaserver 1290/tcp # WinJaServer
+winjaserver 1290/udp # WinJaServer
+seagulllms 1291/tcp # SEAGULLLMS
+seagulllms 1291/udp # SEAGULLLMS
+dsdn 1292/tcp # dsdn
+dsdn 1292/udp # dsdn
+pkt-krb-ipsec 1293/tcp # PKT-KRB-IPSec
+pkt-krb-ipsec 1293/udp # PKT-KRB-IPSec
+cmmdriver 1294/tcp # CMMdriver
+cmmdriver 1294/udp # CMMdriver
+ehtp 1295/tcp # End-by-Hop Transmission Protocol
+ehtp 1295/udp # End-by-Hop Transmission Protocol
+dproxy 1296/tcp # dproxy
+dproxy 1296/udp # dproxy
+sdproxy 1297/tcp # sdproxy
+sdproxy 1297/udp # sdproxy
+lpcp 1298/tcp # lpcp
+lpcp 1298/udp # lpcp
+hp-sci 1299/tcp # hp-sci
+hp-sci 1299/udp # hp-sci
+ci3-software-1 1301/tcp # CI3-Software-1
+ci3-software-1 1301/udp # CI3-Software-1
+ci3-software-2 1302/tcp # CI3-Software-2
+ci3-software-2 1302/udp # CI3-Software-2
+sftsrv 1303/tcp # sftsrv
+sftsrv 1303/udp # sftsrv
+boomerang 1304/tcp # Boomerang
+boomerang 1304/udp # Boomerang
+pe-mike 1305/tcp # pe-mike
+pe-mike 1305/udp # pe-mike
+re-conn-proto 1306/tcp # RE-Conn-Proto
+re-conn-proto 1306/udp # RE-Conn-Proto
+pacmand 1307/tcp # Pacmand
+pacmand 1307/udp # Pacmand
+odsi 1308/tcp # Optical Domain Service Interconnect (ODSI)
+odsi 1308/udp # Optical Domain Service Interconnect (ODSI)
+jtag-server 1309/tcp # JTAG server
+jtag-server 1309/udp # JTAG server
+husky 1310/tcp # Husky
+husky 1310/udp # Husky
+rxmon 1311/tcp # RxMon
+rxmon 1311/udp # RxMon
+sti-envision 1312/tcp # STI Envision
+sti-envision 1312/udp # STI Envision
+bmc_patroldb 1313/udp bmc-patroldb # BMC_PATROLDB
+pdps 1314/tcp # Photoscript Distributed Printing System
+pdps 1314/udp # Photoscript Distributed Printing System
+els 1315/tcp # E.L.S., Event Listener Service
+els 1315/udp # E.L.S., Event Listener Service
+exbit-escp 1316/tcp # Exbit-ESCP
+exbit-escp 1316/udp # Exbit-ESCP
+vrts-ipcserver 1317/tcp # vrts-ipcserver
+vrts-ipcserver 1317/udp # vrts-ipcserver
+krb5gatekeeper 1318/tcp # krb5gatekeeper
+krb5gatekeeper 1318/udp # krb5gatekeeper
+amx-icsp 1319/tcp # AMX-ICSP
+amx-icsp 1319/udp # AMX-ICSP
+amx-axbnet 1320/tcp # AMX-AXBNET
+amx-axbnet 1320/udp # AMX-AXBNET
+novation 1322/tcp # Novation
+novation 1322/udp # Novation
+brcd 1323/tcp # brcd
+brcd 1323/udp # brcd
+delta-mcp 1324/tcp # delta-mcp
+delta-mcp 1324/udp # delta-mcp
+dx-instrument 1325/tcp # DX-Instrument
+dx-instrument 1325/udp # DX-Instrument
+wimsic 1326/tcp # WIMSIC
+wimsic 1326/udp # WIMSIC
+ultrex 1327/tcp # Ultrex
+ultrex 1327/udp # Ultrex
+ewall 1328/tcp # EWALL
+ewall 1328/udp # EWALL
+netdb-export 1329/tcp # netdb-export
+netdb-export 1329/udp # netdb-export
+streetperfect 1330/tcp # StreetPerfect
+streetperfect 1330/udp # StreetPerfect
+intersan 1331/tcp # intersan
+intersan 1331/udp # intersan
+pcia-rxp-b 1332/tcp # PCIA RXP-B
+pcia-rxp-b 1332/udp # PCIA RXP-B
+passwrd-policy 1333/tcp # Password Policy
+passwrd-policy 1333/udp # Password Policy
+writesrv 1334/tcp # writesrv
+writesrv 1334/udp # writesrv
+digital-notary 1335/tcp # Digital Notary Protocol
+digital-notary 1335/udp # Digital Notary Protocol
+ischat 1336/tcp # Instant Service Chat
+ischat 1336/udp # Instant Service Chat
+menandmice-dns 1337/tcp # menandmice DNS
+menandmice-dns 1337/udp # menandmice DNS
+wmc-log-svc 1338/tcp # WMC-log-svr
+wmc-log-svc 1338/udp # WMC-log-svr
+kjtsiteserver 1339/tcp # kjtsiteserver
+kjtsiteserver 1339/udp # kjtsiteserver
+naap 1340/tcp # NAAP
+naap 1340/udp # NAAP
+qubes 1341/tcp # QuBES
+qubes 1341/udp # QuBES
+esbroker 1342/tcp # ESBroker
+esbroker 1342/udp # ESBroker
+re101 1343/tcp # re101
+re101 1343/udp # re101
+icap 1344/tcp # ICAP
+icap 1344/udp # ICAP
+vpjp 1345/tcp # VPJP
+vpjp 1345/udp # VPJP
+alta-ana-lm 1346/tcp # Alta Analytics License Manager
+alta-ana-lm 1346/udp # Alta Analytics License Manager
+bbn-mmc 1347/tcp # multi media conferencing
+bbn-mmc 1347/udp # multi media conferencing
+bbn-mmx 1348/tcp # multi media conferencing
+bbn-mmx 1348/udp # multi media conferencing
+sbook 1349/tcp # Registration Network Protocol
+sbook 1349/udp # Registration Network Protocol
+editbench 1350/tcp # Registration Network Protocol
+editbench 1350/udp # Registration Network Protocol
+equationbuilder 1351/tcp # Digital Tool Works (MIT)
+equationbuilder 1351/udp # Digital Tool Works (MIT)
+lotusnote 1352/tcp # Lotus Note
+lotusnote 1352/udp # Lotus Note
+relief 1353/tcp # Relief Consulting
+relief 1353/udp # Relief Consulting
+XSIP-network 1354/tcp # Five Across XSIP Network
+XSIP-network 1354/udp # Five Across XSIP Network
+intuitive-edge 1355/tcp # Intuitive Edge
+intuitive-edge 1355/udp # Intuitive Edge
+cuillamartin 1356/tcp # CuillaMartin Company
+cuillamartin 1356/udp # CuillaMartin Company
+pegboard 1357/tcp # Electronic PegBoard
+pegboard 1357/udp # Electronic PegBoard
+connlcli 1358/tcp # CONNLCLI
+connlcli 1358/udp # CONNLCLI
+ftsrv 1359/tcp # FTSRV
+ftsrv 1359/udp # FTSRV
+mimer 1360/tcp # MIMER
+mimer 1360/udp # MIMER
+linx 1361/tcp # LinX
+linx 1361/udp # LinX
+timeflies 1362/tcp # TimeFlies
+timeflies 1362/udp # TimeFlies
+ndm-requester 1363/tcp # Network DataMover Requester
+ndm-requester 1363/udp # Network DataMover Requester
+ndm-server 1364/tcp # Network DataMover Server
+ndm-server 1364/udp # Network DataMover Server
+adapt-sna 1365/tcp # Network Software Associates
+adapt-sna 1365/udp # Network Software Associates
+netware-csp 1366/tcp # Novell NetWare Comm Service Platform
+netware-csp 1366/udp # Novell NetWare Comm Service Platform
+dcs 1367/tcp # DCS
+dcs 1367/udp # DCS
+screencast 1368/tcp # ScreenCast
+screencast 1368/udp # ScreenCast
+gv-us 1369/tcp # GlobalView to Unix Shell
+gv-us 1369/udp # GlobalView to Unix Shell
+us-gv 1370/tcp # Unix Shell to GlobalView
+us-gv 1370/udp # Unix Shell to GlobalView
+fc-cli 1371/tcp # Fujitsu Config Protocol
+fc-cli 1371/udp # Fujitsu Config Protocol
+fc-ser 1372/tcp # Fujitsu Config Protocol
+fc-ser 1372/udp # Fujitsu Config Protocol
+chromagrafx 1373/tcp # Chromagrafx
+chromagrafx 1373/udp # Chromagrafx
+molly 1374/tcp # EPI Software Systems
+molly 1374/udp # EPI Software Systems
+bytex 1375/tcp # Bytex
+bytex 1375/udp # Bytex
+ibm-pps 1376/tcp # IBM Person to Person Software
+ibm-pps 1376/udp # IBM Person to Person Software
+cichlid 1377/tcp # Cichlid License Manager
+cichlid 1377/udp # Cichlid License Manager
+elan 1378/tcp # Elan License Manager
+elan 1378/udp # Elan License Manager
+dbreporter 1379/tcp # Integrity Solutions
+dbreporter 1379/udp # Integrity Solutions
+telesis-licman 1380/tcp # Telesis Network License Manager
+telesis-licman 1380/udp # Telesis Network License Manager
+apple-licman 1381/tcp # Apple Network License Manager
+apple-licman 1381/udp # Apple Network License Manager
+udt_os 1382/tcp # udt_os
+udt_os 1382/udp # udt_os
+gwha 1383/tcp # GW Hannaway Network License Manager
+gwha 1383/udp # GW Hannaway Network License Manager
+os-licman 1384/tcp # Objective Solutions License Manager
+os-licman 1384/udp # Objective Solutions License Manager
+atex_elmd 1385/tcp atex-elmd # Atex Publishing License Manager
+atex_elmd 1385/udp atex-elmd # Atex Publishing License Manager
+checksum 1386/tcp # CheckSum License Manager
+checksum 1386/udp # CheckSum License Manager
+cadsi-lm 1387/tcp # Computer Aided Design Software Inc LM
+cadsi-lm 1387/udp # Computer Aided Design Software Inc LM
+objective-dbc 1388/tcp # Objective Solutions DataBase Cache
+objective-dbc 1388/udp # Objective Solutions DataBase Cache
+iclpv-dm 1389/tcp # Document Manager
+iclpv-dm 1389/udp # Document Manager
+iclpv-sc 1390/tcp # Storage Controller
+iclpv-sc 1390/udp # Storage Controller
+iclpv-sas 1391/tcp # Storage Access Server
+iclpv-sas 1391/udp # Storage Access Server
+iclpv-pm 1392/tcp # Print Manager
+iclpv-pm 1392/udp # Print Manager
+iclpv-nls 1393/tcp # Network Log Server
+iclpv-nls 1393/udp # Network Log Server
+iclpv-nlc 1394/tcp # Network Log Client
+iclpv-nlc 1394/udp # Network Log Client
+iclpv-wsm 1395/tcp # PC Workstation Manager software
+iclpv-wsm 1395/udp # PC Workstation Manager software
+dvl-activemail 1396/tcp # DVL Active Mail
+dvl-activemail 1396/udp # DVL Active Mail
+audio-activmail 1397/tcp # Audio Active Mail
+audio-activmail 1397/udp # Audio Active Mail
+video-activmail 1398/tcp # Video Active Mail
+video-activmail 1398/udp # Video Active Mail
+cadkey-licman 1399/tcp # Cadkey License Manager
+cadkey-licman 1399/udp # Cadkey License Manager
+cadkey-tablet 1400/tcp # Cadkey Tablet Daemon
+cadkey-tablet 1400/udp # Cadkey Tablet Daemon
+goldleaf-licman 1401/tcp # Goldleaf License Manager
+goldleaf-licman 1401/udp # Goldleaf License Manager
+prm-sm-np 1402/tcp # Prospero Resource Manager
+prm-sm-np 1402/udp # Prospero Resource Manager
+prm-nm-np 1403/tcp # Prospero Resource Manager
+prm-nm-np 1403/udp # Prospero Resource Manager
+igi-lm 1404/tcp # Infinite Graphics License Manager
+igi-lm 1404/udp # Infinite Graphics License Manager
+ibm-res 1405/tcp # IBM Remote Execution Starter
+ibm-res 1405/udp # IBM Remote Execution Starter
+netlabs-lm 1406/tcp # NetLabs License Manager
+netlabs-lm 1406/udp # NetLabs License Manager
+tibet-server 1407/tcp # TIBET Data Server
+sophia-lm 1408/tcp # Sophia License Manager
+sophia-lm 1408/udp # Sophia License Manager
+here-lm 1409/tcp # Here License Manager
+here-lm 1409/udp # Here License Manager
+hiq 1410/tcp # HiQ License Manager
+hiq 1410/udp # HiQ License Manager
+af 1411/tcp # AudioFile
+af 1411/udp # AudioFile
+innosys 1412/tcp # InnoSys
+innosys 1412/udp # InnoSys
+innosys-acl 1413/tcp # Innosys-ACL
+innosys-acl 1413/udp # Innosys-ACL
+ibm-mqseries 1414/tcp # IBM MQSeries
+ibm-mqseries 1414/udp # IBM MQSeries
+dbstar 1415/tcp # DBStar
+dbstar 1415/udp # DBStar
+novell-lu6.2 1416/tcp novell-lu6-2 # Novell LU6.2
+novell-lu6.2 1416/udp novell-lu6-2 # Novell LU6.2
+timbuktu-srv1 1417/tcp # Timbuktu Service 1 Port
+timbuktu-srv1 1417/udp # Timbuktu Service 1 Port
+timbuktu-srv2 1418/tcp # Timbuktu Service 2 Port
+timbuktu-srv2 1418/udp # Timbuktu Service 2 Port
+timbuktu-srv3 1419/tcp # Timbuktu Service 3 Port
+timbuktu-srv3 1419/udp # Timbuktu Service 3 Port
+timbuktu-srv4 1420/tcp # Timbuktu Service 4 Port
+timbuktu-srv4 1420/udp # Timbuktu Service 4 Port
+gandalf-lm 1421/tcp # Gandalf License Manager
+gandalf-lm 1421/udp # Gandalf License Manager
+autodesk-lm 1422/tcp # Autodesk License Manager
+autodesk-lm 1422/udp # Autodesk License Manager
+essbase 1423/tcp # Essbase Arbor Software
+essbase 1423/udp # Essbase Arbor Software
+hybrid 1424/tcp # Hybrid Encryption Protocol
+hybrid 1424/udp # Hybrid Encryption Protocol
+zion-lm 1425/tcp # Zion Software License Manager
+zion-lm 1425/udp # Zion Software License Manager
+sais 1426/tcp # Satellite-data Acquisition System 1
+sais 1426/udp # Satellite-data Acquisition System 1
+mloadd 1427/tcp # mloadd monitoring tool
+mloadd 1427/udp # mloadd monitoring tool
+informatik-lm 1428/tcp # Informatik License Manager
+informatik-lm 1428/udp # Informatik License Manager
+nms 1429/tcp # Hypercom NMS
+nms 1429/udp # Hypercom NMS
+tpdu 1430/tcp # Hypercom TPDU
+tpdu 1430/udp # Hypercom TPDU
+rgtp 1431/tcp # Reverse Gossip Transport
+rgtp 1431/udp # Reverse Gossip Transport
+blueberry-lm 1432/tcp # Blueberry Software License Manager
+blueberry-lm 1432/udp # Blueberry Software License Manager
+ibm-cics 1435/tcp # IBM CICS
+ibm-cics 1435/udp # IBM CICS
+saism 1436/tcp # Satellite-data Acquisition System 2
+saism 1436/udp # Satellite-data Acquisition System 2
+tabula 1437/tcp # Tabula
+tabula 1437/udp # Tabula
+eicon-server 1438/tcp # Eicon Security Agent/Server
+eicon-server 1438/udp # Eicon Security Agent/Server
+eicon-x25 1439/tcp # Eicon X25/SNA Gateway
+eicon-x25 1439/udp # Eicon X25/SNA Gateway
+eicon-slp 1440/tcp # Eicon Service Location Protocol
+eicon-slp 1440/udp # Eicon Service Location Protocol
+cadis-1 1441/tcp # Cadis License Management
+cadis-1 1441/udp # Cadis License Management
+cadis-2 1442/tcp # Cadis License Management
+cadis-2 1442/udp # Cadis License Management
+ies-lm 1443/tcp # Integrated Engineering Software
+ies-lm 1443/udp # Integrated Engineering Software
+marcam-lm 1444/tcp # Marcam License Management
+marcam-lm 1444/udp # Marcam License Management
+proxima-lm 1445/tcp # Proxima License Manager
+proxima-lm 1445/udp # Proxima License Manager
+ora-lm 1446/tcp # Optical Research Associates License Manager
+ora-lm 1446/udp # Optical Research Associates License Manager
+apri-lm 1447/tcp # Applied Parallel Research LM
+apri-lm 1447/udp # Applied Parallel Research LM
+oc-lm 1448/tcp # OpenConnect License Manager
+oc-lm 1448/udp # OpenConnect License Manager
+peport 1449/tcp # PEport
+peport 1449/udp # PEport
+dwf 1450/tcp # Tandem Distributed Workbench Facility
+dwf 1450/udp # Tandem Distributed Workbench Facility
+infoman 1451/tcp # IBM Information Management
+infoman 1451/udp # IBM Information Management
+gtegsc-lm 1452/tcp # GTE Government Systems License Man
+gtegsc-lm 1452/udp # GTE Government Systems License Man
+genie-lm 1453/tcp # Genie License Manager
+genie-lm 1453/udp # Genie License Manager
+interhdl_elmd 1454/tcp interhdl-elmd # interHDL License Manager
+interhdl_elmd 1454/udp interhdl-elmd # interHDL License Manager
+esl-lm 1455/tcp # ESL License Manager
+esl-lm 1455/udp # ESL License Manager
+dca 1456/tcp # DCA
+dca 1456/udp # DCA
+valisys-lm 1457/tcp # Valisys License Manager
+valisys-lm 1457/udp # Valisys License Manager
+nrcabq-lm 1458/tcp # Nichols Research Corp.
+nrcabq-lm 1458/udp # Nichols Research Corp.
+proshare1 1459/tcp # Proshare Notebook Application
+proshare1 1459/udp # Proshare Notebook Application
+proshare2 1460/tcp # Proshare Notebook Application
+proshare2 1460/udp # Proshare Notebook Application
+ibm_wrless_lan 1461/tcp ibm-wrless-lan # IBM Wireless LAN
+ibm_wrless_lan 1461/udp ibm-wrless-lan # IBM Wireless LAN
+world-lm 1462/tcp # World License Manager
+world-lm 1462/udp # World License Manager
+nucleus 1463/tcp # Nucleus
+nucleus 1463/udp # Nucleus
+msl_lmd 1464/tcp msl-lmd # MSL License Manager
+msl_lmd 1464/udp msl-lmd # MSL License Manager
+pipes 1465/tcp # Pipes Platform
+pipes 1465/udp # Pipes Platform
+oceansoft-lm 1466/tcp # Ocean Software License Manager
+oceansoft-lm 1466/udp # Ocean Software License Manager
+aal-lm 1469/tcp # Active Analysis Limited License Manager
+aal-lm 1469/udp # Active Analysis Limited License Manager
+uaiact 1470/tcp # Universal Analytics
+uaiact 1470/udp # Universal Analytics
+openmath 1473/tcp # OpenMath
+openmath 1473/udp # OpenMath
+telefinder 1474/tcp # Telefinder
+telefinder 1474/udp # Telefinder
+taligent-lm 1475/tcp # Taligent License Manager
+taligent-lm 1475/udp # Taligent License Manager
+clvm-cfg 1476/tcp # clvm-cfg
+clvm-cfg 1476/udp # clvm-cfg
+ms-sna-server 1477/tcp # ms-sna-server
+ms-sna-server 1477/udp # ms-sna-server
+ms-sna-base 1478/tcp # ms-sna-base
+ms-sna-base 1478/udp # ms-sna-base
+dberegister 1479/tcp # dberegister
+dberegister 1479/udp # dberegister
+pacerforum 1480/tcp # PacerForum
+pacerforum 1480/udp # PacerForum
+airs 1481/tcp # AIRS
+airs 1481/udp # AIRS
+miteksys-lm 1482/tcp # Miteksys License Manager
+miteksys-lm 1482/udp # Miteksys License Manager
+afs 1483/tcp # AFS License Manager
+afs 1483/udp # AFS License Manager
+confluent 1484/tcp # Confluent License Manager
+confluent 1484/udp # Confluent License Manager
+lansource 1485/tcp # LANSource
+lansource 1485/udp # LANSource
+nms_topo_serv 1486/tcp nms-topo-serv # nms_topo_serv
+nms_topo_serv 1486/udp nms-topo-serv # nms_topo_serv
+localinfosrvr 1487/tcp # LocalInfoSrvr
+localinfosrvr 1487/udp # LocalInfoSrvr
+docstor 1488/tcp # DocStor
+docstor 1488/udp # DocStor
+dmdocbroker 1489/tcp # dmdocbroker
+dmdocbroker 1489/udp # dmdocbroker
+insitu-conf 1490/tcp # insitu-conf
+insitu-conf 1490/udp # insitu-conf
+stone-design-1 1492/tcp # stone-design-1
+stone-design-1 1492/udp # stone-design-1
+netmap_lm 1493/tcp netmap-lm # netmap_lm
+netmap_lm 1493/udp netmap-lm # netmap_lm
+cvc 1495/tcp # cvc
+cvc 1495/udp # cvc
+liberty-lm 1496/tcp # liberty-lm
+liberty-lm 1496/udp # liberty-lm
+rfx-lm 1497/tcp # rfx-lm
+rfx-lm 1497/udp # rfx-lm
+sybase-sqlany 1498/tcp # Sybase SQL Any
+sybase-sqlany 1498/udp # Sybase SQL Any
+fhc 1499/tcp # Federico Heinz Consultora
+fhc 1499/udp # Federico Heinz Consultora
+vlsi-lm 1500/tcp # VLSI License Manager
+vlsi-lm 1500/udp # VLSI License Manager
+saiscm 1501/tcp # Satellite-data Acquisition System 3
+saiscm 1501/udp # Satellite-data Acquisition System 3
+shivadiscovery 1502/tcp # Shiva
+shivadiscovery 1502/udp # Shiva
+imtc-mcs 1503/tcp # Databeam
+imtc-mcs 1503/udp # Databeam
+evb-elm 1504/tcp # EVB Software Engineering License Manager
+evb-elm 1504/udp # EVB Software Engineering License Manager
+funkproxy 1505/tcp # Funk Software, Inc.
+funkproxy 1505/udp # Funk Software, Inc.
+utcd 1506/tcp # Universal Time daemon (utcd)
+utcd 1506/udp # Universal Time daemon (utcd)
+symplex 1507/tcp # symplex
+symplex 1507/udp # symplex
+diagmond 1508/tcp # diagmond
+diagmond 1508/udp # diagmond
+robcad-lm 1509/tcp # Robcad, Ltd. License Manager
+robcad-lm 1509/udp # Robcad, Ltd. License Manager
+mvx-lm 1510/tcp # Midland Valley Exploration Ltd. Lic. Man.
+mvx-lm 1510/udp # Midland Valley Exploration Ltd. Lic. Man.
+3l-l1 1511/tcp # 3l-l1
+3l-l1 1511/udp # 3l-l1
+fujitsu-dtc 1513/tcp # Fujitsu Systems Business of America, Inc
+fujitsu-dtc 1513/udp # Fujitsu Systems Business of America, Inc
+fujitsu-dtcns 1514/tcp # Fujitsu Systems Business of America, Inc
+fujitsu-dtcns 1514/udp # Fujitsu Systems Business of America, Inc
+ifor-protocol 1515/tcp # ifor-protocol
+ifor-protocol 1515/udp # ifor-protocol
+vpad 1516/tcp # Virtual Places Audio data
+vpad 1516/udp # Virtual Places Audio data
+vpac 1517/tcp # Virtual Places Audio control
+vpac 1517/udp # Virtual Places Audio control
+vpvd 1518/tcp # Virtual Places Video data
+vpvd 1518/udp # Virtual Places Video data
+vpvc 1519/tcp # Virtual Places Video control
+vpvc 1519/udp # Virtual Places Video control
+atm-zip-office 1520/tcp # atm zip office
+atm-zip-office 1520/udp # atm zip office
+ncube-lm 1521/tcp # nCube License Manager
+ncube-lm 1521/udp # nCube License Manager
+cichild-lm 1523/tcp # cichild
+cichild-lm 1523/udp # cichild
+pdap-np 1526/tcp # Prospero Data Access Prot non-priv
+pdap-np 1526/udp # Prospero Data Access Prot non-priv
+tlisrv 1527/tcp # oracle
+tlisrv 1527/udp # oracle
+coauthor 1529/udp # oracle
+rap-service 1530/tcp # rap-service
+rap-service 1530/udp # rap-service
+rap-listen 1531/tcp # rap-listen
+rap-listen 1531/udp # rap-listen
+miroconnect 1532/tcp # miroconnect
+miroconnect 1532/udp # miroconnect
+virtual-places 1533/tcp # Virtual Places Software
+virtual-places 1533/udp # Virtual Places Software
+micromuse-lm 1534/tcp # micromuse-lm
+micromuse-lm 1534/udp # micromuse-lm
+ampr-info 1535/tcp # ampr-info
+ampr-info 1535/udp # ampr-info
+ampr-inter 1536/tcp # ampr-inter
+ampr-inter 1536/udp # ampr-inter
+sdsc-lm 1537/tcp # isi-lm
+sdsc-lm 1537/udp # isi-lm
+3ds-lm 1538/tcp # 3ds-lm
+3ds-lm 1538/udp # 3ds-lm
+intellistor-lm 1539/tcp # Intellistor License Manager
+intellistor-lm 1539/udp # Intellistor License Manager
+rds 1540/tcp # rds
+rds 1540/udp # rds
+rds2 1541/tcp # rds2
+rds2 1541/udp # rds2
+gridgen-elmd 1542/tcp # gridgen-elmd
+gridgen-elmd 1542/udp # gridgen-elmd
+simba-cs 1543/tcp # simba-cs
+simba-cs 1543/udp # simba-cs
+aspeclmd 1544/tcp # aspeclmd
+aspeclmd 1544/udp # aspeclmd
+vistium-share 1545/tcp # vistium-share
+vistium-share 1545/udp # vistium-share
+abbaccuray 1546/tcp # abbaccuray
+abbaccuray 1546/udp # abbaccuray
+laplink 1547/tcp # laplink
+laplink 1547/udp # laplink
+axon-lm 1548/tcp # Axon License Manager
+axon-lm 1548/udp # Axon License Manager
+shivahose 1549/tcp # Shiva Hose
+shivasound 1549/udp # Shiva Sound
+3m-image-lm 1550/tcp # Image Storage license manager 3M Company
+3m-image-lm 1550/udp # Image Storage license manager 3M Company
+hecmtl-db 1551/tcp # HECMTL-DB
+hecmtl-db 1551/udp # HECMTL-DB
+pciarray 1552/tcp # pciarray
+pciarray 1552/udp # pciarray
+sna-cs 1553/tcp # sna-cs
+sna-cs 1553/udp # sna-cs
+caci-lm 1554/tcp # CACI Products Company License Manager
+caci-lm 1554/udp # CACI Products Company License Manager
+livelan 1555/tcp # livelan
+livelan 1555/udp # livelan
+veritas_pbx 1556/tcp veritas-pbx # VERITAS Private Branch Exchange
+veritas_pbx 1556/udp veritas-pbx # VERITAS Private Branch Exchange
+arbortext-lm 1557/tcp # ArborText License Manager
+arbortext-lm 1557/udp # ArborText License Manager
+xingmpeg 1558/tcp # xingmpeg
+xingmpeg 1558/udp # xingmpeg
+web2host 1559/tcp # web2host
+web2host 1559/udp # web2host
+asci-val 1560/tcp # ASCI-RemoteSHADOW
+asci-val 1560/udp # ASCI-RemoteSHADOW
+facilityview 1561/tcp # facilityview
+facilityview 1561/udp # facilityview
+pconnectmgr 1562/tcp # pconnectmgr
+pconnectmgr 1562/udp # pconnectmgr
+cadabra-lm 1563/tcp # Cadabra License Manager
+cadabra-lm 1563/udp # Cadabra License Manager
+pay-per-view 1564/tcp # Pay-Per-View
+pay-per-view 1564/udp # Pay-Per-View
+winddlb 1565/tcp # WinDD
+winddlb 1565/udp # WinDD
+corelvideo 1566/tcp # CORELVIDEO
+corelvideo 1566/udp # CORELVIDEO
+jlicelmd 1567/tcp # jlicelmd
+jlicelmd 1567/udp # jlicelmd
+tsspmap 1568/tcp # tsspmap
+tsspmap 1568/udp # tsspmap
+ets 1569/tcp # ets
+ets 1569/udp # ets
+orbixd 1570/tcp # orbixd
+orbixd 1570/udp # orbixd
+rdb-dbs-disp 1571/tcp # Oracle Remote Data Base
+rdb-dbs-disp 1571/udp # Oracle Remote Data Base
+chip-lm 1572/tcp # Chipcom License Manager
+chip-lm 1572/udp # Chipcom License Manager
+itscomm-ns 1573/tcp # itscomm-ns
+itscomm-ns 1573/udp # itscomm-ns
+mvel-lm 1574/tcp # mvel-lm
+mvel-lm 1574/udp # mvel-lm
+oraclenames 1575/tcp # oraclenames
+oraclenames 1575/udp # oraclenames
+moldflow-lm 1576/tcp # Moldflow License Manager
+moldflow-lm 1576/udp # Moldflow License Manager
+hypercube-lm 1577/tcp # hypercube-lm
+hypercube-lm 1577/udp # hypercube-lm
+jacobus-lm 1578/tcp # Jacobus License Manager
+jacobus-lm 1578/udp # Jacobus License Manager
+ioc-sea-lm 1579/tcp # ioc-sea-lm
+ioc-sea-lm 1579/udp # ioc-sea-lm
+tn-tl-r1 1580/tcp # tn-tl-r1
+tn-tl-r2 1580/udp # tn-tl-r2
+mil-2045-47001 1581/tcp # MIL-2045-47001
+mil-2045-47001 1581/udp # MIL-2045-47001
+msims 1582/tcp # MSIMS
+msims 1582/udp # MSIMS
+simbaexpress 1583/tcp # simbaexpress
+simbaexpress 1583/udp # simbaexpress
+tn-tl-fd2 1584/tcp # tn-tl-fd2
+tn-tl-fd2 1584/udp # tn-tl-fd2
+intv 1585/tcp # intv
+intv 1585/udp # intv
+ibm-abtact 1586/tcp # ibm-abtact
+ibm-abtact 1586/udp # ibm-abtact
+pra_elmd 1587/tcp pra-elmd # pra_elmd
+pra_elmd 1587/udp pra-elmd # pra_elmd
+triquest-lm 1588/tcp # triquest-lm
+triquest-lm 1588/udp # triquest-lm
+vqp 1589/tcp # VQP
+vqp 1589/udp # VQP
+gemini-lm 1590/tcp # gemini-lm
+gemini-lm 1590/udp # gemini-lm
+ncpm-pm 1591/tcp # ncpm-pm
+ncpm-pm 1591/udp # ncpm-pm
+commonspace 1592/tcp # commonspace
+commonspace 1592/udp # commonspace
+mainsoft-lm 1593/tcp # mainsoft-lm
+mainsoft-lm 1593/udp # mainsoft-lm
+sixtrak 1594/tcp # sixtrak
+sixtrak 1594/udp # sixtrak
+radio 1595/tcp # radio
+radio 1595/udp # radio
+radio-sm 1596/tcp # radio-sm
+radio-bc 1596/udp # radio-bc
+orbplus-iiop 1597/tcp # orbplus-iiop
+orbplus-iiop 1597/udp # orbplus-iiop
+picknfs 1598/tcp # picknfs
+picknfs 1598/udp # picknfs
+simbaservices 1599/tcp # simbaservices
+simbaservices 1599/udp # simbaservices
+issd 1600/tcp # issd
+issd 1600/udp # issd
+aas 1601/tcp # aas
+aas 1601/udp # aas
+inspect 1602/tcp # inspect
+inspect 1602/udp # inspect
+picodbc 1603/tcp # pickodbc
+picodbc 1603/udp # pickodbc
+icabrowser 1604/tcp # icabrowser
+icabrowser 1604/udp # icabrowser
+slp 1605/tcp # Salutation Manager (Salutation Protocol)
+slp 1605/udp # Salutation Manager (Salutation Protocol)
+slm-api 1606/tcp # Salutation Manager (SLM-API)
+slm-api 1606/udp # Salutation Manager (SLM-API)
+stt 1607/tcp # stt
+stt 1607/udp # stt
+smart-lm 1608/tcp # Smart Corp. License Manager
+smart-lm 1608/udp # Smart Corp. License Manager
+isysg-lm 1609/tcp # isysg-lm
+isysg-lm 1609/udp # isysg-lm
+taurus-wh 1610/tcp # taurus-wh
+taurus-wh 1610/udp # taurus-wh
+ill 1611/tcp # Inter Library Loan
+ill 1611/udp # Inter Library Loan
+netbill-trans 1612/tcp # NetBill Transaction Server
+netbill-trans 1612/udp # NetBill Transaction Server
+netbill-keyrep 1613/tcp # NetBill Key Repository
+netbill-keyrep 1613/udp # NetBill Key Repository
+netbill-cred 1614/tcp # NetBill Credential Server
+netbill-cred 1614/udp # NetBill Credential Server
+netbill-auth 1615/tcp # NetBill Authorization Server
+netbill-auth 1615/udp # NetBill Authorization Server
+netbill-prod 1616/tcp # NetBill Product Server
+netbill-prod 1616/udp # NetBill Product Server
+nimrod-agent 1617/tcp # Nimrod Inter-Agent Communication
+nimrod-agent 1617/udp # Nimrod Inter-Agent Communication
+skytelnet 1618/tcp # skytelnet
+skytelnet 1618/udp # skytelnet
+xs-openstorage 1619/tcp # xs-openstorage
+xs-openstorage 1619/udp # xs-openstorage
+faxportwinport 1620/tcp # faxportwinport
+faxportwinport 1620/udp # faxportwinport
+softdataphone 1621/tcp # softdataphone
+softdataphone 1621/udp # softdataphone
+ontime 1622/tcp # ontime
+ontime 1622/udp # ontime
+jaleosnd 1623/tcp # jaleosnd
+jaleosnd 1623/udp # jaleosnd
+udp-sr-port 1624/tcp # udp-sr-port
+udp-sr-port 1624/udp # udp-sr-port
+svs-omagent 1625/tcp # svs-omagent
+svs-omagent 1625/udp # svs-omagent
+shockwave 1626/tcp # Shockwave
+shockwave 1626/udp # Shockwave
+t128-gateway 1627/tcp # T.128 Gateway
+t128-gateway 1627/udp # T.128 Gateway
+lontalk-norm 1628/tcp # LonTalk normal
+lontalk-norm 1628/udp # LonTalk normal
+lontalk-urgnt 1629/tcp # LonTalk urgent
+lontalk-urgnt 1629/udp # LonTalk urgent
+oraclenet8cman 1630/tcp # Oracle Net8 Cman
+oraclenet8cman 1630/udp # Oracle Net8 Cman
+visitview 1631/tcp # Visit view
+visitview 1631/udp # Visit view
+pammratc 1632/tcp # PAMMRATC
+pammratc 1632/udp # PAMMRATC
+pammrpc 1633/tcp # PAMMRPC
+pammrpc 1633/udp # PAMMRPC
+loaprobe 1634/tcp # Log On America Probe
+loaprobe 1634/udp # Log On America Probe
+edb-server1 1635/tcp # EDB Server 1
+edb-server1 1635/udp # EDB Server 1
+isdc 1636/tcp # ISP shared public data control
+isdc 1636/udp # ISP shared public data control
+islc 1637/tcp # ISP shared local data control
+islc 1637/udp # ISP shared local data control
+ismc 1638/tcp # ISP shared management control
+ismc 1638/udp # ISP shared management control
+cert-initiator 1639/tcp # cert-initiator
+cert-initiator 1639/udp # cert-initiator
+cert-responder 1640/tcp # cert-responder
+cert-responder 1640/udp # cert-responder
+invision 1641/tcp # InVision
+invision 1641/udp # InVision
+isis-am 1642/tcp # isis-am
+isis-am 1642/udp # isis-am
+isis-ambc 1643/tcp # isis-ambc
+isis-ambc 1643/udp # isis-ambc
+saiseh 1644/tcp # Satellite-data Acquisition System 4
+saiseh 1644/udp # Satellite-data Acquisition System 4
+rsap 1647/tcp # rsap
+rsap 1647/udp # rsap
+concurrent-lm 1648/tcp # concurrent-lm
+concurrent-lm 1648/udp # concurrent-lm
+nkd 1650/tcp # nkdn
+nkd 1650/udp # nkd
+shiva_confsrvr 1651/tcp shiva-confsrvr # shiva_confsrvr
+shiva_confsrvr 1651/udp shiva-confsrvr # shiva_confsrvr
+xnmp 1652/tcp # xnmp
+xnmp 1652/udp # xnmp
+alphatech-lm 1653/tcp # alphatech-lm
+alphatech-lm 1653/udp # alphatech-lm
+stargatealerts 1654/tcp # stargatealerts
+stargatealerts 1654/udp # stargatealerts
+dec-mbadmin 1655/tcp # dec-mbadmin
+dec-mbadmin 1655/udp # dec-mbadmin
+dec-mbadmin-h 1656/tcp # dec-mbadmin-h
+dec-mbadmin-h 1656/udp # dec-mbadmin-h
+fujitsu-mmpdc 1657/tcp # fujitsu-mmpdc
+fujitsu-mmpdc 1657/udp # fujitsu-mmpdc
+sixnetudr 1658/tcp # sixnetudr
+sixnetudr 1658/udp # sixnetudr
+sg-lm 1659/tcp # Silicon Grail License Manager
+sg-lm 1659/udp # Silicon Grail License Manager
+skip-mc-gikreq 1660/tcp # skip-mc-gikreq
+skip-mc-gikreq 1660/udp # skip-mc-gikreq
+netview-aix-1 1661/tcp # netview-aix-1
+netview-aix-1 1661/udp # netview-aix-1
+netview-aix-2 1662/tcp # netview-aix-2
+netview-aix-2 1662/udp # netview-aix-2
+netview-aix-3 1663/tcp # netview-aix-3
+netview-aix-3 1663/udp # netview-aix-3
+netview-aix-4 1664/tcp # netview-aix-4
+netview-aix-4 1664/udp # netview-aix-4
+netview-aix-5 1665/tcp # netview-aix-5
+netview-aix-5 1665/udp # netview-aix-5
+netview-aix-6 1666/tcp # netview-aix-6
+netview-aix-6 1666/udp # netview-aix-6
+netview-aix-7 1667/tcp # netview-aix-7
+netview-aix-7 1667/udp # netview-aix-7
+netview-aix-8 1668/tcp # netview-aix-8
+netview-aix-8 1668/udp # netview-aix-8
+netview-aix-9 1669/tcp # netview-aix-9
+netview-aix-9 1669/udp # netview-aix-9
+netview-aix-10 1670/tcp # netview-aix-10
+netview-aix-10 1670/udp # netview-aix-10
+netview-aix-11 1671/tcp # netview-aix-11
+netview-aix-11 1671/udp # netview-aix-11
+netview-aix-12 1672/tcp # netview-aix-12
+netview-aix-12 1672/udp # netview-aix-12
+proshare-mc-1 1673/tcp # Intel Proshare Multicast
+proshare-mc-1 1673/udp # Intel Proshare Multicast
+proshare-mc-2 1674/tcp # Intel Proshare Multicast
+proshare-mc-2 1674/udp # Intel Proshare Multicast
+pdp 1675/tcp # Pacific Data Products
+pdp 1675/udp # Pacific Data Products
+netcomm1 1676/tcp # netcomm1
+netcomm2 1676/udp # netcomm2
+groupwise 1677/tcp # groupwise
+groupwise 1677/udp # groupwise
+prolink 1678/tcp # prolink
+prolink 1678/udp # prolink
+darcorp-lm 1679/tcp # darcorp-lm
+darcorp-lm 1679/udp # darcorp-lm
+microcom-sbp 1680/tcp # microcom-sbp
+microcom-sbp 1680/udp # microcom-sbp
+sd-elmd 1681/tcp # sd-elmd
+sd-elmd 1681/udp # sd-elmd
+lanyon-lantern 1682/tcp # lanyon-lantern
+lanyon-lantern 1682/udp # lanyon-lantern
+ncpm-hip 1683/tcp # ncpm-hip
+ncpm-hip 1683/udp # ncpm-hip
+snaresecure 1684/tcp # SnareSecure
+snaresecure 1684/udp # SnareSecure
+n2nremote 1685/tcp # n2nremote
+n2nremote 1685/udp # n2nremote
+cvmon 1686/tcp # cvmon
+cvmon 1686/udp # cvmon
+nsjtp-ctrl 1687/tcp # nsjtp-ctrl
+nsjtp-ctrl 1687/udp # nsjtp-ctrl
+nsjtp-data 1688/tcp # nsjtp-data
+nsjtp-data 1688/udp # nsjtp-data
+firefox 1689/tcp # firefox
+firefox 1689/udp # firefox
+ng-umds 1690/tcp # ng-umds
+ng-umds 1690/udp # ng-umds
+empire-empuma 1691/tcp # empire-empuma
+empire-empuma 1691/udp # empire-empuma
+sstsys-lm 1692/tcp # sstsys-lm
+sstsys-lm 1692/udp # sstsys-lm
+rrirtr 1693/tcp # rrirtr
+rrirtr 1693/udp # rrirtr
+rrimwm 1694/tcp # rrimwm
+rrimwm 1694/udp # rrimwm
+rrilwm 1695/tcp # rrilwm
+rrilwm 1695/udp # rrilwm
+rrifmm 1696/tcp # rrifmm
+rrifmm 1696/udp # rrifmm
+rrisat 1697/tcp # rrisat
+rrisat 1697/udp # rrisat
+rsvp-encap-1 1698/tcp # RSVP-ENCAPSULATION-1
+rsvp-encap-1 1698/udp # RSVP-ENCAPSULATION-1
+rsvp-encap-2 1699/tcp # RSVP-ENCAPSULATION-2
+rsvp-encap-2 1699/udp # RSVP-ENCAPSULATION-2
+mps-raft 1700/tcp # mps-raft
+mps-raft 1700/udp # mps-raft
+deskshare 1702/tcp # deskshare
+deskshare 1702/udp # deskshare
+hb-engine 1703/tcp # hb-engine
+hb-engine 1703/udp # hb-engine
+bcs-broker 1704/tcp # bcs-broker
+bcs-broker 1704/udp # bcs-broker
+slingshot 1705/tcp # slingshot
+slingshot 1705/udp # slingshot
+jetform 1706/tcp # jetform
+jetform 1706/udp # jetform
+vdmplay 1707/tcp # vdmplay
+vdmplay 1707/udp # vdmplay
+gat-lmd 1708/tcp # gat-lmd
+gat-lmd 1708/udp # gat-lmd
+centra 1709/tcp # centra
+centra 1709/udp # centra
+impera 1710/tcp # impera
+impera 1710/udp # impera
+pptconference 1711/tcp # pptconference
+pptconference 1711/udp # pptconference
+registrar 1712/tcp # resource monitoring service
+registrar 1712/udp # resource monitoring service
+conferencetalk 1713/tcp # ConferenceTalk
+conferencetalk 1713/udp # ConferenceTalk
+sesi-lm 1714/tcp # sesi-lm
+sesi-lm 1714/udp # sesi-lm
+houdini-lm 1715/tcp # houdini-lm
+houdini-lm 1715/udp # houdini-lm
+xmsg 1716/tcp # xmsg
+xmsg 1716/udp # xmsg
+fj-hdnet 1717/tcp # fj-hdnet
+fj-hdnet 1717/udp # fj-hdnet
+caicci 1721/tcp # caicci
+caicci 1721/udp # caicci
+hks-lm 1722/tcp # HKS License Manager
+hks-lm 1722/udp # HKS License Manager
+pptp 1723/tcp # pptp
+pptp 1723/udp # pptp
+csbphonemaster 1724/tcp # csbphonemaster
+csbphonemaster 1724/udp # csbphonemaster
+iden-ralp 1725/tcp # iden-ralp
+iden-ralp 1725/udp # iden-ralp
+iberiagames 1726/tcp # IBERIAGAMES
+iberiagames 1726/udp # IBERIAGAMES
+winddx 1727/tcp # winddx
+winddx 1727/udp # winddx
+telindus 1728/tcp # TELINDUS
+telindus 1728/udp # TELINDUS
+citynl 1729/tcp # CityNL License Management
+citynl 1729/udp # CityNL License Management
+roketz 1730/tcp # roketz
+roketz 1730/udp # roketz
+msiccp 1731/tcp # MSICCP
+msiccp 1731/udp # MSICCP
+proxim 1732/tcp # proxim
+proxim 1732/udp # proxim
+siipat 1733/tcp # SIMS - SIIPAT Protocol for Alarm Transmission
+siipat 1733/udp # SIMS - SIIPAT Protocol for Alarm Transmission
+cambertx-lm 1734/tcp # Camber Corporation License Management
+cambertx-lm 1734/udp # Camber Corporation License Management
+privatechat 1735/tcp # PrivateChat
+privatechat 1735/udp # PrivateChat
+street-stream 1736/tcp # street-stream
+street-stream 1736/udp # street-stream
+ultimad 1737/tcp # ultimad
+ultimad 1737/udp # ultimad
+gamegen1 1738/tcp # GameGen1
+gamegen1 1738/udp # GameGen1
+webaccess 1739/tcp # webaccess
+webaccess 1739/udp # webaccess
+encore 1740/tcp # encore
+encore 1740/udp # encore
+cisco-net-mgmt 1741/tcp # cisco-net-mgmt
+cisco-net-mgmt 1741/udp # cisco-net-mgmt
+3Com-nsd 1742/tcp # 3Com-nsd
+3Com-nsd 1742/udp # 3Com-nsd
+cinegrfx-lm 1743/tcp # Cinema Graphics License Manager
+cinegrfx-lm 1743/udp # Cinema Graphics License Manager
+ncpm-ft 1744/tcp # ncpm-ft
+ncpm-ft 1744/udp # ncpm-ft
+remote-winsock 1745/tcp # remote-winsock
+remote-winsock 1745/udp # remote-winsock
+ftrapid-1 1746/tcp # ftrapid-1
+ftrapid-1 1746/udp # ftrapid-1
+ftrapid-2 1747/tcp # ftrapid-2
+ftrapid-2 1747/udp # ftrapid-2
+oracle-em1 1748/tcp # oracle-em1
+oracle-em1 1748/udp # oracle-em1
+aspen-services 1749/tcp # aspen-services
+aspen-services 1749/udp # aspen-services
+sslp 1750/tcp # Simple Socket Library's PortMaster
+sslp 1750/udp # Simple Socket Library's PortMaster
+swiftnet 1751/tcp # SwiftNet
+swiftnet 1751/udp # SwiftNet
+lofr-lm 1752/tcp # Leap of Faith Research License Manager
+lofr-lm 1752/udp # Leap of Faith Research License Manager
+predatar-comms 1753/tcp # Predatar Comms Service
+oracle-em2 1754/tcp # oracle-em2
+oracle-em2 1754/udp # oracle-em2
+ms-streaming 1755/tcp # ms-streaming
+ms-streaming 1755/udp # ms-streaming
+capfast-lmd 1756/tcp # capfast-lmd
+capfast-lmd 1756/udp # capfast-lmd
+cnhrp 1757/tcp # cnhrp
+cnhrp 1757/udp # cnhrp
+spss-lm 1759/tcp # SPSS License Manager
+www-ldap-gw 1760/tcp # www-ldap-gw
+www-ldap-gw 1760/udp # www-ldap-gw
+cft-0 1761/tcp # cft-0
+cft-0 1761/udp # cft-0
+cft-1 1762/tcp # cft-1
+cft-1 1762/udp # cft-1
+cft-2 1763/tcp # cft-2
+cft-2 1763/udp # cft-2
+cft-3 1764/tcp # cft-3
+cft-3 1764/udp # cft-3
+cft-4 1765/tcp # cft-4
+cft-4 1765/udp # cft-4
+cft-5 1766/tcp # cft-5
+cft-5 1766/udp # cft-5
+cft-6 1767/tcp # cft-6
+cft-6 1767/udp # cft-6
+cft-7 1768/tcp # cft-7
+cft-7 1768/udp # cft-7
+bmc-net-adm 1769/tcp # bmc-net-adm
+bmc-net-adm 1769/udp # bmc-net-adm
+bmc-net-svc 1770/tcp # bmc-net-svc
+bmc-net-svc 1770/udp # bmc-net-svc
+vaultbase 1771/tcp # vaultbase
+vaultbase 1771/udp # vaultbase
+essweb-gw 1772/tcp # EssWeb Gateway
+essweb-gw 1772/udp # EssWeb Gateway
+kmscontrol 1773/tcp # KMSControl
+kmscontrol 1773/udp # KMSControl
+global-dtserv 1774/tcp # global-dtserv
+global-dtserv 1774/udp # global-dtserv
+femis 1776/tcp # Federal Emergency Management Information System
+femis 1776/udp # Federal Emergency Management Information System
+powerguardian 1777/tcp # powerguardian
+powerguardian 1777/udp # powerguardian
+prodigy-intrnet 1778/tcp # prodigy-internet
+prodigy-intrnet 1778/udp # prodigy-internet
+pharmasoft 1779/tcp # pharmasoft
+pharmasoft 1779/udp # pharmasoft
+dpkeyserv 1780/tcp # dpkeyserv
+dpkeyserv 1780/udp # dpkeyserv
+answersoft-lm 1781/tcp # answersoft-lm
+answersoft-lm 1781/udp # answersoft-lm
+hp-hcip 1782/tcp # hp-hcip
+hp-hcip 1782/udp # hp-hcip
+finle-lm 1784/tcp # Finle License Manager
+finle-lm 1784/udp # Finle License Manager
+windlm 1785/tcp # Wind River Systems License Manager
+windlm 1785/udp # Wind River Systems License Manager
+funk-logger 1786/tcp # funk-logger
+funk-logger 1786/udp # funk-logger
+funk-license 1787/tcp # funk-license
+funk-license 1787/udp # funk-license
+psmond 1788/tcp # psmond
+psmond 1788/udp # psmond
+ea1 1791/tcp # EA1
+ea1 1791/udp # EA1
+ibm-dt-2 1792/tcp # ibm-dt-2
+ibm-dt-2 1792/udp # ibm-dt-2
+rsc-robot 1793/tcp # rsc-robot
+rsc-robot 1793/udp # rsc-robot
+cera-bcm 1794/tcp # cera-bcm
+cera-bcm 1794/udp # cera-bcm
+dpi-proxy 1795/tcp # dpi-proxy
+dpi-proxy 1795/udp # dpi-proxy
+vocaltec-admin 1796/tcp # Vocaltec Server Administration
+vocaltec-admin 1796/udp # Vocaltec Server Administration
+etp 1798/tcp # Event Transfer Protocol
+etp 1798/udp # Event Transfer Protocol
+netrisk 1799/tcp # NETRISK
+netrisk 1799/udp # NETRISK
+ansys-lm 1800/tcp # ANSYS-License manager
+ansys-lm 1800/udp # ANSYS-License manager
+msmq 1801/tcp # Microsoft Message Que
+msmq 1801/udp # Microsoft Message Que
+concomp1 1802/tcp # ConComp1
+concomp1 1802/udp # ConComp1
+hp-hcip-gwy 1803/tcp # HP-HCIP-GWY
+hp-hcip-gwy 1803/udp # HP-HCIP-GWY
+enl 1804/tcp # ENL
+enl 1804/udp # ENL
+enl-name 1805/tcp # ENL-Name
+enl-name 1805/udp # ENL-Name
+musiconline 1806/tcp # Musiconline
+musiconline 1806/udp # Musiconline
+fhsp 1807/tcp # Fujitsu Hot Standby Protocol
+fhsp 1807/udp # Fujitsu Hot Standby Protocol
+oracle-vp2 1808/tcp # Oracle-VP2
+oracle-vp2 1808/udp # Oracle-VP2
+oracle-vp1 1809/tcp # Oracle-VP1
+oracle-vp1 1809/udp # Oracle-VP1
+jerand-lm 1810/tcp # Jerand License Manager
+jerand-lm 1810/udp # Jerand License Manager
+scientia-sdb 1811/tcp # Scientia-SDB
+scientia-sdb 1811/udp # Scientia-SDB
+tdp-suite 1814/tcp # TDP Suite
+tdp-suite 1814/udp # TDP Suite
+mmpft 1815/tcp # MMPFT
+mmpft 1815/udp # MMPFT
+harp 1816/tcp # HARP
+harp 1816/udp # HARP
+rkb-oscs 1817/tcp # RKB-OSCS
+rkb-oscs 1817/udp # RKB-OSCS
+etftp 1818/tcp # Enhanced Trivial File Transfer Protocol
+etftp 1818/udp # Enhanced Trivial File Transfer Protocol
+plato-lm 1819/tcp # Plato License Manager
+plato-lm 1819/udp # Plato License Manager
+mcagent 1820/tcp # mcagent
+mcagent 1820/udp # mcagent
+donnyworld 1821/tcp # donnyworld
+donnyworld 1821/udp # donnyworld
+es-elmd 1822/tcp # es-elmd
+es-elmd 1822/udp # es-elmd
+unisys-lm 1823/tcp # Unisys Natural Language License Manager
+unisys-lm 1823/udp # Unisys Natural Language License Manager
+metrics-pas 1824/tcp # metrics-pas
+metrics-pas 1824/udp # metrics-pas
+direcpc-video 1825/tcp # DirecPC Video
+direcpc-video 1825/udp # DirecPC Video
+ardt 1826/tcp # ARDT
+ardt 1826/udp # ARDT
+asi 1827/tcp # ASI
+asi 1827/udp # ASI
+itm-mcell-u 1828/tcp # itm-mcell-u
+itm-mcell-u 1828/udp # itm-mcell-u
+optika-emedia 1829/tcp # Optika eMedia
+optika-emedia 1829/udp # Optika eMedia
+net8-cman 1830/tcp # Oracle Net8 CMan Admin
+net8-cman 1830/udp # Oracle Net8 CMan Admin
+myrtle 1831/tcp # Myrtle
+myrtle 1831/udp # Myrtle
+tht-treasure 1832/tcp # ThoughtTreasure
+tht-treasure 1832/udp # ThoughtTreasure
+udpradio 1833/tcp # udpradio
+udpradio 1833/udp # udpradio
+ardusuni 1834/tcp # ARDUS Unicast
+ardusuni 1834/udp # ARDUS Unicast
+ardusmul 1835/tcp # ARDUS Multicast
+ardusmul 1835/udp # ARDUS Multicast
+ste-smsc 1836/tcp # ste-smsc
+ste-smsc 1836/udp # ste-smsc
+csoft1 1837/tcp # csoft1
+csoft1 1837/udp # csoft1
+talnet 1838/tcp # TALNET
+talnet 1838/udp # TALNET
+netopia-vo1 1839/tcp # netopia-vo1
+netopia-vo1 1839/udp # netopia-vo1
+netopia-vo2 1840/tcp # netopia-vo2
+netopia-vo2 1840/udp # netopia-vo2
+netopia-vo3 1841/tcp # netopia-vo3
+netopia-vo3 1841/udp # netopia-vo3
+netopia-vo4 1842/tcp # netopia-vo4
+netopia-vo4 1842/udp # netopia-vo4
+netopia-vo5 1843/tcp # netopia-vo5
+netopia-vo5 1843/udp # netopia-vo5
+direcpc-dll 1844/tcp # DirecPC-DLL
+direcpc-dll 1844/udp # DirecPC-DLL
+altalink 1845/tcp # altalink
+altalink 1845/udp # altalink
+tunstall-pnc 1846/tcp # Tunstall PNC
+tunstall-pnc 1846/udp # Tunstall PNC
+slp-notify 1847/tcp # SLP Notification
+slp-notify 1847/udp # SLP Notification
+fjdocdist 1848/tcp # fjdocdist
+fjdocdist 1848/udp # fjdocdist
+alpha-sms 1849/tcp # ALPHA-SMS
+alpha-sms 1849/udp # ALPHA-SMS
+gsi 1850/tcp # GSI
+gsi 1850/udp # GSI
+ctcd 1851/tcp # ctcd
+ctcd 1851/udp # ctcd
+virtual-time 1852/tcp # Virtual Time
+virtual-time 1852/udp # Virtual Time
+vids-avtp 1853/tcp # VIDS-AVTP
+vids-avtp 1853/udp # VIDS-AVTP
+buddy-draw 1854/tcp # Buddy Draw
+buddy-draw 1854/udp # Buddy Draw
+fiorano-rtrsvc 1855/tcp # Fiorano RtrSvc
+fiorano-rtrsvc 1855/udp # Fiorano RtrSvc
+fiorano-msgsvc 1856/tcp # Fiorano MsgSvc
+fiorano-msgsvc 1856/udp # Fiorano MsgSvc
+datacaptor 1857/tcp # DataCaptor
+datacaptor 1857/udp # DataCaptor
+privateark 1858/tcp # PrivateArk
+privateark 1858/udp # PrivateArk
+gammafetchsvr 1859/tcp # Gamma Fetcher Server
+gammafetchsvr 1859/udp # Gamma Fetcher Server
+sunscalar-svc 1860/tcp # SunSCALAR Services
+sunscalar-svc 1860/udp # SunSCALAR Services
+lecroy-vicp 1861/tcp # LeCroy VICP
+lecroy-vicp 1861/udp # LeCroy VICP
+mysql-cm-agent 1862/tcp # MySQL Cluster Manager Agent
+mysql-cm-agent 1862/udp # MySQL Cluster Manager Agent
+msnp 1863/tcp # MSNP
+msnp 1863/udp # MSNP
+paradym-31port 1864/tcp # Paradym 31 Port
+paradym-31port 1864/udp # Paradym 31 Port
+entp 1865/tcp # ENTP
+entp 1865/udp # ENTP
+swrmi 1866/tcp # swrmi
+swrmi 1866/udp # swrmi
+udrive 1867/tcp # UDRIVE
+udrive 1867/udp # UDRIVE
+viziblebrowser 1868/tcp # VizibleBrowser
+viziblebrowser 1868/udp # VizibleBrowser
+transact 1869/tcp # TransAct
+transact 1869/udp # TransAct
+sunscalar-dns 1870/tcp # SunSCALAR DNS Service
+sunscalar-dns 1870/udp # SunSCALAR DNS Service
+canocentral0 1871/tcp # Cano Central 0
+canocentral0 1871/udp # Cano Central 0
+canocentral1 1872/tcp # Cano Central 1
+canocentral1 1872/udp # Cano Central 1
+fjmpjps 1873/tcp # Fjmpjps
+fjmpjps 1873/udp # Fjmpjps
+fjswapsnp 1874/tcp # Fjswapsnp
+fjswapsnp 1874/udp # Fjswapsnp
+westell-stats 1875/tcp # westell stats
+westell-stats 1875/udp # westell stats
+ewcappsrv 1876/tcp # ewcappsrv
+ewcappsrv 1876/udp # ewcappsrv
+hp-webqosdb 1877/tcp # hp-webqosdb
+hp-webqosdb 1877/udp # hp-webqosdb
+drmsmc 1878/tcp # drmsmc
+drmsmc 1878/udp # drmsmc
+nettgain-nms 1879/tcp # NettGain NMS
+nettgain-nms 1879/udp # NettGain NMS
+vsat-control 1880/tcp # Gilat VSAT Control
+vsat-control 1880/udp # Gilat VSAT Control
+ibm-mqseries2 1881/tcp # IBM WebSphere MQ Everyplace
+ibm-mqseries2 1881/udp # IBM WebSphere MQ Everyplace
+ecsqdmn 1882/tcp # CA eTrust Common Services
+ecsqdmn 1882/udp # CA eTrust Common Services
+mqtt 1883/tcp # Message Queuing Telemetry
+mqtt 1883/udp # Message Queuing Telemetry
+idmaps 1884/tcp # Internet Distance Map Svc
+idmaps 1884/udp # Internet Distance Map Svc
+vrtstrapserver 1885/tcp # Veritas Trap Server
+vrtstrapserver 1885/udp # Veritas Trap Server
+leoip 1886/tcp # Leonardo over IP
+leoip 1886/udp # Leonardo over IP
+filex-lport 1887/tcp # FileX Listening Port
+filex-lport 1887/udp # FileX Listening Port
+ncconfig 1888/tcp # NC Config Port
+ncconfig 1888/udp # NC Config Port
+unify-adapter 1889/tcp # Unify Web Adapter Service
+unify-adapter 1889/udp # Unify Web Adapter Service
+wilkenlistener 1890/tcp # wilkenListener
+wilkenlistener 1890/udp # wilkenListener
+childkey-notif 1891/tcp # ChildKey Notification
+childkey-notif 1891/udp # ChildKey Notification
+childkey-ctrl 1892/tcp # ChildKey Control
+childkey-ctrl 1892/udp # ChildKey Control
+elad 1893/tcp # ELAD Protocol
+elad 1893/udp # ELAD Protocol
+o2server-port 1894/tcp # O2Server Port
+o2server-port 1894/udp # O2Server Port
+b-novative-ls 1896/tcp # b-novative license server
+b-novative-ls 1896/udp # b-novative license server
+metaagent 1897/tcp # MetaAgent
+metaagent 1897/udp # MetaAgent
+cymtec-port 1898/tcp # Cymtec secure management
+cymtec-port 1898/udp # Cymtec secure management
+mc2studios 1899/tcp # MC2Studios
+mc2studios 1899/udp # MC2Studios
+ssdp 1900/tcp # SSDP
+ssdp 1900/udp # SSDP
+fjicl-tep-a 1901/tcp # Fujitsu ICL Terminal Emulator Program A
+fjicl-tep-a 1901/udp # Fujitsu ICL Terminal Emulator Program A
+fjicl-tep-b 1902/tcp # Fujitsu ICL Terminal Emulator Program B
+fjicl-tep-b 1902/udp # Fujitsu ICL Terminal Emulator Program B
+linkname 1903/tcp # Local Link Name Resolution
+linkname 1903/udp # Local Link Name Resolution
+fjicl-tep-c 1904/tcp # Fujitsu ICL Terminal Emulator Program C
+fjicl-tep-c 1904/udp # Fujitsu ICL Terminal Emulator Program C
+sugp 1905/tcp # Secure UP.Link Gateway Protocol
+sugp 1905/udp # Secure UP.Link Gateway Protocol
+tpmd 1906/tcp # TPortMapperReq
+tpmd 1906/udp # TPortMapperReq
+intrastar 1907/tcp # IntraSTAR
+intrastar 1907/udp # IntraSTAR
+dawn 1908/tcp # Dawn
+dawn 1908/udp # Dawn
+global-wlink 1909/tcp # Global World Link
+global-wlink 1909/udp # Global World Link
+ultrabac 1910/tcp # UltraBac Software communications port
+ultrabac 1910/udp # UltraBac Software communications port
+rhp-iibp 1912/tcp # rhp-iibp
+rhp-iibp 1912/udp # rhp-iibp
+armadp 1913/tcp # armadp
+armadp 1913/udp # armadp
+elm-momentum 1914/tcp # Elm-Momentum
+elm-momentum 1914/udp # Elm-Momentum
+facelink 1915/tcp # FACELINK
+facelink 1915/udp # FACELINK
+persona 1916/tcp # Persoft Persona
+persona 1916/udp # Persoft Persona
+noagent 1917/tcp # nOAgent
+noagent 1917/udp # nOAgent
+can-nds 1918/tcp # IBM Tivole Directory Service - NDS
+can-nds 1918/udp # IBM Tivole Directory Service - NDS
+can-dch 1919/tcp # IBM Tivoli Directory Service - DCH
+can-dch 1919/udp # IBM Tivoli Directory Service - DCH
+can-ferret 1920/tcp # IBM Tivoli Directory Service - FERRET
+can-ferret 1920/udp # IBM Tivoli Directory Service - FERRET
+noadmin 1921/tcp # NoAdmin
+noadmin 1921/udp # NoAdmin
+tapestry 1922/tcp # Tapestry
+tapestry 1922/udp # Tapestry
+spice 1923/tcp # SPICE
+spice 1923/udp # SPICE
+xiip 1924/tcp # XIIP
+xiip 1924/udp # XIIP
+discovery-port 1925/tcp # Surrogate Discovery Port
+discovery-port 1925/udp # Surrogate Discovery Port
+egs 1926/tcp # Evolution Game Server
+egs 1926/udp # Evolution Game Server
+videte-cipc 1927/tcp # Videte CIPC Port
+videte-cipc 1927/udp # Videte CIPC Port
+emsd-port 1928/tcp # Expnd Maui Srvr Dscovr
+emsd-port 1928/udp # Expnd Maui Srvr Dscovr
+bandwiz-system 1929/tcp # Bandwiz System - Server
+bandwiz-system 1929/udp # Bandwiz System - Server
+driveappserver 1930/tcp # Drive AppServer
+driveappserver 1930/udp # Drive AppServer
+amdsched 1931/tcp # AMD SCHED
+amdsched 1931/udp # AMD SCHED
+ctt-broker 1932/tcp # CTT Broker
+ctt-broker 1932/udp # CTT Broker
+xmapi 1933/tcp # IBM LM MT Agent
+xmapi 1933/udp # IBM LM MT Agent
+xaapi 1934/tcp # IBM LM Appl Agent
+xaapi 1934/udp # IBM LM Appl Agent
+macromedia-fcs 1935/tcp # Macromedia Flash Communications Server MX
+macromedia-fcs 1935/udp # Macromedia Flash Communications server MX
+jetcmeserver 1936/tcp # JetCmeServer Server Port
+jetcmeserver 1936/udp # JetCmeServer Server Port
+jwserver 1937/tcp # JetVWay Server Port
+jwserver 1937/udp # JetVWay Server Port
+jwclient 1938/tcp # JetVWay Client Port
+jwclient 1938/udp # JetVWay Client Port
+jvserver 1939/tcp # JetVision Server Port
+jvserver 1939/udp # JetVision Server Port
+jvclient 1940/tcp # JetVision Client Port
+jvclient 1940/udp # JetVision Client Port
+dic-aida 1941/tcp # DIC-Aida
+dic-aida 1941/udp # DIC-Aida
+res 1942/tcp # Real Enterprise Service
+res 1942/udp # Real Enterprise Service
+beeyond-media 1943/tcp # Beeyond Media
+beeyond-media 1943/udp # Beeyond Media
+close-combat 1944/tcp # close-combat
+close-combat 1944/udp # close-combat
+dialogic-elmd 1945/tcp # dialogic-elmd
+dialogic-elmd 1945/udp # dialogic-elmd
+tekpls 1946/tcp # tekpls
+tekpls 1946/udp # tekpls
+sentinelsrm 1947/tcp # SentinelSRM
+sentinelsrm 1947/udp # SentinelSRM
+eye2eye 1948/tcp # eye2eye
+eye2eye 1948/udp # eye2eye
+ismaeasdaqlive 1949/tcp # ISMA Easdaq Live
+ismaeasdaqlive 1949/udp # ISMA Easdaq Live
+ismaeasdaqtest 1950/tcp # ISMA Easdaq Test
+ismaeasdaqtest 1950/udp # ISMA Easdaq Test
+bcs-lmserver 1951/tcp # bcs-lmserver
+bcs-lmserver 1951/udp # bcs-lmserver
+mpnjsc 1952/tcp # mpnjsc
+mpnjsc 1952/udp # mpnjsc
+rapidbase 1953/tcp # Rapid Base
+rapidbase 1953/udp # Rapid Base
+abr-api 1954/tcp # ABR-API (diskbridge)
+abr-api 1954/udp # ABR-API (diskbridge)
+abr-secure 1955/tcp # ABR-Secure Data (diskbridge)
+abr-secure 1955/udp # ABR-Secure Data (diskbridge)
+vrtl-vmf-ds 1956/tcp # Vertel VMF DS
+vrtl-vmf-ds 1956/udp # Vertel VMF DS
+unix-status 1957/tcp # unix-status
+unix-status 1957/udp # unix-status
+dxadmind 1958/tcp # CA Administration Daemon
+dxadmind 1958/udp # CA Administration Daemon
+simp-all 1959/tcp # SIMP Channel
+simp-all 1959/udp # SIMP Channel
+nasmanager 1960/tcp # Merit DAC NASmanager
+nasmanager 1960/udp # Merit DAC NASmanager
+bts-appserver 1961/tcp # BTS APPSERVER
+bts-appserver 1961/udp # BTS APPSERVER
+biap-mp 1962/tcp # BIAP-MP
+biap-mp 1962/udp # BIAP-MP
+webmachine 1963/tcp # WebMachine
+webmachine 1963/udp # WebMachine
+solid-e-engine 1964/tcp # SOLID E ENGINE
+solid-e-engine 1964/udp # SOLID E ENGINE
+tivoli-npm 1965/tcp # Tivoli NPM
+tivoli-npm 1965/udp # Tivoli NPM
+slush 1966/tcp # Slush
+slush 1966/udp # Slush
+sns-quote 1967/tcp # SNS Quote
+sns-quote 1967/udp # SNS Quote
+lipsinc 1968/tcp # LIPSinc
+lipsinc 1968/udp # LIPSinc
+lipsinc1 1969/tcp # LIPSinc 1
+lipsinc1 1969/udp # LIPSinc 1
+netop-rc 1970/tcp # NetOp Remote Control
+netop-rc 1970/udp # NetOp Remote Control
+netop-school 1971/tcp # NetOp School
+netop-school 1971/udp # NetOp School
+intersys-cache 1972/tcp # Cache
+intersys-cache 1972/udp # Cache
+dlsrap 1973/tcp # Data Link Switching Remote Access Protocol
+dlsrap 1973/udp # Data Link Switching Remote Access Protocol
+drp 1974/tcp # DRP
+drp 1974/udp # DRP
+tcoflashagent 1975/tcp # TCO Flash Agent
+tcoflashagent 1975/udp # TCO Flash Agent
+tcoregagent 1976/tcp # TCO Reg Agent
+tcoregagent 1976/udp # TCO Reg Agent
+tcoaddressbook 1977/tcp # TCO Address Book
+tcoaddressbook 1977/udp # TCO Address Book
+unisql 1978/tcp # UniSQL
+unisql 1978/udp # UniSQL
+unisql-java 1979/tcp # UniSQL Java
+unisql-java 1979/udp # UniSQL Java
+pearldoc-xact 1980/tcp # PearlDoc XACT
+pearldoc-xact 1980/udp # PearlDoc XACT
+p2pq 1981/tcp # p2pQ
+p2pq 1981/udp # p2pQ
+estamp 1982/tcp # Evidentiary Timestamp
+estamp 1982/udp # Evidentiary Timestamp
+lhtp 1983/tcp # Loophole Test Protocol
+lhtp 1983/udp # Loophole Test Protocol
+bb 1984/tcp # BB
+bb 1984/udp # BB
+tr-rsrb-p1 1987/tcp # cisco RSRB Priority 1 port
+tr-rsrb-p1 1987/udp # cisco RSRB Priority 1 port
+tr-rsrb-p2 1988/tcp # cisco RSRB Priority 2 port
+tr-rsrb-p2 1988/udp # cisco RSRB Priority 2 port
+tr-rsrb-p3 1989/tcp # cisco RSRB Priority 3 port
+tr-rsrb-p3 1989/udp # cisco RSRB Priority 3 port
+#mshnet 1989/tcp # MHSnet system
+#mshnet 1989/udp # MHSnet system
+stun-p1 1990/tcp # cisco STUN Priority 1 port
+stun-p1 1990/udp # cisco STUN Priority 1 port
+stun-p2 1991/tcp # cisco STUN Priority 2 port
+stun-p2 1991/udp # cisco STUN Priority 2 port
+stun-p3 1992/tcp # cisco STUN Priority 3 port
+stun-p3 1992/udp # cisco STUN Priority 3 port
+#ipsendmsg 1992/tcp # IPsendmsg
+#ipsendmsg 1992/udp # IPsendmsg
+snmp-tcp-port 1993/tcp # cisco SNMP TCP port
+snmp-tcp-port 1993/udp # cisco SNMP TCP port
+stun-port 1994/tcp # cisco serial tunnel port
+stun-port 1994/udp # cisco serial tunnel port
+perf-port 1995/tcp # cisco perf port
+perf-port 1995/udp # cisco perf port
+tr-rsrb-port 1996/tcp # cisco Remote SRB port
+tr-rsrb-port 1996/udp # cisco Remote SRB port
+x25-svc-port 1998/tcp # cisco X.25 service (XOT)
+x25-svc-port 1998/udp # cisco X.25 service (XOT)
+tcp-id-port 1999/tcp # cisco identification port
+tcp-id-port 1999/udp # cisco identification port
+dc 2001/tcp #
+wizard 2001/udp # curry
+globe 2002/tcp #
+globe 2002/udp #
+brutus 2003/udp # Brutus Server
+mailbox 2004/tcp #
+emce 2004/udp # CCWS mm conf
+berknet 2005/tcp csync # csync for cyrus-imapd
+oracle 2005/udp csync # csync for cyrus-imapd
+invokator 2006/tcp #
+dectalk 2007/tcp #
+raid-am 2007/udp #
+conf 2008/tcp #
+terminaldb 2008/udp #
+news 2009/tcp #
+whosockami 2009/udp #
+search 2010/tcp #
+pipe_server 2010/udp pipe-server #
+raid-cc 2011/tcp # raid
+servserv 2011/udp #
+ttyinfo 2012/tcp #
+raid-ac 2012/udp #
+raid-am 2013/tcp #
+troff 2014/tcp #
+raid-sf 2014/udp #
+cypress 2015/tcp #
+raid-cs 2015/udp #
+bootserver 2016/tcp #
+bootserver 2016/udp #
+cypress-stat 2017/tcp #
+bootclient 2017/udp #
+terminaldb 2018/tcp #
+rellpack 2018/udp #
+whosockami 2019/tcp #
+about 2019/udp #
+xinupageserver 2020/tcp #
+xinupageserver 2020/udp #
+servexec 2021/tcp #
+xinuexpansion1 2021/udp #
+down 2022/tcp #
+xinuexpansion2 2022/udp #
+xinuexpansion3 2023/tcp #
+xinuexpansion3 2023/udp #
+xinuexpansion4 2024/tcp #
+xinuexpansion4 2024/udp #
+ellpack 2025/tcp #
+xribs 2025/udp #
+scrabble 2026/tcp #
+scrabble 2026/udp #
+shadowserver 2027/tcp #
+shadowserver 2027/udp #
+submitserver 2028/tcp #
+submitserver 2028/udp #
+hsrpv6 2029/tcp # Hot Standby Router Protocol IPv6
+hsrpv6 2029/udp # Hot Standby Router Protocol IPv6
+device2 2030/tcp #
+device2 2030/udp #
+mobrien-chat 2031/tcp # mobrien-chat
+mobrien-chat 2031/udp # mobrien-chat
+blackboard 2032/tcp #
+blackboard 2032/udp #
+glogger 2033/tcp #
+glogger 2033/udp #
+scoremgr 2034/tcp #
+scoremgr 2034/udp #
+imsldoc 2035/tcp #
+imsldoc 2035/udp #
+e-dpnet 2036/tcp # Ethernet WS DP network
+e-dpnet 2036/udp # Ethernet WS DP network
+applus 2037/tcp # APplus Application Server
+applus 2037/udp # APplus Application Server
+objectmanager 2038/tcp #
+objectmanager 2038/udp #
+prizma 2039/tcp # Prizma Monitoring Service
+prizma 2039/udp # Prizma Monitoring Service
+lam 2040/tcp #
+lam 2040/udp #
+interbase 2041/tcp #
+interbase 2041/udp #
+isis 2042/tcp # isis
+isis 2042/udp # isis
+isis-bcast 2043/tcp # isis-bcast
+isis-bcast 2043/udp # isis-bcast
+rimsl 2044/tcp #
+rimsl 2044/udp #
+cdfunc 2045/tcp #
+cdfunc 2045/udp #
+sdfunc 2046/tcp #
+sdfunc 2046/udp #
+dls-monitor 2048/tcp #
+dls-monitor 2048/udp #
+av-emb-config 2050/tcp # Avaya EMB Config Port
+av-emb-config 2050/udp # Avaya EMB Config Port
+epnsdp 2051/tcp # EPNSDP
+epnsdp 2051/udp # EPNSDP
+clearvisn 2052/tcp # clearVisn Services Port
+clearvisn 2052/udp # clearVisn Services Port
+lot105-ds-upd 2053/udp # Lot105 DSuper Updates
+weblogin 2054/tcp # Weblogin Port
+weblogin 2054/udp # Weblogin Port
+iop 2055/tcp # Iliad-Odyssey Protocol
+iop 2055/udp # Iliad-Odyssey Protocol
+omnisky 2056/tcp # OmniSky Port
+omnisky 2056/udp # OmniSky Port
+rich-cp 2057/tcp # Rich Content Protocol
+rich-cp 2057/udp # Rich Content Protocol
+newwavesearch 2058/tcp # NewWaveSearchables RMI
+newwavesearch 2058/udp # NewWaveSearchables RMI
+bmc-messaging 2059/tcp # BMC Messaging Service
+bmc-messaging 2059/udp # BMC Messaging Service
+teleniumdaemon 2060/tcp # Telenium Daemon IF
+teleniumdaemon 2060/udp # Telenium Daemon IF
+netmount 2061/tcp # NetMount
+netmount 2061/udp # NetMount
+icg-swp 2062/tcp # ICG SWP Port
+icg-swp 2062/udp # ICG SWP Port
+icg-bridge 2063/tcp # ICG Bridge Port
+icg-bridge 2063/udp # ICG Bridge Port
+icg-iprelay 2064/tcp # ICG IP Relay Port
+icg-iprelay 2064/udp # ICG IP Relay Port
+dlsrpn 2065/tcp # Data Link Switch Read Port Number
+dlsrpn 2065/udp # Data Link Switch Read Port Number
+aura 2066/tcp # AVM USB Remote Architecture
+aura 2066/udp # AVM USB Remote Architecture
+dlswpn 2067/tcp # Data Link Switch Write Port Number
+dlswpn 2067/udp # Data Link Switch Write Port Number
+avauthsrvprtcl 2068/tcp # Avocent AuthSrv Protocol
+avauthsrvprtcl 2068/udp # Avocent AuthSrv Protocol
+event-port 2069/tcp # HTTP Event Port
+event-port 2069/udp # HTTP Event Port
+ah-esp-encap 2070/tcp # AH and ESP Encapsulated in UDP packet
+ah-esp-encap 2070/udp # AH and ESP Encapsulated in UDP packet
+acp-port 2071/tcp # Axon Control Protocol
+acp-port 2071/udp # Axon Control Protocol
+msync 2072/tcp # GlobeCast mSync
+msync 2072/udp # GlobeCast mSync
+gxs-data-port 2073/tcp # DataReel Database Socket
+gxs-data-port 2073/udp # DataReel Database Socket
+vrtl-vmf-sa 2074/tcp # Vertel VMF SA
+vrtl-vmf-sa 2074/udp # Vertel VMF SA
+newlixengine 2075/tcp # Newlix ServerWare Engine
+newlixengine 2075/udp # Newlix ServerWare Engine
+newlixconfig 2076/tcp # Newlix JSPConfig
+newlixconfig 2076/udp # Newlix JSPConfig
+tsrmagt 2077/tcp # Old Tivoli Storage Manager
+tsrmagt 2077/udp # Old Tivoli Storage Manager
+tpcsrvr 2078/tcp # IBM Total Productivity Center Server
+tpcsrvr 2078/udp # IBM Total Productivity Center Server
+idware-router 2079/tcp # IDWARE Router Port
+idware-router 2079/udp # IDWARE Router Port
+autodesk-nlm 2080/tcp # Autodesk NLM (FLEXlm)
+autodesk-nlm 2080/udp # Autodesk NLM (FLEXlm)
+kme-trap-port 2081/tcp # KME PRINTER TRAP PORT
+kme-trap-port 2081/udp # KME PRINTER TRAP PORT
+infowave 2082/tcp # Infowave Mobility Server
+infowave 2082/udp # Infowave Mobility Server
+radsec 2083/tcp # Secure Radius Service
+radsec 2083/udp # Secure Radius Service
+sunclustergeo 2084/tcp # SunCluster Geographic
+sunclustergeo 2084/udp # SunCluster Geographic
+ada-cip 2085/tcp # ADA Control
+ada-cip 2085/udp # ADA Control
+gnunet 2086/tcp # GNUnet
+gnunet 2086/udp # GNUnet
+eli 2087/tcp # ELI - Event Logging Integration
+eli 2087/udp # ELI - Event Logging Integration
+ip-blf 2088/tcp # IP Busy Lamp Field
+ip-blf 2088/udp # IP Busy Lamp Field
+sep 2089/tcp # Security Encapsulation Protocol - SEP
+sep 2089/udp # Security Encapsulation Protocol - SEP
+lrp 2090/tcp # Load Report Protocol
+lrp 2090/udp # Load Report Protocol
+prp 2091/tcp # PRP
+prp 2091/udp # PRP
+descent3 2092/tcp # Descent 3
+descent3 2092/udp # Descent 3
+nbx-cc 2093/tcp # NBX CC
+nbx-cc 2093/udp # NBX CC
+nbx-au 2094/tcp # NBX AU
+nbx-au 2094/udp # NBX AU
+nbx-ser 2095/tcp # NBX SER
+nbx-ser 2095/udp # NBX SER
+nbx-dir 2096/tcp # NBX DIR
+nbx-dir 2096/udp # NBX DIR
+jetformpreview 2097/tcp # Jet Form Preview
+jetformpreview 2097/udp # Jet Form Preview
+dialog-port 2098/tcp # Dialog Port
+dialog-port 2098/udp # Dialog Port
+h2250-annex-g 2099/tcp # H.225.0 Annex G
+h2250-annex-g 2099/udp # H.225.0 Annex G
+amiganetfs 2100/tcp # Amiga Network Filesystem
+amiganetfs 2100/udp # Amiga Network Filesystem
+rtcm-sc104 2101/tcp # rtcm-sc104
+rtcm-sc104 2101/udp # rtcm-sc104
+minipay 2105/udp # MiniPay
+mzap 2106/tcp # MZAP
+mzap 2106/udp # MZAP
+bintec-admin 2107/tcp # BinTec Admin
+bintec-admin 2107/udp # BinTec Admin
+comcam 2108/tcp # Comcam
+comcam 2108/udp # Comcam
+ergolight 2109/tcp # Ergolight
+ergolight 2109/udp # Ergolight
+umsp 2110/tcp # UMSP
+umsp 2110/udp # UMSP
+dsatp 2111/tcp # DSATP
+dsatp 2111/udp # DSATP
+idonix-metanet 2112/tcp # Idonix MetaNet
+idonix-metanet 2112/udp # Idonix MetaNet
+hsl-storm 2113/tcp # HSL StoRM
+hsl-storm 2113/udp # HSL StoRM
+newheights 2114/tcp # NEWHEIGHTS
+newheights 2114/udp # NEWHEIGHTS
+kdm 2115/tcp # Key Distribution Manager
+kdm 2115/udp # Key Distribution Manager
+ccowcmr 2116/tcp # CCOWCMR
+ccowcmr 2116/udp # CCOWCMR
+mentaclient 2117/tcp # MENTACLIENT
+mentaclient 2117/udp # MENTACLIENT
+mentaserver 2118/tcp # MENTASERVER
+mentaserver 2118/udp # MENTASERVER
+gsigatekeeper 2119/tcp # GSIGATEKEEPER
+gsigatekeeper 2119/udp # GSIGATEKEEPER
+qencp 2120/tcp # Quick Eagle Networks CP
+qencp 2120/udp # Quick Eagle Networks CP
+scientia-ssdb 2121/tcp # SCIENTIA-SSDB
+scientia-ssdb 2121/udp # SCIENTIA-SSDB
+caupc-remote 2122/tcp # CauPC Remote Control
+caupc-remote 2122/udp # CauPC Remote Control
+gtp-control 2123/tcp # GTP-Control Plane (3GPP)
+gtp-control 2123/udp # GTP-Control Plane (3GPP)
+elatelink 2124/tcp # ELATELINK
+elatelink 2124/udp # ELATELINK
+lockstep 2125/tcp # LOCKSTEP
+lockstep 2125/udp # LOCKSTEP
+pktcable-cops 2126/tcp # PktCable-COPS
+pktcable-cops 2126/udp # PktCable-COPS
+index-pc-wb 2127/tcp # INDEX-PC-WB
+index-pc-wb 2127/udp # INDEX-PC-WB
+net-steward 2128/tcp # Net Steward Control
+net-steward 2128/udp # Net Steward Control
+cs-live 2129/tcp # cs-live.com
+cs-live 2129/udp # cs-live.com
+xds 2130/tcp # XDS
+xds 2130/udp # XDS
+avantageb2b 2131/tcp # Avantageb2b
+avantageb2b 2131/udp # Avantageb2b
+solera-epmap 2132/tcp # SoleraTec End Point Map
+solera-epmap 2132/udp # SoleraTec End Point Map
+zymed-zpp 2133/tcp # ZYMED-ZPP
+zymed-zpp 2133/udp # ZYMED-ZPP
+avenue 2134/tcp # AVENUE
+avenue 2134/udp # AVENUE
+gris 2135/tcp # Grid Resource Information Server
+gris 2135/udp # Grid Resource Information Server
+appworxsrv 2136/tcp # APPWORXSRV
+appworxsrv 2136/udp # APPWORXSRV
+connect 2137/tcp # CONNECT
+connect 2137/udp # CONNECT
+unbind-cluster 2138/tcp # UNBIND-CLUSTER
+unbind-cluster 2138/udp # UNBIND-CLUSTER
+ias-auth 2139/tcp # IAS-AUTH
+ias-auth 2139/udp # IAS-AUTH
+ias-reg 2140/tcp # IAS-REG
+ias-reg 2140/udp # IAS-REG
+ias-admind 2141/tcp # IAS-ADMIND
+ias-admind 2141/udp # IAS-ADMIND
+tdmoip 2142/tcp # TDM OVER IP
+tdmoip 2142/udp # TDM OVER IP
+lv-jc 2143/tcp # Live Vault Job Control
+lv-jc 2143/udp # Live Vault Job Control
+lv-ffx 2144/tcp # Live Vault Fast Object Transfer
+lv-ffx 2144/udp # Live Vault Fast Object Transfer
+lv-pici 2145/tcp # Live Vault Remote Diagnostic Console Support
+lv-pici 2145/udp # Live Vault Remote Diagnostic Console Support
+lv-not 2146/tcp # Live Vault Admin Event Notification
+lv-not 2146/udp # Live Vault Admin Event Notification
+lv-auth 2147/tcp # Live Vault Authentication
+lv-auth 2147/udp # Live Vault Authentication
+veritas-ucl 2148/tcp # VERITAS UNIVERSAL COMMUNICATION LAYER
+veritas-ucl 2148/udp # VERITAS UNIVERSAL COMMUNICATION LAYER
+acptsys 2149/tcp # ACPTSYS
+acptsys 2149/udp # ACPTSYS
+docent 2151/tcp # DOCENT
+docent 2151/udp # DOCENT
+gtp-user 2152/tcp # GTP-User Plane (3GPP)
+gtp-user 2152/udp # GTP-User Plane (3GPP)
+ctlptc 2153/tcp # Control Protocol
+ctlptc 2153/udp # Control Protocol
+stdptc 2154/tcp # Standard Protocol
+stdptc 2154/udp # Standard Protocol
+brdptc 2155/tcp # Bridge Protocol
+brdptc 2155/udp # Bridge Protocol
+trp 2156/tcp # Talari Reliable Protocol
+trp 2156/udp # Talari Reliable Protocol
+xnds 2157/tcp # Xerox Network Document Scan Protocol
+xnds 2157/udp # Xerox Network Document Scan Protocol
+touchnetplus 2158/tcp # TouchNetPlus Service
+touchnetplus 2158/udp # TouchNetPlus Service
+gdbremote 2159/tcp # GDB Remote Debug Port
+gdbremote 2159/udp # GDB Remote Debug Port
+apc-2160 2160/tcp # APC 2160
+apc-2160 2160/udp # APC 2160
+apc-2161 2161/tcp # APC 2161
+apc-2161 2161/udp # APC 2161
+navisphere 2162/tcp # Navisphere
+navisphere 2162/udp # Navisphere
+navisphere-sec 2163/tcp # Navisphere Secure
+navisphere-sec 2163/udp # Navisphere Secure
+ddns-v3 2164/tcp # Dynamic DNS Version 3
+ddns-v3 2164/udp # Dynamic DNS Version 3
+x-bone-api 2165/tcp # X-Bone API
+x-bone-api 2165/udp # X-Bone API
+iwserver 2166/tcp # iwserver
+iwserver 2166/udp # iwserver
+raw-serial 2167/tcp # Raw Async Serial Link
+raw-serial 2167/udp # Raw Async Serial Link
+easy-soft-mux 2168/tcp # easy-soft Multiplexer
+easy-soft-mux 2168/udp # easy-soft Multiplexer
+brain 2169/tcp # Backbone for Academic Information Notification (BRAIN)
+brain 2169/udp # Backbone for Academic Information Notification (BRAIN)
+eyetv 2170/tcp # EyeTV Server Port
+eyetv 2170/udp # EyeTV Server Port
+msfw-storage 2171/tcp # MS Firewall Storage
+msfw-storage 2171/udp # MS Firewall Storage
+msfw-s-storage 2172/tcp # MS Firewall SecureStorage
+msfw-s-storage 2172/udp # MS Firewall SecureStorage
+msfw-replica 2173/tcp # MS Firewall Replication
+msfw-replica 2173/udp # MS Firewall Replication
+msfw-array 2174/tcp # MS Firewall Intra Array
+msfw-array 2174/udp # MS Firewall Intra Array
+airsync 2175/tcp # Microsoft Desktop AirSync Protocol
+airsync 2175/udp # Microsoft Desktop AirSync Protocol
+rapi 2176/tcp # Microsoft ActiveSync Remote API
+rapi 2176/udp # Microsoft ActiveSync Remote API
+qwave 2177/tcp # qWAVE Bandwidth Estimate
+qwave 2177/udp # qWAVE Bandwidth Estimate
+bitspeer 2178/tcp # Peer Services for BITS
+bitspeer 2178/udp # Peer Services for BITS
+vmrdp 2179/tcp # Microsoft RDP for virtual machines
+vmrdp 2179/udp # Microsoft RDP for virtual machines
+mc-gt-srv 2180/tcp # Millicent Vendor Gateway Server
+mc-gt-srv 2180/udp # Millicent Vendor Gateway Server
+eforward 2181/tcp # eforward
+eforward 2181/udp # eforward
+cgn-stat 2182/tcp # CGN status
+cgn-stat 2182/udp # CGN status
+cgn-config 2183/tcp # Code Green configuration
+cgn-config 2183/udp # Code Green configuration
+onbase-dds 2185/tcp # OnBase Distributed Disk Services
+onbase-dds 2185/udp # OnBase Distributed Disk Services
+gtaua 2186/tcp # Guy-Tek Automated Update Applications
+gtaua 2186/udp # Guy-Tek Automated Update Applications
+ssmc 2187/tcp # Sepehr System Management Control
+ssmd 2187/udp # Sepehr System Management Data
+radware-rpm 2188/tcp # Radware Resource Pool Manager
+radware-rpm-s 2189/tcp # Secure Radware Resource Pool Manager
+tivoconnect 2190/tcp # TiVoConnect Beacon
+tivoconnect 2190/udp # TiVoConnect Beacon
+tvbus 2191/tcp # TvBus Messaging
+tvbus 2191/udp # TvBus Messaging
+asdis 2192/tcp # ASDIS software management
+asdis 2192/udp # ASDIS software management
+drwcs 2193/tcp # Dr.Web Enterprise Management Service
+drwcs 2193/udp # Dr.Web Enterprise Management Service
+mnp-exchange 2197/tcp # MNP data exchange
+mnp-exchange 2197/udp # MNP data exchange
+onehome-remote 2198/tcp # OneHome Remote Access
+onehome-remote 2198/udp # OneHome Remote Access
+onehome-help 2199/tcp # OneHome Service Port
+onehome-help 2199/udp # OneHome Service Port
+ici 2200/tcp # ICI
+ici 2200/udp # ICI
+ats 2201/tcp # Advanced Training System Program
+ats 2201/udp # Advanced Training System Program
+imtc-map 2202/tcp # Int. Multimedia Teleconferencing Cosortium
+imtc-map 2202/udp # Int. Multimedia Teleconferencing Cosortium
+b2-runtime 2203/tcp # b2 Runtime Protocol
+b2-runtime 2203/udp # b2 Runtime Protocol
+b2-license 2204/tcp # b2 License Server
+b2-license 2204/udp # b2 License Server
+jps 2205/tcp # Java Presentation Server
+jps 2205/udp # Java Presentation Server
+hpocbus 2206/tcp # HP OpenCall bus
+hpocbus 2206/udp # HP OpenCall bus
+hpssd 2207/tcp # HP Status and Services
+hpssd 2207/udp # HP Status and Services
+hpiod 2208/tcp # HP I/O Backend
+hpiod 2208/udp # HP I/O Backend
+rimf-ps 2209/tcp # HP RIM for Files Portal Service
+rimf-ps 2209/udp # HP RIM for Files Portal Service
+noaaport 2210/tcp # NOAAPORT Broadcast Network
+noaaport 2210/udp # NOAAPORT Broadcast Network
+emwin 2211/tcp # EMWIN
+emwin 2211/udp # EMWIN
+leecoposserver 2212/tcp # LeeCO POS Server Service
+leecoposserver 2212/udp # LeeCO POS Server Service
+kali 2213/tcp # Kali
+kali 2213/udp # Kali
+rpi 2214/tcp # RDQ Protocol Interface
+rpi 2214/udp # RDQ Protocol Interface
+ipcore 2215/tcp # IPCore.co.za GPRS
+ipcore 2215/udp # IPCore.co.za GPRS
+vtu-comms 2216/tcp # VTU data service
+vtu-comms 2216/udp # VTU data service
+gotodevice 2217/tcp # GoToDevice Device Management
+gotodevice 2217/udp # GoToDevice Device Management
+bounzza 2218/tcp # Bounzza IRC Proxy
+bounzza 2218/udp # Bounzza IRC Proxy
+netiq-ncap 2219/tcp # NetIQ NCAP Protocol
+netiq-ncap 2219/udp # NetIQ NCAP Protocol
+netiq 2220/tcp # NetIQ End2End
+netiq 2220/udp # NetIQ End2End
+ethernet-ip-s 2221/tcp # EtherNet/IP over TLS
+ethernet-ip-s 2221/udp # EtherNet/IP over DTLS
+EtherNet/IP-1 2222/tcp EtherNet-IP-1 # EtherNet/IP I/O
+EtherNet/IP-1 2222/udp EtherNet-IP-1 # EtherNet/IP I/O
+rockwell-csp2 2223/tcp # Rockwell CSP2
+rockwell-csp2 2223/udp # Rockwell CSP2
+efi-mg 2224/tcp # Easy Flexible Internet/Multiplayer Games
+efi-mg 2224/udp # Easy Flexible Internet/Multiplayer Games
+rcip-itu 2225/tcp # Resource Connection Initiation Protocol
+rcip-itu 2225/sctp # Resource Connection Initiation Protocol
+di-drm 2226/tcp # Digital Instinct DRM
+di-drm 2226/udp # Digital Instinct DRM
+di-msg 2227/tcp # DI Messaging Service
+di-msg 2227/udp # DI Messaging Service
+ehome-ms 2228/tcp # eHome Message Server
+ehome-ms 2228/udp # eHome Message Server
+datalens 2229/tcp # DataLens Service
+datalens 2229/udp # DataLens Service
+queueadm 2230/tcp # MetaSoft Job Queue Administration Service
+queueadm 2230/udp # MetaSoft Job Queue Administration Service
+wimaxasncp 2231/tcp # WiMAX ASN Control Plane Protocol
+wimaxasncp 2231/udp # WiMAX ASN Control Plane Protocol
+ivs-video 2232/tcp # IVS Video default
+ivs-video 2232/udp # IVS Video default
+infocrypt 2233/tcp # INFOCRYPT
+infocrypt 2233/udp # INFOCRYPT
+directplay 2234/tcp # DirectPlay
+directplay 2234/udp # DirectPlay
+sercomm-wlink 2235/tcp # Sercomm-WLink
+sercomm-wlink 2235/udp # Sercomm-WLink
+nani 2236/tcp # Nani
+nani 2236/udp # Nani
+optech-port1-lm 2237/tcp # Optech Port1 License Manager
+optech-port1-lm 2237/udp # Optech Port1 License Manager
+aviva-sna 2238/tcp # AVIVA SNA SERVER
+aviva-sna 2238/udp # AVIVA SNA SERVER
+imagequery 2239/tcp # Image Query
+imagequery 2239/udp # Image Query
+recipe 2240/tcp # RECIPe
+recipe 2240/udp # RECIPe
+ivsd 2241/tcp # IVS Daemon
+ivsd 2241/udp # IVS Daemon
+foliocorp 2242/tcp # Folio Remote Server
+foliocorp 2242/udp # Folio Remote Server
+magicom 2243/tcp # Magicom Protocol
+magicom 2243/udp # Magicom Protocol
+nmsserver 2244/tcp # NMS Server
+nmsserver 2244/udp # NMS Server
+hao 2245/tcp # HaO
+hao 2245/udp # HaO
+pc-mta-addrmap 2246/tcp # PacketCable MTA Addr Map
+pc-mta-addrmap 2246/udp # PacketCable MTA Addr Map
+antidotemgrsvr 2247/tcp # Antidote Deployment Manager Service
+antidotemgrsvr 2247/udp # Antidote Deployment Manager Service
+ums 2248/tcp # User Management Service
+ums 2248/udp # User Management Service
+rfmp 2249/tcp # RISO File Manager Protocol
+rfmp 2249/udp # RISO File Manager Protocol
+remote-collab 2250/tcp # remote-collab
+remote-collab 2250/udp # remote-collab
+dif-port 2251/tcp # Distributed Framework Port
+dif-port 2251/udp # Distributed Framework Port
+njenet-ssl 2252/tcp # NJENET using SSL
+njenet-ssl 2252/udp # NJENET using SSL
+dtv-chan-req 2253/tcp # DTV Channel Request
+dtv-chan-req 2253/udp # DTV Channel Request
+seispoc 2254/tcp # Seismic P.O.C. Port
+seispoc 2254/udp # Seismic P.O.C. Port
+vrtp 2255/tcp # VRTP - ViRtue Transfer Protocol
+vrtp 2255/udp # VRTP - ViRtue Transfer Protocol
+pcc-mfp 2256/tcp # PCC MFP
+pcc-mfp 2256/udp # PCC MFP
+simple-tx-rx 2257/tcp # simple text/file transfer
+simple-tx-rx 2257/udp # simple text/file transfer
+rcts 2258/tcp # Rotorcraft Communications Test System
+rcts 2258/udp # Rotorcraft Communications Test System
+apc-2260 2260/tcp # APC 2260
+apc-2260 2260/udp # APC 2260
+comotionmaster 2261/tcp # CoMotion Master Server
+comotionmaster 2261/udp # CoMotion Master Server
+comotionback 2262/tcp # CoMotion Backup Server
+comotionback 2262/udp # CoMotion Backup Server
+ecwcfg 2263/tcp # ECweb Configuration Service
+ecwcfg 2263/udp # ECweb Configuration Service
+apx500api-1 2264/tcp # Audio Precision Apx500 API Port 1
+apx500api-1 2264/udp # Audio Precision Apx500 API Port 1
+apx500api-2 2265/tcp # Audio Precision Apx500 API Port 2
+apx500api-2 2265/udp # Audio Precision Apx500 API Port 2
+mfserver 2266/tcp # M-Files Server
+mfserver 2266/udp # M-files Server
+ontobroker 2267/tcp # OntoBroker
+ontobroker 2267/udp # OntoBroker
+amt 2268/tcp # AMT
+amt 2268/udp # AMT
+mikey 2269/tcp # MIKEY
+mikey 2269/udp # MIKEY
+starschool 2270/tcp # starSchool
+starschool 2270/udp # starSchool
+mmcals 2271/tcp # Secure Meeting Maker Scheduling
+mmcals 2271/udp # Secure Meeting Maker Scheduling
+mmcal 2272/tcp # Meeting Maker Scheduling
+mmcal 2272/udp # Meeting Maker Scheduling
+mysql-im 2273/tcp # MySQL Instance Manager
+mysql-im 2273/udp # MySQL Instance Manager
+pcttunnell 2274/tcp # PCTTunneller
+pcttunnell 2274/udp # PCTTunneller
+ibridge-data 2275/tcp # iBridge Conferencing
+ibridge-data 2275/udp # iBridge Conferencing
+ibridge-mgmt 2276/tcp # iBridge Management
+ibridge-mgmt 2276/udp # iBridge Management
+bluectrlproxy 2277/tcp # Bt device control proxy
+bluectrlproxy 2277/udp # Bt device control proxy
+s3db 2278/tcp # Simple Stacked Sequences Database
+s3db 2278/udp # Simple Stacked Sequences Database
+xmquery 2279/tcp # xmquery
+xmquery 2279/udp # xmquery
+lnvpoller 2280/tcp # LNVPOLLER
+lnvpoller 2280/udp # LNVPOLLER
+lnvconsole 2281/tcp # LNVCONSOLE
+lnvconsole 2281/udp # LNVCONSOLE
+lnvalarm 2282/tcp # LNVALARM
+lnvalarm 2282/udp # LNVALARM
+lnvstatus 2283/tcp # LNVSTATUS
+lnvstatus 2283/udp # LNVSTATUS
+lnvmaps 2284/tcp # LNVMAPS
+lnvmaps 2284/udp # LNVMAPS
+lnvmailmon 2285/tcp # LNVMAILMON
+lnvmailmon 2285/udp # LNVMAILMON
+nas-metering 2286/tcp # NAS-Metering
+nas-metering 2286/udp # NAS-Metering
+dna 2287/tcp # DNA
+dna 2287/udp # DNA
+netml 2288/tcp # NETML
+netml 2288/udp # NETML
+dict-lookup 2289/tcp # Lookup dict server
+dict-lookup 2289/udp # Lookup dict server
+sonus-logging 2290/tcp # Sonus Logging Services
+sonus-logging 2290/udp # Sonus Logging Services
+eapsp 2291/tcp # EPSON Advanced Printer Share Protocol
+eapsp 2291/udp # EPSON Advanced Printer Share Protocol
+mib-streaming 2292/tcp # Sonus Element Management Services
+mib-streaming 2292/udp # Sonus Element Management Services
+npdbgmngr 2293/tcp # Network Platform Debug Manager
+npdbgmngr 2293/udp # Network Platform Debug Manager
+konshus-lm 2294/tcp # Konshus License Manager (FLEX)
+konshus-lm 2294/udp # Konshus License Manager (FLEX)
+advant-lm 2295/tcp # Advant License Manager
+advant-lm 2295/udp # Advant License Manager
+theta-lm 2296/tcp # Theta License Manager (Rainbow)
+theta-lm 2296/udp # Theta License Manager (Rainbow)
+d2k-datamover1 2297/tcp # D2K DataMover 1
+d2k-datamover1 2297/udp # D2K DataMover 1
+d2k-datamover2 2298/tcp # D2K DataMover 2
+d2k-datamover2 2298/udp # D2K DataMover 2
+pc-telecommute 2299/tcp # PC Telecommute
+pc-telecommute 2299/udp # PC Telecommute
+cvmmon 2300/tcp # CVMMON
+cvmmon 2300/udp # CVMMON
+cpq-wbem 2301/tcp # Compaq HTTP
+cpq-wbem 2301/udp # Compaq HTTP
+binderysupport 2302/tcp # Bindery Support
+binderysupport 2302/udp # Bindery Support
+proxy-gateway 2303/tcp # Proxy Gateway
+proxy-gateway 2303/udp # Proxy Gateway
+attachmate-uts 2304/tcp # Attachmate UTS
+attachmate-uts 2304/udp # Attachmate UTS
+mt-scaleserver 2305/tcp # MT ScaleServer
+mt-scaleserver 2305/udp # MT ScaleServer
+tappi-boxnet 2306/tcp # TAPPI BoxNet
+tappi-boxnet 2306/udp # TAPPI BoxNet
+pehelp 2307/tcp # pehelp
+pehelp 2307/udp # pehelp
+sdhelp 2308/tcp # sdhelp
+sdhelp 2308/udp # sdhelp
+sdserver 2309/tcp # SD Server
+sdserver 2309/udp # SD Server
+sdclient 2310/tcp # SD Client
+sdclient 2310/udp # SD Client
+messageservice 2311/tcp # Message Service
+messageservice 2311/udp # Message Service
+wanscaler 2312/tcp # WANScaler Communication Service
+wanscaler 2312/udp # WANScaler Communication Service
+iapp 2313/tcp # IAPP (Inter Access Point Protocol)
+iapp 2313/udp # IAPP (Inter Access Point Protocol)
+cr-websystems 2314/tcp # CR WebSystems
+cr-websystems 2314/udp # CR WebSystems
+precise-sft 2315/tcp # Precise Sft.
+precise-sft 2315/udp # Precise Sft.
+sent-lm 2316/tcp # SENT License Manager
+sent-lm 2316/udp # SENT License Manager
+attachmate-g32 2317/tcp # Attachmate G32
+attachmate-g32 2317/udp # Attachmate G32
+cadencecontrol 2318/tcp # Cadence Control
+cadencecontrol 2318/udp # Cadence Control
+infolibria 2319/tcp # InfoLibria
+infolibria 2319/udp # InfoLibria
+siebel-ns 2320/tcp # Siebel NS
+siebel-ns 2320/udp # Siebel NS
+rdlap 2321/tcp # RDLAP
+rdlap 2321/udp # RDLAP
+ofsd 2322/tcp # ofsd
+ofsd 2322/udp # ofsd
+3d-nfsd 2323/tcp # 3d-nfsd
+3d-nfsd 2323/udp # 3d-nfsd
+cosmocall 2324/tcp # Cosmocall
+cosmocall 2324/udp # Cosmocall
+ansysli 2325/tcp # ANSYS Licensing Interconnect
+ansysli 2325/udp # ANSYS Licensing Interconnect
+idcp 2326/tcp # IDCP
+idcp 2326/udp # IDCP
+xingcsm 2327/tcp # xingcsm
+xingcsm 2327/udp # xingcsm
+netrix-sftm 2328/tcp # Netrix SFTM
+netrix-sftm 2328/udp # Netrix SFTM
+tscchat 2330/tcp # TSCCHAT
+tscchat 2330/udp # TSCCHAT
+agentview 2331/tcp # AGENTVIEW
+agentview 2331/udp # AGENTVIEW
+rcc-host 2332/tcp # RCC Host
+rcc-host 2332/udp # RCC Host
+snapp 2333/tcp # SNAPP
+snapp 2333/udp # SNAPP
+ace-client 2334/tcp # ACE Client Auth
+ace-client 2334/udp # ACE Client Auth
+ace-proxy 2335/tcp # ACE Proxy
+ace-proxy 2335/udp # ACE Proxy
+appleugcontrol 2336/tcp # Apple UG Control
+appleugcontrol 2336/udp # Apple UG Control
+ideesrv 2337/tcp # ideesrv
+ideesrv 2337/udp # ideesrv
+norton-lambert 2338/tcp # Norton Lambert
+norton-lambert 2338/udp # Norton Lambert
+3com-webview 2339/tcp # 3Com WebView
+3com-webview 2339/udp # 3Com WebView
+wrs_registry 2340/tcp wrs-registry # WRS Registry
+wrs_registry 2340/udp wrs-registry # WRS Registry
+xiostatus 2341/tcp # XIO Status
+xiostatus 2341/udp # XIO Status
+manage-exec 2342/tcp # Seagate Manage Exec
+manage-exec 2342/udp # Seagate Manage Exec
+nati-logos 2343/tcp # nati logos
+nati-logos 2343/udp # nati logos
+fcmsys 2344/tcp # fcmsys
+fcmsys 2344/udp # fcmsys
+dbm 2345/tcp # dbm
+dbm 2345/udp # dbm
+redstorm_join 2346/tcp redstorm-join # Game Connection Port
+redstorm_join 2346/udp redstorm-join # Game Connection Port
+redstorm_find 2347/tcp redstorm-find # Game Announcement and Location
+redstorm_find 2347/udp redstorm-find # Game Announcement and Location
+redstorm_info 2348/tcp redstorm-info # Information to query for game status
+redstorm_info 2348/udp redstorm-info # Information to query for game status
+redstorm_diag 2349/tcp redstorm-diag # Diagnostics Port
+redstorm_diag 2349/udp redstorm-diag # Diagnostics Port
+psbserver 2350/tcp # Pharos Booking Server
+psbserver 2350/udp # Pharos Booking Server
+psrserver 2351/tcp # psrserver
+psrserver 2351/udp # psrserver
+pslserver 2352/tcp # pslserver
+pslserver 2352/udp # pslserver
+pspserver 2353/tcp # pspserver
+pspserver 2353/udp # pspserver
+psprserver 2354/tcp # psprserver
+psprserver 2354/udp # psprserver
+psdbserver 2355/tcp # psdbserver
+psdbserver 2355/udp # psdbserver
+gxtelmd 2356/tcp # GXT License Managemant
+gxtelmd 2356/udp # GXT License Managemant
+unihub-server 2357/tcp # UniHub Server
+unihub-server 2357/udp # UniHub Server
+futrix 2358/tcp # Futrix
+futrix 2358/udp # Futrix
+flukeserver 2359/tcp # FlukeServer
+flukeserver 2359/udp # FlukeServer
+nexstorindltd 2360/tcp # NexstorIndLtd
+nexstorindltd 2360/udp # NexstorIndLtd
+tl1 2361/tcp # TL1
+tl1 2361/udp # TL1
+digiman 2362/tcp # digiman
+digiman 2362/udp # digiman
+mediacntrlnfsd 2363/tcp # Media Central NFSD
+mediacntrlnfsd 2363/udp # Media Central NFSD
+oi-2000 2364/tcp # OI-2000
+oi-2000 2364/udp # OI-2000
+dbref 2365/tcp # dbref
+dbref 2365/udp # dbref
+qip-login 2366/tcp # qip-login
+qip-login 2366/udp # qip-login
+service-ctrl 2367/tcp # Service Control
+service-ctrl 2367/udp # Service Control
+opentable 2368/tcp # OpenTable
+opentable 2368/udp # OpenTable
+l3-hbmon 2370/tcp # L3-HBMon
+l3-hbmon 2370/udp # L3-HBMon
+hp-rda 2371/tcp # HP Remote Device Access
+lanmessenger 2372/tcp # LanMessenger
+lanmessenger 2372/udp # LanMessenger
+remographlm 2373/tcp # Remograph License Manager
+docker 2375/tcp # Docker REST API (plain text)
+docker-s 2376/tcp # Docker REST API (ssl)
+etcd-client 2379/tcp # etcd client communication
+etcd-server 2380/tcp # etcd server to server communication
+hydra 2374/tcp # Hydra RPC
+compaq-https 2381/tcp # Compaq HTTPS
+compaq-https 2381/udp # Compaq HTTPS
+ms-olap3 2382/tcp # Microsoft OLAP
+ms-olap3 2382/udp # Microsoft OLAP
+ms-olap4 2383/tcp # Microsoft OLAP
+ms-olap4 2383/udp # Microsoft OLAP
+sd-request 2384/tcp # SD-REQUEST
+sd-capacity 2384/udp # SD-CAPACITY
+sd-data 2385/tcp # SD-DATA
+sd-data 2385/udp # SD-DATA
+virtualtape 2386/tcp # Virtual Tape
+virtualtape 2386/udp # Virtual Tape
+vsamredirector 2387/tcp # VSAM Redirector
+vsamredirector 2387/udp # VSAM Redirector
+mynahautostart 2388/tcp # MYNAH AutoStart
+mynahautostart 2388/udp # MYNAH AutoStart
+ovsessionmgr 2389/tcp # OpenView Session Mgr
+ovsessionmgr 2389/udp # OpenView Session Mgr
+rsmtp 2390/tcp # RSMTP
+rsmtp 2390/udp # RSMTP
+3com-net-mgmt 2391/tcp # 3COM Net Management
+3com-net-mgmt 2391/udp # 3COM Net Management
+tacticalauth 2392/tcp # Tactical Auth
+tacticalauth 2392/udp # Tactical Auth
+ms-olap1 2393/tcp # MS OLAP 1
+ms-olap1 2393/udp # MS OLAP 1
+ms-olap2 2394/tcp # MS OLAP 2
+ms-olap2 2394/udp # MS OLAP 2
+lan900_remote 2395/tcp lan900-remote # LAN900 Remote
+lan900_remote 2395/udp lan900-remote # LAN900 Remote
+wusage 2396/tcp # Wusage
+wusage 2396/udp # Wusage
+ncl 2397/tcp # NCL
+ncl 2397/udp # NCL
+orbiter 2398/tcp # Orbiter
+orbiter 2398/udp # Orbiter
+fmpro-fdal 2399/tcp # FileMaker, Inc. - Data Access Layer
+fmpro-fdal 2399/udp # FileMaker, Inc. - Data Access Layer
+opequus-server 2400/tcp # OpEquus Server
+opequus-server 2400/udp # OpEquus Server
+taskmaster2000 2402/tcp # TaskMaster 2000 Server
+taskmaster2000 2402/udp # TaskMaster 2000 Server
+#taskmaster2000 2403/tcp # TaskMaster 2000 Web
+#taskmaster2000 2403/udp # TaskMaster 2000 Web
+iec-104 2404/tcp # IEC 60870-5-104 process control over IP
+iec-104 2404/udp # IEC 60870-5-104 process control over IP
+trc-netpoll 2405/tcp # TRC Netpoll
+trc-netpoll 2405/udp # TRC Netpoll
+jediserver 2406/tcp # JediServer
+jediserver 2406/udp # JediServer
+orion 2407/tcp # Orion
+orion 2407/udp # Orion
+railgun-webaccl 2408/tcp # CloudFlare Railgun Web
+sns-protocol 2409/tcp # SNS Protocol
+sns-protocol 2409/udp # SNS Protocol
+vrts-registry 2410/tcp # VRTS Registry
+vrts-registry 2410/udp # VRTS Registry
+netwave-ap-mgmt 2411/tcp # Netwave AP Management
+netwave-ap-mgmt 2411/udp # Netwave AP Management
+cdn 2412/tcp # CDN
+cdn 2412/udp # CDN
+orion-rmi-reg 2413/tcp # orion-rmi-reg
+orion-rmi-reg 2413/udp # orion-rmi-reg
+beeyond 2414/tcp # Beeyond
+beeyond 2414/udp # Beeyond
+codima-rtp 2415/tcp # Codima Remote Transaction Protocol
+codima-rtp 2415/udp # Codima Remote Transaction Protocol
+rmtserver 2416/tcp # RMT Server
+rmtserver 2416/udp # RMT Server
+composit-server 2417/tcp # Composit Server
+composit-server 2417/udp # Composit Server
+cas 2418/tcp # cas
+cas 2418/udp # cas
+attachmate-s2s 2419/tcp # Attachmate S2S
+attachmate-s2s 2419/udp # Attachmate S2S
+dslremote-mgmt 2420/tcp # DSL Remote Management
+dslremote-mgmt 2420/udp # DSL Remote Management
+g-talk 2421/tcp # G-Talk
+g-talk 2421/udp # G-Talk
+crmsbits 2422/tcp # CRMSBITS
+crmsbits 2422/udp # CRMSBITS
+rnrp 2423/tcp # RNRP
+rnrp 2423/udp # RNRP
+kofax-svr 2424/tcp # KOFAX-SVR
+kofax-svr 2424/udp # KOFAX-SVR
+fjitsuappmgr 2425/tcp # Fujitsu App Manager
+fjitsuappmgr 2425/udp # Fujitsu App Manager
+vcmp 2426/tcp # VeloCloud MultiPath Protocol
+vcmp 2426/udp # VeloCloud MultiPath Protocol
+mgcp-gateway 2427/tcp # Media Gateway Control Protocol Gateway
+mgcp-gateway 2427/udp # Media Gateway Control Protocol Gateway
+ott 2428/tcp # One Way Trip Time
+ott 2428/udp # One Way Trip Time
+ft-role 2429/tcp # FT-ROLE
+ft-role 2429/udp # FT-ROLE
+pxc-epmap 2434/tcp # pxc-epmap
+pxc-epmap 2434/udp # pxc-epmap
+optilogic 2435/tcp # OptiLogic
+optilogic 2435/udp # OptiLogic
+topx 2436/tcp # TOP/X
+topx 2436/udp # TOP/X
+sybasedbsynch 2439/tcp # SybaseDBSynch
+sybasedbsynch 2439/udp # SybaseDBSynch
+spearway 2440/tcp # Spearway Lockers
+spearway 2440/udp # Spearway Lockers
+pvsw-inet 2441/tcp # Pervasive I*net Data Server
+pvsw-inet 2441/udp # Pervasive I*net Data Server
+netangel 2442/tcp # Netangel
+netangel 2442/udp # Netangel
+powerclientcsf 2443/tcp # PowerClient Central Storage Facility
+powerclientcsf 2443/udp # PowerClient Central Storage Facility
+btpp2sectrans 2444/tcp # BT PP2 Sectrans
+btpp2sectrans 2444/udp # BT PP2 Sectrans
+dtn1 2445/tcp # DTN1
+dtn1 2445/udp # DTN1
+bues_service 2446/tcp bues-service # bues_service
+bues_service 2446/udp bues-service # bues_service
+ovwdb 2447/tcp # OpenView NNM daemon
+ovwdb 2447/udp # OpenView NNM daemon
+hpppssvr 2448/tcp # hpppsvr
+hpppssvr 2448/udp # hpppsvr
+ratl 2449/tcp # RATL
+ratl 2449/udp # RATL
+netadmin 2450/tcp # netadmin
+netadmin 2450/udp # netadmin
+netchat 2451/tcp # netchat
+netchat 2451/udp # netchat
+snifferclient 2452/tcp # SnifferClient
+snifferclient 2452/udp # SnifferClient
+madge-ltd 2453/tcp # madge ltd
+madge-ltd 2453/udp # madge ltd
+indx-dds 2454/tcp # IndX-DDS
+indx-dds 2454/udp # IndX-DDS
+wago-io-system 2455/tcp # WAGO-IO-SYSTEM
+wago-io-system 2455/udp # WAGO-IO-SYSTEM
+altav-remmgt 2456/tcp # altav-remmgt
+altav-remmgt 2456/udp # altav-remmgt
+rapido-ip 2457/tcp # Rapido_IP
+rapido-ip 2457/udp # Rapido_IP
+griffin 2458/tcp # griffin
+griffin 2458/udp # griffin
+community 2459/tcp # Community
+community 2459/udp # Community
+ms-theater 2460/tcp # ms-theater
+ms-theater 2460/udp # ms-theater
+qadmifoper 2461/tcp # qadmifoper
+qadmifoper 2461/udp # qadmifoper
+qadmifevent 2462/tcp # qadmifevent
+qadmifevent 2462/udp # qadmifevent
+lsi-raid-mgmt 2463/tcp # LSI RAID Management
+lsi-raid-mgmt 2463/udp # LSI RAID Management
+direcpc-si 2464/tcp # DirecPC SI
+direcpc-si 2464/udp # DirecPC SI
+lbm 2465/tcp # Load Balance Management
+lbm 2465/udp # Load Balance Management
+lbf 2466/tcp # Load Balance Forwarding
+lbf 2466/udp # Load Balance Forwarding
+high-criteria 2467/tcp # High Criteria
+high-criteria 2467/udp # High Criteria
+qip-msgd 2468/tcp # qip_msgd
+qip-msgd 2468/udp # qip_msgd
+mti-tcs-comm 2469/tcp # MTI-TCS-COMM
+mti-tcs-comm 2469/udp # MTI-TCS-COMM
+taskman-port 2470/tcp # taskman port
+taskman-port 2470/udp # taskman port
+seaodbc 2471/tcp # SeaODBC
+seaodbc 2471/udp # SeaODBC
+c3 2472/tcp # C3
+c3 2472/udp # C3
+aker-cdp 2473/tcp # Aker-cdp
+aker-cdp 2473/udp # Aker-cdp
+vitalanalysis 2474/tcp # Vital Analysis
+vitalanalysis 2474/udp # Vital Analysis
+ace-server 2475/tcp # ACE Server
+ace-server 2475/udp # ACE Server
+ace-svr-prop 2476/tcp # ACE Server Propagation
+ace-svr-prop 2476/udp # ACE Server Propagation
+ssm-cvs 2477/tcp # SecurSight Certificate Valifation Service
+ssm-cvs 2477/udp # SecurSight Certificate Valifation Service
+ssm-cssps 2478/tcp # SecurSight Authentication Server (SSL)
+ssm-cssps 2478/udp # SecurSight Authentication Server (SSL)
+ssm-els 2479/tcp # SecurSight Event Logging Server (SSL)
+ssm-els 2479/udp # SecurSight Event Logging Server (SSL)
+powerexchange 2480/tcp # Informatica PowerExchange Listener
+powerexchange 2480/udp # Informatica PowerExchange Listener
+giop 2481/tcp # Oracle GIOP
+giop 2481/udp # Oracle GIOP
+giop-ssl 2482/tcp # Oracle GIOP SSL
+giop-ssl 2482/udp # Oracle GIOP SSL
+ttc 2483/tcp # Oracle TTC
+ttc 2483/udp # Oracle TTC
+ttc-ssl 2484/tcp # Oracle TTC SSL
+ttc-ssl 2484/udp # Oracle TTC SSL
+netobjects1 2485/tcp # Net Objects1
+netobjects1 2485/udp # Net Objects1
+netobjects2 2486/tcp # Net Objects2
+netobjects2 2486/udp # Net Objects2
+pns 2487/tcp # Policy Notice Service
+pns 2487/udp # Policy Notice Service
+moy-corp 2488/tcp # Moy Corporation
+moy-corp 2488/udp # Moy Corporation
+tsilb 2489/tcp # TSILB
+tsilb 2489/udp # TSILB
+qip-qdhcp 2490/tcp # qip_qdhcp
+qip-qdhcp 2490/udp # qip_qdhcp
+conclave-cpp 2491/tcp # Conclave CPP
+conclave-cpp 2491/udp # Conclave CPP
+groove 2492/tcp # GROOVE
+groove 2492/udp # GROOVE
+talarian-mqs 2493/tcp # Talarian MQS
+talarian-mqs 2493/udp # Talarian MQS
+bmc-ar 2494/tcp # BMC AR
+bmc-ar 2494/udp # BMC AR
+fast-rem-serv 2495/tcp # Fast Remote Services
+fast-rem-serv 2495/udp # Fast Remote Services
+dirgis 2496/tcp # DIRGIS
+dirgis 2496/udp # DIRGIS
+quaddb 2497/tcp # Quad DB
+quaddb 2497/udp # Quad DB
+odn-castraq 2498/tcp # ODN-CasTraq
+odn-castraq 2498/udp # ODN-CasTraq
+rtsserv 2500/tcp # Resource Tracking system server
+rtsserv 2500/udp # Resource Tracking system server
+rtsclient 2501/tcp # Resource Tracking system client
+rtsclient 2501/udp # Resource Tracking system client
+kentrox-prot 2502/tcp # Kentrox Protocol
+kentrox-prot 2502/udp # Kentrox Protocol
+nms-dpnss 2503/tcp # NMS-DPNSS
+nms-dpnss 2503/udp # NMS-DPNSS
+wlbs 2504/tcp # WLBS
+wlbs 2504/udp # WLBS
+ppcontrol 2505/tcp # PowerPlay Control
+ppcontrol 2505/udp # PowerPlay Control
+jbroker 2506/tcp # jbroker
+jbroker 2506/udp # jbroker
+spock 2507/tcp # spock
+spock 2507/udp # spock
+jdatastore 2508/tcp # JDataStore
+jdatastore 2508/udp # JDataStore
+fjmpss 2509/tcp # fjmpss
+fjmpss 2509/udp # fjmpss
+fjappmgrbulk 2510/tcp # fjappmgrbulk
+fjappmgrbulk 2510/udp # fjappmgrbulk
+metastorm 2511/tcp # Metastorm
+metastorm 2511/udp # Metastorm
+citrixima 2512/tcp # Citrix IMA
+citrixima 2512/udp # Citrix IMA
+citrixadmin 2513/tcp # Citrix ADMIN
+citrixadmin 2513/udp # Citrix ADMIN
+facsys-ntp 2514/tcp # Facsys NTP
+facsys-ntp 2514/udp # Facsys NTP
+facsys-router 2515/tcp # Facsys Router
+facsys-router 2515/udp # Facsys Router
+maincontrol 2516/tcp # Main Control
+maincontrol 2516/udp # Main Control
+call-sig-trans 2517/tcp # H.323 Annex E call signaling transport
+call-sig-trans 2517/udp # H.323 Annex E call signaling transport
+willy 2518/tcp # Willy
+willy 2518/udp # Willy
+globmsgsvc 2519/tcp # globmsgsvc
+globmsgsvc 2519/udp # globmsgsvc
+pvsw 2520/tcp # Pervasive Listener
+pvsw 2520/udp # Pervasive Listener
+adaptecmgr 2521/tcp # Adaptec Manager
+adaptecmgr 2521/udp # Adaptec Manager
+windb 2522/tcp # WinDb
+windb 2522/udp # WinDb
+qke-llc-v3 2523/tcp # Qke LLC V.3
+qke-llc-v3 2523/udp # Qke LLC V.3
+optiwave-lm 2524/tcp # Optiwave License Management
+optiwave-lm 2524/udp # Optiwave License Management
+ms-v-worlds 2525/tcp # MS V-Worlds
+ms-v-worlds 2525/udp # MS V-Worlds
+ema-sent-lm 2526/tcp # EMA License Manager
+ema-sent-lm 2526/udp # EMA License Manager
+iqserver 2527/tcp # IQ Server
+iqserver 2527/udp # IQ Server
+ncr_ccl 2528/tcp ncr-ccl # NCR CCL
+ncr_ccl 2528/udp ncr-ccl # NCR CCL
+utsftp 2529/tcp # UTS FTP
+utsftp 2529/udp # UTS FTP
+vrcommerce 2530/tcp # VR Commerce
+vrcommerce 2530/udp # VR Commerce
+ito-e-gui 2531/tcp # ITO-E GUI
+ito-e-gui 2531/udp # ITO-E GUI
+ovtopmd 2532/tcp # OVTOPMD
+ovtopmd 2532/udp # OVTOPMD
+snifferserver 2533/tcp # SnifferServer
+snifferserver 2533/udp # SnifferServer
+combox-web-acc 2534/tcp # Combox Web Access
+combox-web-acc 2534/udp # Combox Web Access
+madcap 2535/tcp # MADCAP
+madcap 2535/udp # MADCAP
+btpp2audctr1 2536/tcp # btpp2audctr1
+btpp2audctr1 2536/udp # btpp2audctr1
+upgrade 2537/tcp # Upgrade Protocol
+upgrade 2537/udp # Upgrade Protocol
+vnwk-prapi 2538/tcp # vnwk-prapi
+vnwk-prapi 2538/udp # vnwk-prapi
+vsiadmin 2539/tcp # VSI Admin
+vsiadmin 2539/udp # VSI Admin
+lonworks 2540/tcp # LonWorks
+lonworks 2540/udp # LonWorks
+lonworks2 2541/tcp # LonWorks2
+lonworks2 2541/udp # LonWorks2
+udrawgraph 2542/tcp # uDraw(Graph)
+udrawgraph 2542/udp # uDraw(Graph)
+reftek 2543/tcp # REFTEK
+reftek 2543/udp # REFTEK
+novell-zen 2544/tcp # Management Daemon Refresh
+novell-zen 2544/udp # Management Daemon Refresh
+sis-emt 2545/tcp # sis-emt
+sis-emt 2545/udp # sis-emt
+vytalvaultbrtp 2546/tcp # vytalvaultbrtp
+vytalvaultbrtp 2546/udp # vytalvaultbrtp
+vytalvaultvsmp 2547/tcp # vytalvaultvsmp
+vytalvaultvsmp 2547/udp # vytalvaultvsmp
+vytalvaultpipe 2548/tcp # vytalvaultpipe
+vytalvaultpipe 2548/udp # vytalvaultpipe
+ipass 2549/tcp # IPASS
+ipass 2549/udp # IPASS
+ads 2550/tcp # ADS
+ads 2550/udp # ADS
+isg-uda-server 2551/tcp # ISG UDA Server
+isg-uda-server 2551/udp # ISG UDA Server
+call-logging 2552/tcp # Call Logging
+call-logging 2552/udp # Call Logging
+efidiningport 2553/tcp # efidiningport
+efidiningport 2553/udp # efidiningport
+vcnet-link-v10 2554/tcp # VCnet-Link v10
+vcnet-link-v10 2554/udp # VCnet-Link v10
+compaq-wcp 2555/tcp # Compaq WCP
+compaq-wcp 2555/udp # Compaq WCP
+nicetec-nmsvc 2556/tcp # nicetec-nmsvc
+nicetec-nmsvc 2556/udp # nicetec-nmsvc
+nicetec-mgmt 2557/tcp # nicetec-mgmt
+nicetec-mgmt 2557/udp # nicetec-mgmt
+pclemultimedia 2558/tcp # PCLE Multi Media
+pclemultimedia 2558/udp # PCLE Multi Media
+lstp 2559/tcp # LSTP
+lstp 2559/udp # LSTP
+labrat 2560/tcp # labrat
+labrat 2560/udp # labrat
+mosaixcc 2561/tcp # MosaixCC
+mosaixcc 2561/udp # MosaixCC
+delibo 2562/tcp # Delibo
+delibo 2562/udp # Delibo
+cti-redwood 2563/tcp # CTI Redwood
+cti-redwood 2563/udp # CTI Redwood
+hp-3000-telnet 2564/tcp # HP 3000 NS/VT block mode telnet
+hp-3000-telnet 2564/udp # HP 3000 NS/VT block mode telnet
+coord-svr 2565/tcp # Coordinator Server
+coord-svr 2565/udp # Coordinator Server
+pcs-pcw 2566/tcp # pcs-pcw
+pcs-pcw 2566/udp # pcs-pcw
+clp 2567/tcp # Cisco Line Protocol
+clp 2567/udp # Cisco Line Protocol
+spamtrap 2568/tcp # SPAM TRAP
+spamtrap 2568/udp # SPAM TRAP
+sonuscallsig 2569/tcp # Sonus Call Signal
+sonuscallsig 2569/udp # Sonus Call Signal
+hs-port 2570/tcp # HS Port
+hs-port 2570/udp # HS Port
+cecsvc 2571/tcp # CECSVC
+cecsvc 2571/udp # CECSVC
+ibp 2572/tcp # IBP
+ibp 2572/udp # IBP
+trustestablish 2573/tcp # Trust Establish
+trustestablish 2573/udp # Trust Establish
+blockade-bpsp 2574/tcp # Blockade BPSP
+blockade-bpsp 2574/udp # Blockade BPSP
+hl7 2575/tcp # HL7
+hl7 2575/udp # HL7
+tclprodebugger 2576/tcp # TCL Pro Debugger
+tclprodebugger 2576/udp # TCL Pro Debugger
+scipticslsrvr 2577/tcp # Scriptics Lsrvr
+scipticslsrvr 2577/udp # Scriptics Lsrvr
+rvs-isdn-dcp 2578/tcp # RVS ISDN DCP
+rvs-isdn-dcp 2578/udp # RVS ISDN DCP
+mpfoncl 2579/tcp # mpfoncl
+mpfoncl 2579/udp # mpfoncl
+tributary 2580/tcp # Tributary
+tributary 2580/udp # Tributary
+argis-te 2581/tcp # ARGIS TE
+argis-te 2581/udp # ARGIS TE
+argis-ds 2582/tcp # ARGIS DS
+argis-ds 2582/udp # ARGIS DS
+mon 2583/tcp # MON
+mon 2583/udp # MON
+cyaserv 2584/tcp # cyaserv
+cyaserv 2584/udp # cyaserv
+netx-server 2585/tcp # NETX Server
+netx-server 2585/udp # NETX Server
+netx-agent 2586/tcp # NETX Agent
+netx-agent 2586/udp # NETX Agent
+masc 2587/tcp # MASC
+masc 2587/udp # MASC
+privilege 2588/tcp # Privilege
+privilege 2588/udp # Privilege
+quartus-tcl 2589/tcp # quartus tcl
+quartus-tcl 2589/udp # quartus tcl
+idotdist 2590/tcp # idotdist
+idotdist 2590/udp # idotdist
+maytagshuffle 2591/tcp # Maytag Shuffle
+maytagshuffle 2591/udp # Maytag Shuffle
+netrek 2592/tcp # netrek
+netrek 2592/udp # netrek
+mns-mail 2593/tcp # MNS Mail Notice Service
+mns-mail 2593/udp # MNS Mail Notice Service
+dts 2594/tcp # Data Base Server
+dts 2594/udp # Data Base Server
+worldfusion1 2595/tcp # World Fusion 1
+worldfusion1 2595/udp # World Fusion 1
+worldfusion2 2596/tcp # World Fusion 2
+worldfusion2 2596/udp # World Fusion 2
+homesteadglory 2597/tcp # Homestead Glory
+homesteadglory 2597/udp # Homestead Glory
+citriximaclient 2598/tcp # Citrix MA Client
+citriximaclient 2598/udp # Citrix MA Client
+snapd 2599/tcp # Snap Discovery
+snapd 2599/udp # Snap Discovery
+connection 2607/tcp # Dell Connection
+connection 2607/udp # Dell Connection
+wag-service 2608/tcp # Wag Service
+wag-service 2608/udp # Wag Service
+system-monitor 2609/tcp # System Monitor
+system-monitor 2609/udp # System Monitor
+versa-tek 2610/tcp # VersaTek
+versa-tek 2610/udp # VersaTek
+lionhead 2611/tcp # LIONHEAD
+lionhead 2611/udp # LIONHEAD
+qpasa-agent 2612/tcp # Qpasa Agent
+qpasa-agent 2612/udp # Qpasa Agent
+smntubootstrap 2613/tcp # SMNTUBootstrap
+smntubootstrap 2613/udp # SMNTUBootstrap
+neveroffline 2614/tcp # Never Offline
+neveroffline 2614/udp # Never Offline
+firepower 2615/tcp # firepower
+firepower 2615/udp # firepower
+appswitch-emp 2616/tcp # appswitch-emp
+appswitch-emp 2616/udp # appswitch-emp
+cmadmin 2617/tcp # Clinical Context Managers
+cmadmin 2617/udp # Clinical Context Managers
+priority-e-com 2618/tcp # Priority E-Com
+priority-e-com 2618/udp # Priority E-Com
+bruce 2619/tcp # bruce
+bruce 2619/udp # bruce
+lpsrecommender 2620/tcp # LPSRecommender
+lpsrecommender 2620/udp # LPSRecommender
+miles-apart 2621/tcp # Miles Apart Jukebox Server
+miles-apart 2621/udp # Miles Apart Jukebox Server
+metricadbc 2622/tcp # MetricaDBC
+metricadbc 2622/udp # MetricaDBC
+lmdp 2623/tcp # LMDP
+lmdp 2623/udp # LMDP
+aria 2624/tcp # Aria
+aria 2624/udp # Aria
+blwnkl-port 2625/tcp # Blwnkl Port
+blwnkl-port 2625/udp # Blwnkl Port
+gbjd816 2626/tcp # gbjd816
+gbjd816 2626/udp # gbjd816
+moshebeeri 2627/tcp # Moshe Beeri
+moshebeeri 2627/udp # Moshe Beeri
+sitaraserver 2629/tcp # Sitara Server
+sitaraserver 2629/udp # Sitara Server
+sitaramgmt 2630/tcp # Sitara Management
+sitaramgmt 2630/udp # Sitara Management
+sitaradir 2631/tcp # Sitara Dir
+sitaradir 2631/udp # Sitara Dir
+irdg-post 2632/tcp # IRdg Post
+irdg-post 2632/udp # IRdg Post
+interintelli 2633/tcp # InterIntelli
+interintelli 2633/udp # InterIntelli
+pk-electronics 2634/tcp # PK Electronics
+pk-electronics 2634/udp # PK Electronics
+backburner 2635/tcp # Back Burner
+backburner 2635/udp # Back Burner
+solve 2636/tcp # Solve
+solve 2636/udp # Solve
+imdocsvc 2637/tcp # Import Document Service
+imdocsvc 2637/udp # Import Document Service
+sybaseanywhere 2638/tcp # Sybase Anywhere
+sybaseanywhere 2638/udp # Sybase Anywhere
+aminet 2639/tcp # AMInet
+aminet 2639/udp # AMInet
+ami-control 2640/tcp # Alcorn McBride Inc protocol
+ami-control 2640/udp # Alcorn McBride Inc protocol
+hdl-srv 2641/tcp # HDL Server
+hdl-srv 2641/udp # HDL Server
+tragic 2642/tcp # Tragic
+tragic 2642/udp # Tragic
+gte-samp 2643/tcp # GTE-SAMP
+gte-samp 2643/udp # GTE-SAMP
+travsoft-ipx-t 2644/tcp # Travsoft IPX Tunnel
+travsoft-ipx-t 2644/udp # Travsoft IPX Tunnel
+novell-ipx-cmd 2645/tcp # Novell IPX CMD
+novell-ipx-cmd 2645/udp # Novell IPX CMD
+and-lm 2646/tcp # AND License Manager
+and-lm 2646/udp # AND License Manager
+syncserver 2647/tcp # SyncServer
+syncserver 2647/udp # SyncServer
+upsnotifyprot 2648/tcp # Upsnotifyprot
+upsnotifyprot 2648/udp # Upsnotifyprot
+vpsipport 2649/tcp # VPSIPPORT
+vpsipport 2649/udp # VPSIPPORT
+eristwoguns 2650/tcp # eristwoguns
+eristwoguns 2650/udp # eristwoguns
+ebinsite 2651/tcp # EBInSite
+ebinsite 2651/udp # EBInSite
+interpathpanel 2652/tcp # InterPathPanel
+interpathpanel 2652/udp # InterPathPanel
+sonus 2653/tcp # Sonus
+sonus 2653/udp # Sonus
+corel_vncadmin 2654/tcp corel-vncadmin # Corel VNC Admin
+corel_vncadmin 2654/udp corel-vncadmin # Corel VNC Admin
+unglue 2655/tcp # UNIX Nt Glue
+unglue 2655/udp # UNIX Nt Glue
+kana 2656/tcp # Kana
+kana 2656/udp # Kana
+sns-dispatcher 2657/tcp # SNS Dispatcher
+sns-dispatcher 2657/udp # SNS Dispatcher
+sns-admin 2658/tcp # SNS Admin
+sns-admin 2658/udp # SNS Admin
+sns-query 2659/tcp # SNS Query
+sns-query 2659/udp # SNS Query
+gcmonitor 2660/tcp # GC Monitor
+gcmonitor 2660/udp # GC Monitor
+olhost 2661/tcp # OLHOST
+olhost 2661/udp # OLHOST
+bintec-capi 2662/tcp # BinTec-CAPI
+bintec-capi 2662/udp # BinTec-CAPI
+bintec-tapi 2663/tcp # BinTec-TAPI
+bintec-tapi 2663/udp # BinTec-TAPI
+patrol-mq-gm 2664/tcp # Patrol for MQ GM
+patrol-mq-gm 2664/udp # Patrol for MQ GM
+patrol-mq-nm 2665/tcp # Patrol for MQ NM
+patrol-mq-nm 2665/udp # Patrol for MQ NM
+extensis 2666/tcp # extensis
+extensis 2666/udp # extensis
+alarm-clock-s 2667/tcp # Alarm Clock Server
+alarm-clock-s 2667/udp # Alarm Clock Server
+alarm-clock-c 2668/tcp # Alarm Clock Client
+alarm-clock-c 2668/udp # Alarm Clock Client
+toad 2669/tcp # TOAD
+toad 2669/udp # TOAD
+tve-announce 2670/tcp # TVE Announce
+tve-announce 2670/udp # TVE Announce
+newlixreg 2671/tcp # newlixreg
+newlixreg 2671/udp # newlixreg
+nhserver 2672/tcp # nhserver
+nhserver 2672/udp # nhserver
+firstcall42 2673/tcp # First Call 42
+firstcall42 2673/udp # First Call 42
+ewnn 2674/tcp # ewnn
+ewnn 2674/udp # ewnn
+ttc-etap 2675/tcp # TTC ETAP
+ttc-etap 2675/udp # TTC ETAP
+simslink 2676/tcp # SIMSLink
+simslink 2676/udp # SIMSLink
+gadgetgate1way 2677/tcp # Gadget Gate 1 Way
+gadgetgate1way 2677/udp # Gadget Gate 1 Way
+gadgetgate2way 2678/tcp # Gadget Gate 2 Way
+gadgetgate2way 2678/udp # Gadget Gate 2 Way
+syncserverssl 2679/tcp # Sync Server SSL
+syncserverssl 2679/udp # Sync Server SSL
+pxc-sapxom 2680/tcp # pxc-sapxom
+pxc-sapxom 2680/udp # pxc-sapxom
+mpnjsomb 2681/tcp # mpnjsomb
+mpnjsomb 2681/udp # mpnjsomb
+ncdloadbalance 2683/tcp # NCDLoadBalance
+ncdloadbalance 2683/udp # NCDLoadBalance
+mpnjsosv 2684/tcp # mpnjsosv
+mpnjsosv 2684/udp # mpnjsosv
+mpnjsocl 2685/tcp # mpnjsocl
+mpnjsocl 2685/udp # mpnjsocl
+mpnjsomg 2686/tcp # mpnjsomg
+mpnjsomg 2686/udp # mpnjsomg
+pq-lic-mgmt 2687/tcp # pq-lic-mgmt
+pq-lic-mgmt 2687/udp # pq-lic-mgmt
+md-cg-http 2688/tcp # md-cf-http
+md-cg-http 2688/udp # md-cf-http
+fastlynx 2689/tcp # FastLynx
+fastlynx 2689/udp # FastLynx
+hp-nnm-data 2690/tcp # HP NNM Embedded Database
+hp-nnm-data 2690/udp # HP NNM Embedded Database
+itinternet 2691/tcp # ITInternet ISM Server
+itinternet 2691/udp # ITInternet ISM Server
+admins-lms 2692/tcp # Admins LMS
+admins-lms 2692/udp # Admins LMS
+pwrsevent 2694/tcp # pwrsevent
+pwrsevent 2694/udp # pwrsevent
+vspread 2695/tcp # VSPREAD
+vspread 2695/udp # VSPREAD
+unifyadmin 2696/tcp # Unify Admin
+unifyadmin 2696/udp # Unify Admin
+oce-snmp-trap 2697/tcp # Oce SNMP Trap Port
+oce-snmp-trap 2697/udp # Oce SNMP Trap Port
+mck-ivpip 2698/tcp # MCK-IVPIP
+mck-ivpip 2698/udp # MCK-IVPIP
+csoft-plusclnt 2699/tcp # Csoft Plus Client
+csoft-plusclnt 2699/udp # Csoft Plus Client
+tqdata 2700/tcp # tqdata
+tqdata 2700/udp # tqdata
+sms-rcinfo 2701/tcp # SMS RCINFO
+sms-rcinfo 2701/udp # SMS RCINFO
+sms-xfer 2702/tcp # SMS XFER
+sms-xfer 2702/udp # SMS XFER
+sms-chat 2703/tcp # SMS CHAT
+sms-chat 2703/udp # SMS CHAT
+sms-remctrl 2704/tcp # SMS REMCTRL
+sms-remctrl 2704/udp # SMS REMCTRL
+sds-admin 2705/tcp # SDS Admin
+sds-admin 2705/udp # SDS Admin
+ncdmirroring 2706/tcp # NCD Mirroring
+ncdmirroring 2706/udp # NCD Mirroring
+emcsymapiport 2707/tcp # EMCSYMAPIPORT
+emcsymapiport 2707/udp # EMCSYMAPIPORT
+banyan-net 2708/tcp # Banyan-Net
+banyan-net 2708/udp # Banyan-Net
+supermon 2709/tcp # Supermon
+supermon 2709/udp # Supermon
+sso-service 2710/tcp # SSO Service
+sso-service 2710/udp # SSO Service
+sso-control 2711/tcp # SSO Control
+sso-control 2711/udp # SSO Control
+aocp 2712/tcp # Axapta Object Communication Protocol
+aocp 2712/udp # Axapta Object Communication Protocol
+raventbs 2713/tcp # Raven Trinity Broker Service
+raventbs 2713/udp # Raven Trinity Broker Service
+raventdm 2714/tcp # Raven Trinity Data Mover
+raventdm 2714/udp # Raven Trinity Data Mover
+hpstgmgr2 2715/tcp # HPSTGMGR2
+hpstgmgr2 2715/udp # HPSTGMGR2
+inova-ip-disco 2716/tcp # Inova IP Disco
+inova-ip-disco 2716/udp # Inova IP Disco
+pn-requester 2717/tcp # PN REQUESTER
+pn-requester 2717/udp # PN REQUESTER
+pn-requester2 2718/tcp # PN REQUESTER 2
+pn-requester2 2718/udp # PN REQUESTER 2
+scan-change 2719/tcp # Scan & Change
+scan-change 2719/udp # Scan & Change
+wkars 2720/tcp # wkars
+wkars 2720/udp # wkars
+smart-diagnose 2721/tcp # Smart Diagnose
+smart-diagnose 2721/udp # Smart Diagnose
+proactivesrvr 2722/tcp # Proactive Server
+proactivesrvr 2722/udp # Proactive Server
+watchdog-nt 2723/tcp # WatchDog NT Protocol
+watchdog-nt 2723/udp # WatchDog NT Protocol
+qotps 2724/tcp # qotps
+qotps 2724/udp # qotps
+msolap-ptp2 2725/tcp # MSOLAP PTP2
+msolap-ptp2 2725/udp # MSOLAP PTP2
+tams 2726/tcp # TAMS
+tams 2726/udp # TAMS
+mgcp-callagent 2727/tcp # Media Gateway Control Protocol Call Agent
+mgcp-callagent 2727/udp # Media Gateway Control Protocol Call Agent
+sqdr 2728/tcp # SQDR
+sqdr 2728/udp # SQDR
+tcim-control 2729/tcp # TCIM Control
+tcim-control 2729/udp # TCIM Control
+nec-raidplus 2730/tcp # NEC RaidPlus
+nec-raidplus 2730/udp # NEC RaidPlus
+fyre-messanger 2731/tcp # Fyre Messanger
+fyre-messanger 2731/udp # Fyre Messagner
+g5m 2732/tcp # G5M
+g5m 2732/udp # G5M
+signet-ctf 2733/tcp # Signet CTF
+signet-ctf 2733/udp # Signet CTF
+ccs-software 2734/tcp # CCS Software
+ccs-software 2734/udp # CCS Software
+netiq-mc 2735/tcp # NetIQ Monitor Console
+netiq-mc 2735/udp # NetIQ Monitor Console
+radwiz-nms-srv 2736/tcp # RADWIZ NMS SRV
+radwiz-nms-srv 2736/udp # RADWIZ NMS SRV
+srp-feedback 2737/tcp # SRP Feedback
+srp-feedback 2737/udp # SRP Feedback
+ndl-tcp-ois-gw 2738/tcp # NDL TCP-OSI Gateway
+ndl-tcp-ois-gw 2738/udp # NDL TCP-OSI Gateway
+tn-timing 2739/tcp # TN Timing
+tn-timing 2739/udp # TN Timing
+alarm 2740/tcp # Alarm
+alarm 2740/udp # Alarm
+tsb 2741/tcp # TSB
+tsb 2741/udp # TSB
+tsb2 2742/tcp # TSB2
+tsb2 2742/udp # TSB2
+murx 2743/tcp # murx
+murx 2743/udp # murx
+honyaku 2744/tcp # honyaku
+honyaku 2744/udp # honyaku
+urbisnet 2745/tcp # URBISNET
+urbisnet 2745/udp # URBISNET
+cpudpencap 2746/tcp # CPUDPENCAP
+cpudpencap 2746/udp # CPUDPENCAP
+fjippol-swrly 2747/tcp #
+fjippol-swrly 2747/udp #
+fjippol-polsvr 2748/tcp #
+fjippol-polsvr 2748/udp #
+fjippol-cnsl 2749/tcp #
+fjippol-cnsl 2749/udp #
+fjippol-port1 2750/tcp #
+fjippol-port1 2750/udp #
+fjippol-port2 2751/tcp #
+fjippol-port2 2751/udp #
+rsisysaccess 2752/tcp # RSISYS ACCESS
+rsisysaccess 2752/udp # RSISYS ACCESS
+de-spot 2753/tcp # de-spot
+de-spot 2753/udp # de-spot
+apollo-cc 2754/tcp # APOLLO CC
+apollo-cc 2754/udp # APOLLO CC
+expresspay 2755/tcp # Express Pay
+expresspay 2755/udp # Express Pay
+simplement-tie 2756/tcp # simplement-tie
+simplement-tie 2756/udp # simplement-tie
+cnrp 2757/tcp # CNRP
+cnrp 2757/udp # CNRP
+apollo-status 2758/tcp # APOLLO Status
+apollo-status 2758/udp # APOLLO Status
+apollo-gms 2759/tcp # APOLLO GMS
+apollo-gms 2759/udp # APOLLO GMS
+sabams 2760/tcp # Saba MS
+sabams 2760/udp # Saba MS
+dicom-iscl 2761/tcp # DICOM ISCL
+dicom-iscl 2761/udp # DICOM ISCL
+dicom-tls 2762/tcp # DICOM TLS
+dicom-tls 2762/udp # DICOM TLS
+desktop-dna 2763/tcp # Desktop DNA
+desktop-dna 2763/udp # Desktop DNA
+data-insurance 2764/tcp # Data Insurance
+data-insurance 2764/udp # Data Insurance
+qip-audup 2765/tcp # qip-audup
+qip-audup 2765/udp # qip-audup
+compaq-scp 2766/tcp # Compaq SCP
+compaq-scp 2766/udp # Compaq SCP
+uadtc 2767/tcp # UADTC
+uadtc 2767/udp # UADTC
+uacs 2768/tcp # UACS
+uacs 2768/udp # UACS
+exce 2769/tcp # eXcE
+exce 2769/udp # eXcE
+veronica 2770/tcp # Veronica
+veronica 2770/udp # Veronica
+vergencecm 2771/tcp # Vergence CM
+vergencecm 2771/udp # Vergence CM
+auris 2772/tcp # auris
+auris 2772/udp # auris
+rbakcup1 2773/tcp # RBackup Remote Backup
+rbakcup1 2773/udp # RBackup Remote Backup
+rbakcup2 2774/tcp # RBackup Remote Backup
+rbakcup2 2774/udp # RBackup Remote Backup
+smpp 2775/tcp # SMPP
+smpp 2775/udp # SMPP
+ridgeway1 2776/tcp # Ridgeway Systems & Software
+ridgeway1 2776/udp # Ridgeway Systems & Software
+ridgeway2 2777/tcp # Ridgeway Systems & Software
+ridgeway2 2777/udp # Ridgeway Systems & Software
+gwen-sonya 2778/tcp # Gwen-Sonya
+gwen-sonya 2778/udp # Gwen-Sonya
+lbc-sync 2779/tcp # LBC Sync
+lbc-sync 2779/udp # LBC Sync
+lbc-control 2780/tcp # LBC Control
+lbc-control 2780/udp # LBC Control
+whosells 2781/tcp # whosells
+whosells 2781/udp # whosells
+everydayrc 2782/tcp # everydayrc
+everydayrc 2782/udp # everydayrc
+aises 2783/tcp # AISES
+aises 2783/udp # AISES
+www-dev 2784/tcp # world wide web - development
+www-dev 2784/udp # world wide web - development
+aic-np 2785/tcp # aic-np
+aic-np 2785/udp # aic-np
+aic-oncrpc 2786/tcp # aic-oncrpc - Destiny MCD database
+aic-oncrpc 2786/udp # aic-oncrpc - Destiny MCD database
+piccolo 2787/tcp # piccolo - Cornerstone Software
+piccolo 2787/udp # piccolo - Cornerstone Software
+fryeserv 2788/tcp # NetWare Loadable Module - Seagate Software
+fryeserv 2788/udp # NetWare Loadable Module - Seagate Software
+media-agent 2789/tcp # Media Agent
+media-agent 2789/udp # Media Agent
+plgproxy 2790/tcp # PLG Proxy
+plgproxy 2790/udp # PLG Proxy
+mtport-regist 2791/tcp # MT Port Registrator
+mtport-regist 2791/udp # MT Port Registrator
+f5-globalsite 2792/tcp # f5-globalsite
+f5-globalsite 2792/udp # f5-globalsite
+initlsmsad 2793/tcp # initlsmsad
+initlsmsad 2793/udp # initlsmsad
+livestats 2795/tcp # LiveStats
+livestats 2795/udp # LiveStats
+ac-tech 2796/tcp # ac-tech
+ac-tech 2796/udp # ac-tech
+esp-encap 2797/tcp # esp-encap
+esp-encap 2797/udp # esp-encap
+tmesis-upshot 2798/tcp # TMESIS-UPShot
+tmesis-upshot 2798/udp # TMESIS-UPShot
+icon-discover 2799/tcp # ICON Discover
+icon-discover 2799/udp # ICON Discover
+acc-raid 2800/tcp # ACC RAID
+acc-raid 2800/udp # ACC RAID
+igcp 2801/tcp # IGCP
+igcp 2801/udp # IGCP
+veritas-tcp1 2802/tcp # Veritas TCP1
+veritas-udp1 2802/udp # Veritas UDP1
+btprjctrl 2803/tcp # btprjctrl
+btprjctrl 2803/udp # btprjctrl
+dvr-esm 2804/tcp # March Networks Digital Video Recorders and Enterprise Service Manager products
+dvr-esm 2804/udp # March Networks Digital Video Recorders and Enterprise Service Manager products
+wta-wsp-s 2805/tcp # WTA WSP-S
+wta-wsp-s 2805/udp # WTA WSP-S
+cspuni 2806/tcp # cspuni
+cspuni 2806/udp # cspuni
+cspmulti 2807/tcp # cspmulti
+cspmulti 2807/udp # cspmulti
+j-lan-p 2808/tcp # J-LAN-P
+j-lan-p 2808/udp # J-LAN-P
+corbaloc 2809/udp # CORBA LOC
+netsteward 2810/tcp # Active Net Steward
+netsteward 2810/udp # Active Net Steward
+gsiftp 2811/tcp # GSI FTP
+gsiftp 2811/udp # GSI FTP
+atmtcp 2812/tcp # atmtcp
+atmtcp 2812/udp # atmtcp
+llm-pass 2813/tcp # llm-pass
+llm-pass 2813/udp # llm-pass
+llm-csv 2814/tcp # llm-csv
+llm-csv 2814/udp # llm-csv
+lbc-measure 2815/tcp # LBC Measurement
+lbc-measure 2815/udp # LBC Measurement
+lbc-watchdog 2816/tcp # LBC Watchdog
+lbc-watchdog 2816/udp # LBC Watchdog
+rmlnk 2818/tcp # rmlnk
+rmlnk 2818/udp # rmlnk
+fc-faultnotify 2819/tcp # FC Fault Notification
+fc-faultnotify 2819/udp # FC Fault Notification
+univision 2820/tcp # UniVision
+univision 2820/udp # UniVision
+vrts-at-port 2821/tcp # VERITAS Authentication Service
+vrts-at-port 2821/udp # VERITAS Authentication Service
+ka0wuc 2822/tcp # ka0wuc
+ka0wuc 2822/udp # ka0wuc
+cqg-netlan 2823/tcp # CQG Net/LAN
+cqg-netlan 2823/udp # CQG Net/LAN
+cqg-netlan-1 2824/tcp # CQG Net/LAN 1
+cqg-netlan-1 2824/udp # CQG Net/Lan 1
+slc-systemlog 2826/tcp # slc systemlog
+slc-systemlog 2826/udp # slc systemlog
+slc-ctrlrloops 2827/tcp # slc ctrlrloops
+slc-ctrlrloops 2827/udp # slc ctrlrloops
+itm-lm 2828/tcp # ITM License Manager
+itm-lm 2828/udp # ITM License Manager
+silkp1 2829/tcp # silkp1
+silkp1 2829/udp # silkp1
+silkp2 2830/tcp # silkp2
+silkp2 2830/udp # silkp2
+silkp3 2831/tcp # silkp3
+silkp3 2831/udp # silkp3
+silkp4 2832/tcp # silkp4
+silkp4 2832/udp # silkp4
+glishd 2833/tcp # glishd
+glishd 2833/udp # glishd
+evtp 2834/tcp # EVTP
+evtp 2834/udp # EVTP
+evtp-data 2835/tcp # EVTP-DATA
+evtp-data 2835/udp # EVTP-DATA
+catalyst 2836/tcp # catalyst
+catalyst 2836/udp # catalyst
+repliweb 2837/tcp # Repliweb
+repliweb 2837/udp # Repliweb
+starbot 2838/tcp # Starbot
+starbot 2838/udp # Starbot
+l3-exprt 2840/tcp # l3-exprt
+l3-exprt 2840/udp # l3-exprt
+l3-ranger 2841/tcp # l3-ranger
+l3-ranger 2841/udp # l3-ranger
+l3-hawk 2842/tcp # l3-hawk
+l3-hawk 2842/udp # l3-hawk
+pdnet 2843/tcp # PDnet
+pdnet 2843/udp # PDnet
+bpcp-poll 2844/tcp # BPCP POLL
+bpcp-poll 2844/udp # BPCP POLL
+bpcp-trap 2845/tcp # BPCP TRAP
+bpcp-trap 2845/udp # BPCP TRAP
+aimpp-hello 2846/tcp # AIMPP Hello
+aimpp-hello 2846/udp # AIMPP Hello
+aimpp-port-req 2847/tcp # AIMPP Port Req
+aimpp-port-req 2847/udp # AIMPP Port Req
+amt-blc-port 2848/tcp # AMT-BLC-PORT
+amt-blc-port 2848/udp # AMT-BLC-PORT
+metaconsole 2850/tcp # MetaConsole
+metaconsole 2850/udp # MetaConsole
+webemshttp 2851/tcp # webemshttp
+webemshttp 2851/udp # webemshttp
+bears-01 2852/tcp # bears-01
+bears-01 2852/udp # bears-01
+ispipes 2853/tcp # ISPipes
+ispipes 2853/udp # ISPipes
+infomover 2854/tcp # InfoMover
+infomover 2854/udp # InfoMover
+msrp 2855/tcp # MSRP over TCP
+cesdinv 2856/tcp # cesdinv
+cesdinv 2856/udp # cesdinv
+simctlp 2857/tcp # SimCtIP
+simctlp 2857/udp # SimCtIP
+ecnp 2858/tcp # ECNP
+ecnp 2858/udp # ECNP
+activememory 2859/tcp # Active Memory
+activememory 2859/udp # Active Memory
+dialpad-voice1 2860/tcp # Dialpad Voice 1
+dialpad-voice1 2860/udp # Dialpad Voice 1
+dialpad-voice2 2861/tcp # Dialpad Voice 2
+dialpad-voice2 2861/udp # Dialpad Voice 2
+ttg-protocol 2862/tcp # TTG Protocol
+ttg-protocol 2862/udp # TTG Protocol
+sonardata 2863/tcp # Sonar Data
+sonardata 2863/udp # Sonar Data
+astromed-main 2864/tcp # main 5001 cmd
+astromed-main 2864/udp # main 5001 cmd
+pit-vpn 2865/tcp # pit-vpn
+pit-vpn 2865/udp # pit-vpn
+iwlistener 2866/tcp # iwlistener
+iwlistener 2866/udp # iwlistener
+esps-portal 2867/tcp # esps-portal
+esps-portal 2867/udp # esps-portal
+npep-messaging 2868/tcp # NPEP Messaging
+npep-messaging 2868/udp # NPEP Messaging
+icslap 2869/tcp # ICSLAP
+icslap 2869/udp # ICSLAP
+daishi 2870/tcp # daishi
+daishi 2870/udp # daishi
+msi-selectplay 2871/tcp # MSI Select Play
+msi-selectplay 2871/udp # MSI Select Play
+radix 2872/tcp # RADIX
+radix 2872/udp # RADIX
+dxmessagebase1 2874/tcp # DX Message Base Transport Protocol
+dxmessagebase1 2874/udp # DX Message Base Transport Protocol
+dxmessagebase2 2875/tcp # DX Message Base Transport Protocol
+dxmessagebase2 2875/udp # DX Message Base Transport Protocol
+sps-tunnel 2876/tcp # SPS Tunnel
+sps-tunnel 2876/udp # SPS Tunnel
+bluelance 2877/tcp # BLUELANCE
+bluelance 2877/udp # BLUELANCE
+aap 2878/tcp # AAP
+aap 2878/udp # AAP
+ucentric-ds 2879/tcp # ucentric-ds
+ucentric-ds 2879/udp # ucentric-ds
+synapse 2880/tcp # Synapse Transport
+synapse 2880/udp # Synapse Transport
+ndsp 2881/tcp # NDSP
+ndsp 2881/udp # NDSP
+ndtp 2882/tcp # NDTP
+ndtp 2882/udp # NDTP
+ndnp 2883/tcp # NDNP
+ndnp 2883/udp # NDNP
+flashmsg 2884/tcp # Flash Msg
+flashmsg 2884/udp # Flash Msg
+topflow 2885/tcp # TopFlow
+topflow 2885/udp # TopFlow
+responselogic 2886/tcp # RESPONSELOGIC
+responselogic 2886/udp # RESPONSELOGIC
+aironetddp 2887/tcp # aironet
+aironetddp 2887/udp # aironet
+spcsdlobby 2888/tcp # SPCSDLOBBY
+spcsdlobby 2888/udp # SPCSDLOBBY
+rsom 2889/tcp # RSOM
+rsom 2889/udp # RSOM
+cspclmulti 2890/tcp # CSPCLMULTI
+cspclmulti 2890/udp # CSPCLMULTI
+cinegrfx-elmd 2891/tcp # CINEGRFX-ELMD License Manager
+cinegrfx-elmd 2891/udp # CINEGRFX-ELMD License Manager
+snifferdata 2892/tcp # SNIFFERDATA
+snifferdata 2892/udp # SNIFFERDATA
+vseconnector 2893/tcp # VSECONNECTOR
+vseconnector 2893/udp # VSECONNECTOR
+abacus-remote 2894/tcp # ABACUS-REMOTE
+abacus-remote 2894/udp # ABACUS-REMOTE
+natuslink 2895/tcp # NATUS LINK
+natuslink 2895/udp # NATUS LINK
+ecovisiong6-1 2896/tcp # ECOVISIONG6-1
+ecovisiong6-1 2896/udp # ECOVISIONG6-1
+citrix-rtmp 2897/tcp # Citrix RTMP
+citrix-rtmp 2897/udp # Citrix RTMP
+appliance-cfg 2898/tcp # APPLIANCE-CFG
+appliance-cfg 2898/udp # APPLIANCE-CFG
+powergemplus 2899/tcp # POWERGEMPLUS
+powergemplus 2899/udp # POWERGEMPLUS
+quicksuite 2900/tcp # QUICKSUITE
+quicksuite 2900/udp # QUICKSUITE
+allstorcns 2901/tcp # ALLSTORCNS
+allstorcns 2901/udp # ALLSTORCNS
+netaspi 2902/tcp # NET ASPI
+netaspi 2902/udp # NET ASPI
+suitcase 2903/tcp # SUITCASE
+suitcase 2903/udp # SUITCASE
+m2ua 2904/tcp # M2UA
+m2ua 2904/udp # M2UA
+m2ua 2904/sctp # M2UA
+m3ua 2905/tcp # M3UA
+m3ua 2905/sctp # M3UA
+caller9 2906/tcp # CALLER9
+caller9 2906/udp # CALLER9
+webmethods-b2b 2907/tcp # WEBMETHODS B2B
+webmethods-b2b 2907/udp # WEBMETHODS B2B
+mao 2908/tcp # mao
+mao 2908/udp # mao
+funk-dialout 2909/tcp # Funk Dialout
+funk-dialout 2909/udp # Funk Dialout
+tdaccess 2910/tcp # TDAccess
+tdaccess 2910/udp # TDAccess
+blockade 2911/tcp # Blockade
+blockade 2911/udp # Blockade
+epicon 2912/tcp # Epicon
+epicon 2912/udp # Epicon
+boosterware 2913/tcp # Booster Ware
+boosterware 2913/udp # Booster Ware
+gamelobby 2914/tcp # Game Lobby
+gamelobby 2914/udp # Game Lobby
+tksocket 2915/tcp # TK Socket
+tksocket 2915/udp # TK Socket
+elvin_server 2916/tcp elvin-server # Elvin Server
+elvin_server 2916/udp elvin-server # Elvin Server
+elvin_client 2917/tcp elvin-client # Elvin Client
+elvin_client 2917/udp elvin-client # Elvin Client
+kastenchasepad 2918/tcp # Kasten Chase Pad
+kastenchasepad 2918/udp # Kasten Chase Pad
+roboer 2919/tcp # roboER
+roboer 2919/udp # roboER
+roboeda 2920/tcp # roboEDA
+roboeda 2920/udp # roboEDA
+cesdcdman 2921/tcp # CESD Contents Delivery Management
+cesdcdman 2921/udp # CESD Contents Delivery Management
+cesdcdtrn 2922/tcp # CESD Contents Delivery Data Transfer
+cesdcdtrn 2922/udp # CESD Contents Delivery Data Transfer
+wta-wsp-wtp-s 2923/tcp # WTA-WSP-WTP-S
+wta-wsp-wtp-s 2923/udp # WTA-WSP-WTP-S
+precise-vip 2924/tcp # PRECISE-VIP
+precise-vip 2924/udp # PRECISE-VIP
+mobile-file-dl 2926/tcp # MOBILE-FILE-DL
+mobile-file-dl 2926/udp # MOBILE-FILE-DL
+unimobilectrl 2927/tcp # UNIMOBILECTRL
+unimobilectrl 2927/udp # UNIMOBILECTRL
+redstone-cpss 2928/tcp # REDSTONE-CPSS
+redstone-cpss 2928/udp # REDSTONE-CPSS
+amx-webadmin 2929/tcp # AMX-WEBADMIN
+amx-webadmin 2929/udp # AMX-WEBADMIN
+amx-weblinx 2930/tcp # AMX-WEBLINX
+amx-weblinx 2930/udp # AMX-WEBLINX
+circle-x 2931/tcp # Circle-X
+circle-x 2931/udp # Circle-X
+incp 2932/tcp # INCP
+incp 2932/udp # INCP
+4-tieropmgw 2933/tcp # 4-TIER OPM GW
+4-tieropmgw 2933/udp # 4-TIER OPM GW
+4-tieropmcli 2934/tcp # 4-TIER OPM CLI
+4-tieropmcli 2934/udp # 4-TIER OPM CLI
+qtp 2935/tcp # QTP
+qtp 2935/udp # QTP
+otpatch 2936/tcp # OTPatch
+otpatch 2936/udp # OTPatch
+pnaconsult-lm 2937/tcp # PNACONSULT-LM
+pnaconsult-lm 2937/udp # PNACONSULT-LM
+sm-pas-1 2938/tcp # SM-PAS-1
+sm-pas-1 2938/udp # SM-PAS-1
+sm-pas-2 2939/tcp # SM-PAS-2
+sm-pas-2 2939/udp # SM-PAS-2
+sm-pas-3 2940/tcp # SM-PAS-3
+sm-pas-3 2940/udp # SM-PAS-3
+sm-pas-4 2941/tcp # SM-PAS-4
+sm-pas-4 2941/udp # SM-PAS-4
+sm-pas-5 2942/tcp # SM-PAS-5
+sm-pas-5 2942/udp # SM-PAS-5
+ttnrepository 2943/tcp # TTNRepository
+ttnrepository 2943/udp # TTNRepository
+megaco-h248 2944/tcp # Megaco H-248
+megaco-h248 2944/udp # Megaco H-248
+megaco-h248 2944/sctp # Megaco-H.248 text
+h248-binary 2945/tcp # H248 Binary
+h248-binary 2945/udp # H248 Binary
+h248-binary 2945/sctp # Megaco/H.248 binary
+fjsvmpor 2946/tcp # FJSVmpor
+fjsvmpor 2946/udp # FJSVmpor
+gpsd 2947/tcp # GPSD
+gpsd 2947/udp # GPSD
+wap-push 2948/tcp # WAP PUSH
+wap-push 2948/udp # WAP PUSH
+wap-pushsecure 2949/tcp # WAP PUSH SECURE
+wap-pushsecure 2949/udp # WAP PUSH SECURE
+esip 2950/tcp # ESIP
+esip 2950/udp # ESIP
+ottp 2951/tcp # OTTP
+ottp 2951/udp # OTTP
+mpfwsas 2952/tcp # MPFWSAS
+mpfwsas 2952/udp # MPFWSAS
+ovalarmsrv 2953/tcp # OVALARMSRV
+ovalarmsrv 2953/udp # OVALARMSRV
+ovalarmsrv-cmd 2954/tcp # OVALARMSRV-CMD
+ovalarmsrv-cmd 2954/udp # OVALARMSRV-CMD
+csnotify 2955/tcp # CSNOTIFY
+csnotify 2955/udp # CSNOTIFY
+ovrimosdbman 2956/tcp # OVRIMOSDBMAN
+ovrimosdbman 2956/udp # OVRIMOSDBMAN
+jmact5 2957/tcp # JAMCT5
+jmact5 2957/udp # JAMCT5
+jmact6 2958/tcp # JAMCT6
+jmact6 2958/udp # JAMCT6
+rmopagt 2959/tcp # RMOPAGT
+rmopagt 2959/udp # RMOPAGT
+dfoxserver 2960/tcp # DFOXSERVER
+dfoxserver 2960/udp # DFOXSERVER
+boldsoft-lm 2961/tcp # BOLDSOFT-LM
+boldsoft-lm 2961/udp # BOLDSOFT-LM
+iph-policy-cli 2962/tcp # IPH-POLICY-CLI
+iph-policy-cli 2962/udp # IPH-POLICY-CLI
+iph-policy-adm 2963/tcp # IPH-POLICY-ADM
+iph-policy-adm 2963/udp # IPH-POLICY-ADM
+bullant-srap 2964/tcp # BULLANT SRAP
+bullant-srap 2964/udp # BULLANT SRAP
+bullant-rap 2965/tcp # BULLANT RAP
+bullant-rap 2965/udp # BULLANT RAP
+idp-infotrieve 2966/tcp # IDP-INFOTRIEVE
+idp-infotrieve 2966/udp # IDP-INFOTRIEVE
+ssc-agent 2967/tcp # SSC-AGENT
+ssc-agent 2967/udp # SSC-AGENT
+enpp 2968/tcp # ENPP
+enpp 2968/udp # ENPP
+essp 2969/tcp # ESSP
+essp 2969/udp # ESSP
+index-net 2970/tcp # INDEX-NET
+index-net 2970/udp # INDEX-NET
+netclip 2971/tcp # NetClip clipboard daemon
+netclip 2971/udp # NetClip clipboard daemon
+pmsm-webrctl 2972/tcp # PMSM Webrctl
+pmsm-webrctl 2972/udp # PMSM Webrctl
+svnetworks 2973/tcp # SV Networks
+svnetworks 2973/udp # SV Networks
+signal 2974/tcp # Signal
+signal 2974/udp # Signal
+fjmpcm 2975/tcp # Fujitsu Configuration Management Service
+fjmpcm 2975/udp # Fujitsu Configuration Management Service
+cns-srv-port 2976/tcp # CNS Server Port
+cns-srv-port 2976/udp # CNS Server Port
+ttc-etap-ns 2977/tcp # TTCs Enterprise Test Access Protocol - NS
+ttc-etap-ns 2977/udp # TTCs Enterprise Test Access Protocol - NS
+ttc-etap-ds 2978/tcp # TTCs Enterprise Test Access Protocol - DS
+ttc-etap-ds 2978/udp # TTCs Enterprise Test Access Protocol - DS
+h263-video 2979/tcp # H.263 Video Streaming
+h263-video 2979/udp # H.263 Video Streaming
+wimd 2980/tcp # Instant Messaging Service
+wimd 2980/udp # Instant Messaging Service
+mylxamport 2981/tcp # MYLXAMPORT
+mylxamport 2981/udp # MYLXAMPORT
+iwb-whiteboard 2982/tcp # IWB-WHITEBOARD
+iwb-whiteboard 2982/udp # IWB-WHITEBOARD
+netplan 2983/tcp # NETPLAN
+netplan 2983/udp # NETPLAN
+hpidsadmin 2984/tcp # HPIDSADMIN
+hpidsadmin 2984/udp # HPIDSADMIN
+hpidsagent 2985/tcp # HPIDSAGENT
+hpidsagent 2985/udp # HPIDSAGENT
+stonefalls 2986/tcp # STONEFALLS
+stonefalls 2986/udp # STONEFALLS
+identify 2987/tcp # identify
+identify 2987/udp # identify
+zarkov 2989/tcp # ZARKOV Intelligent Agent Communication
+zarkov 2989/udp # ZARKOV Intelligent Agent Communication
+boscap 2990/tcp # BOSCAP
+boscap 2990/udp # BOSCAP
+wkstn-mon 2991/tcp # WKSTN-MON
+wkstn-mon 2991/udp # WKSTN-MON
+avenyo 2992/tcp # Avenyo Server
+avenyo 2992/udp # Avenyo Server
+veritas-vis1 2993/tcp # VERITAS VIS1
+veritas-vis1 2993/udp # VERITAS VIS1
+veritas-vis2 2994/tcp # VERITAS VIS2
+veritas-vis2 2994/udp # VERITAS VIS2
+idrs 2995/tcp # IDRS
+idrs 2995/udp # IDRS
+vsixml 2996/tcp # vsixml
+vsixml 2996/udp # vsixml
+rebol 2997/tcp # REBOL
+rebol 2997/udp # REBOL
+realsecure 2998/tcp # Real Secure
+realsecure 2998/udp # Real Secure
+remoteware-un 2999/tcp # RemoteWare Unassigned
+remoteware-un 2999/udp # RemoteWare Unassigned
+hbci 3000/tcp ceph # HBCI or CEPH monitor
+hbci 3000/udp # HBCI
+#remoteware-cl 3000/tcp # RemoteWare Client
+#remoteware-cl 3000/udp # RemoteWare Client
+origo-native 3001/tcp # OrigoDB Server Native
+exlm-agent 3002/tcp # EXLM Agent
+exlm-agent 3002/udp # EXLM Agent
+#remoteware-srv 3002/tcp # RemoteWare Server
+#remoteware-srv 3002/udp # RemoteWare Server
+cgms 3003/tcp # CGMS
+cgms 3003/udp # CGMS
+csoftragent 3004/tcp # Csoft Agent
+csoftragent 3004/udp # Csoft Agent
+geniuslm 3005/tcp # Genius License Manager
+geniuslm 3005/udp # Genius License Manager
+ii-admin 3006/tcp # Instant Internet Admin
+ii-admin 3006/udp # Instant Internet Admin
+lotusmtap 3007/tcp # Lotus Mail Tracking Agent Protocol
+lotusmtap 3007/udp # Lotus Mail Tracking Agent Protocol
+midnight-tech 3008/tcp # Midnight Technologies
+midnight-tech 3008/udp # Midnight Technologies
+pxc-ntfy 3009/tcp # PXC-NTFY
+pxc-ntfy 3009/udp # PXC-NTFY
+gw 3010/tcp # Telerate Workstation
+ping-pong 3010/udp # Telerate Workstation
+trusted-web 3011/tcp # Trusted Web
+trusted-web 3011/udp # Trusted Web
+twsdss 3012/tcp # Trusted Web Client
+twsdss 3012/udp # Trusted Web Client
+gilatskysurfer 3013/tcp # Gilat Sky Surfer
+gilatskysurfer 3013/udp # Gilat Sky Surfer
+broker_service 3014/tcp broker-service # Broker Service
+broker_service 3014/udp broker-service # Broker Service
+nati-dstp 3015/tcp # NATI DSTP
+nati-dstp 3015/udp # NATI DSTP
+notify_srvr 3016/tcp notify-srvr # Notify Server
+notify_srvr 3016/udp notify-srvr # Notify Server
+event_listener 3017/tcp event-listener # Event Listener
+event_listener 3017/udp event-listener # Event Listener
+srvc_registry 3018/tcp srvc-registry # Service Registry
+srvc_registry 3018/udp srvc-registry # Service Registry
+resource_mgr 3019/tcp resource-mgr # Resource Manager
+resource_mgr 3019/udp resource-mgr # Resource Manager
+cifs 3020/tcp # CIFS
+cifs 3020/udp # CIFS
+agriserver 3021/tcp # AGRI Server
+agriserver 3021/udp # AGRI Server
+csregagent 3022/tcp # CSREGAGENT
+csregagent 3022/udp # CSREGAGENT
+magicnotes 3023/tcp # magicnotes
+magicnotes 3023/udp # magicnotes
+nds_sso 3024/tcp nds-sso # NDS_SSO
+nds_sso 3024/udp nds-sso # NDS_SSO
+arepa-raft 3025/tcp # Arepa Raft
+arepa-raft 3025/udp # Arepa Raft
+agri-gateway 3026/tcp # AGRI Gateway
+agri-gateway 3026/udp # AGRI Gateway
+LiebDevMgmt_C 3027/tcp LiebDevMgmt-C # LiebDevMgmt_C
+LiebDevMgmt_C 3027/udp LiebDevMgmt-C # LiebDevMgmt_C
+LiebDevMgmt_DM 3028/tcp LiebDevMgmt-DM # LiebDevMgmt_DM
+LiebDevMgmt_DM 3028/udp LiebDevMgmt-DM # LiebDevMgmt_DM
+LiebDevMgmt_A 3029/tcp LiebDevMgmt-A # LiebDevMgmt_A
+LiebDevMgmt_A 3029/udp LiebDevMgmt-A # LiebDevMgmt_A
+arepa-cas 3030/tcp # Arepa Cas
+arepa-cas 3030/udp # Arepa Cas
+eppc 3031/tcp # Remote AppleEvents/PPC Toolbox
+eppc 3031/udp # Remote AppleEvents/PPC Toolbox
+redwood-chat 3032/tcp # Redwood Chat
+redwood-chat 3032/udp # Redwood Chat
+pdb 3033/tcp # PDB
+pdb 3033/udp # PDB
+osmosis-aeea 3034/tcp # Osmosis / Helix (R) AEEA Port
+osmosis-aeea 3034/udp # Osmosis / Helix (R) AEEA Port
+fjsv-gssagt 3035/tcp # FJSV gssagt
+fjsv-gssagt 3035/udp # FJSV gssagt
+hagel-dump 3036/tcp # Hagel DUMP
+hagel-dump 3036/udp # Hagel DUMP
+hp-san-mgmt 3037/tcp # HP SAN Mgmt
+hp-san-mgmt 3037/udp # HP SAN Mgmt
+santak-ups 3038/tcp # Santak UPS
+santak-ups 3038/udp # Santak UPS
+cogitate 3039/tcp # Cogitate, Inc.
+cogitate 3039/udp # Cogitate, Inc.
+tomato-springs 3040/tcp # Tomato Springs
+tomato-springs 3040/udp # Tomato Springs
+di-traceware 3041/tcp # di-traceware
+di-traceware 3041/udp # di-traceware
+journee 3042/tcp # journee
+journee 3042/udp # journee
+brp 3043/tcp # Broadcast Routing Protocol
+brp 3043/udp # Broadcast Routing Protocol
+responsenet 3045/tcp # ResponseNet
+responsenet 3045/udp # ResponseNet
+di-ase 3046/tcp # di-ase
+di-ase 3046/udp # di-ase
+hlserver 3047/tcp # Fast Security HL Server
+hlserver 3047/udp # Fast Security HL Server
+pctrader 3048/tcp # Sierra Net PC Trader
+pctrader 3048/udp # Sierra Net PC Trader
+nsws 3049/tcp # NSWS
+nsws 3049/udp # NSWS
+gds_db 3050/tcp gds-db # gds_db
+gds_db 3050/udp gds-db # gds_db
+galaxy-server 3051/tcp # Galaxy Server
+galaxy-server 3051/udp # Galaxy Server
+apc-3052 3052/tcp # APC 3052
+apc-3052 3052/udp # APC 3052
+dsom-server 3053/tcp # dsom-server
+dsom-server 3053/udp # dsom-server
+amt-cnf-prot 3054/tcp # AMT CNF PROT
+amt-cnf-prot 3054/udp # AMT CNF PROT
+policyserver 3055/tcp # Policy Server
+policyserver 3055/udp # Policy Server
+cdl-server 3056/tcp # CDL Server
+cdl-server 3056/udp # CDL Server
+goahead-fldup 3057/tcp # GoAhead FldUp
+goahead-fldup 3057/udp # GoAhead FldUp
+videobeans 3058/tcp # videobeans
+videobeans 3058/udp # videobeans
+qsoft 3059/tcp # qsoft
+qsoft 3059/udp # qsoft
+interserver 3060/tcp # interserver
+interserver 3060/udp # interserver
+cautcpd 3061/tcp # cautcpd
+cautcpd 3061/udp # cautcpd
+ncacn-ip-tcp 3062/tcp # ncacn-ip-tcp
+ncacn-ip-tcp 3062/udp # ncacn-ip-tcp
+ncadg-ip-udp 3063/tcp # ncadg-ip-udp
+ncadg-ip-udp 3063/udp # ncadg-ip-udp
+rprt 3064/tcp # Remote Port Redirector
+rprt 3064/udp # Remote Port Redirector
+slinterbase 3065/tcp # slinterbase
+slinterbase 3065/udp # slinterbase
+netattachsdmp 3066/tcp # NETATTACHSDMP
+netattachsdmp 3066/udp # NETATTACHSDMP
+fjhpjp 3067/tcp # FJHPJP
+fjhpjp 3067/udp # FJHPJP
+ls3bcast 3068/tcp # ls3 Broadcast
+ls3bcast 3068/udp # ls3 Broadcast
+ls3 3069/tcp # ls3
+ls3 3069/udp # ls3
+mgxswitch 3070/tcp # MGXSWITCH
+mgxswitch 3070/udp # MGXSWITCH
+csd-mgmt-port 3071/tcp # ContinuStor Manager Port
+csd-mgmt-port 3071/udp # ContinuStor Manager Port
+csd-monitor 3072/tcp # ContinuStor Monitor Port
+csd-monitor 3072/udp # ContinuStor Monitor Port
+vcrp 3073/tcp # Very simple chatroom prot
+vcrp 3073/udp # Very simple chatroom prot
+xbox 3074/tcp # Xbox game port
+xbox 3074/udp # Xbox game port
+orbix-locator 3075/tcp # Orbix 2000 Locator
+orbix-locator 3075/udp # Orbix 2000 Locator
+orbix-config 3076/tcp # Orbix 2000 Config
+orbix-config 3076/udp # Orbix 2000 Config
+orbix-loc-ssl 3077/tcp # Orbix 2000 Locator SSL
+orbix-loc-ssl 3077/udp # Orbix 2000 Locator SSL
+orbix-cfg-ssl 3078/tcp # Orbix 2000 Locator SSL
+orbix-cfg-ssl 3078/udp # Orbix 2000 Locator SSL
+lv-frontpanel 3079/tcp # LV Front Panel
+lv-frontpanel 3079/udp # LV Front Panel
+stm_pproc 3080/tcp stm-pproc # stm_pproc
+stm_pproc 3080/udp stm-pproc # stm_pproc
+tl1-lv 3081/tcp # TL1-LV
+tl1-lv 3081/udp # TL1-LV
+tl1-raw 3082/tcp # TL1-RAW
+tl1-raw 3082/udp # TL1-RAW
+tl1-telnet 3083/tcp # TL1-TELNET
+tl1-telnet 3083/udp # TL1-TELNET
+itm-mccs 3084/tcp # ITM-MCCS
+itm-mccs 3084/udp # ITM-MCCS
+pcihreq 3085/tcp # PCIHReq
+pcihreq 3085/udp # PCIHReq
+jdl-dbkitchen 3086/tcp # JDL-DBKitchen
+jdl-dbkitchen 3086/udp # JDL-DBKitchen
+asoki-sma 3087/tcp # Asoki SMA
+asoki-sma 3087/udp # Asoki SMA
+xdtp 3088/tcp # eXtensible Data Transfer Protocol
+xdtp 3088/udp # eXtensible Data Transfer Protocol
+ptk-alink 3089/tcp # ParaTek Agent Linking
+ptk-alink 3089/udp # ParaTek Agent Linking
+stss 3090/tcp # Senforce Session Services
+stss 3090/udp # Senforce Session Services
+1ci-smcs 3091/tcp # 1Ci Server Management
+1ci-smcs 3091/udp # 1Ci Server Management
+rapidmq-center 3093/tcp # Jiiva RapidMQ Center
+rapidmq-center 3093/udp # Jiiva RapidMQ Center
+rapidmq-reg 3094/tcp # Jiiva RapidMQ Registry
+rapidmq-reg 3094/udp # Jiiva RapidMQ Registry
+panasas 3095/tcp # Panasas rendevous port
+panasas 3095/udp # Panasas rendevous port
+ndl-aps 3096/tcp # Active Print Server Port
+ndl-aps 3096/udp # Active Print Server Port
+itu-bicc-stc 3097/sctp # ITU-T Q.1902.1/Q.2150.3
+umm-port 3098/tcp # Universal Message Manager
+umm-port 3098/udp # Universal Message Manager
+chmd 3099/tcp # CHIPSY Machine Daemon
+chmd 3099/udp # CHIPSY Machine Daemon
+opcon-xps 3100/tcp # OpCon/xps
+opcon-xps 3100/udp # OpCon/xps
+hp-pxpib 3101/tcp # HP PolicyXpert PIB Server
+hp-pxpib 3101/udp # HP PolicyXpert PIB Server
+slslavemon 3102/tcp # SoftlinK Slave Mon Port
+slslavemon 3102/udp # SoftlinK Slave Mon Port
+autocuesmi 3103/tcp # Autocue SMI Protocol
+autocuesmi 3103/udp # Autocue SMI Protocol
+autocuelog 3104/tcp # Autocue Logger Protocol
+autocuetime 3104/udp # Autocue Time Service
+cardbox 3105/tcp # Cardbox
+cardbox 3105/udp # Cardbox
+cardbox-http 3106/tcp # Cardbox HTTP
+cardbox-http 3106/udp # Cardbox HTTP
+business 3107/tcp # Business protocol
+business 3107/udp # Business protocol
+geolocate 3108/tcp # Geolocate protocol
+geolocate 3108/udp # Geolocate protocol
+personnel 3109/tcp # Personnel protocol
+personnel 3109/udp # Personnel protocol
+sim-control 3110/tcp # simulator control port
+sim-control 3110/udp # simulator control port
+wsynch 3111/tcp # Web Synchronous Services
+wsynch 3111/udp # Web Synchronous Services
+ksysguard 3112/tcp # KDE System Guard
+ksysguard 3112/udp # KDE System Guard
+cs-auth-svr 3113/tcp # CS-Authenticate Svr Port
+cs-auth-svr 3113/udp # CS-Authenticate Svr Port
+ccmad 3114/tcp # CCM AutoDiscover
+ccmad 3114/udp # CCM AutoDiscover
+mctet-master 3115/tcp # MCTET Master
+mctet-master 3115/udp # MCTET Master
+mctet-gateway 3116/tcp # MCTET Gateway
+mctet-gateway 3116/udp # MCTET Gateway
+mctet-jserv 3117/tcp # MCTET Jserv
+mctet-jserv 3117/udp # MCTET Jserv
+pkagent 3118/tcp # PKAgent
+pkagent 3118/udp # PKAgent
+d2000kernel 3119/tcp # D2000 Kernel Port
+d2000kernel 3119/udp # D2000 Kernel Port
+d2000webserver 3120/tcp # D2000 Webserver Port
+d2000webserver 3120/udp # D2000 Webserver Port
+pcmk-remote 3121/tcp # pacemaker remote service
+vtr-emulator 3122/tcp # MTI VTR Emulator port
+vtr-emulator 3122/udp # MTI VTR Emulator port
+edix 3123/tcp # EDI Translation Protocol
+edix 3123/udp # EDI Translation Protocol
+beacon-port 3124/tcp # Beacon Port
+beacon-port 3124/udp # Beacon Port
+a13-an 3125/tcp # A13-AN Interface
+a13-an 3125/udp # A13-AN Interface
+ctx-bridge 3127/tcp # CTX Bridge Port
+ctx-bridge 3127/udp # CTX Bridge Port
+ndl-aas 3128/udp # Active API Server Port
+netport-id 3129/tcp # NetPort Discovery Port
+netport-id 3129/udp # NetPort Discovery Port
+netbookmark 3131/tcp # Net Book Mark
+netbookmark 3131/udp # Net Book Mark
+ms-rule-engine 3132/tcp # Microsoft Business Rule Engine Update Service
+ms-rule-engine 3132/udp # Microsoft Business Rule Engine Update Service
+prism-deploy 3133/tcp # Prism Deploy User Port
+prism-deploy 3133/udp # Prism Deploy User Port
+ecp 3134/tcp # Extensible Code Protocol
+ecp 3134/udp # Extensible Code Protocol
+peerbook-port 3135/tcp # PeerBook Port
+peerbook-port 3135/udp # PeerBook Port
+grubd 3136/tcp # Grub Server Port
+grubd 3136/udp # Grub Server Port
+rtnt-1 3137/tcp # rtnt-1 data packets
+rtnt-1 3137/udp # rtnt-1 data packets
+rtnt-2 3138/tcp # rtnt-2 data packets
+rtnt-2 3138/udp # rtnt-2 data packets
+incognitorv 3139/tcp # Incognito Rendez-Vous
+incognitorv 3139/udp # Incognito Rendez-Vous
+ariliamulti 3140/tcp # Arilia Multiplexor
+ariliamulti 3140/udp # Arilia Multiplexor
+vmodem 3141/tcp # VMODEM
+vmodem 3141/udp # VMODEM
+rdc-wh-eos 3142/tcp # RDC WH EOS
+rdc-wh-eos 3142/udp # RDC WH EOS
+seaview 3143/tcp # Sea View
+seaview 3143/udp # Sea View
+tarantella 3144/tcp # Tarantella
+tarantella 3144/udp # Tarantella
+csi-lfap 3145/tcp # CSI-LFAP
+csi-lfap 3145/udp # CSI-LFAP
+bears-02 3146/tcp # bears-02
+bears-02 3146/udp # bears-02
+rfio 3147/tcp # RFIO
+rfio 3147/udp # RFIO
+nm-game-admin 3148/tcp # NetMike Game Administrator
+nm-game-admin 3148/udp # NetMike Game Administrator
+nm-game-server 3149/tcp # NetMike Game Server
+nm-game-server 3149/udp # NetMike Game Server
+nm-asses-admin 3150/tcp # NetMike Assessor Administrator
+nm-asses-admin 3150/udp # NetMike Assessor Administrator
+nm-assessor 3151/tcp # NetMike Assessor
+nm-assessor 3151/udp # NetMike Assessor
+feitianrockey 3152/tcp # FeiTian Port
+feitianrockey 3152/udp # FeiTian Port
+s8-client-port 3153/tcp # S8Cargo Client Port
+s8-client-port 3153/udp # S8Cargo Client Port
+ccmrmi 3154/tcp # ON RMI Registry
+ccmrmi 3154/udp # ON RMI Registry
+jpegmpeg 3155/tcp # JpegMpeg Port
+jpegmpeg 3155/udp # JpegMpeg Port
+indura 3156/tcp # Indura Collector
+indura 3156/udp # Indura Collector
+e3consultants 3157/tcp # CCC Listener Port
+e3consultants 3157/udp # CCC Listener Port
+stvp 3158/tcp # SmashTV Protocol
+stvp 3158/udp # SmashTV Protocol
+navegaweb-port 3159/tcp # NavegaWeb Tarification
+navegaweb-port 3159/udp # NavegaWeb Tarification
+tip-app-server 3160/tcp # TIP Application Server
+tip-app-server 3160/udp # TIP Application Server
+doc1lm 3161/tcp # DOC1 License Manager
+doc1lm 3161/udp # DOC1 License Manager
+sflm 3162/tcp # SFLM
+sflm 3162/udp # SFLM
+res-sap 3163/tcp # RES-SAP
+res-sap 3163/udp # RES-SAP
+imprs 3164/tcp # IMPRS
+imprs 3164/udp # IMPRS
+newgenpay 3165/tcp # Newgenpay Engine Service
+newgenpay 3165/udp # Newgenpay Engine Service
+sossecollector 3166/tcp # Quest Spotlight Out-Of-Process Collector
+sossecollector 3166/udp # Quest Spotlight Out-Of-Process Collector
+nowcontact 3167/tcp # Now Contact Public Server
+nowcontact 3167/udp # Now Contact Public Server
+poweronnud 3168/tcp # Now Up-to-Date Public Server
+poweronnud 3168/udp # Now Up-to-Date Public Server
+serverview-as 3169/tcp # SERVERVIEW-AS
+serverview-as 3169/udp # SERVERVIEW-AS
+serverview-asn 3170/tcp # SERVERVIEW-ASN
+serverview-asn 3170/udp # SERVERVIEW-ASN
+serverview-gf 3171/tcp # SERVERVIEW-GF
+serverview-gf 3171/udp # SERVERVIEW-GF
+serverview-rm 3172/tcp # SERVERVIEW-RM
+serverview-rm 3172/udp # SERVERVIEW-RM
+serverview-icc 3173/tcp # SERVERVIEW-ICC
+serverview-icc 3173/udp # SERVERVIEW-ICC
+armi-server 3174/tcp # ARMI Server
+armi-server 3174/udp # ARMI Server
+t1-e1-over-ip 3175/tcp # T1_E1_Over_IP
+t1-e1-over-ip 3175/udp # T1_E1_Over_IP
+ars-master 3176/tcp # ARS Master
+ars-master 3176/udp # ARS Master
+phonex-port 3177/tcp # Phonex Protocol
+phonex-port 3177/udp # Phonex Protocol
+radclientport 3178/tcp # Radiance UltraEdge Port
+radclientport 3178/udp # Radiance UltraEdge Port
+h2gf-w-2m 3179/tcp # H2GF W.2m Handover prot.
+h2gf-w-2m 3179/udp # H2GF W.2m Handover prot.
+mc-brk-srv 3180/tcp # Millicent Broker Server
+mc-brk-srv 3180/udp # Millicent Broker Server
+bmcpatrolagent 3181/tcp # BMC Patrol Agent
+bmcpatrolagent 3181/udp # BMC Patrol Agent
+bmcpatrolrnvu 3182/tcp # BMC Patrol Rendezvous
+bmcpatrolrnvu 3182/udp # BMC Patrol Rendezvous
+cops-tls 3183/tcp # COPS/TLS
+cops-tls 3183/udp # COPS/TLS
+apogeex-port 3184/tcp # ApogeeX Port
+apogeex-port 3184/udp # ApogeeX Port
+smpppd 3185/tcp # SuSE Meta PPPD
+smpppd 3185/udp # SuSE Meta PPPD
+iiw-port 3186/tcp # IIW Monitor User Port
+iiw-port 3186/udp # IIW Monitor User Port
+odi-port 3187/tcp # Open Design Listen Port
+odi-port 3187/udp # Open Design Listen Port
+brcm-comm-port 3188/tcp # Broadcom Port
+brcm-comm-port 3188/udp # Broadcom Port
+pcle-infex 3189/tcp # Pinnacle Sys InfEx Port
+pcle-infex 3189/udp # Pinnacle Sys InfEx Port
+csvr-proxy 3190/tcp # ConServR Proxy
+csvr-proxy 3190/udp # ConServR Proxy
+csvr-sslproxy 3191/tcp # ConServR SSL Proxy
+csvr-sslproxy 3191/udp # ConServR SSL Proxy
+firemonrcc 3192/tcp # FireMon Revision Control
+firemonrcc 3192/udp # FireMon Revision Control
+spandataport 3193/tcp # SpanDataPort
+spandataport 3193/udp # SpanDataPort
+magbind 3194/tcp # Rockstorm MAG protocol
+magbind 3194/udp # Rockstorm MAG protocol
+ncu-1 3195/tcp # Network Control Unit
+ncu-1 3195/udp # Network Control Unit
+ncu-2 3196/tcp # Network Control Unit
+ncu-2 3196/udp # Network Control Unit
+embrace-dp-s 3197/tcp # Embrace Device Protocol Server
+embrace-dp-s 3197/udp # Embrace Device Protocol Server
+embrace-dp-c 3198/tcp # Embrace Device Protocol Client
+embrace-dp-c 3198/udp # Embrace Device Protocol Client
+dmod-workspace 3199/tcp # DMOD WorkSpace
+dmod-workspace 3199/udp # DMOD WorkSpace
+tick-port 3200/tcp # Press-sense Tick Port
+tick-port 3200/udp # Press-sense Tick Port
+cpq-tasksmart 3201/tcp # CPQ-TaskSmart
+cpq-tasksmart 3201/udp # CPQ-TaskSmart
+intraintra 3202/tcp # IntraIntra
+intraintra 3202/udp # IntraIntra
+netwatcher-mon 3203/tcp # Network Watcher Monitor
+netwatcher-mon 3203/udp # Network Watcher Monitor
+netwatcher-db 3204/tcp # Network Watcher DB Access
+netwatcher-db 3204/udp # Network Watcher DB Access
+isns 3205/tcp # iSNS Server Port
+isns 3205/udp # iSNS Server Port
+ironmail 3206/tcp # IronMail POP Proxy
+ironmail 3206/udp # IronMail POP Proxy
+vx-auth-port 3207/tcp # Veritas Authentication Port
+vx-auth-port 3207/udp # Veritas Authentication Port
+pfu-prcallback 3208/tcp # PFU PR Callback
+pfu-prcallback 3208/udp # PFU PR Callback
+netwkpathengine 3209/tcp # HP OpenView Network Path Engine Server
+netwkpathengine 3209/udp # HP OpenView Network Path Engine Server
+flamenco-proxy 3210/tcp # Flamenco Networks Proxy
+flamenco-proxy 3210/udp # Flamenco Networks Proxy
+avsecuremgmt 3211/tcp # Avocent Secure Management
+avsecuremgmt 3211/udp # Avocent Secure Management
+surveyinst 3212/tcp # Survey Instrument
+surveyinst 3212/udp # Survey Instrument
+neon24x7 3213/tcp # NEON 24X7 Mission Control
+neon24x7 3213/udp # NEON 24X7 Mission Control
+jmq-daemon-1 3214/tcp # JMQ Daemon Port 1
+jmq-daemon-1 3214/udp # JMQ Daemon Port 1
+jmq-daemon-2 3215/tcp # JMQ Daemon Port 2
+jmq-daemon-2 3215/udp # JMQ Daemon Port 2
+ferrari-foam 3216/tcp # Ferrari electronic FOAM
+ferrari-foam 3216/udp # Ferrari electronic FOAM
+unite 3217/tcp # Unified IP & Telecomm Environment
+unite 3217/udp # Unified IP & Telecomm Environment
+smartpackets 3218/tcp # EMC SmartPackets
+smartpackets 3218/udp # EMC SmartPackets
+wms-messenger 3219/tcp # WMS Messenger
+wms-messenger 3219/udp # WMS Messenger
+xnm-ssl 3220/tcp # XML NM over SSL
+xnm-ssl 3220/udp # XML NM over SSL
+xnm-clear-text 3221/tcp # XML NM over TCP
+xnm-clear-text 3221/udp # XML NM over TCP
+glbp 3222/tcp # Gateway Load Balancing Pr
+glbp 3222/udp # Gateway Load Balancing Pr
+digivote 3223/tcp # DIGIVOTE (R) Vote-Server
+digivote 3223/udp # DIGIVOTE (R) Vote-Server
+aes-discovery 3224/tcp # AES Discovery Port
+aes-discovery 3224/udp # AES Discovery Port
+fcip-port 3225/tcp # FCIP
+fcip-port 3225/udp # FCIP
+isi-irp 3226/tcp # ISI Industry Software IRP
+isi-irp 3226/udp # ISI Industry Software IRP
+dwnmshttp 3227/tcp # DiamondWave NMS Server
+dwnmshttp 3227/udp # DiamondWave NMS Server
+dwmsgserver 3228/tcp # DiamondWave MSG Server
+dwmsgserver 3228/udp # DiamondWave MSG Server
+global-cd-port 3229/tcp # Global CD Port
+global-cd-port 3229/udp # Global CD Port
+sftdst-port 3230/tcp # Software Distributor Port
+sftdst-port 3230/udp # Software Distributor Port
+vidigo 3231/tcp # VidiGo communication
+vidigo 3231/udp # VidiGo communication
+mdtp 3232/tcp # MDT port
+mdtp 3232/udp # MDT port
+whisker 3233/tcp # WhiskerControl main port
+whisker 3233/udp # WhiskerControl main port
+alchemy 3234/tcp # Alchemy Server
+alchemy 3234/udp # Alchemy Server
+mdap-port 3235/tcp # MDAP port
+mdap-port 3235/udp # MDAP Port
+apparenet-ts 3236/tcp # appareNet Test Server
+apparenet-ts 3236/udp # appareNet Test Server
+apparenet-tps 3237/tcp # appareNet Test Packet Sequencer
+apparenet-tps 3237/udp # appareNet Test Packet Sequencer
+apparenet-as 3238/tcp # appareNet Analysis Server
+apparenet-as 3238/udp # appareNet Analysis Server
+apparenet-ui 3239/tcp # appareNet User Interface
+apparenet-ui 3239/udp # appareNet User Interface
+triomotion 3240/tcp # Trio Motion Control Port
+triomotion 3240/udp # Trio Motion Control Port
+sysorb 3241/tcp # SysOrb Monitoring Server
+sysorb 3241/udp # SysOrb Monitoring Server
+sdp-id-port 3242/tcp # Session Description ID
+sdp-id-port 3242/udp # Session Description ID
+timelot 3243/tcp # Timelot Port
+timelot 3243/udp # Timelot Port
+onesaf 3244/tcp # OneSAF
+onesaf 3244/udp # OneSAF
+vieo-fe 3245/tcp # VIEO Fabric Executive
+vieo-fe 3245/udp # VIEO Fabric Executive
+dvt-system 3246/tcp # DVT SYSTEM PORT
+dvt-system 3246/udp # DVT SYSTEM PORT
+dvt-data 3247/tcp # DVT DATA LINK
+dvt-data 3247/udp # DVT DATA LINK
+procos-lm 3248/tcp # PROCOS LM
+procos-lm 3248/udp # PROCOS LM
+ssp 3249/tcp # State Sync Protocol
+ssp 3249/udp # State Sync Protocol
+hicp 3250/tcp # HMS hicp port
+hicp 3250/udp # HMS hicp port
+sysscanner 3251/tcp # Sys Scanner
+sysscanner 3251/udp # Sys Scanner
+dhe 3252/tcp # DHE port
+dhe 3252/udp # DHE port
+pda-data 3253/tcp # PDA Data
+pda-data 3253/udp # PDA Data
+pda-sys 3254/tcp # PDA System
+pda-sys 3254/udp # PDA System
+semaphore 3255/tcp # Semaphore Connection Port
+semaphore 3255/udp # Semaphore Connection Port
+cpqrpm-agent 3256/tcp # Compaq RPM Agent Port
+cpqrpm-agent 3256/udp # Compaq RPM Agent Port
+cpqrpm-server 3257/tcp # Compaq RPM Server Port
+cpqrpm-server 3257/udp # Compaq RPM Server Port
+ivecon-port 3258/tcp # Ivecon Server Port
+ivecon-port 3258/udp # Ivecon Server Port
+epncdp2 3259/tcp # Epson Network Common Devi
+epncdp2 3259/udp # Epson Network Common Devi
+iscsi-target 3260/tcp # iSCSI port
+iscsi-target 3260/udp # iSCSI port
+winshadow 3261/tcp # winShadow
+winshadow 3261/udp # winShadow
+necp 3262/tcp # NECP
+necp 3262/udp # NECP
+ecolor-imager 3263/tcp # E-Color Enterprise Imager
+ecolor-imager 3263/udp # E-Color Enterprise Imager
+ccmail 3264/tcp # cc:mail/lotus
+ccmail 3264/udp # cc:mail/lotus
+altav-tunnel 3265/tcp # Altav Tunnel
+altav-tunnel 3265/udp # Altav Tunnel
+ns-cfg-server 3266/tcp # NS CFG Server
+ns-cfg-server 3266/udp # NS CFG Server
+ibm-dial-out 3267/tcp # IBM Dial Out
+ibm-dial-out 3267/udp # IBM Dial Out
+msft-gc 3268/tcp # Microsoft Global Catalog
+msft-gc 3268/udp # Microsoft Global Catalog
+msft-gc-ssl 3269/tcp # Microsoft Global Catalog with LDAP/SSL
+msft-gc-ssl 3269/udp # Microsoft Global Catalog with LDAP/SSL
+verismart 3270/tcp # Verismart
+verismart 3270/udp # Verismart
+csoft-prev 3271/tcp # CSoft Prev Port
+csoft-prev 3271/udp # CSoft Prev Port
+user-manager 3272/tcp # Fujitsu User Manager
+user-manager 3272/udp # Fujitsu User Manager
+sxmp 3273/tcp # Simple Extensible Multiplexed Protocol
+sxmp 3273/udp # Simple Extensible Multiplexed Protocol
+ordinox-server 3274/tcp # Ordinox Server
+ordinox-server 3274/udp # Ordinox Server
+samd 3275/tcp # SAMD
+samd 3275/udp # SAMD
+maxim-asics 3276/tcp # Maxim ASICs
+maxim-asics 3276/udp # Maxim ASICs
+awg-proxy 3277/tcp # AWG Proxy
+awg-proxy 3277/udp # AWG Proxy
+lkcmserver 3278/tcp # LKCM Server
+lkcmserver 3278/udp # LKCM Server
+admind 3279/tcp # admind
+admind 3279/udp # admind
+vs-server 3280/tcp # VS Server
+vs-server 3280/udp # VS Server
+sysopt 3281/tcp # SYSOPT
+sysopt 3281/udp # SYSOPT
+datusorb 3282/tcp # Datusorb
+datusorb 3282/udp # Datusorb
+net-assistant 3283/tcp # Apple Remote Desktop - Net Assistant
+net-assistant 3283/udp # Apple Remote Desktop - Net Assistant
+4talk 3284/tcp # 4Talk
+4talk 3284/udp # 4Talk
+plato 3285/tcp # Plato
+plato 3285/udp # Plato
+e-net 3286/tcp # E-Net
+e-net 3286/udp # E-Net
+directvdata 3287/tcp # DIRECTVDATA
+directvdata 3287/udp # DIRECTVDATA
+cops 3288/tcp # COPS
+cops 3288/udp # COPS
+enpc 3289/tcp # ENPC
+enpc 3289/udp # ENPC
+caps-lm 3290/tcp # CAPS LOGISTICS TOOLKIT - LM
+caps-lm 3290/udp # CAPS LOGISTICS TOOLKIT - LM
+sah-lm 3291/tcp # S A Holditch & Associates - LM
+sah-lm 3291/udp # S A Holditch & Associates - LM
+cart-o-rama 3292/tcp # Cart O Rama
+cart-o-rama 3292/udp # Cart O Rama
+fg-fps 3293/tcp # fg-fps
+fg-fps 3293/udp # fg-fps
+fg-gip 3294/tcp # fg-gip
+fg-gip 3294/udp # fg-gip
+dyniplookup 3295/tcp # Dynamic IP Lookup
+dyniplookup 3295/udp # Dynamic IP Lookup
+rib-slm 3296/tcp # Rib License Manager
+rib-slm 3296/udp # Rib License Manager
+cytel-lm 3297/tcp # Cytel License Manager
+cytel-lm 3297/udp # Cytel License Manager
+deskview 3298/tcp # DeskView
+deskview 3298/udp # DeskView
+pdrncs 3299/tcp # pdrncs
+pdrncs 3299/udp # pdrncs
+mcs-fastmail 3302/tcp # MCS Fastmail
+mcs-fastmail 3302/udp # MCS Fastmail
+opsession-clnt 3303/tcp # OP Session Client
+opsession-clnt 3303/udp # OP Session Client
+opsession-srvr 3304/tcp # OP Session Server
+opsession-srvr 3304/udp # OP Session Server
+odette-ftp 3305/tcp # ODETTE-FTP
+odette-ftp 3305/udp # ODETTE-FTP
+opsession-prxy 3307/tcp # OP Session Proxy
+opsession-prxy 3307/udp # OP Session Proxy
+tns-server 3308/tcp # TNS Server
+tns-server 3308/udp # TNS Server
+tns-adv 3309/tcp # TNS ADV
+tns-adv 3309/udp # TNS ADV
+dyna-access 3310/tcp # Dyna Access
+dyna-access 3310/udp # Dyna Access
+mcns-tel-ret 3311/tcp # MCNS Tel Ret
+mcns-tel-ret 3311/udp # MCNS Tel Ret
+appman-server 3312/tcp # Application Management Server
+appman-server 3312/udp # Application Management Server
+uorb 3313/tcp # Unify Object Broker
+uorb 3313/udp # Unify Object Broker
+uohost 3314/tcp # Unify Object Host
+uohost 3314/udp # Unify Object Host
+cdid 3315/tcp # CDID
+cdid 3315/udp # CDID
+aicc-cmi 3316/tcp # AICC/CMI
+aicc-cmi 3316/udp # AICC/CMI
+vsaiport 3317/tcp # VSAI PORT
+vsaiport 3317/udp # VSAI PORT
+ssrip 3318/tcp # Swith to Swith Routing Information Protocol
+ssrip 3318/udp # Swith to Swith Routing Information Protocol
+sdt-lmd 3319/tcp # SDT License Manager
+sdt-lmd 3319/udp # SDT License Manager
+officelink2000 3320/tcp # Office Link 2000
+officelink2000 3320/udp # Office Link 2000
+vnsstr 3321/tcp # VNSSTR
+vnsstr 3321/udp # VNSSTR
+sftu 3326/tcp # SFTU
+sftu 3326/udp # SFTU
+bbars 3327/tcp # BBARS
+bbars 3327/udp # BBARS
+egptlm 3328/tcp # Eaglepoint License Manager
+egptlm 3328/udp # Eaglepoint License Manager
+hp-device-disc 3329/tcp # HP Device Disc
+hp-device-disc 3329/udp # HP Device Disc
+mcs-calypsoicf 3330/tcp # MCS Calypso ICF
+mcs-calypsoicf 3330/udp # MCS Calypso ICF
+mcs-messaging 3331/tcp # MCS Messaging
+mcs-messaging 3331/udp # MCS Messaging
+mcs-mailsvr 3332/tcp # MCS Mail Server
+mcs-mailsvr 3332/udp # MCS Mail Server
+dec-notes 3333/tcp # DEC Notes
+dec-notes 3333/udp # DEC Notes
+directv-web 3334/tcp # Direct TV Webcasting
+directv-web 3334/udp # Direct TV Webcasting
+directv-soft 3335/tcp # Direct TV Software Updates
+directv-soft 3335/udp # Direct TV Software Updates
+directv-tick 3336/tcp # Direct TV Tickers
+directv-tick 3336/udp # Direct TV Tickers
+directv-catlg 3337/tcp # Direct TV Data Catalog
+directv-catlg 3337/udp # Direct TV Data Catalog
+anet-b 3338/tcp # OMF data b
+anet-b 3338/udp # OMF data b
+anet-l 3339/tcp # OMF data l
+anet-l 3339/udp # OMF data l
+anet-m 3340/tcp # OMF data m
+anet-m 3340/udp # OMF data m
+anet-h 3341/tcp # OMF data h
+anet-h 3341/udp # OMF data h
+webtie 3342/tcp # WebTIE
+webtie 3342/udp # WebTIE
+ms-cluster-net 3343/tcp # MS Cluster Net
+ms-cluster-net 3343/udp # MS Cluster Net
+bnt-manager 3344/tcp # BNT Manager
+bnt-manager 3344/udp # BNT Manager
+influence 3345/tcp # Influence
+influence 3345/udp # Influence
+phoenix-rpc 3347/tcp # Phoenix RPC
+phoenix-rpc 3347/udp # Phoenix RPC
+pangolin-laser 3348/tcp # Pangolin Laser
+pangolin-laser 3348/udp # Pangolin Laser
+chevinservices 3349/tcp # Chevin Services
+chevinservices 3349/udp # Chevin Services
+findviatv 3350/tcp # FINDVIATV
+findviatv 3350/udp # FINDVIATV
+btrieve 3351/tcp # Btrieve port
+btrieve 3351/udp # Btrieve port
+ssql 3352/tcp # Scalable SQL
+ssql 3352/udp # Scalable SQL
+fatpipe 3353/tcp # FATPIPE
+fatpipe 3353/udp # FATPIPE
+suitjd 3354/tcp # SUITJD
+suitjd 3354/udp # SUITJD
+ordinox-dbase 3355/tcp # Ordinox Dbase
+ordinox-dbase 3355/udp # Ordinox Dbase
+upnotifyps 3356/tcp # UPNOTIFYPS
+upnotifyps 3356/udp # UPNOTIFYPS
+adtech-test 3357/tcp # Adtech Test IP
+adtech-test 3357/udp # Adtech Test IP
+mpsysrmsvr 3358/tcp # Mp Sys Rmsvr
+mpsysrmsvr 3358/udp # Mp Sys Rmsvr
+wg-netforce 3359/tcp # WG NetForce
+wg-netforce 3359/udp # WG NetForce
+kv-server 3360/tcp # KV Server
+kv-server 3360/udp # KV Server
+kv-agent 3361/tcp # KV Agent
+kv-agent 3361/udp # KV Agent
+dj-ilm 3362/tcp # DJ ILM
+dj-ilm 3362/udp # DJ ILM
+nati-vi-server 3363/tcp # NATI Vi Server
+nati-vi-server 3363/udp # NATI Vi Server
+tip2 3372/tcp # TIP 2
+tip2 3372/udp # TIP 2
+lavenir-lm 3373/tcp # Lavenir License Manager
+lavenir-lm 3373/udp # Lavenir License Manager
+cluster-disc 3374/tcp # Cluster Disc
+cluster-disc 3374/udp # Cluster Disc
+vsnm-agent 3375/tcp # VSNM Agent
+vsnm-agent 3375/udp # VSNM Agent
+cdbroker 3376/tcp # CD Broker
+cdbroker 3376/udp # CD Broker
+cogsys-lm 3377/tcp # Cogsys Network License Manager
+cogsys-lm 3377/udp # Cogsys Network License Manager
+wsicopy 3378/tcp # WSICOPY
+wsicopy 3378/udp # WSICOPY
+socorfs 3379/tcp # SOCORFS
+socorfs 3379/udp # SOCORFS
+sns-channels 3380/tcp # SNS Channels
+sns-channels 3380/udp # SNS Channels
+geneous 3381/tcp # Geneous
+geneous 3381/udp # Geneous
+fujitsu-neat 3382/tcp # Fujitsu Network Enhanced Antitheft function
+fujitsu-neat 3382/udp # Fujitsu Network Enhanced Antitheft function
+esp-lm 3383/tcp # Enterprise Software Products License Manager
+esp-lm 3383/udp # Enterprise Software Products License Manager
+hp-clic 3384/tcp # Cluster Management Services
+hp-clic 3384/udp # Hardware Management
+qnxnetman 3385/tcp # qnxnetman
+qnxnetman 3385/udp # qnxnetman
+gprs-data 3386/tcp # GPRS Data
+gprs-sig 3386/udp # GPRS SIG
+backroomnet 3387/tcp # Back Room Net
+backroomnet 3387/udp # Back Room Net
+cbserver 3388/tcp # CB Server
+cbserver 3388/udp # CB Server
+ms-wbt-server 3389/tcp # MS WBT Server
+ms-wbt-server 3389/udp # MS WBT Server
+dsc 3390/tcp # Distributed Service Coordinator
+dsc 3390/udp # Distributed Service Coordinator
+savant 3391/tcp # SAVANT
+savant 3391/udp # SAVANT
+efi-lm 3392/tcp # EFI License Management
+efi-lm 3392/udp # EFI License Management
+d2k-tapestry1 3393/tcp # D2K Tapestry Client to Server
+d2k-tapestry1 3393/udp # D2K Tapestry Client to Server
+d2k-tapestry2 3394/tcp # D2K Tapestry Server to Server
+d2k-tapestry2 3394/udp # D2K Tapestry Server to Server
+dyna-lm 3395/tcp # Dyna License Manager (Elam)
+dyna-lm 3395/udp # Dyna License Manager (Elam)
+printer_agent 3396/tcp printer-agent # Printer Agent
+printer_agent 3396/udp printer-agent # Printer Agent
+cloanto-lm 3397/tcp # Cloanto License Manager
+cloanto-lm 3397/udp # Cloanto License Manager
+mercantile 3398/tcp # Mercantile
+mercantile 3398/udp # Mercantile
+csms 3399/tcp # CSMS
+csms 3399/udp # CSMS
+csms2 3400/tcp # CSMS2
+csms2 3400/udp # CSMS2
+filecast 3401/tcp # filecast
+filecast 3401/udp # filecast
+fxaengine-net 3402/tcp # FXa Engine Network Port
+fxaengine-net 3402/udp # FXa Engine Network Port
+nokia-ann-ch1 3405/tcp # Nokia Announcement ch 1
+nokia-ann-ch1 3405/udp # Nokia Announcement ch 1
+nokia-ann-ch2 3406/tcp # Nokia Announcement ch 2
+nokia-ann-ch2 3406/udp # Nokia Announcement ch 2
+ldap-admin 3407/tcp # LDAP admin server port
+ldap-admin 3407/udp # LDAP admin server port
+BESApi 3408/tcp # BES Api Port
+BESApi 3408/udp # BES Api Port
+networklens 3409/tcp # NetworkLens Event Port
+networklens 3409/udp # NetworkLens Event Port
+networklenss 3410/tcp # NetworkLens SSL Event
+networklenss 3410/udp # NetworkLens SSL Event
+biolink-auth 3411/tcp # BioLink Authenteon server
+biolink-auth 3411/udp # BioLink Authenteon server
+xmlblaster 3412/tcp # xmlBlaster
+xmlblaster 3412/udp # xmlBlaster
+svnet 3413/tcp # SpecView Networking
+svnet 3413/udp # SpecView Networking
+wip-port 3414/tcp # BroadCloud WIP Port
+wip-port 3414/udp # BroadCloud WIP Port
+bcinameservice 3415/tcp # BCI Name Service
+bcinameservice 3415/udp # BCI Name Service
+commandport 3416/tcp # AirMobile IS Command Port
+commandport 3416/udp # AirMobile IS Command Port
+csvr 3417/tcp # ConServR file translation
+csvr 3417/udp # ConServR file translation
+rnmap 3418/tcp # Remote nmap
+rnmap 3418/udp # Remote nmap
+softaudit 3419/tcp # Isogon SoftAudit
+softaudit 3419/udp # ISogon SoftAudit
+ifcp-port 3420/tcp # iFCP User Port
+ifcp-port 3420/udp # iFCP User Port
+bmap 3421/tcp # Bull Apprise portmapper
+bmap 3421/udp # Bull Apprise portmapper
+rusb-sys-port 3422/tcp # Remote USB System Port
+rusb-sys-port 3422/udp # Remote USB System Port
+xtrm 3423/tcp # xTrade Reliable Messaging
+xtrm 3423/udp # xTrade Reliable Messaging
+xtrms 3424/tcp # xTrade over TLS/SSL
+xtrms 3424/udp # xTrade over TLS/SSL
+agps-port 3425/tcp # AGPS Access Port
+agps-port 3425/udp # AGPS Access Port
+arkivio 3426/tcp # Arkivio Storage Protocol
+arkivio 3426/udp # Arkivio Storage Protocol
+websphere-snmp 3427/tcp # WebSphere SNMP
+websphere-snmp 3427/udp # WebSphere SNMP
+twcss 3428/tcp # 2Wire CSS
+twcss 3428/udp # 2Wire CSS
+gcsp 3429/tcp # GCSP user port
+gcsp 3429/udp # GCSP user port
+ssdispatch 3430/tcp # Scott Studios Dispatch
+ssdispatch 3430/udp # Scott Studios Dispatch
+ndl-als 3431/tcp # Active License Server Port
+ndl-als 3431/udp # Active License Server Port
+osdcp 3432/tcp # Secure Device Protocol
+osdcp 3432/udp # Secure Device Protocol
+opnet-smp 3433/tcp # OPNET Service Management Platform
+opnet-smp 3433/udp # OPNET Service Management Platform
+opencm 3434/tcp # OpenCM Server
+opencm 3434/udp # OpenCM Server
+pacom 3435/tcp # Pacom Security User Port
+pacom 3435/udp # Pacom Security User Port
+gc-config 3436/tcp # GuardControl Exchange Protocol
+gc-config 3436/udp # GuardControl Exchange Protocol
+autocueds 3437/tcp # Autocue Directory Service
+autocueds 3437/udp # Autocue Directory Service
+spiral-admin 3438/tcp # Spiralcraft Admin
+spiral-admin 3438/udp # Spiralcraft Admin
+hri-port 3439/tcp # HRI Interface Port
+hri-port 3439/udp # HRI Interface Port
+ans-console 3440/tcp # Net Steward Mgmt Console
+ans-console 3440/udp # Net Steward Mgmt Console
+connect-client 3441/tcp # OC Connect Client
+connect-client 3441/udp # OC Connect Client
+connect-server 3442/tcp # OC Connect Server
+connect-server 3442/udp # OC Connect Server
+ov-nnm-websrv 3443/tcp # OpenView Network Node Manager WEB Server
+ov-nnm-websrv 3443/udp # OpenView Network Node Manager WEB Server
+denali-server 3444/tcp # Denali Server
+denali-server 3444/udp # Denali Server
+monp 3445/tcp # Media Object Network
+monp 3445/udp # Media Object Network
+3comfaxrpc 3446/tcp # 3Com FAX RPC port
+3comfaxrpc 3446/udp # 3Com FAX RPC port
+directnet 3447/tcp # DirectNet IM System
+directnet 3447/udp # DirectNet IM System
+dnc-port 3448/tcp # Discovery and Net Config
+dnc-port 3448/udp # Discovery and Net Config
+hotu-chat 3449/tcp # HotU Chat
+hotu-chat 3449/udp # HotU Chat
+castorproxy 3450/tcp # CAStorProxy
+castorproxy 3450/udp # CAStorProxy
+asam 3451/tcp # ASAM Services
+asam 3451/udp # ASAM Services
+sabp-signal 3452/tcp # SABP-Signalling Protocol
+sabp-signal 3452/udp # SABP-Signalling Protocol
+pscupd 3453/tcp # PSC Update Port
+pscupd 3453/udp # PSC Update Port
+mira 3454/tcp # Apple Remote Access Protocol
+mira 3454/udp # Apple Remote Access Protocol
+vat 3456/tcp # VAT default data
+vat 3456/udp # VAT default data
+vat-control 3457/tcp # VAT default control
+vat-control 3457/udp # VAT default control
+d3winosfi 3458/tcp # D3WinOSFI
+d3winosfi 3458/udp # D3WinOSFI
+integral 3459/tcp # TIP Integral
+integral 3459/udp # TIP Integral
+edm-manager 3460/tcp # EDM Manger
+edm-manager 3460/udp # EDM Manger
+edm-stager 3461/tcp # EDM Stager
+edm-stager 3461/udp # EDM Stager
+edm-std-notify 3462/tcp # EDM STD Notify
+edm-std-notify 3462/udp # EDM STD Notify
+edm-adm-notify 3463/tcp # EDM ADM Notify
+edm-adm-notify 3463/udp # EDM ADM Notify
+edm-mgr-sync 3464/tcp # EDM MGR Sync
+edm-mgr-sync 3464/udp # EDM MGR Sync
+edm-mgr-cntrl 3465/tcp # EDM MGR Cntrl
+edm-mgr-cntrl 3465/udp # EDM MGR Cntrl
+workflow 3466/tcp # WORKFLOW
+workflow 3466/udp # WORKFLOW
+rcst 3467/tcp # RCST
+rcst 3467/udp # RCST
+ttcmremotectrl 3468/tcp # TTCM Remote Controll
+ttcmremotectrl 3468/udp # TTCM Remote Controll
+pluribus 3469/tcp # Pluribus
+pluribus 3469/udp # Pluribus
+jt400 3470/tcp # jt400
+jt400 3470/udp # jt400
+jt400-ssl 3471/tcp # jt400-ssl
+jt400-ssl 3471/udp # jt400-ssl
+jaugsremotec-1 3472/tcp # JAUGS N-G Remotec 1
+jaugsremotec-1 3472/udp # JAUGS N-G Remotec 1
+jaugsremotec-2 3473/tcp # JAUGS N-G Remotec 2
+jaugsremotec-2 3473/udp # JAUGS N-G Remotec 2
+ttntspauto 3474/tcp # TSP Automation
+ttntspauto 3474/udp # TSP Automation
+genisar-port 3475/tcp # Genisar Comm Port
+genisar-port 3475/udp # Genisar Comm Port
+nppmp 3476/tcp # NVIDIA Mgmt Protocol
+nppmp 3476/udp # NVIDIA Mgmt Protocol
+ecomm 3477/tcp # eComm link port
+ecomm 3477/udp # eComm link port
+stun 3478/tcp stun-behavior turn # Session Traversal Utilities for NAT (STUN) port, TURN over TCP
+stun 3478/udp stun-behavior turn # Session Traversal Utilities for NAT (STUN) port, TURN over UDP
+twrpc 3479/tcp # 2Wire RPC
+twrpc 3479/udp # 2Wire RPC
+plethora 3480/tcp # Secure Virtual Workspace
+plethora 3480/udp # Secure Virtual Workspace
+cleanerliverc 3481/tcp # CleanerLive remote ctrl
+cleanerliverc 3481/udp # CleanerLive remote ctrl
+vulture 3482/tcp # Vulture Monitoring System
+vulture 3482/udp # Vulture Monitoring System
+slim-devices 3483/tcp # Slim Devices Protocol
+slim-devices 3483/udp # Slim Devices Protocol
+gbs-stp 3484/tcp # GBS SnapTalk Protocol
+gbs-stp 3484/udp # GBS SnapTalk Protocol
+celatalk 3485/tcp # CelaTalk
+celatalk 3485/udp # CelaTalk
+ifsf-hb-port 3486/tcp # IFSF Heartbeat Port
+ifsf-hb-port 3486/udp # IFSF Heartbeat Port
+ltctcp 3487/tcp # LISA TCP Transfer Channel
+ltcudp 3487/udp # LISA UDP Transfer Channel
+fs-rh-srv 3488/tcp # FS Remote Host Server
+fs-rh-srv 3488/udp # FS Remote Host Server
+dtp-dia 3489/tcp # DTP/DIA
+dtp-dia 3489/udp # DTP/DIA
+colubris 3490/tcp # Colubris Management Port
+colubris 3490/udp # Colubris Management Port
+swr-port 3491/tcp # SWR Port
+swr-port 3491/udp # SWR Port
+tvdumtray-port 3492/tcp # TVDUM Tray Port
+tvdumtray-port 3492/udp # TVDUM Tray Port
+nut 3493/tcp # Network UPS Tools
+nut 3493/udp # Network UPS Tools
+ibm3494 3494/tcp # IBM 3494
+ibm3494 3494/udp # IBM 3494
+seclayer-tcp 3495/tcp # securitylayer over tcp
+seclayer-tcp 3495/udp # securitylayer over tcp
+seclayer-tls 3496/tcp # securitylayer over tls
+seclayer-tls 3496/udp # securitylayer over tls
+ipether232port 3497/tcp # ipEther232Port
+ipether232port 3497/udp # ipEther232Port
+dashpas-port 3498/tcp # DASHPAS user port
+dashpas-port 3498/udp # DASHPAS user port
+sccip-media 3499/tcp # SccIP Media
+sccip-media 3499/udp # SccIP Media
+rtmp-port 3500/tcp # RTMP Port
+rtmp-port 3500/udp # RTMP Port
+isoft-p2p 3501/tcp # iSoft-P2P
+isoft-p2p 3501/udp # iSoft-P2P
+avinstalldisc 3502/tcp # Avocent Install Discovery
+avinstalldisc 3502/udp # Avocent Install Discovery
+lsp-ping 3503/tcp # MPLS LSP-echo Port
+lsp-ping 3503/udp # MPLS LSP-echo Port
+ironstorm 3504/tcp # IronStorm game server
+ironstorm 3504/udp # IronStorm game server
+ccmcomm 3505/tcp # CCM communications port
+ccmcomm 3505/udp # CCM communications port
+apc-3506 3506/tcp # APC 3506
+apc-3506 3506/udp # APC 3506
+nesh-broker 3507/tcp # Nesh Broker Port
+nesh-broker 3507/udp # Nesh Broker Port
+interactionweb 3508/tcp # Interaction Web
+interactionweb 3508/udp # Interaction Web
+vt-ssl 3509/tcp # Virtual Token SSL Port
+vt-ssl 3509/udp # Virtual Token SSL Port
+xss-port 3510/tcp # XSS Port
+xss-port 3510/udp # XSS Port
+webmail-2 3511/tcp # WebMail/2
+webmail-2 3511/udp # WebMail/2
+aztec 3512/tcp # Aztec Distribution Port
+aztec 3512/udp # Aztec Distribution Port
+arcpd 3513/tcp # Adaptec Remote Protocol
+arcpd 3513/udp # Adaptec Remote Protocol
+must-p2p 3514/tcp # MUST Peer to Peer
+must-p2p 3514/udp # MUST Peer to Peer
+must-backplane 3515/tcp # MUST Backplane
+must-backplane 3515/udp # MUST Backplane
+smartcard-port 3516/tcp # Smartcard Port
+smartcard-port 3516/udp # Smartcard Port
+802-11-iapp 3517/tcp # IEEE 802.11 WLANs WG IAPP
+802-11-iapp 3517/udp # IEEE 802.11 WLANs WG IAPP
+artifact-msg 3518/tcp # Artifact Message Server
+artifact-msg 3518/udp # Artifact Message Server
+nvmsgd 3519/tcp # Netvion Messenger Port
+galileo 3519/udp # Netvion Galileo Port
+galileolog 3520/tcp # Netvion Galileo Log Port
+galileolog 3520/udp # Netvion Galileo Log Port
+mc3ss 3521/tcp # Telequip Labs MC3SS
+mc3ss 3521/udp # Telequip Labs MC3SS
+nssocketport 3522/tcp # DO over NSSocketPort
+nssocketport 3522/udp # DO over NSSocketPort
+odeumservlink 3523/tcp # Odeum Serverlink
+odeumservlink 3523/udp # Odeum Serverlink
+ecmport 3524/tcp # ECM Server port
+ecmport 3524/udp # ECM Server port
+eisport 3525/tcp # EIS Server port
+eisport 3525/udp # EIS Server port
+starquiz-port 3526/tcp # starQuiz Port
+starquiz-port 3526/udp # starQuiz Port
+beserver-msg-q 3527/tcp # VERITAS Backup Exec Server
+beserver-msg-q 3527/udp # VERITAS Backup Exec Server
+jboss-iiop 3528/tcp # JBoss IIOP
+jboss-iiop 3528/udp # JBoss IIOP
+jboss-iiop-ssl 3529/tcp # JBoss IIOP/SSL
+jboss-iiop-ssl 3529/udp # JBoss IIOP/SSL
+gf 3530/tcp # Grid Friendly
+gf 3530/udp # Grid Friendly
+joltid 3531/tcp # Joltid
+joltid 3531/udp # Joltid
+raven-rmp 3532/tcp # Raven Remote Management Control
+raven-rmp 3532/udp # Raven Remote Management Control
+raven-rdp 3533/tcp # Raven Remote Management Data
+raven-rdp 3533/udp # Raven Remote Management Data
+urld-port 3534/tcp # URL Daemon Port
+urld-port 3534/udp # URL Daemon Port
+ms-la 3535/tcp # MS-LA
+ms-la 3535/udp # MS-LA
+snac 3536/tcp # SNAC
+snac 3536/udp # SNAC
+ni-visa-remote 3537/tcp # Remote NI-VISA port
+ni-visa-remote 3537/udp # Remote NI-VISA port
+ibm-diradm 3538/tcp # IBM Directory Server
+ibm-diradm 3538/udp # IBM Directory Server
+ibm-diradm-ssl 3539/tcp # IBM Directory Server SSL
+ibm-diradm-ssl 3539/udp # IBM Directory Server SSL
+pnrp-port 3540/tcp # PNRP User Port
+pnrp-port 3540/udp # PNRP User Port
+voispeed-port 3541/tcp # VoiSpeed Port
+voispeed-port 3541/udp # VoiSpeed Port
+hacl-monitor 3542/tcp # HA cluster monitor
+hacl-monitor 3542/udp # HA cluster monitor
+qftest-lookup 3543/tcp # qftest Lookup Port
+qftest-lookup 3543/udp # qftest Lookup Port
+teredo 3544/tcp # Teredo Port
+teredo 3544/udp # Teredo Port
+camac 3545/tcp # CAMAC equipment
+camac 3545/udp # CAMAC equipment
+symantec-sim 3547/tcp # Symantec SIM
+symantec-sim 3547/udp # Symantec SIM
+interworld 3548/tcp # Interworld
+interworld 3548/udp # Interworld
+tellumat-nms 3549/tcp # Tellumat MDR NMS
+tellumat-nms 3549/udp # Tellumat MDR NMS
+ssmpp 3550/tcp # Secure SMPP
+ssmpp 3550/udp # Secure SMPP
+apcupsd 3551/tcp # Apcupsd Information Port
+apcupsd 3551/udp # Apcupsd Information Port
+taserver 3552/tcp # TeamAgenda Server Port
+taserver 3552/udp # TeamAgenda Server Port
+rbr-discovery 3553/tcp # Red Box Recorder ADP
+rbr-discovery 3553/udp # Red Box Recorder ADP
+questnotify 3554/tcp # Quest Notification Server
+questnotify 3554/udp # Quest Notification Server
+razor 3555/tcp # Vipul's Razor
+razor 3555/udp # Vipul's Razor
+sky-transport 3556/tcp # Sky Transport Protocol
+sky-transport 3556/udp # Sky Transport Protocol
+personalos-001 3557/tcp # PersonalOS Comm Port
+personalos-001 3557/udp # PersonalOS Comm Port
+mcp-port 3558/tcp # MCP user port
+mcp-port 3558/udp # MCP user port
+cctv-port 3559/tcp # CCTV control port
+cctv-port 3559/udp # CCTV control port
+iniserve-port 3560/tcp # INIServe port
+iniserve-port 3560/udp # INIServe port
+bmc-onekey 3561/tcp # BMC-OneKey
+bmc-onekey 3561/udp # BMC-OneKey
+sdbproxy 3562/tcp # SDBProxy
+sdbproxy 3562/udp # SDBProxy
+watcomdebug 3563/tcp # Watcom Debug
+watcomdebug 3563/udp # Watcom Debug
+esimport 3564/tcp # Electromed SIM port
+esimport 3564/udp # Electromed SIM port
+m2pa 3565/tcp # M2PA
+m2pa 3565/sctp # M2PA
+quest-data-hub 3566/tcp # Quest Data Hub
+dof-eps 3567/tcp # DOF protocol stack
+dof-eps 3567/udp # DOF protocol stack
+dof-tunnel-sec 3568/tcp # DOF secure tunnel
+dof-tunnel-sec 3568/udp # DOF secure tunnel
+mbg-ctrl 3569/tcp # Meinberg Control Service
+mbg-ctrl 3569/udp # Meinberg Control Service
+mccwebsvr-port 3570/tcp # MCC Web Server Port
+mccwebsvr-port 3570/udp # MCC Web Server Port
+megardsvr-port 3571/tcp # MegaRAID Server Port
+megardsvr-port 3571/udp # MegaRAID Server Port
+megaregsvrport 3572/tcp # Registration Server Port
+megaregsvrport 3572/udp # Registration Server Port
+tag-ups-1 3573/tcp # Advantage Group UPS Suite
+tag-ups-1 3573/udp # Advantage Group UPS Suite
+dmaf-server 3574/tcp # DMAF Server
+dmaf-caster 3574/udp # DMAF Caster
+ccm-port 3575/tcp # Coalsere CCM Port
+ccm-port 3575/udp # Coalsere CCM Port
+cmc-port 3576/tcp # Coalsere CMC Port
+cmc-port 3576/udp # Coalsere CMC Port
+config-port 3577/tcp # Configuration Port
+config-port 3577/udp # Configuration Port
+data-port 3578/tcp # Data Port
+data-port 3578/udp # Data Port
+ttat3lb 3579/tcp # Tarantella Load Balancing
+ttat3lb 3579/udp # Tarantella Load Balancing
+nati-svrloc 3580/tcp # NATI-ServiceLocator
+nati-svrloc 3580/udp # NATI-ServiceLocator
+kfxaclicensing 3581/tcp # Ascent Capture Licensing
+kfxaclicensing 3581/udp # Ascent Capture Licensing
+press 3582/tcp # PEG PRESS Server
+press 3582/udp # PEG PRESS Server
+canex-watch 3583/tcp # CANEX Watch System
+canex-watch 3583/udp # CANEX Watch System
+u-dbap 3584/tcp # U-DBase Access Protocol
+u-dbap 3584/udp # U-DBase Access Protocol
+emprise-lls 3585/tcp # Emprise License Server
+emprise-lls 3585/udp # Emprise License Server
+emprise-lsc 3586/tcp # License Server Console
+emprise-lsc 3586/udp # License Server Console
+p2pgroup 3587/tcp # Peer to Peer Grouping
+p2pgroup 3587/udp # Peer to Peer Grouping
+sentinel 3588/tcp # Sentinel Server
+sentinel 3588/udp # Sentinel Server
+isomair 3589/tcp # isomair
+isomair 3589/udp # isomair
+wv-csp-sms 3590/tcp # WV CSP SMS Binding
+wv-csp-sms 3590/udp # WV CSP SMS Binding
+gtrack-server 3591/tcp # LOCANIS G-TRACK Server
+gtrack-server 3591/udp # LOCANIS G-TRACK Server
+gtrack-ne 3592/tcp # LOCANIS G-TRACK NE Port
+gtrack-ne 3592/udp # LOCANIS G-TRACK NE Port
+bpmd 3593/tcp # BP Model Debugger
+bpmd 3593/udp # BP Model Debugger
+mediaspace 3594/tcp # MediaSpace
+mediaspace 3594/udp # MediaSpace
+shareapp 3595/tcp # ShareApp
+shareapp 3595/udp # ShareApp
+iw-mmogame 3596/tcp # Illusion Wireless MMOG
+iw-mmogame 3596/udp # Illusion Wireless MMOG
+a14 3597/tcp # A14 (AN-to-SC/MM)
+a14 3597/udp # A14 (AN-to-SC/MM)
+a15 3598/tcp # A15 (AN-to-AN)
+a15 3598/udp # A15 (AN-to-AN)
+quasar-server 3599/tcp # Quasar Accounting Server
+quasar-server 3599/udp # Quasar Accounting Server
+trap-daemon 3600/tcp # text relay-answer
+trap-daemon 3600/udp # text relay-answer
+visinet-gui 3601/tcp # Visinet Gui
+visinet-gui 3601/udp # Visinet Gui
+infiniswitchcl 3602/tcp # InfiniSwitch Mgr Client
+infiniswitchcl 3602/udp # InfiniSwitch Mgr Client
+int-rcv-cntrl 3603/tcp # Integrated Rcvr Control
+int-rcv-cntrl 3603/udp # Integrated Rcvr Control
+bmc-jmx-port 3604/tcp # BMC JMX Port
+bmc-jmx-port 3604/udp # BMC JMX Port
+comcam-io 3605/tcp # ComCam IO Port
+comcam-io 3605/udp # ComCam IO Port
+splitlock 3606/tcp # Splitlock Server
+splitlock 3606/udp # Splitlock Server
+precise-i3 3607/tcp # Precise I3
+precise-i3 3607/udp # Precise I3
+trendchip-dcp 3608/tcp # Trendchip control protocol
+trendchip-dcp 3608/udp # Trendchip control protocol
+cpdi-pidas-cm 3609/tcp # CPDI PIDAS Connection Mon
+cpdi-pidas-cm 3609/udp # CPDI PIDAS Connection Mon
+echonet 3610/tcp # ECHONET
+echonet 3610/udp # ECHONET
+six-degrees 3611/tcp # Six Degrees Port
+six-degrees 3611/udp # Six Degrees Port
+hp-dataprotect 3612/tcp # HP Data Protector
+hp-dataprotect 3612/udp # HP Data Protector
+alaris-disc 3613/tcp # Alaris Device Discovery
+alaris-disc 3613/udp # Alaris Device Discovery
+sigma-port 3614/tcp # Satchwell Sigma
+sigma-port 3614/udp # Satchwell Sigma
+start-network 3615/tcp # Start Messaging Network
+start-network 3615/udp # Start Messaging Network
+cd3o-protocol 3616/tcp # cd3o Control Protocol
+cd3o-protocol 3616/udp # cd3o Control Protocol
+sharp-server 3617/tcp # ATI SHARP Logic Engine
+sharp-server 3617/udp # ATI SHARP Logic Engine
+aairnet-1 3618/tcp # AAIR-Network 1
+aairnet-1 3618/udp # AAIR-Network 1
+aairnet-2 3619/tcp # AAIR-Network 2
+aairnet-2 3619/udp # AAIR-Network 2
+ep-pcp 3620/tcp # EPSON Projector Control Port
+ep-pcp 3620/udp # EPSON Projector Control Port
+ep-nsp 3621/tcp # EPSON Network Screen Port
+ep-nsp 3621/udp # EPSON Network Screen Port
+ff-lr-port 3622/tcp # FF LAN Redundancy Port
+ff-lr-port 3622/udp # FF LAN Redundancy Port
+haipe-discover 3623/tcp # HAIPIS Dynamic Discovery
+haipe-discover 3623/udp # HAIPIS Dynamic Discovery
+dist-upgrade 3624/tcp # Distributed Upgrade Port
+dist-upgrade 3624/udp # Distributed Upgrade Port
+volley 3625/tcp # Volley
+volley 3625/udp # Volley
+bvcdaemon-port 3626/tcp # bvControl Daemon
+bvcdaemon-port 3626/udp # bvControl Daemon
+jamserverport 3627/tcp # Jam Server Port
+jamserverport 3627/udp # Jam Server Port
+ept-machine 3628/tcp # EPT Machine Interface
+ept-machine 3628/udp # EPT Machine Interface
+escvpnet 3629/tcp # ESC/VP.net
+escvpnet 3629/udp # ESC/VP.net
+cs-remote-db 3630/tcp # C&S Remote Database Port
+cs-remote-db 3630/udp # C&S Remote Database Port
+cs-services 3631/tcp # C&S Web Services Port
+cs-services 3631/udp # C&S Web Services Port
+distcc 3632/udp # distributed compiler
+wacp 3633/tcp # Wyrnix AIS port
+wacp 3633/udp # Wyrnix AIS port
+hlibmgr 3634/tcp # hNTSP Library Manager
+hlibmgr 3634/udp # hNTSP Library Manager
+sdo 3635/tcp # Simple Distributed Objects
+sdo 3635/udp # Simple Distributed Objects
+servistaitsm 3636/tcp # SerVistaITSM
+servistaitsm 3636/udp # SerVistaITSM
+scservp 3637/tcp # Customer Service Port
+scservp 3637/udp # Customer Service Port
+ehp-backup 3638/tcp # EHP Backup Protocol
+ehp-backup 3638/udp # EHP Backup Protocol
+xap-ha 3639/tcp # Extensible Automation
+xap-ha 3639/udp # Extensible Automation
+netplay-port1 3640/tcp # Netplay Port 1
+netplay-port1 3640/udp # Netplay Port 1
+netplay-port2 3641/tcp # Netplay Port 2
+netplay-port2 3641/udp # Netplay Port 2
+juxml-port 3642/tcp # Juxml Replication port
+juxml-port 3642/udp # Juxml Replication port
+audiojuggler 3643/tcp # AudioJuggler
+audiojuggler 3643/udp # AudioJuggler
+ssowatch 3644/tcp # ssowatch
+ssowatch 3644/udp # ssowatch
+cyc 3645/tcp # Cyc
+cyc 3645/udp # Cyc
+xss-srv-port 3646/tcp # XSS Server Port
+xss-srv-port 3646/udp # XSS Server Port
+splitlock-gw 3647/tcp # Splitlock Gateway
+splitlock-gw 3647/udp # Splitlock Gateway
+fjcp 3648/tcp # Fujitsu Cooperation Port
+fjcp 3648/udp # Fujitsu Cooperation Port
+nmmp 3649/tcp # Nishioka Miyuki Msg Protocol
+nmmp 3649/udp # Nishioka Miyuki Msg Protocol
+prismiq-plugin 3650/tcp # PRISMIQ VOD plug-in
+prismiq-plugin 3650/udp # PRISMIQ VOD plug-in
+xrpc-registry 3651/tcp # XRPC Registry
+xrpc-registry 3651/udp # XRPC Registry
+vxcrnbuport 3652/tcp # VxCR NBU Default Port
+vxcrnbuport 3652/udp # VxCR NBU Default Port
+tsp 3653/tcp # Tunnel Setup Protocol
+tsp 3653/udp # Tunnel Setup Protocol
+vaprtm 3654/tcp # VAP RealTime Messenger
+vaprtm 3654/udp # VAP RealTime Messenger
+abatemgr 3655/tcp # ActiveBatch Exec Agent
+abatemgr 3655/udp # ActiveBatch Exec Agent
+abatjss 3656/tcp # ActiveBatch Job Scheduler
+abatjss 3656/udp # ActiveBatch Job Scheduler
+immedianet-bcn 3657/tcp # ImmediaNet Beacon
+immedianet-bcn 3657/udp # ImmediaNet Beacon
+ps-ams 3658/tcp # PlayStation AMS (Secure)
+ps-ams 3658/udp # PlayStation AMS (Secure)
+apple-sasl 3659/tcp # Apple SASL
+apple-sasl 3659/udp # Apple SASL
+can-nds-ssl 3660/tcp # IBM Tivoli Directory Service using SSL
+can-nds-ssl 3660/udp # IBM Tivoli Directory Service using SSL
+can-ferret-ssl 3661/tcp # IBM Tivoli Directory Service using SSL
+can-ferret-ssl 3661/udp # IBM Tivoli Directory Service using SSL
+pserver 3662/tcp # pserver
+pserver 3662/udp # pserver
+dtp 3663/tcp # DIRECWAY Tunnel Protocol
+dtp 3663/udp # DIRECWAY Tunnel Protocol
+ups-engine 3664/tcp # UPS Engine Port
+ups-engine 3664/udp # UPS Engine Port
+ent-engine 3665/tcp # Enterprise Engine Port
+ent-engine 3665/udp # Enterprise Engine Port
+eserver-pap 3666/tcp # IBM eServer PAP
+eserver-pap 3666/udp # IBM EServer PAP
+infoexch 3667/tcp # IBM Information Exchange
+infoexch 3667/udp # IBM Information Exchange
+dell-rm-port 3668/tcp # Dell Remote Management
+dell-rm-port 3668/udp # Dell Remote Management
+casanswmgmt 3669/tcp # CA SAN Switch Management
+casanswmgmt 3669/udp # CA SAN Switch Management
+smile 3670/tcp # SMILE TCP/UDP Interface
+smile 3670/udp # SMILE TCP/UDP Interface
+efcp 3671/tcp # e Field Control (EIBnet)
+efcp 3671/udp # e Field Control (EIBnet)
+lispworks-orb 3672/tcp # LispWorks ORB
+lispworks-orb 3672/udp # LispWorks ORB
+mediavault-gui 3673/tcp # Openview Media Vault GUI
+mediavault-gui 3673/udp # Openview Media Vault GUI
+wininstall-ipc 3674/tcp # WinINSTALL IPC Port
+wininstall-ipc 3674/udp # WinINSTALL IPC Port
+calltrax 3675/tcp # CallTrax Data Port
+calltrax 3675/udp # CallTrax Data Port
+va-pacbase 3676/tcp # VisualAge Pacbase server
+va-pacbase 3676/udp # VisualAge Pacbase server
+roverlog 3677/tcp # RoverLog IPC
+roverlog 3677/udp # RoverLog IPC
+ipr-dglt 3678/tcp # DataGuardianLT
+ipr-dglt 3678/udp # DataGuardianLT
+newton-dock 3679/tcp # Newton Dock (Escale)
+newton-dock 3679/udp # Newton Dock (Escale)
+npds-tracker 3680/tcp # NPDS Tracker
+npds-tracker 3680/udp # NPDS Tracker
+bts-x73 3681/tcp # BTS X73 Port
+bts-x73 3681/udp # BTS X73 Port
+cas-mapi 3682/tcp # EMC SmartPackets-MAPI
+cas-mapi 3682/udp # EMC SmartPackets-MAPI
+bmc-ea 3683/tcp # BMC EDV/EA
+bmc-ea 3683/udp # BMC EDV/EA
+faxstfx-port 3684/tcp # FAXstfX
+faxstfx-port 3684/udp # FAXstfX
+dsx-agent 3685/tcp # DS Expert Agent
+dsx-agent 3685/udp # DS Expert Agent
+tnmpv2 3686/tcp # Trivial Network Management
+tnmpv2 3686/udp # Trivial Network Management
+simple-push 3687/tcp # simple-push
+simple-push 3687/udp # simple-push
+simple-push-s 3688/tcp # simple-push Secure
+simple-push-s 3688/udp # simple-push Secure
+daap 3689/tcp # Digital Audio Access Protocol
+daap 3689/udp # Digital Audio Access Protocol
+magaya-network 3691/tcp # Magaya Network Port
+magaya-network 3691/udp # Magaya Network Port
+intelsync 3692/tcp # Brimstone IntelSync
+intelsync 3692/udp # Brimstone IntelSync
+easl 3693/tcp # Emergency Automatic
+bmc-data-coll 3695/tcp # BMC Data Collection
+bmc-data-coll 3695/udp # BMC Data Collection
+telnetcpcd 3696/tcp # Telnet Com Port Control
+telnetcpcd 3696/udp # Telnet Com Port Control
+nw-license 3697/tcp # NavisWorks License System
+nw-license 3697/udp # NavisWorks Licnese System
+sagectlpanel 3698/tcp # SAGECTLPANEL
+sagectlpanel 3698/udp # SAGECTLPANEL
+kpn-icw 3699/tcp # Internet Call Waiting
+kpn-icw 3699/udp # Internet Call Waiting
+lrs-paging 3700/tcp # LRS NetPage
+lrs-paging 3700/udp # LRS NetPage
+netcelera 3701/tcp # NetCelera
+netcelera 3701/udp # NetCelera
+ws-discovery 3702/tcp # Web Service Discovery
+ws-discovery 3702/udp # Web Service Discovery
+adobeserver-3 3703/tcp # Adobe Server 3
+adobeserver-3 3703/udp # Adobe Server 3
+adobeserver-4 3704/tcp # Adobe Server 4
+adobeserver-4 3704/udp # Adobe Server 4
+adobeserver-5 3705/tcp # Adobe Server 5
+adobeserver-5 3705/udp # Adobe Server 5
+rt-event 3706/tcp # Real-Time Event Port
+rt-event 3706/udp # Real-Time Event Port
+rt-event-s 3707/tcp # Real-Time Event Secure Port
+rt-event-s 3707/udp # Real-Time Event Secure Port
+sun-as-iiops 3708/tcp # Sun App Svr - Naming
+sun-as-iiops 3708/udp # Sun App Svr - Naming
+ca-idms 3709/tcp # CA-IDMS Server
+ca-idms 3709/udp # CA-IDMS Server
+portgate-auth 3710/tcp # PortGate Authentication
+portgate-auth 3710/udp # PortGate Authentication
+edb-server2 3711/tcp # EBD Server 2
+edb-server2 3711/udp # EBD Server 2
+sentinel-ent 3712/tcp # Sentinel Enterprise
+sentinel-ent 3712/udp # Sentinel Enterprise
+tftps 3713/tcp # TFTP over TLS
+tftps 3713/udp # TFTP over TLS
+delos-dms 3714/tcp # DELOS Direct Messaging
+delos-dms 3714/udp # DELOS Direct Messaging
+anoto-rendezv 3715/tcp # Anoto Rendezvous Port
+anoto-rendezv 3715/udp # Anoto Rendezvous Port
+wv-csp-sms-cir 3716/tcp # WV CSP SMS CIR Channel
+wv-csp-sms-cir 3716/udp # WV CSP SMS CIR Channel
+wv-csp-udp-cir 3717/tcp # WV CSP UDP/IP CIR Channel
+wv-csp-udp-cir 3717/udp # WV CSP UDP/IP CIR Channel
+opus-services 3718/tcp # OPUS Server Port
+opus-services 3718/udp # OPUS Server Port
+itelserverport 3719/tcp # iTel Server Port
+itelserverport 3719/udp # iTel Server Port
+ufastro-instr 3720/tcp # UF Astro. Instr. Services
+ufastro-instr 3720/udp # UF Astro. Instr. Services
+xsync 3721/tcp # Xsync
+xsync 3721/udp # Xsync
+xserveraid 3722/tcp # Xserve RAID
+xserveraid 3722/udp # Xserve RAID
+sychrond 3723/tcp # Sychron Service Daemon
+sychrond 3723/udp # Sychron Service Daemon
+blizwow 3724/tcp # World of Warcraft
+blizwow 3724/udp # World of Warcraft
+na-er-tip 3725/tcp # Netia NA-ER Port
+na-er-tip 3725/udp # Netia NA-ER Port
+array-manager 3726/tcp # Xyratex Array Manager
+array-manager 3726/udp # Xyartex Array Manager
+e-mdu 3727/tcp # Ericsson Mobile Data Unit
+e-mdu 3727/udp # Ericsson Mobile Data Unit
+e-woa 3728/tcp # Ericsson Web on Air
+e-woa 3728/udp # Ericsson Web on Air
+fksp-audit 3729/tcp # Fireking Audit Port
+fksp-audit 3729/udp # Fireking Audit Port
+client-ctrl 3730/tcp # Client Control
+client-ctrl 3730/udp # Client Control
+smap 3731/tcp # Service Manager
+smap 3731/udp # Service Manager
+m-wnn 3732/tcp # Mobile Wnn
+m-wnn 3732/udp # Mobile Wnn
+multip-msg 3733/tcp # Multipuesto Msg Port
+multip-msg 3733/udp # Multipuesto Msg Port
+synel-data 3734/tcp # Synel Data Collection Port
+synel-data 3734/udp # Synel Data Collection Port
+pwdis 3735/tcp # Password Distribution
+pwdis 3735/udp # Password Distribution
+rs-rmi 3736/tcp # RealSpace RMI
+rs-rmi 3736/udp # RealSpace RMI
+xpanel 3737/tcp # Xpanel Daemon
+versatalk 3738/tcp # versaTalk Server Port
+versatalk 3738/udp # versaTalk Server Port
+launchbird-lm 3739/tcp # Launchbird LicenseManager
+launchbird-lm 3739/udp # Launchbird LicenseManager
+heartbeat 3740/tcp # Heartbeat Protocol
+heartbeat 3740/udp # Heartbeat Protocol
+wysdma 3741/tcp # WysDM Agent
+wysdma 3741/udp # WysDM Agent
+cst-port 3742/tcp # CST - Configuration & Service Tracker
+cst-port 3742/udp # CST - Configuration & Service Tracker
+ipcs-command 3743/tcp # IP Control Systems Ltd.
+ipcs-command 3743/udp # IP Control Systems Ltd.
+sasg 3744/tcp # SASG
+sasg 3744/udp # SASG
+gw-call-port 3745/tcp # GWRTC Call Port
+gw-call-port 3745/udp # GWRTC Call Port
+linktest 3746/tcp # LXPRO.COM LinkTest
+linktest 3746/udp # LXPRO.COM LinkTest
+linktest-s 3747/tcp # LXPRO.COM LinkTest SSL
+linktest-s 3747/udp # LXPRO.COM LinkTest SSL
+webdata 3748/tcp # webData
+webdata 3748/udp # webData
+cimtrak 3749/tcp # CimTrak
+cimtrak 3749/udp # CimTrak
+cbos-ip-port 3750/tcp # CBOS/IP ncapsalation port
+cbos-ip-port 3750/udp # CBOS/IP ncapsalatoin port
+gprs-cube 3751/tcp # CommLinx GPRS Cube
+gprs-cube 3751/udp # CommLinx GPRS Cube
+vipremoteagent 3752/tcp # Vigil-IP RemoteAgent
+vipremoteagent 3752/udp # Vigil-IP RemoteAgent
+nattyserver 3753/tcp # NattyServer Port
+nattyserver 3753/udp # NattyServer Port
+timestenbroker 3754/tcp # TimesTen Broker Port
+timestenbroker 3754/udp # TimesTen Broker Port
+sas-remote-hlp 3755/tcp # SAS Remote Help Server
+sas-remote-hlp 3755/udp # SAS Remote Help Server
+canon-capt 3756/tcp # Canon CAPT Port
+canon-capt 3756/udp # Canon CAPT Port
+grf-port 3757/tcp # GRF Server Port
+grf-port 3757/udp # GRF Server Port
+apw-registry 3758/tcp # apw RMI registry
+apw-registry 3758/udp # apw RMI registry
+exapt-lmgr 3759/tcp # Exapt License Manager
+exapt-lmgr 3759/udp # Exapt License Manager
+adtempusclient 3760/tcp # adTempus Client
+adtempusclient 3760/udp # adTEmpus Client
+gsakmp 3761/tcp # gsakmp port
+gsakmp 3761/udp # gsakmp port
+gbs-smp 3762/tcp # GBS SnapMail Protocol
+gbs-smp 3762/udp # GBS SnapMail Protocol
+xo-wave 3763/tcp # XO Wave Control Port
+xo-wave 3763/udp # XO Wave Control Port
+mni-prot-rout 3764/tcp # MNI Protected Routing
+mni-prot-rout 3764/udp # MNI Protected Routing
+rtraceroute 3765/tcp # Remote Traceroute
+rtraceroute 3765/udp # Remote Traceroute
+sitewatch-s 3766/tcp # SSL e-watch sitewatch server
+listmgr-port 3767/tcp # ListMGR Port
+listmgr-port 3767/udp # ListMGR Port
+rblcheckd 3768/tcp # rblcheckd server daemon
+rblcheckd 3768/udp # rblcheckd server daemon
+haipe-otnk 3769/tcp # HAIPE Network Keying
+haipe-otnk 3769/udp # HAIPE Network Keying
+cindycollab 3770/tcp # Cinderella Collaboration
+cindycollab 3770/udp # Cinderella Collaboration
+paging-port 3771/tcp # RTP Paging Port
+paging-port 3771/udp # RTP Paging Port
+ctp 3772/tcp # Chantry Tunnel Protocol
+ctp 3772/udp # Chantry Tunnel Protocol
+ctdhercules 3773/tcp # ctdhercules
+ctdhercules 3773/udp # ctdhercules
+zicom 3774/tcp # ZICOM
+zicom 3774/udp # ZICOM
+ispmmgr 3775/tcp # ISPM Manager Port
+ispmmgr 3775/udp # ISPM Manager Port
+dvcprov-port 3776/tcp # Device Provisioning Port
+dvcprov-port 3776/udp # Device Provisioning Port
+jibe-eb 3777/tcp # Jibe EdgeBurst
+jibe-eb 3777/udp # Jibe EdgeBurst
+c-h-it-port 3778/tcp # Cutler-Hammer IT Port
+c-h-it-port 3778/udp # Cutler-Hammer IT Port
+cognima 3779/tcp # Cognima Replication
+cognima 3779/udp # Cognima Replication
+nnp 3780/tcp # Nuzzler Network Protocol
+nnp 3780/udp # Nuzzler Network Protocol
+abcvoice-port 3781/tcp # ABCvoice server port
+abcvoice-port 3781/udp # ABCvoice server port
+iso-tp0s 3782/tcp # Secure ISO TP0 port
+iso-tp0s 3782/udp # Secure ISO TP0 port
+bim-pem 3783/tcp # Impact Mgr./PEM Gateway
+bim-pem 3783/udp # Impact Mgr./PEM Gateway
+bfd-control 3784/tcp # BFD Control Protocol
+bfd-control 3784/udp # BFD Control Protocol
+bfd-echo 3785/tcp # BFD Echo Protocol
+bfd-echo 3785/udp # BFD Echo Protocol
+upstriggervsw 3786/tcp # VSW Upstrigger port
+upstriggervsw 3786/udp # VSW Upstrigger port
+fintrx 3787/tcp # Fintrx
+fintrx 3787/udp # Fintrx
+isrp-port 3788/tcp # SPACEWAY Routing port
+isrp-port 3788/udp # SPACEWAY Routing port
+remotedeploy 3789/tcp # RemoteDeploy Administration Port
+remotedeploy 3789/udp # RemoteDeploy Administration Port
+quickbooksrds 3790/tcp # QuickBooks RDS
+quickbooksrds 3790/udp # QuickBooks RDS
+tvnetworkvideo 3791/tcp # TV NetworkVideo Data port
+tvnetworkvideo 3791/udp # TV NetworkVideo Data port
+sitewatch 3792/tcp # e-Watch Corporation SiteWatch
+sitewatch 3792/udp # e-Watch Corporation SiteWatch
+dcsoftware 3793/tcp # DataCore Software
+dcsoftware 3793/udp # DataCore Software
+jaus 3794/tcp # JAUS Robots
+jaus 3794/udp # JAUS Robots
+myblast 3795/tcp # myBLAST Mekentosj port
+myblast 3795/udp # myBLAST Mekentosj port
+spw-dialer 3796/tcp # Spaceway Dialer
+spw-dialer 3796/udp # Spaceway Dialer
+idps 3797/tcp # idps
+idps 3797/udp # idps
+minilock 3798/tcp # Minilock
+minilock 3798/udp # Minilock
+radius-dynauth 3799/tcp # RADIUS Dynamic Authorization
+radius-dynauth 3799/udp # RADIUS Dynamic Authorization
+pwgpsi 3800/tcp # Print Services Interface
+pwgpsi 3800/udp # Print Services Interface
+ibm-mgr 3801/tcp # ibm manager service
+ibm-mgr 3801/udp # ibm manager service
+vhd 3802/tcp # VHD
+vhd 3802/udp # VHD
+soniqsync 3803/tcp # SoniqSync
+soniqsync 3803/udp # SoniqSync
+iqnet-port 3804/tcp # Harman IQNet Port
+iqnet-port 3804/udp # Harman IQNet Port
+tcpdataserver 3805/tcp # ThorGuard Server Port
+tcpdataserver 3805/udp # ThorGuard Server Port
+wsmlb 3806/tcp # Remote System Manager
+wsmlb 3806/udp # Remote System Manager
+spugna 3807/tcp # SpuGNA Communication Port
+spugna 3807/udp # SpuGNA Communication Port
+sun-as-iiops-ca 3808/tcp # Sun App Svr-IIOPClntAuth
+sun-as-iiops-ca 3808/udp # Sun App Svr-IIOPClntAuth
+apocd 3809/tcp # Java Desktop System Configuration Agent
+apocd 3809/udp # Java Desktop System Configuration Agent
+wlanauth 3810/tcp # WLAN AS server
+wlanauth 3810/udp # WLAN AS server
+amp 3811/tcp # AMP
+amp 3811/udp # AMP
+neto-wol-server 3812/tcp # netO WOL Server
+neto-wol-server 3812/udp # netO WOL Server
+rap-ip 3813/tcp # Rhapsody Interface Protocol
+rap-ip 3813/udp # Rhapsody Interface Protocol
+neto-dcs 3814/tcp # netO DCS
+neto-dcs 3814/udp # netO DCS
+lansurveyorxml 3815/tcp # LANsurveyor XML
+lansurveyorxml 3815/udp # LANsurveyor XML
+sunlps-http 3816/tcp # Sun Local Patch Server
+sunlps-http 3816/udp # Sun Local Patch Server
+tapeware 3817/tcp # Yosemite Tech Tapeware
+tapeware 3817/udp # Yosemite Tech Tapeware
+crinis-hb 3818/tcp # Crinis Heartbeat
+crinis-hb 3818/udp # Crinis Heartbeat
+epl-slp 3819/tcp # EPL Sequ Layer Protocol
+epl-slp 3819/udp # EPL Sequ Layer Protocol
+scp 3820/tcp # Siemens AuD SCP
+scp 3820/udp # Siemens AuD SCP
+pmcp 3821/tcp # ATSC PMCP Standard
+pmcp 3821/udp # ATSC PMCP Standard
+acp-discovery 3822/tcp # Compute Pool Discovery
+acp-discovery 3822/udp # Compute Pool Discovery
+acp-conduit 3823/tcp # Compute Pool Conduit
+acp-conduit 3823/udp # Compute Pool Conduit
+acp-policy 3824/tcp # Compute Pool Policy
+acp-policy 3824/udp # Compute Pool Policy
+ffserver 3825/tcp # Antera FlowFusion Process Simulation
+ffserver 3825/udp # Antera FlowFusion Process Simulation
+warmux 3826/tcp # WarMUX game server
+warmux 3826/udp # WarMUX game server
+netmpi 3827/tcp # Netadmin Systems MPI service
+netmpi 3827/udp # Netadmin Systems MPI service
+neteh 3828/tcp # Netadmin Systems Event Handler
+neteh 3828/udp # Netadmin Systems Event Handler
+neteh-ext 3829/tcp # Netadmin Systems Event Handler External
+neteh-ext 3829/udp # Netadmin Systems Event Handler External
+cernsysmgmtagt 3830/tcp # Cerner System Management Agent
+cernsysmgmtagt 3830/udp # Cerner System Management Agent
+dvapps 3831/tcp # Docsvault Application Service
+dvapps 3831/udp # Docsvault Application Service
+xxnetserver 3832/tcp # xxNETserver
+xxnetserver 3832/udp # xxNETserver
+aipn-auth 3833/tcp # AIPN LS Authentication
+aipn-auth 3833/udp # AIPN LS Authentication
+spectardata 3834/tcp # Spectar Data Stream Service
+spectardata 3834/udp # Spectar Data Stream Service
+spectardb 3835/tcp # Spectar Database Rights Service
+spectardb 3835/udp # Spectar Database Rights Service
+markem-dcp 3836/tcp # MARKEM NEXTGEN DCP
+markem-dcp 3836/udp # MARKEM NEXTGEN DCP
+mkm-discovery 3837/tcp # MARKEM Auto-Discovery
+mkm-discovery 3837/udp # MARKEM Auto-Discovery
+sos 3838/tcp # Scito Object Server
+sos 3838/udp # Scito Object Server
+amx-rms 3839/tcp # AMX Resource Management Suite
+amx-rms 3839/udp # AMX Resource Management Suite
+flirtmitmir 3840/tcp # www.FlirtMitMir.de
+flirtmitmir 3840/udp # www.FlirtMitMir.de
+shiprush-db-svr 3841/tcp # ShipRush Database Server
+nhci 3842/tcp # NHCI status port
+nhci 3842/udp # NHCI status port
+quest-agent 3843/tcp # Quest Common Agent
+quest-agent 3843/udp # Quest Common Agent
+rnm 3844/tcp # RNM
+rnm 3844/udp # RNM
+v-one-spp 3845/tcp # V-ONE Single Port Proxy
+v-one-spp 3845/udp # V-ONE Single Port Proxy
+an-pcp 3846/tcp # Astare Network PCP
+an-pcp 3846/udp # Astare Network PCP
+msfw-control 3847/tcp # MS Firewall Control
+msfw-control 3847/udp # MS Firewall Control
+item 3848/tcp # IT Environmental Monitor
+item 3848/udp # IT Environmental Monitor
+spw-dnspreload 3849/tcp # SPACEWAY DNS Preload
+spw-dnspreload 3849/udp # SPACEWAY DNS Prelaod
+qtms-bootstrap 3850/tcp # QTMS Bootstrap Protocol
+qtms-bootstrap 3850/udp # QTMS Bootstrap Protocol
+spectraport 3851/tcp # SpectraTalk Port
+spectraport 3851/udp # SpectraTalk Port
+sse-app-config 3852/tcp # SSE App Configuration
+sse-app-config 3852/udp # SSE App Configuration
+sscan 3853/tcp # SONY scanning protocol
+sscan 3853/udp # SONY scanning protocol
+stryker-com 3854/tcp # Stryker Comm Port
+stryker-com 3854/udp # Stryker Comm Port
+opentrac 3855/tcp # OpenTRAC
+opentrac 3855/udp # OpenTRAC
+informer 3856/tcp # INFORMER
+informer 3856/udp # INFORMER
+trap-port 3857/tcp # Trap Port
+trap-port 3857/udp # Trap Port
+trap-port-mom 3858/tcp # Trap Port MOM
+trap-port-mom 3858/udp # Trap Port MOM
+nav-port 3859/tcp # Navini Port
+nav-port 3859/udp # Navini Port
+sasp 3860/tcp # Server/Application State Protocol (SASP)
+sasp 3860/udp # Server/Application State Protocol (SASP)
+winshadow-hd 3861/tcp # winShadow Host Discovery
+winshadow-hd 3861/udp # winShadow Host Discovery
+giga-pocket 3862/tcp # GIGA-POCKET
+giga-pocket 3862/udp # GIGA-POCKET
+asap-tcp 3863/tcp # asap tcp port
+asap-udp 3863/udp # asap udp port
+asap-sctp 3863/sctp # asap sctp
+asap-tcp-tls 3864/tcp # asap/tls tcp port
+asap-sctp-tls 3864/sctp # asap-sctp/tls
+xpl 3865/tcp # xpl automation protocol
+xpl 3865/udp # xpl automation protocol
+dzdaemon 3866/tcp # Sun SDViz DZDAEMON Port
+dzdaemon 3866/udp # Sun SDViz DZDAEMON Port
+dzoglserver 3867/tcp # Sun SDViz DZOGLSERVER Port
+dzoglserver 3867/udp # Sun SDViz DZOGLSERVER Port
+diameter 3868/tcp # DIAMETER
+diameter 3868/sctp # DIAMETER
+ovsam-mgmt 3869/tcp # hp OVSAM MgmtServer Disco
+ovsam-mgmt 3869/udp # hp OVSAM MgmtServer Disco
+ovsam-d-agent 3870/tcp # hp OVSAM HostAgent Disco
+ovsam-d-agent 3870/udp # hp OVSAM HostAgent Disco
+avocent-adsap 3871/tcp # Avocent DS Authorization
+avocent-adsap 3871/udp # Avocent DS Authorization
+oem-agent 3872/tcp # OEM Agent
+oem-agent 3872/udp # OEM Agent
+fagordnc 3873/tcp # fagordnc
+fagordnc 3873/udp # fagordnc
+sixxsconfig 3874/tcp # SixXS Configuration
+sixxsconfig 3874/udp # SixXS Configuration
+pnbscada 3875/tcp # PNBSCADA
+pnbscada 3875/udp # PNBSCADA
+dl_agent 3876/tcp dl-agent # DirectoryLockdown Agent
+dl_agent 3876/udp dl-agent # DirectoryLockdown Agent
+xmpcr-interface 3877/tcp # XMPCR Interface Port
+xmpcr-interface 3877/udp # XMPCR Interface Port
+fotogcad 3878/tcp # FotoG CAD interface
+fotogcad 3878/udp # FotoG CAD interface
+appss-lm 3879/tcp # appss license manager
+appss-lm 3879/udp # appss license manager
+igrs 3880/tcp # IGRS
+igrs 3880/udp # IGRS
+idac 3881/tcp # Data Acquisition and Control
+idac 3881/udp # Data Acquisition and Control
+msdts1 3882/tcp # DTS Service Port
+msdts1 3882/udp # DTS Service Port
+vrpn 3883/tcp # VR Peripheral Network
+vrpn 3883/udp # VR Peripheral Network
+softrack-meter 3884/tcp # SofTrack Metering
+softrack-meter 3884/udp # SofTrack Metering
+topflow-ssl 3885/tcp # TopFlow SSL
+topflow-ssl 3885/udp # TopFlow SSL
+nei-management 3886/tcp # NEI management port
+nei-management 3886/udp # NEI management port
+ciphire-data 3887/tcp # Ciphire Data Transport
+ciphire-data 3887/udp # Ciphire Data Transport
+ciphire-serv 3888/tcp # Ciphire Services
+ciphire-serv 3888/udp # Ciphire Services
+dandv-tester 3889/tcp # D and V Tester Control Port
+dandv-tester 3889/udp # D and V Tester Control Port
+ndsconnect 3890/tcp # Niche Data Server Connect
+ndsconnect 3890/udp # Niche Data Server Connect
+rtc-pm-port 3891/tcp # Oracle RTC-PM port
+rtc-pm-port 3891/udp # Oracle RTC-PM port
+pcc-image-port 3892/tcp # PCC-image-port
+pcc-image-port 3892/udp # PCC-image-port
+cgi-starapi 3893/tcp # CGI StarAPI Server
+cgi-starapi 3893/udp # CGI StarAPI Server
+syam-agent 3894/tcp # SyAM Agent Port
+syam-agent 3894/udp # SyAM Agent Port
+syam-smc 3895/tcp # SyAm SMC Service Port
+syam-smc 3895/udp # SyAm SMC Service Port
+sdo-tls 3896/tcp # Simple Distributed Objects over TLS
+sdo-tls 3896/udp # Simple Distributed Objects over TLS
+sdo-ssh 3897/tcp # Simple Distributed Objects over SSH
+sdo-ssh 3897/udp # Simple Distributed Objects over SSH
+senip 3898/tcp # IAS, Inc. SmartEye NET Internet Protocol
+senip 3898/udp # IAS, Inc. SmartEye NET Internet Protocol
+itv-control 3899/tcp # ITV Port
+itv-control 3899/udp # ITV Port
+### 3900 tcp/udp reserved udt-os/udt_os
+nimsh 3901/tcp # NIM Service Handler
+nimsh 3901/udp # NIM Service Handler
+nimaux 3902/tcp # NIMsh Auxiliary Port
+nimaux 3902/udp # NIMsh Auxiliary Port
+charsetmgr 3903/tcp # CharsetMGR
+charsetmgr 3903/udp # CharsetMGR
+omnilink-port 3904/tcp # Arnet Omnilink Port
+omnilink-port 3904/udp # Arnet Omnilink Port
+mupdate 3905/tcp # Mailbox Update (MUPDATE) protocol
+mupdate 3905/udp # Mailbox Update (MUPDATE) protocol
+topovista-data 3906/tcp # TopoVista elevation data
+topovista-data 3906/udp # TopoVista elevation data
+imoguia-port 3907/tcp # Imoguia Port
+imoguia-port 3907/udp # Imoguia Port
+hppronetman 3908/tcp # HP Procurve NetManagement
+hppronetman 3908/udp # HP Procurve NetManagement
+surfcontrolcpa 3909/tcp # SurfControl CPA
+surfcontrolcpa 3909/udp # SurfControl CPA
+prnrequest 3910/tcp # Printer Request Port
+prnrequest 3910/udp # Printer Request Port
+prnstatus 3911/tcp # Printer Status Port
+prnstatus 3911/udp # Printer Status Port
+gbmt-stars 3912/tcp # Global Maintech Stars
+gbmt-stars 3912/udp # Global Maintech Stars
+listcrt-port 3913/tcp # ListCREATOR Port
+listcrt-port 3913/udp # ListCREATOR Port
+listcrt-port-2 3914/tcp # ListCREATOR Port 2
+listcrt-port-2 3914/udp # ListCREATOR Port 2
+agcat 3915/tcp # Auto-Graphics Cataloging
+agcat 3915/udp # Auto-Graphics Cataloging
+wysdmc 3916/tcp # WysDM Controller
+wysdmc 3916/udp # WysDM Controller
+aftmux 3917/tcp # AFT multiplex port
+aftmux 3917/udp # AFT multiples port
+pktcablemmcops 3918/tcp # PacketCableMultimediaCOPS
+pktcablemmcops 3918/udp # PacketCableMultimediaCOPS
+hyperip 3919/tcp # HyperIP
+hyperip 3919/udp # HyperIP
+exasoftport1 3920/tcp # Exasoft IP Port
+exasoftport1 3920/udp # Exasoft IP Port
+herodotus-net 3921/tcp # Herodotus Net
+herodotus-net 3921/udp # Herodotus Net
+sor-update 3922/tcp # Soronti Update Port
+sor-update 3922/udp # Soronti Update Port
+symb-sb-port 3923/tcp # Symbian Service Broker
+symb-sb-port 3923/udp # Symbian Service Broker
+mpl-gprs-port 3924/tcp # MPL_GPRS_PORT
+mpl-gprs-port 3924/udp # MPL_GPRS_Port
+zmp 3925/tcp # Zoran Media Port
+zmp 3925/udp # Zoran Media Port
+winport 3926/tcp # WINPort
+winport 3926/udp # WINPort
+natdataservice 3927/tcp # ScsTsr
+natdataservice 3927/udp # ScsTsr
+netboot-pxe 3928/tcp # PXE NetBoot Manager
+netboot-pxe 3928/udp # PXE NetBoot Manager
+smauth-port 3929/tcp # AMS Port
+smauth-port 3929/udp # AMS Port
+syam-webserver 3930/tcp # Syam Web Server Port
+syam-webserver 3930/udp # Syam Web Server Port
+msr-plugin-port 3931/tcp # MSR Plugin Port
+msr-plugin-port 3931/udp # MSR Plugin Port
+dyn-site 3932/tcp # Dynamic Site System
+dyn-site 3932/udp # Dynamic Site System
+plbserve-port 3933/tcp # PL/B App Server User Port
+plbserve-port 3933/udp # PL/B App Server User Port
+sunfm-port 3934/tcp # PL/B File Manager Port
+sunfm-port 3934/udp # PL/B File Manager Port
+sdp-portmapper 3935/tcp # SDP Port Mapper Protocol
+sdp-portmapper 3935/udp # SDP Port Mapper Protocol
+mailprox 3936/tcp # Mailprox
+mailprox 3936/udp # Mailprox
+dvbservdsc 3937/tcp # DVB Service Discovery
+dvbservdsc 3937/udp # DVB Service Discovery
+dbcontrol_agent 3938/tcp dbcontrol-agent # Oracle dbControl Agent po
+dbcontrol_agent 3938/udp dbcontrol-agent # Oracle dbControl Agent po
+aamp 3939/tcp # Anti-virus Application Management Port
+aamp 3939/udp # Anti-virus Application Management Port
+xecp-node 3940/tcp # XeCP Node Service
+xecp-node 3940/udp # XeCP Node Service
+homeportal-web 3941/tcp # Home Portal Web Server
+homeportal-web 3941/udp # Home Portal Web Server
+srdp 3942/tcp # satellite distribution
+srdp 3942/udp # satellite distribution
+tig 3943/tcp # TetraNode Ip Gateway
+tig 3943/udp # TetraNode Ip Gateway
+sops 3944/tcp # S-Ops Management
+sops 3944/udp # S-Ops Management
+emcads 3945/tcp # EMCADS Server Port
+emcads 3945/udp # EMCADS Server Port
+backupedge 3946/tcp # BackupEDGE Server
+backupedge 3946/udp # BackupEDGE Server
+ccp 3947/tcp # Connect and Control Protocol for Consumer, Commercial, and Industrial Electronic Devices
+ccp 3947/udp # Connect and Control Protocol for Consumer, Commercial, and Industrial Electronic Devices
+apdap 3948/tcp # Anton Paar Device Administration Protocol
+apdap 3948/udp # Anton Paar Device Administration Protocol
+drip 3949/tcp # Dynamic Routing Information Protocol
+drip 3949/udp # Dynamic Routing Information Protocol
+namemunge 3950/tcp # Name Munging
+namemunge 3950/udp # Name Munging
+pwgippfax 3951/tcp # PWG IPP Facsimile
+pwgippfax 3951/udp # PWG IPP Facsimile
+i3-sessionmgr 3952/tcp # I3 Session Manager
+i3-sessionmgr 3952/udp # I3 Session Manager
+xmlink-connect 3953/tcp # Eydeas XMLink Connect
+xmlink-connect 3953/udp # Eydeas XMLink Connect
+adrep 3954/tcp # AD Replication RPC
+adrep 3954/udp # AD Replication RPC
+p2pcommunity 3955/tcp # p2pCommunity
+p2pcommunity 3955/udp # p2pCommunity
+gvcp 3956/tcp # GigE Vision Control
+gvcp 3956/udp # GigE Vision Control
+mqe-broker 3957/tcp # MQEnterprise Broker
+mqe-broker 3957/udp # MQEnterprise Broker
+mqe-agent 3958/tcp # MQEnterprise Agent
+mqe-agent 3958/udp # MQEnterprise Agent
+treehopper 3959/tcp # Tree Hopper Networking
+treehopper 3959/udp # Tree Hopper Networking
+bess 3960/tcp # Bess Peer Assessment
+bess 3960/udp # Bess Peer Assessment
+proaxess 3961/tcp # ProAxess Server
+proaxess 3961/udp # ProAxess Server
+sbi-agent 3962/tcp # SBI Agent Protocol
+sbi-agent 3962/udp # SBI Agent Protocol
+thrp 3963/tcp # Teran Hybrid Routing Protocol
+thrp 3963/udp # Teran Hybrid Routing Protocol
+sasggprs 3964/tcp # SASG GPRS
+sasggprs 3964/udp # SASG GPRS
+ati-ip-to-ncpe 3965/tcp # Avanti IP to NCPE API
+ati-ip-to-ncpe 3965/udp # Avanti IP to NCPE API
+bflckmgr 3966/tcp # BuildForge Lock Manager
+bflckmgr 3966/udp # BuildForge Lock Manager
+ppsms 3967/tcp # PPS Message Service
+ppsms 3967/udp # PPS Message Service
+ianywhere-dbns 3968/tcp # iAnywhere DBNS
+ianywhere-dbns 3968/udp # iAnywhere DBNS
+landmarks 3969/tcp # Landmark Messages
+landmarks 3969/udp # Landmark Messages
+lanrevagent 3970/tcp # LANrev Agent
+lanrevagent 3970/udp # LANrev Agent
+lanrevserver 3971/tcp # LANrev Server
+lanrevserver 3971/udp # LANrev Server
+iconp 3972/tcp # ict-control Protocol
+iconp 3972/udp # ict-control Protocol
+progistics 3973/tcp # ConnectShip Progistics
+progistics 3973/udp # ConnectShip Progistics
+citysearch 3974/tcp # Remote Applicant Tracking Service
+citysearch 3974/udp # Remote Applicant Tracking Service
+airshot 3975/tcp # Air Shot
+airshot 3975/udp # Air Shot
+opswagent 3976/tcp # Server Automation Agent
+opswagent 3976/udp # Server Automation Agent
+opswmanager 3977/tcp # Opsware Manager
+opswmanager 3977/udp # Opsware Manager
+secure-cfg-svr 3978/tcp # Secured Configuration Server
+secure-cfg-svr 3978/udp # Secured Configuration Server
+smwan 3979/tcp # Smith Micro Wide Area Network Service
+smwan 3979/udp # Smith Micro Wide Area Network Service
+acms 3980/tcp # Aircraft Cabin Management System
+acms 3980/udp # Aircraft Cabin Management System
+starfish 3981/tcp # Starfish System Admin
+starfish 3981/udp # Starfish System Admin
+eis 3982/tcp # ESRI Image Server
+eis 3982/udp # ESRI Image Server
+eisp 3983/tcp # ESRI Image Service
+eisp 3983/udp # ESRI Image Service
+mapper-nodemgr 3984/tcp # MAPPER network node manager
+mapper-nodemgr 3984/udp # MAPPER network node manager
+mapper-mapethd 3985/tcp # MAPPER TCP/IP server
+mapper-mapethd 3985/udp # MAPPER TCP/IP server
+mapper-ws_ethd 3986/tcp mapper-ws-ethd # MAPPER workstation server
+mapper-ws_ethd 3986/udp mapper-ws-ethd # MAPPER workstation server
+centerline 3987/tcp # Centerline
+centerline 3987/udp # Centerline
+dcs-config 3988/tcp # DCS Configuration Port
+dcs-config 3988/udp # DCS Configuration Port
+bv-queryengine 3989/tcp # BindView-Query Engine
+bv-queryengine 3989/udp # BindView-Query Engine
+bv-is 3990/tcp # BindView-IS
+bv-is 3990/udp # BindView-IS
+bv-smcsrv 3991/tcp # BindView-SMCServer
+bv-smcsrv 3991/udp # BindView-SMCServer
+bv-ds 3992/tcp # BindView-DirectoryServer
+bv-ds 3992/udp # BindView-DirectoryServer
+bv-agent 3993/tcp # BindView-Agent
+bv-agent 3993/udp # BindView-Agent
+iss-mgmt-ssl 3995/tcp # ISS Management Svcs SSL
+iss-mgmt-ssl 3995/udp # ISS Management Svcs SSL
+abcsoftware 3996/tcp # abcsoftware-01
+abcsoftware 3996/udp # abcsoftware-01
+agentsease-db 3997/tcp # aes_db
+agentsease-db 3997/udp # aes_db
+dnx 3998/tcp # Distributed Nagios Executor Service
+dnx 3998/udp # Distributed Nagios Executor Service
+nvcnet 3999/tcp # Norman distributes scanning service
+nvcnet 3999/udp # Norman distributes scanning service
+terabase 4000/tcp # Terabase
+terabase 4000/udp # Terabase
+newoak 4001/tcp # NewOak
+newoak 4001/udp # NewOak
+pxc-spvr-ft 4002/tcp # pxc-spvr-ft
+pxc-spvr-ft 4002/udp # pxc-spvr-ft
+pxc-splr-ft 4003/tcp # pxc-splr-ft
+pxc-splr-ft 4003/udp # pxc-splr-ft
+pxc-roid 4004/tcp # pxc-roid
+pxc-roid 4004/udp # pxc-roid
+pxc-pin 4005/tcp # pxc-pin
+pxc-pin 4005/udp # pxc-pin
+pxc-spvr 4006/tcp # pxc-spvr
+pxc-spvr 4006/udp # pxc-spvr
+pxc-splr 4007/tcp # pxc-splr
+pxc-splr 4007/udp # pxc-splr
+netcheque 4008/tcp # NetCheque accounting
+netcheque 4008/udp # NetCheque accounting
+chimera-hwm 4009/tcp # Chimera HWM
+chimera-hwm 4009/udp # Chimera HWM
+samsung-unidex 4010/tcp # Samsung Unidex
+samsung-unidex 4010/udp # Samsung Unidex
+altserviceboot 4011/tcp # Alternate Service Boot
+pda-gate 4012/tcp # PDA Gate
+pda-gate 4012/udp # PDA Gate
+acl-manager 4013/tcp # ACL Manager
+acl-manager 4013/udp # ACL Manager
+taiclock 4014/tcp # TAICLOCK
+taiclock 4014/udp # TAICLOCK
+talarian-mcast1 4015/tcp # Talarian Mcast
+talarian-mcast1 4015/udp # Talarian Mcast
+talarian-mcast2 4016/tcp # Talarian Mcast
+talarian-mcast2 4016/udp # Talarian Mcast
+talarian-mcast3 4017/tcp # Talarian Mcast
+talarian-mcast3 4017/udp # Talarian Mcast
+talarian-mcast4 4018/tcp # Talarian Mcast
+talarian-mcast4 4018/udp # Talarian Mcast
+talarian-mcast5 4019/tcp # Talarian Mcast
+talarian-mcast5 4019/udp # Talarian Mcast
+trap 4020/tcp # TRAP Port
+trap 4020/udp # TRAP Port
+nexus-portal 4021/tcp # Nexus Portal
+nexus-portal 4021/udp # Nexus Portal
+dnox 4022/tcp # DNOX
+dnox 4022/udp # DNOX
+esnm-zoning 4023/tcp # ESNM Zoning Port
+esnm-zoning 4023/udp # ESNM Zoning Port
+tnp1-port 4024/tcp # TNP1 User Port
+tnp1-port 4024/udp # TNP1 User Port
+partimage 4025/tcp # Partition Image Port
+partimage 4025/udp # Partition Image Port
+as-debug 4026/tcp # Graphical Debug Server
+as-debug 4026/udp # Graphical Debug Server
+bxp 4027/tcp # bitxpress
+bxp 4027/udp # bitxpress
+dtserver-port 4028/tcp # DTServer Port
+dtserver-port 4028/udp # DTServer Port
+ip-qsig 4029/tcp # IP Q signaling protocol
+ip-qsig 4029/udp # IP Q signaling protocol
+jdmn-port 4030/tcp # Accell/JSP Daemon Port
+jdmn-port 4030/udp # Accell/JSP Daemon Port
+suucp 4031/tcp # UUCP over SSL
+suucp 4031/udp # UUCP over SSL
+vrts-auth-port 4032/tcp # VERITAS Authorization Service
+vrts-auth-port 4032/udp # VERITAS Authorization Service
+sanavigator 4033/tcp # SANavigator Peer Port
+sanavigator 4033/udp # SANavigator Peer Port
+ubxd 4034/tcp # Ubiquinox Daemon
+ubxd 4034/udp # Ubiquinox Daemon
+wap-push-http 4035/tcp # WAP Push OTA-HTTP port
+wap-push-http 4035/udp # WAP Push OTA-HTTP port
+wap-push-https 4036/tcp # WAP Push OTA-HTTP secure
+wap-push-https 4036/udp # WAP Push OTA-HTTP secure
+ravehd 4037/tcp # RaveHD network control
+ravehd 4037/udp # RaveHD network control
+fazzt-ptp 4038/tcp # Fazzt Point-To-Point
+fazzt-ptp 4038/udp # Fazzt Point-To-Point
+fazzt-admin 4039/tcp # Fazzt Administration
+fazzt-admin 4039/udp # Fazzt Administration
+yo-main 4040/tcp # Yo.net main service
+yo-main 4040/udp # Yo.net main service
+houston 4041/tcp # Rocketeer-Houston
+houston 4041/udp # Rocketeer-Houston
+ldxp 4042/tcp # LDXP
+ldxp 4042/udp # LDXP
+nirp 4043/tcp # Neighbour Identity Resolution
+nirp 4043/udp # Neighbour Identity Resolution
+ltp 4044/tcp # Location Tracking Protocol
+ltp 4044/udp # Location Tracking Protocol
+acp-proto 4046/tcp # Accounting Protocol
+acp-proto 4046/udp # Accounting Protocol
+ctp-state 4047/tcp # Context Transfer Protocol
+ctp-state 4047/udp # Context Transfer Protocol
+wafs 4049/tcp # Wide Area File Services
+wafs 4049/udp # Wide Area File Services
+cisco-wafs 4050/tcp # Wide Area File Services
+cisco-wafs 4050/udp # Wide Area File Services
+cppdp 4051/tcp # Cisco Peer to Peer Distribution Protocol
+cppdp 4051/udp # Cisco Peer to Peer Distribution Protocol
+interact 4052/tcp # VoiceConnect Interact
+interact 4052/udp # VoiceConnect Interact
+ccu-comm-1 4053/tcp # CosmoCall Universe Communications Port 1
+ccu-comm-1 4053/udp # CosmoCall Universe Communications Port 1
+ccu-comm-2 4054/tcp # CosmoCall Universe Communications Port 2
+ccu-comm-2 4054/udp # CosmoCall Universe Communications Port 2
+ccu-comm-3 4055/tcp # CosmoCall Universe Communications Port 3
+ccu-comm-3 4055/udp # CosmoCall Universe Communications Port 3
+lms 4056/tcp # Location Message Service
+lms 4056/udp # Location Message Service
+wfm 4057/tcp # Servigistics WFM server
+wfm 4057/udp # Servigistics WFM server
+kingfisher 4058/tcp # Kingfisher protocol
+kingfisher 4058/udp # Kingfisher protocol
+dlms-cosem 4059/tcp # DLMS/COSEM
+dlms-cosem 4059/udp # DLMS/COSEM
+dsmeter_iatc 4060/tcp dsmeter-iatc # DSMETER Inter-Agent Transfer Channel
+dsmeter_iatc 4060/udp dsmeter-iatc # DSMETER Inter-Agent Transfer Channel
+ice-location 4061/tcp # Ice Location Service (TCP)
+ice-location 4061/udp # Ice Location Service (TCP)
+ice-slocation 4062/tcp # Ice Location Service (SSL)
+ice-slocation 4062/udp # Ice Location Service (SSL)
+ice-router 4063/tcp # Ice Firewall Traversal Service (TCP)
+ice-router 4063/udp # Ice Firewall Traversal Service (TCP)
+ice-srouter 4064/tcp # Ice Firewall Traversal Service (SSL)
+ice-srouter 4064/udp # Ice Firewall Traversal Service (SSL)
+avanti_cdp 4065/tcp avanti-cdp # Avanti Common Data
+avanti_cdp 4065/udp avanti-cdp # Avanti Common Data
+pmas 4066/tcp # Performance Measurement and Analysis
+pmas 4066/udp # Performance Measurement and Analysis
+idp 4067/tcp # Information Distribution Protocol
+idp 4067/udp # Information Distribution Protocol
+ipfltbcst 4068/tcp # IP Fleet Broadcast
+ipfltbcst 4068/udp # IP Fleet Broadcast
+minger 4069/tcp # Minger Email Address Validation Service
+minger 4069/udp # Minger Email Address Validation Service
+tripe 4070/tcp # Trivial IP Encryption (TrIPE)
+tripe 4070/udp # Trivial IP Encryption (TrIPE)
+aibkup 4071/tcp # Automatically Incremental Backup
+aibkup 4071/udp # Automatically Incremental Backup
+zieto-sock 4072/tcp # Zieto Socket Communications
+zieto-sock 4072/udp # Zieto Socket Communications
+iRAPP 4073/tcp # iRAPP Server Protocol
+iRAPP 4073/udp # iRAPP Server Protocol
+cequint-cityid 4074/tcp # Cequint City ID UI trigger
+cequint-cityid 4074/udp # Cequint City ID UI trigger
+perimlan 4075/tcp # ISC Alarm Message Service
+perimlan 4075/udp # ISC Alarm Message Service
+seraph 4076/tcp # Seraph DCS
+seraph 4076/udp # Seraph DCS
+ascomalarm 4077/udp # Ascom IP Alarming
+cssp 4078/tcp # Coordinated Security Service Protocol
+santools 4079/tcp # SANtools Diagnostic Server
+santools 4079/udp # SANtools Diagnostic Server
+lorica-in 4080/tcp # Lorica inside facing
+lorica-in 4080/udp # Lorica inside facing
+lorica-in-sec 4081/tcp # Lorica inside facing (SSL)
+lorica-in-sec 4081/udp # Lorica inside facing (SSL)
+lorica-out 4082/tcp # Lorica outside facing
+lorica-out 4082/udp # Lorica outside facing
+lorica-out-sec 4083/tcp # Lorica outside facing (SSL)
+lorica-out-sec 4083/udp # Lorica outside facing (SSL)
+fortisphere-vm 4084/udp # Fortisphere VM Service
+ezmessagesrv 4085/tcp # EZNews Newsroom Message Service
+ftsync 4086/udp # Firewall/NAT state table synchronization
+applusservice 4087/tcp # APplus Service
+npsp 4088/tcp # Noah Printing Service Protocol
+opencore 4089/tcp # OpenCORE Remote Control Service
+opencore 4089/udp # OpenCORE Remote Control Service
+omasgport 4090/tcp # OMA BCAST Service Guide
+omasgport 4090/udp # OMA BCAST Service Guide
+ewinstaller 4091/tcp # EminentWare Installer
+ewinstaller 4091/udp # EminentWare Installer
+ewdgs 4092/tcp # EminentWare DGS
+ewdgs 4092/udp # EminentWare DGS
+pvxpluscs 4093/tcp # Pvx Plus CS Host
+pvxpluscs 4093/udp # Pvx Plus CS Host
+sysrqd 4094/tcp # sysrq daemon
+sysrqd 4094/udp # sysrq daemon
+xtgui 4095/tcp # xtgui information service
+xtgui 4095/udp # xtgui information service
+bre 4096/tcp # BRE (Bridge Relay Element)
+bre 4096/udp # BRE (Bridge Relay Element)
+patrolview 4097/tcp # Patrol View
+patrolview 4097/udp # Patrol View
+drmsfsd 4098/tcp # drmsfsd
+drmsfsd 4098/udp # drmsfsd
+dpcp 4099/tcp # DPCP
+dpcp 4099/udp # DPCP
+igo-incognito 4100/tcp # IGo Incognito Data Port
+igo-incognito 4100/udp # IGo Incognito Data Port
+brlp-0 4101/tcp # Braille protocol
+brlp-0 4101/udp # Braille protocol
+brlp-1 4102/tcp # Braille protocol
+brlp-1 4102/udp # Braille protocol
+brlp-2 4103/tcp # Braille protocol
+brlp-2 4103/udp # Braille protocol
+brlp-3 4104/tcp # Braille protocol
+brlp-3 4104/udp # Braille protocol
+shofar 4105/tcp # Shofar
+shofar 4105/udp # Shofar
+synchronite 4106/tcp # Synchronite
+synchronite 4106/udp # Synchronite
+j-ac 4107/tcp # JDL Accounting LAN Service
+j-ac 4107/udp # JDL Accounting LAN Service
+accel 4108/tcp # ACCEL
+accel 4108/udp # ACCEL
+izm 4109/tcp # Instantiated Zero-control Messaging
+izm 4109/udp # Instantiated Zero-control Messaging
+g2tag 4110/tcp # G2 RFID Tag Telemetry Data
+g2tag 4110/udp # G2 RFID Tag Telemetry Data
+xgrid 4111/tcp # Xgrid
+xgrid 4111/udp # Xgrid
+apple-vpns-rp 4112/tcp # Apple VPN Server Reporting Protocol
+apple-vpns-rp 4112/udp # Apple VPN Server Reporting Protocol
+aipn-reg 4113/tcp # AIPN LS Registration
+aipn-reg 4113/udp # AIPN LS Registration
+jomamqmonitor 4114/tcp # JomaMQMonitor
+jomamqmonitor 4114/udp # JomaMQMonitor
+cds 4115/tcp # CDS Transfer Agent
+cds 4115/udp # CDS Transfer Agent
+smartcard-tls 4116/tcp # smartcard-TLS
+smartcard-tls 4116/udp # smartcard-TLS
+hillrserv 4117/tcp # Hillr Connection Manager
+hillrserv 4117/udp # Hillr Connection Manager
+netscript 4118/tcp # Netadmin Systems NETscript service
+netscript 4118/udp # Netadmin Systems NETscript service
+assuria-slm 4119/tcp # Assuria Log Manager
+assuria-slm 4119/udp # Assuria Log Manager
+e-builder 4121/tcp # e-Builder Application Communication
+e-builder 4121/udp # e-Builder Application Communication
+fprams 4122/tcp # Fiber Patrol Alarm Service
+fprams 4122/udp # Fiber Patrol Alarm Service
+z-wave 4123/tcp # Zensys Z-Wave Control Protocol
+z-wave 4123/udp # Zensys Z-Wave Control Protocol
+tigv2 4124/tcp # Rohill TetraNode Ip Gateway v2
+tigv2 4124/udp # Rohill TetraNode Ip Gateway v2
+opsview-envoy 4125/tcp # Opsview Envoy
+opsview-envoy 4125/udp # Opsview Envoy
+ddrepl 4126/tcp # Data Domain Replication Service
+ddrepl 4126/udp # Data Domain Replication Service
+unikeypro 4127/tcp # NetUniKeyServer
+unikeypro 4127/udp # NetUniKeyServer
+nufw 4128/tcp # NuFW decision delegation protocol
+nufw 4128/udp # NuFW decision delegation protocol
+nuauth 4129/tcp # NuFW authentication protocol
+nuauth 4129/udp # NuFW authentication protocol
+fronet 4130/tcp # FRONET message protocol
+fronet 4130/udp # FRONET message protocol
+stars 4131/tcp # Global Maintech Stars
+stars 4131/udp # Global Maintech Stars
+nuts_dem 4132/tcp nuts-dem # NUTS Daemon
+nuts_dem 4132/udp nuts-dem # NUTS Daemon
+nuts_bootp 4133/tcp nuts-bootp # NUTS Bootp Server
+nuts_bootp 4133/udp nuts-bootp # NUTS Bootp Server
+nifty-hmi 4134/tcp # NIFTY-Serve HMI protocol
+nifty-hmi 4134/udp # NIFTY-Serve HMI protocol
+cl-db-attach 4135/tcp # Classic Line Database Server Attach
+cl-db-attach 4135/udp # Classic Line Database Server Attach
+cl-db-request 4136/tcp # Classic Line Database Server Request
+cl-db-request 4136/udp # Classic Line Database Server Request
+cl-db-remote 4137/tcp # Classic Line Database Server Remote
+cl-db-remote 4137/udp # Classic Line Database Server Remote
+nettest 4138/tcp # nettest
+nettest 4138/udp # nettest
+thrtx 4139/tcp # Imperfect Networks Server
+thrtx 4139/udp # Imperfect Networks Server
+cedros_fds 4140/tcp cedros-fds # Cedros Fraud Detection System
+cedros_fds 4140/udp cedros-fds # Cedros Fraud Detection System
+oirtgsvc 4141/tcp # Workflow Server
+oirtgsvc 4141/udp # Workflow Server
+oidocsvc 4142/tcp # Document Server
+oidocsvc 4142/udp # Document Server
+oidsr 4143/tcp # Document Replication
+oidsr 4143/udp # Document Replication
+vvr-control 4145/tcp # VVR Control
+vvr-control 4145/udp # VVR Control
+tgcconnect 4146/tcp # TGCConnect Beacon
+tgcconnect 4146/udp # TGCConnect Beacon
+vrxpservman 4147/tcp # Multum Service Manager
+vrxpservman 4147/udp # Multum Service Manager
+hhb-handheld 4148/tcp # HHB Handheld Client
+hhb-handheld 4148/udp # HHB Handheld Client
+agslb 4149/tcp # A10 GSLB Service
+agslb 4149/udp # A10 GSLB Service
+PowerAlert-nsa 4150/tcp # PowerAlert Network Shutdown Agent
+PowerAlert-nsa 4150/udp # PowerAlert Network Shutdown Agent
+menandmice_noh 4151/tcp menandmice-noh # Men & Mice Remote Control
+menandmice_noh 4151/udp menandmice-noh # Men & Mice Remote Control
+idig_mux 4152/tcp idig-mux # iDigTech Multiplex
+idig_mux 4152/udp idig-mux # iDigTech Multiplex
+mbl-battd 4153/tcp # MBL Remote Battery Monitoring
+mbl-battd 4153/udp # MBL Remote Battery Monitoring
+atlinks 4154/tcp # atlinks device discovery
+atlinks 4154/udp # atlinks device discovery
+bzr 4155/tcp # Bazaar version control system
+bzr 4155/udp # Bazaar version control system
+stat-results 4156/tcp # STAT Results
+stat-results 4156/udp # STAT Results
+stat-scanner 4157/tcp # STAT Scanner Control
+stat-scanner 4157/udp # STAT Scanner Control
+stat-cc 4158/tcp # STAT Command Center
+stat-cc 4158/udp # STAT Command Center
+nss 4159/tcp # Network Security Service
+nss 4159/udp # Network Security Service
+jini-discovery 4160/tcp # Jini Discovery
+jini-discovery 4160/udp # Jini Discovery
+omscontact 4161/tcp # OMS Contact
+omscontact 4161/udp # OMS Contact
+omstopology 4162/tcp # OMS Topology
+omstopology 4162/udp # OMS Topology
+silverpeakpeer 4163/tcp # Silver Peak Peer Protocol
+silverpeakpeer 4163/udp # Silver Peak Peer Protocol
+silverpeakcomm 4164/tcp # Silver Peak Communication Protocol
+silverpeakcomm 4164/udp # Silver Peak Communication Protocol
+altcp 4165/tcp # ArcLink over Ethernet
+altcp 4165/udp # ArcLink over Ethernet
+joost 4166/tcp # Joost Peer to Peer Protocol
+joost 4166/udp # Joost Peer to Peer Protocol
+ddgn 4167/tcp # DeskDirect Global Network
+ddgn 4167/udp # DeskDirect Global Network
+pslicser 4168/tcp # PrintSoft License Server
+pslicser 4168/udp # PrintSoft License Server
+iadt 4169/tcp # Automation Drive Interface Transport
+iadt-disc 4169/udp # Internet ADT Discovery Protocol
+d-cinema-csp 4170/tcp # SMPTE Content Synchonization Protocol
+ml-svnet 4171/tcp # Maxlogic Supervisor Communication
+pcoip 4172/tcp # PC over IP
+pcoip 4172/udp # PC over IP
+mma-discovery 4173/udp # MMA Device Discovery
+smcluster 4174/tcp # StorMagic Cluster Services
+sm-disc 4174/udp # StorMagic Discovery
+bccp 4175/tcp # Brocade Cluster Communication Protocol
+tl-ipcproxy 4176/tcp # Translattice Cluster IPC Proxy
+wello 4177/tcp # Wello P2P pubsub service
+wello 4177/udp # Wello P2P pubsub service
+storman 4178/tcp # StorMan
+storman 4178/udp # StorMan
+MaxumSP 4179/tcp # Maxum Services
+MaxumSP 4179/udp # Maxum Services
+httpx 4180/tcp # HTTPX
+httpx 4180/udp # HTTPX
+macbak 4181/tcp # MacBak
+macbak 4181/udp # MacBak
+pcptcpservice 4182/tcp # Production Company Pro TCP Service
+pcptcpservice 4182/udp # Production Company Pro TCP Service
+cyborgnet 4183/tcp # CyborgNet communications
+cyborgnet 4183/udp # CyborgNet communications
+universe_suite 4184/tcp universe-suite # UNIVERSE SUITE MESSAGE SERVICE
+universe_suite 4184/udp universe-suite # UNIVERSE SUITE MESSAGE SERVICE
+wcpp 4185/tcp # Woven Control Plane Protocol
+wcpp 4185/udp # Woven Control Plane Protocol
+boxbackupstore 4186/tcp # Box Backup Store Service
+csc_proxy 4187/tcp csc-proxy # Cascade Proxy
+vatata 4188/tcp # Vatata Peer to Peer Protocol
+vatata 4188/udp # Vatata Peer to Peer Protocol
+pcep 4189/tcp # Path Computation Element Communication Protocol
+sieve 4190/tcp # ManageSieve Protocol
+dsmipv6 4191/udp # Dual Stack MIPv6 NAT Traversal
+azeti 4192/tcp # Azeti Agent Service
+azeti-bd 4192/udp # azeti blinddate
+pvxplusio 4193/tcp # PxPlus remote file srvr
+eims-admin 4199/tcp # EIMS ADMIN
+eims-admin 4199/udp # EIMS ADMIN
+corelccam 4300/tcp # Corel CCam
+corelccam 4300/udp # Corel CCam
+d-data 4301/tcp # Diagnostic Data
+d-data 4301/udp # Diagnostic Data
+d-data-control 4302/tcp # Diagnostic Data Control
+d-data-control 4302/udp # Diagnostic Data Control
+srcp 4303/tcp # Simple Railroad Command Protocol
+srcp 4303/udp # Simple Railroad Command Protocol
+owserver 4304/tcp # One-Wire Filesystem Server
+owserver 4304/udp # One-Wire Filesystem Server
+batman 4305/tcp # better approach to mobile ad-hoc networking
+batman 4305/udp # better approach to mobile ad-hoc networking
+pinghgl 4306/tcp # Hellgate London
+pinghgl 4306/udp # Hellgate London
+visicron-vs 4307/tcp # Visicron Videoconference Service
+visicron-vs 4307/udp # Visicron Videoconference Service
+compx-lockview 4308/tcp # CompX-LockView
+compx-lockview 4308/udp # CompX-LockView
+dserver 4309/tcp # Exsequi Appliance Discovery
+dserver 4309/udp # Exsequi Appliance Discovery
+mirrtex 4310/tcp # Mir-RT exchange service
+mirrtex 4310/udp # Mir-RT exchange service
+p6ssmc 4311/tcp # P6R Secure Server Management Console
+pscl-mgt 4312/tcp # Parascale Membership Manager
+perrla 4313/tcp # PERRLA User Services
+choiceview-agt 4314/tcp # ChoiceView Agent
+choiceview-clt 4316/tcp # ChoiceView Client
+fdt-rcatp 4320/tcp # FDT Remote Categorization Protocol
+fdt-rcatp 4320/udp # FDT Remote Categorization Protocol
+trim-event 4322/tcp # TRIM Event Service
+trim-event 4322/udp # TRIM Event Service
+trim-ice 4323/tcp # TRIM ICE Service
+trim-ice 4323/udp # TRIM ICE Service
+geognosisman 4325/tcp # Cadcorp GeognoSIS Manager Service
+geognosisman 4325/udp # Cadcorp GeognoSIS Manager Service
+geognosis 4326/tcp # Cadcorp GeognoSIS Service
+geognosis 4326/udp # Cadcorp GeognoSIS Service
+jaxer-web 4327/tcp # Jaxer Web Protocol
+jaxer-web 4327/udp # Jaxer Web Protocol
+jaxer-manager 4328/tcp # Jaxer Manager Command Protocol
+jaxer-manager 4328/udp # Jaxer Manager Command Protocol
+publiqare-sync 4329/tcp # PubliQare Distributed Environment Synchronisation Engine
+dey-sapi 4330/tcp # DEY Storage Administration
+ktickets-rest 4331/tcp # ktickets REST API for event management and ticketing systems
+ahsp 4333/tcp # ArrowHead Service Protocol
+ahsp 4333/udp # ArrowHead Service Protocol
+ahsp 4333/sctp # ArrowHead Service Protocol
+netconf-ch-ssh 4334/tcp # NETCONF Call Home (SSH)
+netconf-ch-tls 4335/tcp # NETCONF Call Home (TLS)
+restconf-ch-tls 4336/tcp # RESTCONF Call Home (TLS)
+gaia 4340/tcp # Gaia Connector Protocol
+gaia 4340/udp # Gaia Connector Protocol
+lisp-data 4341/tcp # LISP Data Packets
+lisp-data 4341/udp # LISP Data Packets
+lisp-cons 4342/tcp # LISP-CONS Control
+lisp-control 4342/udp # LISP Control Packets
+unicall 4343/tcp # UNICALL
+unicall 4343/udp # UNICALL
+vinainstall 4344/tcp # VinaInstall
+vinainstall 4344/udp # VinaInstall
+m4-network-as 4345/tcp # Macro 4 Network AS
+m4-network-as 4345/udp # Macro 4 Network AS
+elanlm 4346/tcp # ELAN LM
+elanlm 4346/udp # ELAN LM
+lansurveyor 4347/tcp # LAN Surveyor
+lansurveyor 4347/udp # LAN Surveyor
+itose 4348/tcp # ITOSE
+itose 4348/udp # ITOSE
+fsportmap 4349/tcp # File System Port Map
+fsportmap 4349/udp # File System Port Map
+net-device 4350/tcp # Net Device
+net-device 4350/udp # Net Device
+plcy-net-svcs 4351/tcp # PLCY Net Services
+plcy-net-svcs 4351/udp # PLCY Net Services
+pjlink 4352/tcp # Projector Link
+pjlink 4352/udp # Projector Link
+f5-iquery 4353/tcp # F5 iQuery
+f5-iquery 4353/udp # F5 iQuery
+qsnet-trans 4354/tcp # QSNet Transmitter
+qsnet-trans 4354/udp # QSNet Transmitter
+qsnet-workst 4355/tcp # QSNet Workstation
+qsnet-workst 4355/udp # QSNet Workstation
+qsnet-assist 4356/tcp # QSNet Assistant
+qsnet-assist 4356/udp # QSNet Assistant
+qsnet-cond 4357/tcp # QSNet Conductor
+qsnet-cond 4357/udp # QSNet Conductor
+qsnet-nucl 4358/tcp # QSNet Nucleus
+qsnet-nucl 4358/udp # QSNet Nucleus
+omabcastltkm 4359/tcp # OMA BCAST Long-Term Key Messages
+omabcastltkm 4359/udp # OMA BCAST Long-Term Key Messages
+matrix_vnet 4360/tcp matrix-vnet # Matrix VNet Communication Protocol
+nacnl 4361/udp # Navcom Discovery and Control Port
+afore-vdp-disc 4362/udp # AFORE vNode Discovery protocol
+shadowstream 4366/udp # ShadowStream System
+wxbrief 4368/tcp # WeatherBrief Direct
+wxbrief 4368/udp # WeatherBrief Direct
+epmd 4369/tcp # Erlang Port Mapper Daemon
+epmd 4369/udp # Erlang Port Mapper Daemon
+elpro_tunnel 4370/tcp elpro-tunnel # ELPRO V2 Protocol Tunnel
+elpro_tunnel 4370/udp elpro-tunnel # ELPRO V2 Protocol Tunnel
+
+l2c-control 4371/tcp # LAN2CAN Control
+l2c-disc 4371/udp # LAN2CAN Discovery
+l2c-data 4372/tcp # LAN2CAN Data
+l2c-data 4372/udp # LAN2CAN Data
+remctl 4373/tcp # Remote Authenticated Command Service
+remctl 4373/udp # Remote Authenticated Command Service
+psi-ptt 4374/tcp # PSI Push-to-Talk Protocol
+tolteces 4375/tcp # Toltec EasyShare
+tolteces 4375/udp # Toltec EasyShare
+bip 4376/tcp # BioAPI Interworking
+bip 4376/udp # BioAPI Interworking
+cp-spxsvr 4377/tcp # Cambridge Pixel SPx Server
+cp-spxsvr 4377/udp # Cambridge Pixel SPx Server
+cp-spxdpy 4378/tcp # Cambridge Pixel SPx Display
+cp-spxdpy 4378/udp # Cambridge Pixel SPx Display
+ctdb 4379/tcp # CTDB
+ctdb 4379/udp # CTDB
+xandros-cms 4389/tcp # Xandros Community Management Service
+xandros-cms 4389/udp # Xandros Community Management Service
+wiegand 4390/tcp # Physical Access Control
+wiegand 4390/udp # Physical Access Control
+apwi-imserver 4391/tcp # American Printware IMServer Protocol
+apwi-rxserver 4392/tcp # American Printware RXServer Protocol
+apwi-rxspooler 4393/tcp # American Printware RXSpooler Protocol
+apwi-disc 4394/udp # American Printware Discovery
+omnivisionesx 4395/tcp # OmniVision communication for Virtual environments
+omnivisionesx 4395/udp # OmniVision communication for Virtual environments
+fly 4396/tcp # Fly Object Space
+ds-srv 4400/tcp # ASIGRA Services
+ds-srv 4400/udp # ASIGRA Services
+ds-srvr 4401/tcp # ASIGRA Televaulting DS-System Service
+ds-srvr 4401/udp # ASIGRA Televaulting DS-System Service
+ds-clnt 4402/tcp # ASIGRA Televaulting DS-Client Service
+ds-clnt 4402/udp # ASIGRA Televaulting DS-Client Service
+ds-user 4403/tcp # ASIGRA Televaulting DS-Client Monitoring/Management
+ds-user 4403/udp # ASIGRA Televaulting DS-Client Monitoring/Management
+ds-admin 4404/tcp # ASIGRA Televaulting DS-System Monitoring/Management
+ds-admin 4404/udp # ASIGRA Televaulting DS-System Monitoring/Management
+ds-mail 4405/tcp # ASIGRA Televaulting Message Level Restore service
+ds-mail 4405/udp # ASIGRA Televaulting Message Level Restore service
+ds-slp 4406/tcp # ASIGRA Televaulting DS-Sleeper Service
+ds-slp 4406/udp # ASIGRA Televaulting DS-Sleeper Service
+nacagent 4407/tcp # Network Access Control Agent
+slscc 4408/tcp # SLS Technology Control Centre
+netcabinet-com 4409/tcp # Net-Cabinet comunication
+itwo-server 4410/tcp # RIB iTWO Application Server
+found 4411/tcp # Found Messaging Protocol
+smallchat 4412/udp # SmallChat
+avi-nms 4413/tcp # AVI Systems NMS
+avi-nms-disc 4413/udp # AVI Systems NMS
+updog 4414/tcp # Updog Monitoring and Status
+brcd-vr-req 4415/tcp # Brocade Virtual Router
+pjj-player 4416/tcp # PJJ Media Player
+pjj-player-disc 4416/udp # PJJ Media Player discovery
+workflowdir 4417/tcp # Workflow Director
+axysbridge 4418/udp # AXYS communication protocol
+cbp 4419/tcp # Colnod Binary Protocol
+nvm-express 4420/tcp # NVM Express over Fabrics
+nvm-express 4420/udp # NVM Express over Fabrics
+scaleft 4421/tcp # Management for Cloud
+tsepisp 4422/tcp # TSEP Installation Service
+thingkit 4423/tcp # thingkit secure mesh
+netrockey6 4425/tcp # NetROCKEY6 SMART Plus Service
+netrockey6 4425/udp # NetROCKEY6 SMART Plus Service
+beacon-port-2 4426/tcp # SMARTS Beacon Port
+beacon-port-2 4426/udp # SMARTS Beacon Port
+drizzle 4427/tcp # Drizzle database server
+omviserver 4428/tcp # OMV-Investigation Server-Client
+omviagent 4429/tcp # OMV Investigation Agent-Server
+sqlserver 4430/tcp # REAL SQL Server
+rsqlserver 4430/udp # REAL SQL Server
+wspipe 4431/tcp # adWISE Pipe
+l-acoustics 4432/tcp # L-ACOUSTICS management
+l-acoustics 4432/udp # L-ACOUSTICS management
+vop 4433/tcp # Versile Object Protocol
+netblox 4441/udp # Netblox Protocol
+saris 4442/tcp # Saris
+saris 4442/udp # Saris
+pharos 4443/tcp # Pharos
+pharos 4443/udp # Pharos
+upnotifyp 4445/tcp # UPNOTIFYP
+upnotifyp 4445/udp # UPNOTIFYP
+n1-fwp 4446/tcp # N1-FWP
+n1-fwp 4446/udp # N1-FWP
+n1-rmgmt 4447/tcp # N1-RMGMT
+n1-rmgmt 4447/udp # N1-RMGMT
+asc-slmd 4448/tcp # ASC Licence Manager
+asc-slmd 4448/udp # ASC Licence Manager
+privatewire 4449/tcp # PrivateWire
+privatewire 4449/udp # PrivateWire
+camp 4450/tcp # Camp
+camp 4450/udp # Camp
+ctisystemmsg 4451/tcp # CTI System Msg
+ctisystemmsg 4451/udp # CTI System Msg
+ctiprogramload 4452/tcp # CTI Program Load
+ctiprogramload 4452/udp # CTI Program Load
+nssalertmgr 4453/tcp # NSS Alert Manager
+nssalertmgr 4453/udp # NSS Alert Manager
+nssagentmgr 4454/tcp # NSS Agent Manager
+nssagentmgr 4454/udp # NSS Agent Manager
+prchat-user 4455/tcp # PR Chat User
+prchat-user 4455/udp # PR Chat User
+prchat-server 4456/tcp # PR Chat Server
+prchat-server 4456/udp # PR Chat Server
+prRegister 4457/tcp # PR Register
+prRegister 4457/udp # PR Register
+mcp 4458/tcp # Matrix Configuration Protocol
+mcp 4458/udp # Matrix Configuration Protocol
+hpssmgmt 4484/tcp # hpssmgmt service
+hpssmgmt 4484/udp # hpssmgmt service
+assyst-dr 4485/tcp # Assyst Data Repository Service
+icms 4486/tcp # Integrated Client Message Service
+icms 4486/udp # Integrated Client Message Service
+prex-tcp 4487/tcp # Protocol for Remote Execution over TCP
+awacs-ice 4488/tcp # Apple Wide Area Connectivity Service ICE Bootstrap
+awacs-ice 4488/udp # Apple Wide Area Connectivity Service ICE Bootstrap
+ipsec-nat-t 4500/tcp # IPsec NAT-Traversal
+ipsec-nat-t 4500/udp # IPsec NAT-Traversal
+a25-fap-fgw 4502/sctp # A25 (FAP-FGW)
+armagetronad 4534/udp # Armagetron Advanced Game
+ehs 4535/tcp # Event Heap Server
+ehs 4535/udp # Event Heap Server
+ehs-ssl 4536/tcp # Event Heap Server SSL
+ehs-ssl 4536/udp # Event Heap Server SSL
+wssauthsvc 4537/tcp # WSS Security Service
+wssauthsvc 4537/udp # WSS Security Service
+swx-gate 4538/tcp # Software Data Exchange Gateway
+swx-gate 4538/udp # Software Data Exchange Gateway
+worldscores 4545/tcp # WorldScores
+worldscores 4545/udp # WorldScores
+sf-lm 4546/tcp # SF License Manager (Sentinel)
+sf-lm 4546/udp # SF License Manager (Sentinel)
+lanner-lm 4547/tcp # Lanner License Manager
+lanner-lm 4547/udp # Lanner License Manager
+synchromesh 4548/tcp # Synchromesh
+synchromesh 4548/udp # Synchromesh
+aegate 4549/tcp # Aegate PMR Service
+aegate 4549/udp # Aegate PMR Service
+gds-adppiw-db 4550/tcp # Perman I Interbase Server
+gds-adppiw-db 4550/udp # Perman I Interbase Server
+ieee-mih 4551/tcp # MIH Services
+ieee-mih 4551/udp # MIH Services
+menandmice-mon 4552/tcp # Men and Mice Monitoring
+menandmice-mon 4552/udp # Men and Mice Monitoring
+icshostsvc 4553/tcp # ICS host services
+msfrs 4554/tcp # MS FRS Replication
+msfrs 4554/udp # MS FRS Replication
+rsip 4555/tcp # RSIP Port
+rsip 4555/udp # RSIP Port
+dtn-bundle 4556/tcp # DTN Bundle TCP CL Protocol
+dtn-bundle 4556/udp # DTN Bundle UDP CL Protocol
+dtn-bundle 4556/dccp # DTN Bundle DCCP CL Protocol
+mtcevrunqss 4557/udp # Marathon everRun Quorum Service Server
+mtcevrunqman 4558/udp # Marathon everRun Quorum Service Manager
+hylafax 4559/udp # HylaFAX
+amahi-anywhere 4563/tcp # Amahi Anywhere
+kwtc 4566/tcp # Kids Watch Time Control Service
+kwtc 4566/udp # Kids Watch Time Control Service
+tram 4567/tcp # TRAM
+tram 4567/udp # TRAM
+bmc-reporting 4568/tcp # BMC Reporting
+bmc-reporting 4568/udp # BMC Reporting
+iax 4569/tcp # Inter-Asterisk eXchange
+iax 4569/udp # Inter-Asterisk eXchange
+deploymentmap 4570/tcp # site deployment information for Oracle Communications Suite
+cardifftec-back 4573/tcp # CardiffTec server/client communication
+rid 4590/tcp # RID over HTTP/TLS
+l3t-at-an 4591/tcp # HRPD L3T (AT-AN)
+l3t-at-an 4591/udp # HRPD L3T (AT-AN)
+hrpd-ith-at-an 4592/udp # HRPD-ITH (AT-AN)
+ipt-anri-anri 4593/tcp # IPT (ANRI-ANRI)
+ipt-anri-anri 4593/udp # IPT (ANRI-ANRI)
+ias-session 4594/tcp # IAS-Session (ANRI-ANRI)
+ias-session 4594/udp # IAS-Session (ANRI-ANRI)
+ias-paging 4595/tcp # IAS-Paging (ANRI-ANRI)
+ias-paging 4595/udp # IAS-Paging (ANRI-ANRI)
+ias-neighbor 4596/tcp # IAS-Neighbor (ANRI-ANRI)
+ias-neighbor 4596/udp # IAS-Neighbor (ANRI-ANRI)
+a21-an-1xbs 4597/tcp # A21 (AN-1xBS)
+a21-an-1xbs 4597/udp # A21 (AN-1xBS)
+a16-an-an 4598/tcp # A16 (AN-AN)
+a16-an-an 4598/udp # A16 (AN-AN)
+a17-an-an 4599/tcp # A17 (AN-AN)
+a17-an-an 4599/udp # A17 (AN-AN)
+piranha1 4600/tcp # Piranha1
+piranha1 4600/udp # Piranha1
+piranha2 4601/tcp # Piranha2
+piranha2 4601/udp # Piranha2
+mtsserver 4602/tcp # EAX MTS Server
+menandmice-upg 4603/tcp # Men & Mice Upgrade Agent
+irp 4604/tcp # Identity Registration Protocol
+sixchat 4605/tcp # Direct End to End Secure
+ventoso 4621/udp # remote radio VOIP
+playsta2-app 4658/tcp # PlayStation2 App Port
+playsta2-app 4658/udp # PlayStation2 App Port
+playsta2-lob 4659/tcp # PlayStation2 Lobby Port
+playsta2-lob 4659/udp # PlayStation2 Lobby Port
+smaclmgr 4660/tcp # smaclmgr
+smaclmgr 4660/udp # smaclmgr
+kar2ouche 4661/tcp # Kar2ouche Peer location service
+kar2ouche 4661/udp # Kar2ouche Peer location service
+oms 4662/tcp # OrbitNet Message Service
+oms 4662/udp # OrbitNet Message Service
+noteit 4663/tcp # Note It! Message Service
+noteit 4663/udp # Note It! Message Service
+ems 4664/tcp # Rimage Messaging Server
+ems 4664/udp # Rimage Messaging Server
+contclientms 4665/tcp # Container Client Message Service
+contclientms 4665/udp # Container Client Message Service
+eportcomm 4666/tcp # E-Port Message Service
+eportcomm 4666/udp # E-Port Message Service
+mmacomm 4667/tcp # MMA Comm Services
+mmacomm 4667/udp # MMA Comm Services
+mmaeds 4668/tcp # MMA EDS Service
+mmaeds 4668/udp # MMA EDS Service
+eportcommdata 4669/tcp # E-Port Data Service
+eportcommdata 4669/udp # E-Port Data Service
+light 4670/tcp # Light packets transfer protocol
+light 4670/udp # Light packets transfer protocol
+acter 4671/tcp # Bull RSF action server
+acter 4671/udp # Bull RSF action server
+rfa 4672/tcp # remote file access server
+rfa 4672/udp # remote file access server
+cxws 4673/tcp # CXWS Operations
+cxws 4673/udp # CXWS Operations
+appiq-mgmt 4674/tcp # AppIQ Agent Management
+appiq-mgmt 4674/udp # AppIQ Agent Management
+dhct-status 4675/tcp # BIAP Device Status
+dhct-status 4675/udp # BIAP Device Status
+dhct-alerts 4676/tcp # BIAP Generic Alert
+dhct-alerts 4676/udp # BIAP Generic Alert
+bcs 4677/tcp # Business Continuity Servi
+bcs 4677/udp # Business Continuity Servi
+traversal 4678/tcp # boundary traversal
+traversal 4678/udp # boundary traversal
+mgesupervision 4679/tcp # MGE UPS Supervision
+mgesupervision 4679/udp # MGE UPS Supervision
+mgemanagement 4680/tcp # MGE UPS Management
+mgemanagement 4680/udp # MGE UPS Management
+parliant 4681/tcp # Parliant Telephony System
+parliant 4681/udp # Parliant Telephony System
+finisar 4682/tcp # finisar
+finisar 4682/udp # finisar
+spike 4683/tcp # Spike Clipboard Service
+spike 4683/udp # Spike Clipboard Service
+rfid-rp1 4684/tcp # RFID Reader Protocol 1.0
+rfid-rp1 4684/udp # RFID Reader Protocol 1.0
+autopac 4685/tcp # Autopac Protocol
+autopac 4685/udp # Autopac Protocol
+msp-os 4686/tcp # Manina Service Protocol
+msp-os 4686/udp # Manina Service Protocol
+nst 4687/tcp # Network Scanner Tool FTP
+nst 4687/udp # Network Scanner Tool FTP
+mobile-p2p 4688/tcp # Mobile P2P Service
+mobile-p2p 4688/udp # Mobile P2P Service
+altovacentral 4689/tcp # Altova DatabaseCentral
+altovacentral 4689/udp # Altova DatabaseCentral
+prelude 4690/tcp # Prelude IDS message proto
+prelude 4690/udp # Prelude IDS message proto
+mtn 4691/tcp # Monotone Netsync Protocol
+mtn 4691/udp # Monotone Netsync Protocol
+conspiracy 4692/tcp # Conspiracy messaging
+conspiracy 4692/udp # Conspiracy messaging
+netxms-agent 4700/tcp # NetXMS Agent
+netxms-agent 4700/udp # NetXMS Agent
+netxms-mgmt 4701/tcp # NetXMS Management
+netxms-mgmt 4701/udp # NetXMS Management
+netxms-sync 4702/tcp # NetXMS Server Synchronization
+netxms-sync 4702/udp # NetXMS Server Synchronization
+npqes-test 4703/tcp # Network Performance Quality Evaluation System Test Service
+assuria-ins 4704/tcp # Assuria Insider
+#pulseaudio is not registered in IANA
+pulseaudio 4713/tcp # Pulseaudio
+truckstar 4725/tcp # TruckStar Service
+truckstar 4725/udp # TruckStar Service
+a26-fap-fgw 4726/udp # A26 (FAP-FGW)
+fcis 4727/tcp # F-Link Client Information Service
+fcis-disc 4727/udp # F-Link Client Information Service Discovery
+capmux 4728/tcp # CA Port Multiplexer
+capmux 4728/udp # CA Port Multiplexer
+gsmtap 4729/udp # GSM Interface Tap
+gearman 4730/tcp # Gearman Job Queue System
+gearman 4730/udp # Gearman Job Queue System
+remcap 4731/tcp # Remote Capture Protocol
+ohmtrigger 4732/udp # OHM server trigger
+resorcs 4733/tcp # RES Orchestration Catalog Services
+ipdr-sp 4737/tcp # IPDR/SP
+ipdr-sp 4737/udp # IPDR/SP
+solera-lpn 4738/tcp # SoleraTec Locator
+solera-lpn 4738/udp # SoleraTec Locator
+ipfix 4739/tcp # IP Flow Info Export
+ipfix 4739/udp # IP Flow Info Export
+ipfix 4739/sctp # IP Flow Info Export
+ipfixs 4740/tcp # ipfix protocol over TLS
+ipfixs 4740/sctp # ipfix protocol over DTLS
+ipfixs 4740/udp # ipfix protocol over DTLS
+lumimgrd 4741/tcp # Luminizer Manager
+lumimgrd 4741/udp # Luminizer Manager
+sicct 4742/tcp # SICCT
+sicct-sdp 4742/udp # SICCT Service Discovery Protocol
+openhpid 4743/tcp # openhpi HPI service
+openhpid 4743/udp # openhpi HPI service
+ifsp 4744/tcp # Internet File Synchronization Protocol
+ifsp 4744/udp # Internet File Synchronization Protocol
+fmp 4745/tcp # Funambol Mobile Push
+fmp 4745/udp # Funambol Mobile Push
+buschtrommel 4747/udp # peer-to-peer file exchange
+profilemac 4749/tcp # Profile for Mac
+profilemac 4749/udp # Profile for Mac
+ssad 4750/tcp # Simple Service Auto Discovery
+ssad 4750/udp # Simple Service Auto Discovery
+spocp 4751/tcp # Simple Policy Control Protocol
+spocp 4751/udp # Simple Policy Control Protocol
+snap 4752/tcp # Simple Network Audio Protocol
+snap 4752/udp # Simple Network Audio Protocol
+simon 4753/tcp # Simple Invocation of Methods
+simon-disc 4753/udp # Over Network (SIMON)
+bfd-multi-ctl 4784/tcp # BFD Multihop Control
+bfd-multi-ctl 4784/udp # BFD Multihop Control
+cncp 4785/udp # Cisco Nexus Control Protocol
+smart-install 4786/tcp # Smart Install Service
+sia-ctrl-plane 4787/tcp # Service Insertion Architecture (SIA) Control-Plane
+xmcp 4788/tcp # eXtensible Messaging Client Protocol
+vxlan 4789/udp # Virtual eXtensible Local area network
+vxlan-gpe 4790/udp # Generic Protocol Extension for vxlan
+roce 4791/udp # IP Routable RocE
+iims 4800/tcp # Icona Instant Messenging System
+iims 4800/udp # Icona Instant Messenging System
+iwec 4801/tcp # Icona Web Embedded Chat
+iwec 4801/udp # Icona Web Embedded Chat
+ilss 4802/tcp # Icona License System Server
+ilss 4802/udp # Icona License System Server
+notateit 4803/tcp # Notateit Messaging
+notateit-disc 4803/udp # Notateit Messaging Discovery
+aja-ntv4-disc 4804/udp # AJA ntv4 Video System Discovery
+htcp 4827/tcp # HTCP
+htcp 4827/udp # HTCP
+varadero-0 4837/tcp # Varadero-0
+varadero-0 4837/udp # Varadero-0
+varadero-1 4838/tcp # Varadero-1
+varadero-1 4838/udp # Varadero-1
+varadero-2 4839/tcp # Varadero-2
+varadero-2 4839/udp # Varadero-2
+opcua-tcp 4840/tcp # OPC UA TCP Protocol
+opcua-udp 4840/udp # OPC UA TCP Protocol
+quosa 4841/tcp # QUOSA Virtual Library Service
+quosa 4841/udp # QUOSA Virtual Library Service
+gw-asv 4842/tcp # nCode ICE-flow Library AppServer
+gw-asv 4842/udp # nCode ICE-flow Library AppServer
+opcua-tls 4843/tcp # OPC UA TCP Protocol over TLS/SSL
+opcua-tls 4843/udp # OPC UA TCP Protocol over TLS/SSL
+gw-log 4844/tcp # nCode ICE-flow Library LogServer
+gw-log 4844/udp # nCode ICE-flow Library LogServer
+wcr-remlib 4845/tcp # WordCruncher Remote Library Service
+wcr-remlib 4845/udp # WordCruncher Remote Library Service
+contamac_icm 4846/tcp contamac-icm # Contamac ICM Service
+contamac_icm 4846/udp contamac-icm # Contamac ICM Service
+wfc 4847/tcp # Web Fresh Communication
+wfc 4847/udp # Web Fresh Communication
+appserv-http 4848/tcp # App Server - Admin HTTP
+appserv-http 4848/udp # App Server - Admin HTTP
+appserv-https 4849/tcp # App Server - Admin HTTPS
+appserv-https 4849/udp # App Server - Admin HTTPS
+sun-as-nodeagt 4850/tcp # Sun App Server - NA
+sun-as-nodeagt 4850/udp # Sun App Server - NA
+derby-repli 4851/tcp # Apache Derby Replication
+derby-repli 4851/udp # Apache Derby Replication
+unify-debug 4867/tcp # Unify Debugger
+unify-debug 4867/udp # Unify Debugger
+phrelay 4868/tcp # Photon Relay
+phrelay 4868/udp # Photon Relay
+phrelaydbg 4869/tcp # Photon Relay Debug
+phrelaydbg 4869/udp # Photon Relay Debug
+cc-tracking 4870/tcp # Citcom Tracking Service
+cc-tracking 4870/udp # Citcom Tracking Service
+wired 4871/tcp # Wired
+wired 4871/udp # Wired
+tritium-can 4876/tcp # Tritium CAN Bus Bridge Service
+tritium-can 4876/udp # Tritium CAN Bus Bridge Service
+lmcs 4877/tcp # Lighting Management Control System
+lmcs 4877/udp # Lighting Management Control System
+inst-discovery 4878/udp # Agilent Instrument Discovery
+wsdl-event 4879/tcp # WSDL Event Receiver
+hislip 4880/tcp # IVI High-Speed LAN Instrument Protocol
+socp-t 4881/udp # SOCP Time Synchronization Protocol
+socp-c 4882/udp # SOCP Control Protocol
+wmlserver 4883/tcp # Meier-Phelps License Server
+hivestor 4884/tcp # HiveStor Distributed File System
+hivestor 4884/udp # HiveStor Distributed File System
+abbs 4885/tcp # ABBS
+abbs 4885/udp # ABBS
+lyskom 4894/tcp # LysKOM Protocol A
+lyskom 4894/udp # LysKOM Protocol A
+radmin-port 4899/tcp # RAdmin Port
+radmin-port 4899/udp # RAdmin Port
+hfcs 4900/tcp # HyperFileSQL Client/Server Database Engine
+hfcs 4900/udp # HyperFileSQL Client/Server Database Engine
+flr_agent 4901/tcp flr-agent # FileLocator Remote Search Agent
+magiccontrol 4902/tcp # magicCONROL RF and Data Interface
+lutap 4912/tcp # Technicolor LUT Access Protocol
+lutcp 4913/tcp # LUTher Control Protocol
+bones 4914/tcp # Bones Remote Control
+bones 4914/udp # Bones Remote Control
+frcs 4915/tcp # Fibics Remote Control Service
+an-signaling 4936/udp # Signal protocol port for autonomic networking
+atsc-mh-ssc 4937/udp # ATSC-M/H Service Signaling Channel
+eq-office-4940 4940/tcp # Equitrac Office
+eq-office-4940 4940/udp # Equitrac Office
+eq-office-4941 4941/tcp # Equitrac Office
+eq-office-4941 4941/udp # Equitrac Office
+eq-office-4942 4942/tcp # Equitrac Office
+eq-office-4942 4942/udp # Equitrac Office
+munin 4949/tcp # Munin Graphing Framework
+munin 4949/udp # Munin Graphing Framework
+sybasesrvmon 4950/tcp # Sybase Server Monitor
+sybasesrvmon 4950/udp # Sybase Server Monitor
+pwgwims 4951/tcp # PWG WIMS
+pwgwims 4951/udp # PWG WIMS
+sagxtsds 4952/tcp # SAG Directory Server
+sagxtsds 4952/udp # SAG Directory Server
+dbsyncarbiter 4953/tcp # Synchronization Arbiter
+###UNAUTHORIZED USE: port 4967 by Rockwell FTA#######
+ccss-qmm 4969/tcp # CCSS QMessageMonitor
+ccss-qmm 4969/udp # CCSS QMessageMonitor
+ccss-qsm 4970/tcp # CCSS QSystemMonitor
+ccss-qsm 4970/udp # CCSS QSystemMonitor
+ctxs-vpp 4980/udp # Citrix Virtual Path
+webyast 4984/tcp # WebYast
+gerhcs 4985/tcp # GER HC Standard
+mrip 4986/tcp # Model Railway Interface Program
+mrip 4986/udp # Model Railway Interface Program
+smar-se-port1 4987/tcp # SMAR Ethernet Port 1
+smar-se-port1 4987/udp # SMAR Ethernet Port 1
+smar-se-port2 4988/tcp # SMAR Ethernet Port 2
+smar-se-port2 4988/udp # SMAR Ethernet Port 2
+parallel 4989/tcp # Parallel for GAUSS (tm)
+parallel 4989/udp # Parallel for GAUSS (tm)
+busycal 4990/tcp # BusySync Calendar Synch. Protocol
+busycal 4990/udp # BusySync Calendar Synch. Protocol
+vrt 4991/tcp # VITA Radio Transport
+vrt 4991/udp # VITA Radio Transport
+hfcs-manager 4999/tcp # Hyper File Client/Server Database Engine Manager
+hfcs-manager 4999/udp # Hyper File Client/Server Database Engine Manager
+commplex-main 5000/tcp #
+commplex-main 5000/udp #
+commplex-link 5001/tcp #
+commplex-link 5001/udp #
+fmpro-internal 5003/tcp # FileMaker, Inc. - Proprietary transport
+fmpro-internal 5003/udp # FileMaker, Inc. - Proprietary name binding
+avt-profile-1 5004/tcp # RTP media data [RFC 3551, RFC 4571]
+avt-profile-1 5004/udp # RTP media data [RFC 3551]
+avt-profile-1 5004/dccp # RTP media data [RFC 3551, RFC-ietf-dccp-rtp-07.txt]
+avt-profile-2 5005/tcp # RTP control protocol [RFC 3551, RFC 4571]
+avt-profile-2 5005/udp # RTP control protocol [RFC 3551]
+avt-profile-2 5005/dccp # RTP control protocol [RFC 3551, RFC-ietf-dccp-rtp-07.txt]
+wsm-server 5006/tcp # wsm server
+wsm-server 5006/udp # wsm server
+wsm-server-ssl 5007/tcp # wsm server ssl
+wsm-server-ssl 5007/udp # wsm server ssl
+synapsis-edge 5008/tcp # Synapsis EDGE
+synapsis-edge 5008/udp # Synapsis EDGE
+winfs 5009/tcp # Microsoft Windows Filesystem
+winfs 5009/udp # Microsoft Windows Filesystem
+telelpathstart 5010/tcp # TelepathStart
+telelpathstart 5010/udp # TelepathStart
+telelpathattack 5011/tcp # TelepathAttack
+telelpathattack 5011/udp # TelepathAttack
+nsp 5012/tcp # NetOnTap Service
+nsp 5012/udp # NetOnTap Service
+fmpro-v6 5013/tcp # FileMaker, Inc. - Proprietary transport
+fmpro-v6 5013/udp # FileMaker, Inc. - Proprietary transport
+onpsocket 5014/udp # Overlay Network Protocol
+fmwp 5015/tcp # FileMaker, Inc. - Web publishing
+zenginkyo-1 5020/tcp # zenginkyo-1
+zenginkyo-1 5020/udp # zenginkyo-1
+zenginkyo-2 5021/tcp # zenginkyo-2
+zenginkyo-2 5021/udp # zenginkyo-2
+mice 5022/tcp # mice server
+mice 5022/udp # mice server
+htuilsrv 5023/tcp # Htuil Server for PLD2
+htuilsrv 5023/udp # Htuil Server for PLD2
+scpi-telnet 5024/tcp # SCPI-TELNET
+scpi-telnet 5024/udp # SCPI-TELNET
+scpi-raw 5025/tcp # SCPI-RAW
+scpi-raw 5025/udp # SCPI-RAW
+strexec-d 5026/tcp # Storix I/O daemon (data)
+strexec-d 5026/udp # Storix I/O daemon (data)
+strexec-s 5027/tcp # Storix I/O daemon (stat)
+strexec-s 5027/udp # Storix I/O daemon (stat)
+qvr 5028/tcp # Quiqum Virtual Relais
+infobright 5029/tcp # Infobright Database Server
+infobright 5029/udp # Infobright Database Server
+surfpass 5030/tcp # SurfPass
+surfpass 5030/udp # SurfPass
+dmp 5031/udp # Direct Message Protocol
+signacert-agent 5032/tcp # SignaCert Enterprise Trust Server Agent
+jtnetd-server 5033/tcp # Janstor Secure Data
+jtnetd-status 5034/tcp # Janstor Status
+asnaacceler8db 5042/tcp # asnaacceler8db
+asnaacceler8db 5042/udp # asnaacceler8db
+swxadmin 5043/tcp # ShopWorX Administration
+swxadmin 5043/udp # ShopWorX Administration
+lxi-evntsvc 5044/tcp # LXI Event Service
+lxi-evntsvc 5044/udp # LXI Event Service
+osp 5045/tcp # Open Settlement Protocol
+vpm-udp 5046/udp # Vishay PM UDP Service
+iscape 5047/udp # iSCAPE Data Broadcasting
+texai 5048/tcp # Texai Message Service
+ivocalize 5049/tcp # iVocalize Web Conference
+ivocalize 5049/udp # iVocalize Web Conference
+mmcc 5050/tcp # multimedia conference control tool
+mmcc 5050/udp # multimedia conference control tool
+ita-agent 5051/tcp # ITA Agent
+ita-agent 5051/udp # ITA Agent
+ita-manager 5052/tcp # ITA Manager
+ita-manager 5052/udp # ITA Manager
+rlm 5053/tcp # RLM License Server
+rlm-disc 5053/udp # RLM Discovery Server
+rlm-admin 5054/tcp # RLM administrative interface
+unot 5055/tcp # UNOT
+unot 5055/udp # UNOT
+intecom-ps1 5056/tcp # Intecom Pointspan 1
+intecom-ps1 5056/udp # Intecom Pointspan 1
+intecom-ps2 5057/tcp # Intecom Pointspan 2
+intecom-ps2 5057/udp # Intecom Pointspan 2
+locus-disc 5058/udp # Locus Discovery
+sds 5059/tcp # SIP Directory Services
+sds 5059/udp # SIP Directory Services
+sip 5060/tcp # SIP
+sip 5060/udp # SIP
+sip 5060/sctp # SIP
+sips 5061/tcp # SIP-TLS
+sips 5061/udp # SIP-TLS
+sips 5061/sctp # SIP-TLS
+na-localise 5062/tcp # Localisation access
+na-localise 5062/udp # Localisation access
+csrpc 5063/tcp # centrify secure RPC
+ca-1 5064/tcp # Channel Access 1
+ca-1 5064/udp # Channel Access 1
+ca-2 5065/tcp # Channel Access 2
+ca-2 5065/udp # Channel Access 2
+stanag-5066 5066/tcp # STANAG-5066-SUBNET-INTF
+stanag-5066 5066/udp # STANAG-5066-SUBNET-INTF
+authentx 5067/tcp # Authentx Service
+authentx 5067/udp # Authentx Service
+bitforestsrv 5068/tcp # Bitforest Data Service
+i-net-2000-npr 5069/tcp # I/Net 2000-NPR
+i-net-2000-npr 5069/udp # I/Net 2000-NPR
+vtsas 5070/tcp # VersaTrans Server Agent Service
+vtsas 5070/udp # VersaTrans Server Agent Service
+powerschool 5071/tcp # PowerSchool
+powerschool 5071/udp # PowerSchool
+ayiya 5072/tcp # Anything In Anything
+ayiya 5072/udp # Anything In Anything
+tag-pm 5073/tcp # Advantage Group Port Mgr
+tag-pm 5073/udp # Advantage Group Port Mgr
+alesquery 5074/tcp # ALES Query
+alesquery 5074/udp # ALES Query
+pvaccess 5075/tcp # Experimental Physics and Industrial Control System
+pixelpusher 5078/udp # PixelPusher pixel data
+cp-spxrpts 5079/udp # Cambridge Pixel SPx Reports
+onscreen 5080/tcp # OnScreen Data Collection Service
+onscreen 5080/udp # OnScreen Data Collection Service
+sdl-ets 5081/tcp # SDL - Ent Trans Server
+sdl-ets 5081/udp # SDL - Ent Trans Server
+qcp 5082/tcp # Qpur Communication Protocol
+qcp 5082/udp # Qpur Communication Protocol
+qfp 5083/tcp # Qpur File Protocol
+qfp 5083/udp # Qpur File Protocol
+llrp 5084/tcp # EPCglobal Low-Level Reader Protocol
+llrp 5084/udp # EPCglobal Low-Level Reader Protocol
+encrypted-llrp 5085/tcp # EPCglobal Encrypted LLRP
+encrypted-llrp 5085/udp # EPCglobal Encrypted LLRP
+aprigo-cs 5086/tcp # Aprigo Collection Service
+biotic 5087/tcp # Binary Internet of Things Interoperable Communication
+car 5090/sctp # Candidate AR
+cxtp 5091/sctp # Context Transfer Protocol
+magpie 5092/udp # Magpie Binary
+sentinel-lm 5093/tcp # Sentinel LM
+sentinel-lm 5093/udp # Sentinel LM
+hart-ip 5094/tcp # HART-IP
+hart-ip 5094/udp # HART-IP
+sentlm-srv2srv 5099/tcp # SentLM Srv2Srv
+sentlm-srv2srv 5099/udp # SentLM Srv2Srv
+socalia 5100/tcp # Socalia service mux
+socalia 5100/udp # Socalia service mux
+talarian-tcp 5101/tcp # Talarian_TCP
+talarian-udp 5101/udp # Talarian_UDP
+oms-nonsecure 5102/tcp # Oracle OMS non-secure
+oms-nonsecure 5102/udp # Oracle OMS non-secure
+actifio-c2c 5103/tcp # Actifio C2C
+tinymessage 5104/udp # TinyMessage
+hughes-ap 5105/udp # Hughes Association Protocol
+actifioudsagent 5106/tcp # Actifio UDS Agent
+actifioreplic 5107/tcp # Disk to Disk replication
+taep-as-svc 5111/tcp # TAEP AS service
+taep-as-svc 5111/udp # TAEP AS service
+pm-cmdsvr 5112/tcp # PeerMe Msg Cmd Service
+pm-cmdsvr 5112/udp # PeerMe Msg Cmd Service
+ev-services 5114/tcp # Enterprise Vault Services
+autobuild 5115/tcp # Symantec Autobuild Service
+emb-proj-cmd 5116/udp # EPSON Projecter Image Transfer
+gradecam 5117/tcp # GradeCam Image Processing
+barracuda-bbs 5120/tcp # Barracuda Backup Protocol
+barracuda-bbs 5120/udp # Barracuda Backup Protocol
+nbt-pc 5133/tcp # Policy Commander
+nbt-pc 5133/udp # Policy Commander
+ppactivation 5134/tcp # PP ActivationServer
+erp-scale 5135/tcp # ERP-Scale
+minotaur-sa 5136/udp # Minotaur SA
+ctsd 5137/tcp # MyCTS server port
+ctsd 5137/udp # MyCTS server port
+rmonitor_secure 5145/tcp rmonitor-secure # RMONITOR SECURE
+rmonitor_secure 5145/udp rmonitor-secure # RMONITOR SECURE
+social-alarm 5146/tcp # Social Alarm Service
+atmp 5150/tcp # Ascend Tunnel Management Protocol
+atmp 5150/udp # Ascend Tunnel Management Protocol
+esri_sde 5151/tcp esri-sde # ESRI SDE Instance
+esri_sde 5151/udp esri-sde # ESRI SDE Remote Start
+sde-discovery 5152/tcp # ESRI SDE Instance Discovery
+sde-discovery 5152/udp # ESRI SDE Instance Discovery
+toruxserver 5153/tcp # ToruX Game Server
+bzflag 5154/tcp # BZFlag game server
+bzflag 5154/udp # BZFlag game server
+asctrl-agent 5155/tcp # Oracle asControl Agent
+asctrl-agent 5155/udp # Oracle asControl Agent
+rugameonline 5156/tcp # Russian Online Game
+mediat 5157/tcp # Mediat Remote Object Exchange
+snmpssh 5161/tcp # SNMP over SSH Transport Model
+snmpssh-trap 5162/tcp # SNMP Notification over SSH Transport Model
+sbackup 5163/tcp # Shadow Backup
+vpa 5164/tcp # Virtual Protocol Adapter
+vpa-disc 5164/udp # Virtual Protocol Adapter Discovery
+ife_icorp 5165/tcp ife-icorp # ife_1corp
+ife_icorp 5165/udp ife-icorp # ife_1corp
+winpcs 5166/tcp # WinPCS Service Connection
+winpcs 5166/udp # WinPCS Service Connection
+scte104 5167/tcp # SCTE104 Connection
+scte104 5167/udp # SCTE104 Connection
+scte30 5168/tcp # SCTE30 Connection
+scte30 5168/udp # SCTE30 Connection
+pcoip-mgmt 5172/tcp # PC over IP Endpoint Management
+aol 5190/tcp # America-Online
+aol 5190/udp # America-Online
+aol-1 5191/tcp # AmericaOnline1
+aol-1 5191/udp # AmericaOnline1
+aol-2 5192/tcp # AmericaOnline2
+aol-2 5192/udp # AmericaOnline2
+aol-3 5193/tcp # AmericaOnline3
+aol-3 5193/udp # AmericaOnline3
+cpscomm 5194/tcp # CipherPoint Config Service
+ampl-lic 5195/tcp # AMPL_Optimization - program licenses
+ampl-tableproxy 5196/tcp # AMPL_Optimization - table data
+tunstall-lwp 5197/tcp # Tunstall Lone worker device
+targus-getdata 5200/tcp # TARGUS GetData
+targus-getdata 5200/udp # TARGUS GetData
+targus-getdata1 5201/tcp # TARGUS GetData 1
+targus-getdata1 5201/udp # TARGUS GetData 1
+targus-getdata2 5202/tcp # TARGUS GetData 2
+targus-getdata2 5202/udp # TARGUS GetData 2
+targus-getdata3 5203/tcp # TARGUS GetData 3
+targus-getdata3 5203/udp # TARGUS GetData 3
+nomad 5209/tcp # Nomad Device Video Transfer
+noteza 5215/tcp # NOTEZA Data Safety Service
+noteza 5215/sctp # NOTEZA Data Safety Service
+3exmp 5221/tcp # 3eTI Extensible Management Protocol for OAMP
+xmpp-client 5222/tcp # XMPP Client Connection
+hpvirtgrp 5223/tcp # HP Virtual Machine Group Management
+hpvirtgrp 5223/udp # HP Virtual Machine Group Management
+hpvirtctrl 5224/tcp # HP Virtual Machine Console Operations
+hpvirtctrl 5224/udp # HP Virtual Machine Console Operations
+hp-server 5225/tcp # HP Server
+hp-server 5225/udp # HP Server
+hp-status 5226/tcp # HP Status
+hp-status 5226/udp # HP Status
+perfd 5227/tcp # HP System Performance Metric Service
+perfd 5227/udp # HP System Performance Metric Service
+hpvroom 5228/tcp # HP Virtual Room Service
+jaxflow 5229/tcp # Collector and Forwarder Management
+jaxflow-data 5230/tcp # JaxMP RealFlow application and protocol data
+crusecontrol 5231/tcp # Remote Control of Scan Software for Cruse Scanners
+enfs 5233/tcp # Etinnae Network File Service
+eenet 5234/tcp # EEnet communications
+eenet 5234/udp # EEnet communications
+galaxy-network 5235/tcp # Galaxy Network Service
+galaxy-network 5235/udp # Galaxy Network Service
+padl2sim 5236/tcp #
+padl2sim 5236/udp #
+mnet-discovery 5237/tcp # m-net discovery
+mnet-discovery 5237/udp # m-net discovery
+downtools 5245/tcp # DownTools Control Protocol
+downtools-disc 5245/udp # DownTools Discovery Protocol
+capwap-control 5246/udp # CAPWAP Control Protocol
+capwap-data 5247/udp # CAPWAP Data Protocol
+caacws 5248/tcp # CA Access Control Web Service
+caacws 5248/udp # CA Access Control Web Service
+caaclang2 5249/tcp # CA AC Lang Service
+caaclang2 5249/udp # CA AC Lang Service
+soagateway 5250/tcp # soaGateway
+soagateway 5250/udp # soaGateway
+caevms 5251/tcp # CA eTrust VM Service
+caevms 5251/udp # CA eTrust VM Service
+movaz-ssc 5252/tcp # Movaz SSC
+movaz-ssc 5252/udp # Movaz SSC
+kpdp 5253/tcp # Kohler Power Device Protocol
+logcabin 5254/tcp # LogCabin storage service
+3com-njack-1 5264/tcp # 3Com Network Jack Port 1
+3com-njack-1 5264/udp # 3Com Network Jack Port 1
+3com-njack-2 5265/tcp # 3Com Network Jack Port 2
+3com-njack-2 5265/udp # 3Com Network Jack Port 2
+xmpp-server 5269/tcp # XMPP Server Connection
+cartographerxmp 5270/tcp # Cartographer XMP
+cartographerxmp 5270/udp # Cartographer XMP
+cuelink 5271/tcp # StageSoft CueLink messaging
+cuelink-disc 5271/udp # StageSoft CueLink discovery
+pk 5272/tcp # PK
+pk 5272/udp # PK
+xmpp-bosh 5280/tcp # Bidirectional-streams Over Synchronous HTTP (BOSH)
+undo-lm 5281/tcp # Undo License Manager
+transmit-port 5282/tcp # Marimba Transmitter Port
+transmit-port 5282/udp # Marimba Transmitter Port
+presence 5298/tcp # XMPP Link-Local Messaging
+presence 5298/udp # XMPP Link-Local Messaging
+nlg-data 5299/tcp # NLG Data Service
+nlg-data 5299/udp # NLG Data Service
+hacl-hb 5300/tcp # HA cluster heartbeat
+hacl-hb 5300/udp # HA cluster heartbeat
+hacl-gs 5301/tcp # HA cluster general services
+hacl-gs 5301/udp # HA cluster general services
+hacl-cfg 5302/tcp # HA cluster configuration
+hacl-cfg 5302/udp # HA cluster configuration
+hacl-probe 5303/tcp # HA cluster probing
+hacl-probe 5303/udp # HA cluster probing
+hacl-local 5304/tcp # HA Cluster Commands
+hacl-local 5304/udp # HA Cluster Commands
+hacl-test 5305/tcp # HA Cluster Test
+hacl-test 5305/udp # HA Cluster Test
+sun-mc-grp 5306/tcp # Sun MC Group
+sun-mc-grp 5306/udp # Sun MC Group
+sco-aip 5307/tcp # SCO AIP
+sco-aip 5307/udp # SCO AIP
+jprinter 5309/tcp # J Printer
+jprinter 5309/udp # J Printer
+outlaws 5310/tcp # Outlaws
+outlaws 5310/udp # Outlaws
+permabit-cs 5312/tcp # Permabit Client-Server
+permabit-cs 5312/udp # Permabit Client-Server
+rrdp 5313/tcp # Real-time & Reliable Data
+rrdp 5313/udp # Real-time & Reliable Data
+opalis-rbt-ipc 5314/tcp # opalis-rbt-ipc
+opalis-rbt-ipc 5314/udp # opalis-rbt-ipc
+hacl-poll 5315/tcp # HA Cluster UDP Polling
+hacl-poll 5315/udp # HA Cluster UDP Polling
+hpbladems 5316/tcp # HPBladeSystem Monitor Service
+hpdevms 5317/tcp # HP Device Monitor Service
+pkix-cmc 5318/tcp # PKIX Certificate
+bsfserver-zn 5320/tcp # Webservices-based Zn interface of BSF
+bsfsvr-zn-ssl 5321/tcp # Webservices-based Zn interface of BSF over SSL
+kfserver 5343/tcp # Sculptor Database Server
+kfserver 5343/udp # Sculptor Database Server
+xkotodrcp 5344/tcp # xkoto DRCP
+xkotodrcp 5344/udp # xkoto DRCP
+stuns 5349/tcp stun-behaviors turns # STUN over TLS, TURN over TLS
+stuns 5349/udp stun-behaviors turns # Reserved for a future enhancement of STUN, Reserved for a future enhancement of TURN
+pcp-multicast 5350/tcp # Port Control Protocol
+pcp 5350/udp # Port Control Protocol
+#pcp 5351/udp # Port Control Protocol
+dns-llq 5352/tcp # DNS Long-Lived Queries
+dns-llq 5352/udp # DNS Long-Lived Queries
+mdns 5353/tcp # Multicast DNS
+mdns 5353/udp # Multicast DNS
+mdnsresponder 5354/tcp noclog # Multicast DNS Responder IPC
+mdnsresponder 5354/udp noclog # Multicast DNS Responder IPC
+ms-smlbiz 5356/tcp # Microsoft Small Business
+ms-smlbiz 5356/udp # Microsoft Small Business
+wsdapi 5357/tcp # Web Services for Devices
+wsdapi 5357/udp # Web Services for Devices
+wsdapi-s 5358/tcp # WS for Devices Secured
+wsdapi-s 5358/udp # WS for Devices Secured
+ms-alerter 5359/tcp # Microsoft Alerter
+ms-alerter 5359/udp # Microsoft Alerter
+ms-sideshow 5360/tcp # Protocol for Windows SideShow
+ms-sideshow 5360/udp # Protocol for Windows SideShow
+ms-s-sideshow 5361/tcp # Secure Protocol for Windows SideShow
+ms-s-sideshow 5361/udp # Secure Protocol for Windows SideShow
+serverwsd2 5362/tcp # Microsoft Windows Server WSD2 Service
+serverwsd2 5362/udp # Microsoft Windows Server WSD2 Service
+net-projection 5363/tcp # Windows Network Projection
+net-projection 5363/udp # Windows Network Projection
+kdnet 5364/udp # Microsoft Kernel Debugger
+stresstester 5397/tcp # StressTester(tm) Injector
+stresstester 5397/udp # StressTester(tm) Injector
+elektron-admin 5398/tcp # Elektron Administration
+elektron-admin 5398/udp # Elektron Administration
+securitychase 5399/tcp # SecurityChase
+securitychase 5399/udp # SecurityChase
+excerpt 5400/tcp # Excerpt Search
+excerpt 5400/udp # Excerpt Search
+excerpts 5401/tcp # Excerpt Search Secure
+excerpts 5401/udp # Excerpt Search Secure
+hpoms-ci-lstn 5403/tcp # HPOMS-CI-LSTN
+hpoms-ci-lstn 5403/udp # HPOMS-CI-LSTN
+hpoms-dps-lstn 5404/tcp # HPOMS-DPS-LSTN
+hpoms-dps-lstn 5404/udp # HPOMS-DPS-LSTN
+netsupport 5405/tcp # NetSupport
+netsupport 5405/udp # NetSupport
+systemics-sox 5406/tcp # Systemics Sox
+systemics-sox 5406/udp # Systemics Sox
+foresyte-clear 5407/tcp # Foresyte-Clear
+foresyte-clear 5407/udp # Foresyte-Clear
+foresyte-sec 5408/tcp # Foresyte-Sec
+foresyte-sec 5408/udp # Foresyte-Sec
+salient-dtasrv 5409/tcp # Salient Data Server
+salient-dtasrv 5409/udp # Salient Data Server
+salient-usrmgr 5410/tcp # Salient User Manager
+salient-usrmgr 5410/udp # Salient User Manager
+actnet 5411/tcp # ActNet
+actnet 5411/udp # ActNet
+continuus 5412/tcp # Continuus
+continuus 5412/udp # Continuus
+wwiotalk 5413/tcp # WWIOTALK
+wwiotalk 5413/udp # WWIOTALK
+statusd 5414/tcp # StatusD
+statusd 5414/udp # StatusD
+ns-server 5415/tcp # NS Server
+ns-server 5415/udp # NS Server
+sns-gateway 5416/tcp # SNS Gateway
+sns-gateway 5416/udp # SNS Gateway
+sns-agent 5417/tcp # SNS Agent
+sns-agent 5417/udp # SNS Agent
+mcntp 5418/tcp # MCNTP
+mcntp 5418/udp # MCNTP
+dj-ice 5419/tcp # DJ-ICE
+dj-ice 5419/udp # DJ-ICE
+cylink-c 5420/tcp # Cylink-C
+cylink-c 5420/udp # Cylink-C
+netsupport2 5421/tcp # Net Support 2
+netsupport2 5421/udp # Net Support 2
+salient-mux 5422/tcp # Salient MUX
+salient-mux 5422/udp # Salient MUX
+virtualuser 5423/tcp # VIRTUALUSER
+virtualuser 5423/udp # VIRTUALUSER
+beyond-remote 5424/tcp # Beyond Remote
+beyond-remote 5424/udp # Beyond Remote
+br-channel 5425/tcp # Beyond Remote Command Channel
+br-channel 5425/udp # Beyond Remote Command Channel
+devbasic 5426/tcp # DEVBASIC
+devbasic 5426/udp # DEVBASIC
+sco-peer-tta 5427/tcp # SCO-PEER-TTA
+sco-peer-tta 5427/udp # SCO-PEER-TTA
+telaconsole 5428/tcp # TELACONSOLE
+telaconsole 5428/udp # TELACONSOLE
+base 5429/tcp # Billing and Accounting System Exchange
+base 5429/udp # Billing and Accounting System Exchange
+radec-corp 5430/tcp # RADEC CORP
+radec-corp 5430/udp # RADEC CORP
+park-agent 5431/tcp # PARK AGENT
+park-agent 5431/udp # PARK AGENT
+pyrrho 5433/tcp # Pyrrho DBMS
+pyrrho 5433/udp # Pyrrho DBMS
+sgi-arrayd 5434/tcp # SGI Array Services Daemon
+sgi-arrayd 5434/udp # SGI Array Services Daemon
+sceanics 5435/tcp # SCEANICS situation and action notification
+sceanics 5435/udp # SCEANICS situation and action notification
+pmip6-cntl 5436/udp # pmip6-cntl
+pmip6-data 5437/udp # pmip6-data
+spss 5443/tcp # Pearson HTTPS
+spss 5443/udp # Pearson HTTPS
+smbdirect 5445/tcp # Server Message Block over Remote Direct Memory Access
+smbdirect 5445/sctp # Server Message Block over Remote Direct Memory Access
+tiepie 5450/tcp # TiePie engineering data
+tiepie-disc 5450/udp # TiePie engineering data (discovery)
+surebox 5453/tcp # SureBox
+surebox 5453/udp # SureBox
+apc-5454 5454/tcp # APC 5454
+apc-5454 5454/udp # APC 5454
+apc-5455 5455/tcp # APC 5455
+apc-5455 5455/udp # APC 5455
+apc-5456 5456/tcp # APC 5456
+apc-5456 5456/udp # APC 5456
+silkmeter 5461/tcp # SILKMETER
+silkmeter 5461/udp # SILKMETER
+ttl-publisher 5462/tcp # TTL Publisher
+ttl-publisher 5462/udp # TTL Publisher
+ttlpriceproxy 5463/tcp # TTL Price Proxy
+ttlpriceproxy 5463/udp # TTL Price Proxy
+quailnet 5464/tcp # Quail Networks Object Broker
+quailnet 5464/udp # Quail Networks Object Broker
+netops-broker 5465/tcp # NETOPS-BROKER
+netops-broker 5465/udp # NETOPS-BROKER
+absolab-col 5470/tcp # The Apsolab company's data collection protocol
+absolab-cols 5471/tcp # The Apsolab company's secure data collection protocol
+absolab-tag 5472/tcp # The Apsolab company's dynamic tag protocol
+absolab-tags 5473/tcp # The Apsolab company's secure dynamic tag protocol
+absolab-rpc 5474/udp # The Apsolab company's status query protocol
+absolab-data 5475/tcp # The Apsolab company's data retrieval protocol
+fcp-addr-srvr1 5500/tcp # fcp-addr-srvr1
+fcp-addr-srvr1 5500/udp # fcp-addr-srvr1
+fcp-addr-srvr2 5501/tcp # fcp-addr-srvr2
+fcp-addr-srvr2 5501/udp # fcp-addr-srvr2
+fcp-srvr-inst1 5502/tcp # fcp-srvr-inst1
+fcp-srvr-inst1 5502/udp # fcp-srvr-inst1
+fcp-srvr-inst2 5503/tcp # fcp-srvr-inst2
+fcp-srvr-inst2 5503/udp # fcp-srvr-inst2
+fcp-cics-gw1 5504/tcp # fcp-cics-gw1
+fcp-cics-gw1 5504/udp # fcp-cics-gw1
+checkoutdb 5505/tcp # Checkout Database
+checkoutdb 5505/udp # Checkout Database
+amc 5506/tcp # Amcom Mobile Connect
+amc 5506/udp # Amcom Mobile Connect
+psl-management 5507/tcp # PowerSysLab Electrical
+sgi-eventmond 5553/tcp # SGI Eventmond Port
+sgi-eventmond 5553/udp # SGI Eventmond Port
+sgi-esphttp 5554/tcp # SGI ESP HTTP
+sgi-esphttp 5554/udp # SGI ESP HTTP
+personal-agent 5555/tcp # Personal Agent
+personal-agent 5555/udp # Personal Agent
+freeciv 5556/tcp # Freeciv gameplay
+freeciv 5556/udp # Freeciv gameplay
+farenet 5557/tcp # Sandlab FARENET
+hpe-dp-bura 5565/tcp # HPE Advanced BURA
+westec-connect 5566/tcp # Westec Connect
+dof-dps-mc-sec 5567/tcp # DOF protocol stack
+dof-dps-mc-sec 5567/udp # DOF protocol stack
+sdt 5568/tcp # Session Data Transport Multicast
+sdt 5568/udp # Session Data Transport Multicast
+rdmnet-ctrl 5569/tcp # Management (RDM) controller status notifications
+rdmnet-device 5569/udp # PLASA E1.33, Remote Device Management (RDM) messages
+sdmmp 5573/tcp # SAS Domain Management Messaging Protocol
+sdmmp 5573/udp # SAS Domain Management Messaging Protocol
+lsi-bobcat 5574/tcp # SAS IO Forwarding
+ora-oap 5575/tcp # Oracle Access Protocol
+fdtracks 5579/tcp # FleetDisplay Tracking Service
+tmosms0 5580/tcp # T-Mobile SMS Protocol Message 0
+tmosms0 5580/udp # T-Mobile SMS Protocol Message 0
+tmosms1 5581/tcp # T-Mobile SMS Protocol Message 1
+tmosms1 5581/udp # T-Mobile SMS Protocol Message 1
+fac-restore 5582/tcp # T-Mobile SMS Protocol Message 3
+fac-restore 5582/udp # T-Mobile SMS Protocol Message 3
+tmo-icon-sync 5583/tcp # T-Mobile SMS Protocol Message 2
+tmo-icon-sync 5583/udp # T-Mobile SMS Protocol Message 2
+bis-web 5584/tcp # BeInSync-Web
+bis-web 5584/udp # BeInSync-Web
+bis-sync 5585/tcp # BeInSync-sync
+bis-sync 5585/udp # BeInSync-sync
+att-mt-sms 5586/tcp # mobile terminated SMS, not visible to client(ATT)
+ininmessaging 5597/tcp # inin secure messaging
+ininmessaging 5597/udp # inin secure messaging
+mctfeed 5598/tcp # MCT Market Data Feed
+mctfeed 5598/udp # MCT Market Data Feed
+esinstall 5599/tcp # Enterprise Security Remote Install
+esinstall 5599/udp # Enterprise Security Remote Install
+esmmanager 5600/tcp # Enterprise Security Manager
+esmmanager 5600/udp # Enterprise Security Manager
+esmagent 5601/tcp # Enterprise Security Agent
+esmagent 5601/udp # Enterprise Security Agent
+a1-msc 5602/tcp # A1-MSC
+a1-msc 5602/udp # A1-MSC
+a1-bs 5603/tcp # A1-BS
+a1-bs 5603/udp # A1-BS
+a3-sdunode 5604/tcp # A3-SDUNode
+a3-sdunode 5604/udp # A3-SDUNode
+a4-sdunode 5605/tcp # A4-SDUNode
+a4-sdunode 5605/udp # A4-SDUNode
+efr 5618/tcp # Fiscal Registering Protocol
+ninaf 5627/tcp # Node Initiated Network Association Forma
+ninaf 5627/udp # Node Initiated Network Association Forma
+htrust 5628/tcp # HTrust API
+htrust 5628/udp # HTrust API
+symantec-sfdb 5629/tcp # Symantec Storage Foundation for Database
+symantec-sfdb 5629/udp # Symantec Storage Foundation for Database
+precise-comm 5630/tcp # PreciseCommunication
+precise-comm 5630/udp # PreciseCommunication
+pcanywheredata 5631/tcp # pcANYWHEREdata
+pcanywheredata 5631/udp # pcANYWHEREdata
+pcanywherestat 5632/tcp # pcANYWHEREstat
+pcanywherestat 5632/udp # pcANYWHEREstat
+beorl 5633/tcp # BE Operations Request Listener
+beorl 5633/udp # BE Operations Request Listener
+xprtld 5634/tcp # SF Message Service
+xprtld 5634/udp # SF Message Service
+sfmsso 5635/tcp # SFM Authentication Subsystem
+sfm-db-server 5636/tcp # SFMdb - SFM DB server
+cssc 5637/tcp # Symantec CSSC
+flcrs 5638/tcp # Symantec Fingerprint Lookup and Container Reference
+ics 5639/tcp # Symantec Integrity Checking
+vfmobile 5646/tcp # Ventureforth Mobile
+nrpe 5666/tcp # Nagios Remote Plugin Executor
+filemq 5670/tcp # ZeroMQ file
+zre-disc 5670/udp # Local area discovery and msging over ZeroMQ
+amqps 5671/tcp # amqp protocol over TLS/SSL
+amqps 5671/udp # amqp protocol over TLS/SSL
+amqp 5672/tcp # AMQP
+amqp 5672/udp # AMQP
+amqp 5672/sctp # AMQP
+jms 5673/tcp # JACL Message Server
+jms 5673/udp # JACL Message Server
+hyperscsi-port 5674/tcp # HyperSCSI Port
+hyperscsi-port 5674/udp # HyperSCSI Port
+v5ua 5675/tcp # V5UA application port
+v5ua 5675/udp # V5UA application port
+v5ua 5675/sctp # V5UA application port
+raadmin 5676/tcp # RA Administration
+raadmin 5676/udp # RA Administration
+questdb2-lnchr 5677/tcp # Quest Central DB2 Launchr
+questdb2-lnchr 5677/udp # Quest Central DB2 Launchr
+rrac 5678/tcp # Remote Replication Agent Connection
+rrac 5678/udp # Remote Replication Agent Connection
+dccm 5679/tcp # Direct Cable Connect Manager
+dccm 5679/udp # Direct Cable Connect Manager
+auriga-router 5680/udp # Auriga Router Service
+ncxcp 5681/tcp # Net-coneX Control Protocol
+ncxcp 5681/udp # Net-coneX Control Protocol
+brightcore 5682/udp # BrightCore control & data transfer exchange
+coap 5683/udp # Constrained Application Protocol
+coaps 5684/udp # DTLS-secured CoAP
+gog-multiplayer 5687/udp # GOG multiplayer game protocol
+ggz 5688/tcp # GGZ Gaming Zone
+ggz 5688/udp # GGZ Gaming Zone
+qmvideo 5689/tcp # QM video network management protocol
+qmvideo 5689/udp # QM video network management protocol
+rbsystem 5693/tcp # Robert Bosch Data Transfer
+kmip 5696/tcp # Key Management Interoperability Protocol
+supportassist 5700/tcp # Dell SupportAssist data
+proshareaudio 5713/tcp # proshare conf audio
+proshareaudio 5713/udp # proshare conf audio
+prosharevideo 5714/tcp # proshare conf video
+prosharevideo 5714/udp # proshare conf video
+prosharedata 5715/tcp # proshare conf data
+prosharedata 5715/udp # proshare conf data
+prosharerequest 5716/tcp # proshare conf request
+prosharerequest 5716/udp # proshare conf request
+prosharenotify 5717/tcp # proshare conf notify
+prosharenotify 5717/udp # proshare conf notify
+dpm 5718/tcp # DPM Communication Server
+dpm 5718/udp # DPM Communication Server
+dpm-agent 5719/tcp # DPM Agent Coordinator
+dpm-agent 5719/udp # DPM Agent Coordinator
+ms-licensing 5720/tcp # MS-Licensing
+ms-licensing 5720/udp # MS-Licensing
+dtpt 5721/tcp # Desktop Passthru Service
+dtpt 5721/udp # Desktop Passthru Service
+msdfsr 5722/tcp # Microsoft DFS Replication Service
+msdfsr 5722/udp # Microsoft DFS Replication Service
+omhs 5723/tcp # Operations Manager - Health Service
+omhs 5723/udp # Operations Manager - Health Service
+omsdk 5724/tcp # Operations Manager - SDK Service
+omsdk 5724/udp # Operations Manager - SDK Service
+ms-ilm 5725/tcp # Microsoft Identity Lifecycle Manager
+ms-ilm-sts 5726/tcp # Microsoft Lifecycle Manager Secure Token Service
+asgenf 5727/tcp # ASG Event Notification Framework
+io-dist-data 5728/tcp # Dist. I/O Comm. Service Data and Control
+io-dist-group 5728/udp # Dist. I/O Comm. Service Group Membership
+openmail 5729/tcp # Openmail User Agent Layer
+openmail 5729/udp # Openmail User Agent Layer
+unieng 5730/tcp # Steltor's calendar access
+unieng 5730/udp # Steltor's calendar access
+ida-discover1 5741/tcp # IDA Discover Port 1
+ida-discover1 5741/udp # IDA Discover Port 1
+ida-discover2 5742/tcp # IDA Discover Port 2
+ida-discover2 5742/udp # IDA Discover Port 2
+watchdoc-pod 5743/tcp # Watchdoc NetPOD Protocol
+watchdoc-pod 5743/udp # Watchdoc NetPOD Protocol
+watchdoc 5744/tcp # Watchdoc Server
+watchdoc 5744/udp # Watchdoc Server
+fcopy-server 5745/tcp # fcopy-server
+fcopy-server 5745/udp # fcopy-server
+fcopys-server 5746/tcp # fcopys-server
+fcopys-server 5746/udp # fcopys-server
+tunatic 5747/tcp # Wildbits Tunatic
+tunatic 5747/udp # Wildbits Tunatic
+tunalyzer 5748/tcp # Wildbits Tunalyzer
+tunalyzer 5748/udp # Wildbits Tunalyzer
+rscd 5750/tcp # Bladelogic Agent Service
+rscd 5750/udp # Bladelogic Agent Service
+openmailg 5755/tcp # OpenMail Desk Gateway server
+openmailg 5755/udp # OpenMail Desk Gateway server
+x500ms 5757/tcp # OpenMail X.500 Directory Server
+x500ms 5757/udp # OpenMail X.500 Directory Server
+openmailns 5766/tcp # OpenMail NewMail Server
+openmailns 5766/udp # OpenMail NewMail Server
+s-openmail 5767/tcp # OpenMail Suer Agent Layer (Secure)
+s-openmail 5767/udp # OpenMail Suer Agent Layer (Secure)
+openmailpxy 5768/tcp # OpenMail CMTS Server
+openmailpxy 5768/udp # OpenMail CMTS Server
+spramsca 5769/tcp # x509solutions Internal CA
+spramsca 5769/udp # x509solutions Internal CA
+spramsd 5770/tcp # x509solutions Secure Data
+spramsd 5770/udp # x509solutions Secure Data
+netagent 5771/tcp # NetAgent
+netagent 5771/udp # NetAgent
+dali-port 5777/tcp # DALI Port
+dali-port 5777/udp # DALI Port
+vts-rpc 5780/tcp # Visual Tag System RPC
+3par-evts 5781/tcp # 3PAR Event Reporting Service
+3par-evts 5781/udp # 3PAR Event Reporting Service
+3par-mgmt 5782/tcp # 3PAR Management Service
+3par-mgmt 5782/udp # 3PAR Management Service
+3par-mgmt-ssl 5783/tcp # 3PAR Management Service with SSL
+3par-mgmt-ssl 5783/udp # 3PAR Management Service with SSL
+ibar 5784/udp # Cisco Interbox Application Redundancy
+3par-rcopy 5785/tcp # 3PAR Inform Remote Copy
+3par-rcopy 5785/udp # 3PAR Inform Remote Copy
+cisco-redu 5786/udp # redundancy notification
+waascluster 5787/udp # Cisco WAAS Cluster Protocol
+xtreamx 5793/tcp # XtreamX Supervised Peer message
+xtreamx 5793/udp # XtreamX Supervised Peer message
+spdp 5794/udp # Simple Peered Discovery Protocol
+icmpd 5813/tcp # ICMPD
+icmpd 5813/udp # ICMPD
+spt-automation 5814/tcp # Support Automation
+spt-automation 5814/udp # Support Automation
+shiprush-d-ch 5841/tcp # Z-firm ShipRush interface for web access and bidirectional data
+reversion 5842/tcp # Reversion Backup/Restore
+wherehoo 5859/tcp # WHEREHOO
+wherehoo 5859/udp # WHEREHOO
+ppsuitemsg 5863/tcp # PlanetPress Suite Messeng
+ppsuitemsg 5863/udp # PlanetPress Suite Messeng
+diameters 5868/tcp # Diameter over TLS/TCP
+diameters 5868/sctp # Diameter over DTLS/SCTP
+jute 5883/tcp # Javascript Unit Test Environment
+rfb 5900/tcp # Remote Framebuffer
+rfb 5900/udp # Remote Framebuffer
+cm 5910/tcp # Context Management
+cm 5910/udp # Context Management
+cm 5910/sctp # Context Management
+cpdlc 5911/tcp # Controller Pilot Data Link Communication
+cpdlc 5911/udp # Controller Pilot Data Link Communication
+cpdlc 5911/sctp # Controller Pilot Data Link Communication
+fis 5912/tcp # Flight Information Services
+fis 5912/udp # Flight Information Services
+fis 5912/sctp # Flight Information Services
+ads-c 5913/tcp # Automatic Dependent Surveillance
+ads-c 5913/udp # Automatic Dependent Surveillance
+ads-c 5913/sctp # Automatic Dependent Surveillance
+indy 5963/tcp # Indy Application Server
+indy 5963/udp # Indy Application Server
+mppolicy-v5 5968/tcp # mppolicy-v5
+mppolicy-v5 5968/udp # mppolicy-v5
+mppolicy-mgr 5969/tcp # mppolicy-mgr
+mppolicy-mgr 5969/udp # mppolicy-mgr
+couchdb 5984/tcp # CouchDB
+couchdb 5984/udp # CouchDB
+wsman 5985/tcp # WBEM WS-Management HTTP
+wsman 5985/udp # WBEM WS-Management HTTP
+wsmans 5986/tcp # WBEM WS-Management HTTP over TLS/SSL
+wsmans 5986/udp # WBEM WS-Management HTTP over TLS/SSL
+wbem-rmi 5987/tcp # WBEM RMI
+wbem-rmi 5987/udp # WBEM RMI
+wbem-http 5988/tcp # WBEM CIM-XML (HTTP)
+wbem-http 5988/udp # WBEM CIM-XML (HTTP)
+wbem-https 5989/tcp # WBEM CIM-XML (HTTPS)
+wbem-https 5989/udp # WBEM CIM-XML (HTTPS)
+wbem-exp-https 5990/tcp # WBEM Export HTTPS
+wbem-exp-https 5990/udp # WBEM Export HTTPS
+nuxsl 5991/tcp # NUXSL
+nuxsl 5991/udp # NUXSL
+consul-insight 5992/tcp # Consul InSight Security
+consul-insight 5992/udp # Consul InSight Security
+cim-rs 5993/tcp # DMTF WBEM CIM REST
+ndl-ahp-svc 6064/tcp # NDL-AHP-SVC
+ndl-ahp-svc 6064/udp # NDL-AHP-SVC
+winpharaoh 6065/tcp # WinPharaoh
+winpharaoh 6065/udp # WinPharaoh
+ewctsp 6066/tcp # EWCTSP
+ewctsp 6066/udp # EWCTSP
+gsmp-ancp 6068/tcp # GSMP/ANCP
+trip 6069/tcp # TRIP
+trip 6069/udp # TRIP
+messageasap 6070/tcp # Messageasap
+messageasap 6070/udp # Messageasap
+ssdtp 6071/tcp # SSDTP
+ssdtp 6071/udp # SSDTP
+diagnose-proc 6072/tcp # DIAGNOSE-PROC
+diagnose-proc 6072/udp # DIAGNOSE-PROC
+directplay8 6073/tcp # DirectPlay8
+directplay8 6073/udp # DirectPlay8
+max 6074/tcp # Microsoft Max
+max 6074/udp # Microsoft Max
+dpm-acm 6075/tcp # Microsoft DPM Access Control Manager
+msft-dpm-cert 6076/tcp # Microsoft DPM WCF Certificates
+iconstructsrv 6077/tcp # iConstruct Server
+gue 6080/udp # Generic UDP Encapsulation
+geneve 6081/udp # Generic Network Virtualization Encapsulation
+p25cai 6082/udp # APCO Project 25 Common Air Interface
+miami-bcast 6083/udp # telecomsoftware miami broadcast
+reload-config 6084/tcp # Peer to Peer Infrastructure Protocol
+konspire2b 6085/tcp # konspire2b p2p network
+konspire2b 6085/udp # konspire2b p2p network
+pdtp 6086/tcp # PDTP P2P
+pdtp 6086/udp # PDTP P2P
+ldss 6087/tcp # Local Download Sharing Service
+ldss 6087/udp # Local Download Sharing Service
+doglms 6088/tcp # SuperDog License Manager
+doglms-notify 6088/udp # SuperDog License Manager
+raxa-mgmt 6099/tcp # RAXA Management
+synchronet-db 6100/tcp # SynchroNet-db
+synchronet-db 6100/udp # SynchroNet-db
+synchronet-rtc 6101/tcp # SynchroNet-rtc
+synchronet-rtc 6101/udp # SynchroNet-rtc
+synchronet-upd 6102/tcp # SynchroNet-upd
+synchronet-upd 6102/udp # SynchroNet-upd
+rets 6103/tcp # RETS
+rets 6103/udp # RETS
+dbdb 6104/tcp # DBDB
+dbdb 6104/udp # DBDB
+primaserver 6105/tcp # Prima Server
+primaserver 6105/udp # Prima Server
+mpsserver 6106/tcp # MPS Server
+mpsserver 6106/udp # MPS Server
+etc-control 6107/tcp # ETC Control
+etc-control 6107/udp # ETC Control
+sercomm-scadmin 6108/tcp # Sercomm-SCAdmin
+sercomm-scadmin 6108/udp # Sercomm-SCAdmin
+globecast-id 6109/tcp # GLOBECAST-ID
+globecast-id 6109/udp # GLOBECAST-ID
+softcm 6110/tcp # HP SoftBench CM
+softcm 6110/udp # HP SoftBench CM
+spc 6111/tcp # HP SoftBench Sub-Process Control
+spc 6111/udp # HP SoftBench Sub-Process Control
+dtspcd 6112/tcp # Desk-Top Sub-Process Control Daemon
+dtspcd 6112/udp # Desk-Top Sub-Process Control Daemon
+dayliteserver 6113/tcp # Daylite Server
+wrspice 6114/tcp # WRspice IPC Service
+xic 6115/tcp # Xic IPC Service
+xtlserv 6116/tcp # XicTools License Manager Service
+daylitetouch 6117/tcp # Daylite Touch Sync
+tipc 6118/udp # Transparent Inter Process Communication
+spdy 6121/tcp # SPDY for a faster web
+bex-webadmin 6122/tcp # Backup Express Web Server
+bex-webadmin 6122/udp # Backup Express Web Server
+backup-express 6123/tcp # Backup Express
+backup-express 6123/udp # Backup Express
+pnbs 6124/tcp # Phlexible Network Backup Service
+pnbs 6124/udp # Phlexible Network Backup Service
+damewaremobgtwy 6130/tcp # The DameWare Mobile Gateway Service
+nbt-wol 6133/tcp # New Boundary Tech WOL
+nbt-wol 6133/udp # New Boundary Tech WOL
+pulsonixnls 6140/tcp # Pulsonix Network License Service
+pulsonixnls 6140/udp # Pulsonix Network License Service
+meta-corp 6141/tcp # Meta Corporation License Manager
+meta-corp 6141/udp # Meta Corporation License Manager
+aspentec-lm 6142/tcp # Aspen Technology License Manager
+aspentec-lm 6142/udp # Aspen Technology License Manager
+watershed-lm 6143/tcp # Watershed License Manager
+watershed-lm 6143/udp # Watershed License Manager
+statsci1-lm 6144/tcp # StatSci License Manager - 1
+statsci1-lm 6144/udp # StatSci License Manager - 1
+statsci2-lm 6145/tcp # StatSci License Manager - 2
+statsci2-lm 6145/udp # StatSci License Manager - 2
+lonewolf-lm 6146/tcp # Lone Wolf Systems License Manager
+lonewolf-lm 6146/udp # Lone Wolf Systems License Manager
+montage-lm 6147/tcp # Montage License Manager
+montage-lm 6147/udp # Montage License Manager
+tal-pod 6149/tcp # tal-pod
+tal-pod 6149/udp # tal-pod
+efb-aci 6159/tcp # EFB Application Control Interface
+ecmp 6160/tcp # Emerson Extensible Control and Management Protocol
+ecmp-data 6160/udp # Emerson Extensible Control and Management Protocol Data
+patrol-ism 6161/tcp # PATROL Internet Srv Mgr
+patrol-ism 6161/udp # PATROL Internet Srv Mgr
+patrol-coll 6162/tcp # PATROL Collector
+patrol-coll 6162/udp # PATROL Collector
+pscribe 6163/tcp # Precision Scribe Cnx Port
+pscribe 6163/udp # Precision Scribe Cnx Port
+lm-x 6200/tcp # LM-X License Manager by X-Formation
+lm-x 6200/udp # LM-X License Manager by X-Formation
+thermo-calc 6201/udp # Thermo-Calc_Software
+qmtps 6209/tcp # QMTP over TLS
+qmtps 6209/udp # QMTP over TLS
+radmind 6222/tcp # Radmind Access Protocol
+radmind 6222/udp # Radmind Access Protocol
+jeol-nsdtp-1 6241/tcp # JEOL Network Services Data Transport Protocol 1
+jeol-nsddp-1 6241/udp # JEOL Network Services Dynamic Discovery Protocol 1
+jeol-nsdtp-2 6242/tcp # JEOL Network Services Data Transport Protocol 2
+jeol-nsddp-2 6242/udp # JEOL Network Services Dynamic Discovery Protocol 2
+jeol-nsdtp-3 6243/tcp # JEOL Network Services Data Transport Protocol 3
+jeol-nsddp-3 6243/udp # JEOL Network Services Dynamic Discovery Protocol 3
+jeol-nsdtp-4 6244/tcp # JEOL Network Services Data Transport Protocol 4
+jeol-nsddp-4 6244/udp # JEOL Network Services Dynamic Discovery Protocol 4
+tl1-raw-ssl 6251/tcp # TL1 Raw Over SSL/TLS
+tl1-raw-ssl 6251/udp # TL1 Raw Over SSL/TLS
+tl1-ssh 6252/tcp # TL1 over SSH
+tl1-ssh 6252/udp # TL1 over SSH
+crip 6253/tcp # CRIP
+crip 6253/udp # CRIP
+gld 6267/tcp # GridLAB-D User Interface
+grid 6268/tcp # Grid Authentication
+grid 6268/udp # Grid Authentication
+grid-alt 6269/tcp # Grid Authentication Alt
+grid-alt 6269/udp # Grid Authentication Alt
+bmc-grx 6300/tcp # BMC GRX
+bmc-grx 6300/udp # BMC GRX
+bmc_ctd_ldap 6301/tcp bmc-ctd-ldap # BMC CONTROL-D LDAP SERVER
+bmc_ctd_ldap 6301/udp bmc-ctd-ldap # BMC CONTROL-D LDAP SERVER
+ufmp 6306/tcp # Unified Fabric Management Protocol
+ufmp 6306/udp # Unified Fabric Management Protocol
+scup 6315/tcp # Sensor Control Unit Protocol
+scup-disc 6315/udp # Sensor Control Unit Protocol Discovery Protocol
+abb-escp 6316/tcp # Ethernet Sensor Communications Protocol
+abb-escp 6316/udp # Ethernet Sensor Communications Protocol
+nav-data-cmd 6317/tcp # Navtech Radar Sensor Data command
+nav-data 6317/udp # Navtech Radar Sensor Data
+repsvc 6320/tcp # Double-Take Replication Service
+repsvc 6320/udp # Double-Take Replication Service
+emp-server1 6321/tcp # Empress Software Connectivity Server 1
+emp-server1 6321/udp # Empress Software Connectivity Server 1
+emp-server2 6322/tcp # Empress Software Connectivity Server 2
+emp-server2 6322/udp # Empress Software Connectivity Server 2
+hrd-ncs 6324/tcp # HR Device Network
+hrd-ns-disc 6324/udp # HR Device Network service
+dt-mgmtsvc 6325/tcp # Double-Take Management Service
+dt-vra 6326/tcp # Double-Take Virtual Recovery
+sflow 6343/tcp # sFlow traffic monitoring
+sflow 6343/udp # sFlow traffic monitoring
+streletz 6344/tcp # Argus-Spectr security and fire-prevention systems service
+gnutella-svc 6346/tcp # gnutella-svc
+gnutella-svc 6346/udp # gnutella-svc
+gnutella-rtr 6347/tcp # gnutella-rtr
+gnutella-rtr 6347/udp # gnutella-rtr
+adap 6350/tcp # App Discovery and Access Protocol
+adap 6350/udp # App Discovery and Access Protocol
+pmcs 6355/tcp # PMCS applications
+pmcs 6355/udp # PMCS applications
+metaedit-mu 6360/tcp # MetaEdit+ Multi-User
+metaedit-mu 6360/udp # MetaEdit+ Multi-User
+ndn 6363/udp # Named Data Networking
+metaedit-se 6370/tcp # MetaEdit+ Server Administration
+metaedit-se 6370/udp # MetaEdit+ Server Administration
+redis 6379/tcp # An advanced key-value cache and store
+metatude-mds 6382/tcp # Metatude Dialogue Server
+metatude-mds 6382/udp # Metatude Dialogue Server
+clariion-evr01 6389/tcp # clariion-evr01
+clariion-evr01 6389/udp # clariion-evr01
+metaedit-ws 6390/tcp # MetaEdit+ WebService API
+metaedit-ws 6390/udp # MetaEdit+ WebService API
+boe-cms 6400/tcp # Business Objects CMS contact port
+boe-cms 6400/udp # Business Objects CMS contact port
+boe-was 6401/tcp # boe-was
+boe-was 6401/udp # boe-was
+boe-eventsrv 6402/tcp # boe-eventsrv
+boe-eventsrv 6402/udp # boe-eventsrv
+boe-cachesvr 6403/tcp # boe-cachesvr
+boe-cachesvr 6403/udp # boe-cachesvr
+boe-filesvr 6404/tcp # Business Objects Enterprise internal server
+boe-filesvr 6404/udp # Business Objects Enterprise internal server
+boe-pagesvr 6405/tcp # Business Objects Enterprise internal server
+boe-pagesvr 6405/udp # Business Objects Enterprise internal server
+boe-processsvr 6406/tcp # Business Objects Enterprise internal server
+boe-processsvr 6406/udp # Business Objects Enterprise internal server
+boe-resssvr1 6407/tcp # Business Objects Enterprise internal server
+boe-resssvr1 6407/udp # Business Objects Enterprise internal server
+boe-resssvr2 6408/tcp # Business Objects Enterprise internal server
+boe-resssvr2 6408/udp # Business Objects Enterprise internal server
+boe-resssvr3 6409/tcp # Business Objects Enterprise internal server
+boe-resssvr3 6409/udp # Business Objects Enterprise internal server
+boe-resssvr4 6410/tcp # Business Objects Enterprise internal server
+boe-resssvr4 6410/udp # Business Objects Enterprise internal server
+faxcomservice 6417/tcp # Faxcom Message Service
+faxcomservice 6417/udp # Faxcom Message Service
+syserverremote 6418/tcp # SYserver remote commands
+svdrp 6419/tcp # Simple VDR Protocol
+svdrp-disc 6419/udp # Simple VDR Protocol Discovery
+nim-vdrshell 6420/tcp # NIM_VDRShell
+nim-vdrshell 6420/udp # NIM_VDRShell
+nim-wan 6421/tcp # NIM_WAN
+nim-wan 6421/udp # NIM_WAN
+pgbouncer 6432/tcp # PgBouncer
+tarp 6442/tcp # Transitory Application Request Protocol
+sun-sr-https 6443/tcp # Service Registry Default HTTPS Domain
+sun-sr-https 6443/udp # Service Registry Default HTTPS Domain
+sge_qmaster 6444/tcp sge-qmaster # Grid Engine Qmaster Service
+sge_qmaster 6444/udp sge-qmaster # Grid Engine Qmaster Service
+sge_execd 6445/tcp sge-execd # Grid Engine Execution Service
+sge_execd 6445/udp sge-execd # Grid Engine Execution Service
+mysql-proxy 6446/tcp # MySQL Proxy
+mysql-proxy 6446/udp # MySQL Proxy
+skip-cert-recv 6455/tcp # SKIP Certificate Receive
+skip-cert-recv 6455/udp # SKIP Certificate Receive
+skip-cert-send 6456/tcp # SKIP Certificate Send
+skip-cert-send 6456/udp # SKIP Certificate Send
+lvision-lm 6471/tcp # LVision License Manager
+lvision-lm 6471/udp # LVision License Manager
+sun-sr-http 6480/tcp # Service Registry Default HTTP Domain
+sun-sr-http 6480/udp # Service Registry Default HTTP Domain
+servicetags 6481/tcp # Service Tags
+servicetags 6481/udp # Service Tags
+ldoms-mgmt 6482/tcp # Logical Domains Management Interface
+ldoms-mgmt 6482/udp # Logical Domains Management Interface
+SunVTS-RMI 6483/tcp # SunVTS RMI
+SunVTS-RMI 6483/udp # SunVTS RMI
+sun-sr-jms 6484/tcp # Service Registry Default JMS Domain
+sun-sr-jms 6484/udp # Service Registry Default JMS Domain
+sun-sr-iiop 6485/tcp # Service Registry Default IIOP Domain
+sun-sr-iiop 6485/udp # Service Registry Default IIOP Domain
+sun-sr-iiops 6486/tcp # Service Registry Default IIOPS Domain
+sun-sr-iiops 6486/udp # Service Registry Default IIOPS Domain
+sun-sr-iiop-aut 6487/tcp # Service Registry Default IIOPAuth Domain
+sun-sr-iiop-aut 6487/udp # Service Registry Default IIOPAuth Domain
+sun-sr-jmx 6488/tcp # Service Registry Default JMX Domain
+sun-sr-jmx 6488/udp # Service Registry Default JMX Domain
+sun-sr-admin 6489/tcp # Service Registry Default Admin Domain
+sun-sr-admin 6489/udp # Service Registry Default Admin Domain
+boks 6500/tcp # BoKS Master
+boks 6500/udp # BoKS Master
+boks_servc 6501/tcp boks-servc # BoKS Servc
+boks_servc 6501/udp boks-servc # BoKS Servc
+boks_servm 6502/tcp boks-servm # BoKS Servm
+boks_servm 6502/udp boks-servm # BoKS Servm
+boks_clntd 6503/tcp boks-clntd # BoKS Clntd
+boks_clntd 6503/udp boks-clntd # BoKS Clntd
+badm_priv 6505/tcp badm-priv # BoKS Admin Private Port
+badm_priv 6505/udp badm-priv # BoKS Admin Private Port
+badm_pub 6506/tcp badm-pub # BoKS Admin Public Port
+badm_pub 6506/udp badm-pub # BoKS Admin Public Port
+bdir_priv 6507/tcp bdir-priv # BoKS Dir Server, Private Port
+bdir_priv 6507/udp bdir-priv # BoKS Dir Server, Private Port
+bdir_pub 6508/tcp bdir-pub # BoKS Dir Server, Public Port
+bdir_pub 6508/udp bdir-pub # BoKS Dir Server, Public Port
+mgcs-mfp-port 6509/tcp # MGCS-MFP Port
+mgcs-mfp-port 6509/udp # MGCS-MFP Port
+mcer-port 6510/tcp # MCER Port
+mcer-port 6510/udp # MCER Port
+dccp-udp 6511/udp # Protocol Encapsulation for NAT Traversal
+netconf-tls 6513/tcp # NETCONF over TLS
+syslog-tls 6514/tcp # Syslog over TLS
+syslog-tls 6514/udp # Syslog over TLS
+syslog-tls 6514/dccp # Syslog over TLS
+elipse-rec 6515/tcp # Elipse RPC Protocol
+elipse-rec 6515/udp # Elipse RPC Protocol
+lds-distrib 6543/tcp # lds_distrib
+lds-distrib 6543/udp # lds_distrib
+lds-dump 6544/tcp # LDS Dump Service
+lds-dump 6544/udp # LDS Dump Service
+apc-6547 6547/tcp # APC 6547
+apc-6547 6547/udp # APC 6547
+apc-6548 6548/tcp # APC 6548
+apc-6548 6548/udp # APC 6548
+apc-6549 6549/tcp # APC 6549
+apc-6549 6549/udp # APC 6549
+fg-sysupdate 6550/tcp # fg-sysupdate
+fg-sysupdate 6550/udp # fg-sysupdate
+sum 6551/tcp # Software Update Manager
+sum 6551/udp # Software Update Manager
+xdsxdm 6558/tcp #
+xdsxdm 6558/udp #
+sane-port 6566/tcp # SANE Control Port
+sane-port 6566/udp # SANE Control Port
+canit_store 6568/tcp canit-store # CanIt Storage Manager
+rp-reputation 6568/udp # Roaring Penguin IP Address Reputation Collection
+affiliate 6579/tcp # Affiliate
+affiliate 6579/udp # Affiliate
+parsec-master 6580/tcp # Parsec Masterserver
+parsec-master 6580/udp # Parsec Masterserver
+parsec-peer 6581/tcp # Parsec Peer-to-Peer
+parsec-peer 6581/udp # Parsec Peer-to-Peer
+parsec-game 6582/tcp # Parsec Gameserver
+parsec-game 6582/udp # Parsec Gameserver
+joaJewelSuite 6583/tcp # JOA Jewel Suite
+joaJewelSuite 6583/udp # JOA Jewel Suite
+mshvlm 6600/tcp # Microsoft Hyper-V Live Migration
+mstmg-sstp 6601/tcp # Microsoft Threat Management Gateway SSTP
+wsscomfrmwk 6602/tcp # Windows WSS Communication Framework
+odette-ftps 6619/tcp # ODETTE-FTP over TLS/SSL
+odette-ftps 6619/udp # ODETTE-FTP over TLS/SSL
+kftp-data 6620/tcp # Kerberos V5 FTP Data
+kftp-data 6620/udp # Kerberos V5 FTP Data
+kftp 6621/tcp # Kerberos V5 FTP Control
+kftp 6621/udp # Kerberos V5 FTP Control
+mcftp 6622/tcp # Multicast FTP
+mcftp 6622/udp # Multicast FTP
+ktelnet 6623/tcp # Kerberos V5 Telnet
+ktelnet 6623/udp # Kerberos V5 Telnet
+datascaler-db 6624/tcp # DataScaler database
+datascaler-ctl 6625/tcp # DataScaler control
+wago-service 6626/tcp # WAGO Service and Update
+wago-service 6626/udp # WAGO Service and Update
+nexgen 6627/tcp # Allied Electronics NeXGen
+nexgen 6627/udp # Allied Electronics NeXGen
+afesc-mc 6628/tcp # AFE Stock Channel M/C
+afesc-mc 6628/udp # AFE Stock Channel M/C
+mxodbc-connect 6632/tcp # eGenix mxODBC Connect
+cisco-vpath-tun 6633/udp # Cisco vPath Services Overlay
+mpls-pm 6634/udp # MPLS Performance Measurement out-of-band response
+mpls-udp 6635/udp # Encapsulate MPLS packets in UDP tunnels
+mpls-udp-dtls 6636/udp # Encapsulate MPLS packets in UDP tunnels with DTLS
+ovsdb 6640/tcp # Open vSwitch Database
+openflow 6653/tcp # OpenFlow
+openflow 6653/udp # OpenFlow
+pcs-sf-ui-man 6655/tcp # PC SOFT - Software factory UI/manager
+emgmsg 6656/tcp # Emergency Message Control Service
+palcom-disc 6657/udp # PalCom Discovery
+ircu 6665/tcp # IRCU
+ircu 6665/udp # IRCU
+ircu-2 6666/tcp ircu2 # IRCU
+ircu-2 6666/udp ircu2 # IRCU
+ircu-3 6667/tcp ircd ircu3 # IRCU
+ircu-3 6667/udp ircd ircu3 # IRCU
+ircu-4 6668/tcp ircu4 # IRCU
+ircu-4 6668/udp ircu4 # IRCU
+ircu-5 6669/tcp ircu5 # IRCU
+ircu-5 6669/udp ircu5 # IRCU
+vocaltec-gold 6670/tcp # Vocaltec Global Online Directory
+vocaltec-gold 6670/udp # Vocaltec Global Online Directory
+p4p-portal 6671/tcp # P4P Portal Service
+p4p-portal 6671/udp # P4P Portal Service
+vision_server 6672/tcp vision-server # vision_server
+vision_server 6672/udp vision-server # vision_server
+vision_elmd 6673/tcp vision-elmd # vision_elmd
+vision_elmd 6673/udp vision-elmd # vision_elmd
+vfbp 6678/tcp # Viscount Freedom Bridge Protocol
+vfbp-disc 6678/udp # Viscount Freedom Bridge Discovery
+osaut 6679/tcp # Osorno Automation
+osaut 6679/udp # Osorno Automation
+clever-ctrace 6687/tcp # CleverView for cTrace Message Service
+clever-tcpip 6688/tcp # CleverView for TCP/IP Message Service
+tsa 6689/tcp # Tofino Security Appliance
+tsa 6689/udp # Tofino Security Appliance
+cleverdetect 6690/tcp # CLEVERDetect Message Service
+babel 6696/udp # Babel Routing Protocol
+ircs-u 6697/tcp # Internet Relay Chat via TLS/SSL
+kti-icad-srvr 6701/tcp # KTI/ICAD Nameserver
+kti-icad-srvr 6701/udp # KTI/ICAD Nameserver
+e-design-net 6702/tcp # e-Design network
+e-design-net 6702/udp # e-Design network
+e-design-web 6703/tcp # e-Design web
+e-design-web 6703/udp # e-Design web
+frc-hp 6704/sctp # ForCES HP (High Priority) channel
+frc-mp 6705/sctp # ForCES MP (Medium Priority) channel
+frc-lp 6706/sctp # ForCES LP (Low priority) channel
+ibprotocol 6714/tcp # Internet Backplane Protocol
+ibprotocol 6714/udp # Internet Backplane Protocol
+fibotrader-com 6715/tcp # Fibotrader Communications
+fibotrader-com 6715/udp # Fibotrader Communications
+princity-agent 6716/tcp # Princity Agent
+bmc-perf-agent 6767/tcp # BMC PERFORM AGENT
+bmc-perf-agent 6767/udp # BMC PERFORM AGENT
+bmc-perf-mgrd 6768/tcp # BMC PERFORM MGRD
+bmc-perf-mgrd 6768/udp # BMC PERFORM MGRD
+adi-gxp-srvprt 6769/tcp # ADInstruments GxP Server
+adi-gxp-srvprt 6769/udp # ADInstruments GxP Server
+plysrv-http 6770/tcp # PolyServe http
+plysrv-http 6770/udp # PolyServe http
+plysrv-https 6771/tcp # PolyServe https
+plysrv-https 6771/udp # PolyServe https
+ntz-tracker 6777/tcp # netTsunami Tracker
+ntz-p2p-storage 6778/tcp # netTsunami p2p storage
+bfd-lag 6784/udp # Bidirectional Forwarding Detection on LAG
+dgpf-exchg 6785/tcp # DGPF Individual Exchange
+dgpf-exchg 6785/udp # DGPF Individual Exchange
+smc-jmx 6786/tcp # Sun Java Web Console JMX
+smc-jmx 6786/udp # Sun Java Web Console JMX
+smc-admin 6787/tcp # Sun Web Console Admin
+smc-admin 6787/udp # Sun Web Console Admin
+smc-http 6788/tcp # SMC-HTTP
+smc-http 6788/udp # SMC-HTTP
+smc-https 6789/tcp # SMC-HTTPS
+smc-https 6789/udp # SMC-HTTPS
+hnmp 6790/tcp # HNMP
+hnmp 6790/udp # HNMP
+hnm 6791/tcp # Halcyon Network Manager
+hnm 6791/udp # Halcyon Network Manager
+acnet 6801/tcp # ACNET Control System Protocol
+acnet 6801/udp # ACNET Control System Protocol
+pentbox-sim 6817/tcp # PenTBox Secure IM Protocol
+ambit-lm 6831/tcp # ambit-lm
+ambit-lm 6831/udp # ambit-lm
+netmo-default 6841/tcp # Netmo Default
+netmo-default 6841/udp # Netmo Default
+netmo-http 6842/tcp # Netmo HTTP
+netmo-http 6842/udp # Netmo HTTP
+iccrushmore 6850/tcp # ICCRUSHMORE
+iccrushmore 6850/udp # ICCRUSHMORE
+acctopus-cc 6868/tcp # Acctopus Command Channel
+acctopus-st 6868/udp # Acctopus Status
+muse 6888/tcp # MUSE
+muse 6888/udp # MUSE
+jetstream 6901/tcp # Novell Jetstream messaging protocol
+ethoscan 6935/tcp # EthoScan Service
+ethoscan 6935/udp # EthoScan Service
+xsmsvc 6936/tcp # XenSource Management Service
+xsmsvc 6936/udp # XenSource Management Service
+bioserver 6946/tcp # Biometrics Server
+bioserver 6946/udp # Biometrics Server
+otlp 6951/tcp # OTLP
+otlp 6951/udp # OTLP
+jmact3 6961/tcp # JMACT3
+jmact3 6961/udp # JMACT3
+jmevt2 6962/tcp # jmevt2
+jmevt2 6962/udp # jmevt2
+swismgr1 6963/tcp # swismgr1
+swismgr1 6963/udp # swismgr1
+swismgr2 6964/tcp # swismgr2
+swismgr2 6964/udp # swismgr2
+swistrap 6965/tcp # swistrap
+swistrap 6965/udp # swistrap
+swispol 6966/tcp # swispol
+swispol 6966/udp # swispol
+acmsoda 6969/tcp # acmsoda
+acmsoda 6969/udp # acmsoda
+conductor 6970/tcp # Conductor test coordination
+conductor-mpx 6970/sctp # conductor for multiplex
+MobilitySrv 6997/tcp # Mobility XE Protocol
+MobilitySrv 6997/udp # Mobility XE Protocol
+iatp-highpri 6998/tcp # IATP-highPri
+iatp-highpri 6998/udp # IATP-highPri
+iatp-normalpri 6999/tcp # IATP-normalPri
+iatp-normalpri 6999/udp # IATP-normalPri
+ups-onlinet 7010/tcp # onlinet uninterruptable power supplies
+ups-onlinet 7010/udp # onlinet uninterruptable power supplies
+talon-disc 7011/tcp # Talon Discovery Port
+talon-disc 7011/udp # Talon Discovery Port
+talon-engine 7012/tcp # Talon Engine
+talon-engine 7012/udp # Talon Engine
+microtalon-dis 7013/tcp # Microtalon Discovery
+microtalon-dis 7013/udp # Microtalon Discovery
+microtalon-com 7014/tcp # Microtalon Communications
+microtalon-com 7014/udp # Microtalon Communications
+talon-webserver 7015/tcp # Talon Webserver
+talon-webserver 7015/udp # Talon Webserver
+fisa-svc 7018/tcp # FISA Service
+doceri-ctl 7019/tcp # doceri drawing service control
+doceri-view 7019/udp # doceri drawing service screen view
+dpserve 7020/tcp # DP Serve
+dpserve 7020/udp # DP Serve
+dpserveadmin 7021/tcp # DP Serve Admin
+dpserveadmin 7021/udp # DP Serve Admin
+ctdp 7022/tcp # CT Discovery Protocol
+ctdp 7022/udp # CT Discovery Protocol
+ct2nmcs 7023/tcp # Comtech T2 NMCS
+ct2nmcs 7023/udp # Comtech T2 NMCS
+vmsvc 7024/tcp # Vormetric service
+vmsvc 7024/udp # Vormetric service
+vmsvc-2 7025/tcp # Vormetric Service II
+vmsvc-2 7025/udp # Vormetric Service II
+op-probe 7030/tcp # ObjectPlanet probe
+op-probe 7030/udp # ObjectPlanet probe
+iposplanet 7031/tcp # IPOSPLANET retailing multi devices protocol
+quest-disc 7040/udp # Quest application level network service discovery
+arcp 7070/tcp # ARCP
+arcp 7070/udp # ARCP
+iwg1 7071/tcp # IWGADTS Aircraft Housekeeping Message
+iwg1 7071/udp # IWGADTS Aircraft Housekeeping Message
+martalk 7073/tcp # MarTalk protocol
+empowerid 7080/tcp # EmpowerID Communication
+empowerid 7080/udp # EmpowerID Communication
+zixi-transport 7088/udp # Zixi live video transport
+jdp-disc 7095/udp # Java Discovery Protocol
+lazy-ptop 7099/tcp # lazy-ptop
+lazy-ptop 7099/udp # lazy-ptop
+font-service 7100/udp # X Font Service
+elcn 7101/tcp # Embedded Light Control Network
+elcn 7101/udp # Embedded Light Control Network
+aes-x170 7107/udp # AES-X170
+rothaga 7117/tcp # Encrypted chat and file transfer service
+virprot-lm 7121/tcp # Virtual Prototypes License Manager
+virprot-lm 7121/udp # Virtual Prototypes License Manager
+scenidm 7128/tcp # intelligent data manager
+scenidm 7128/udp # intelligent data manager
+scenccs 7129/tcp # Catalog Content Search
+scenccs 7129/udp # Catalog Content Search
+cabsm-comm 7161/tcp # CA BSM Comm
+cabsm-comm 7161/udp # CA BSM Comm
+caistoragemgr 7162/tcp # CA Storage Manager
+caistoragemgr 7162/udp # CA Storage Manager
+cacsambroker 7163/tcp # CA Connection Broker
+cacsambroker 7163/udp # CA Connection Broker
+fsr 7164/tcp # File System Repository Agent
+fsr 7164/udp # File System Repository Agent
+doc-server 7165/tcp # Document WCF Server
+doc-server 7165/udp # Document WCF Server
+aruba-server 7166/tcp # Aruba eDiscovery Server
+aruba-server 7166/udp # Aruba eDiscovery Server
+casrmagent 7167/tcp # CA SRM Agent
+cnckadserver 7168/tcp # cncKadServer DB & Inventory Services
+ccag-pib 7169/tcp # Consequor Consulting Process Integration Bridge
+ccag-pib 7169/udp # Consequor Consulting Process Integration Bridge
+nsrp 7170/tcp # Adaptive Name/Service Resolution
+nsrp 7170/udp # Adaptive Name/Service Resolution
+drm-production 7171/tcp # Discovery and Retention Mgt Production
+drm-production 7171/udp # Discovery and Retention Mgt Production
+metalbend 7172/tcp # MetalBend programmable interface
+zsecure 7173/tcp # zSecure Server
+clutild 7174/tcp # Clutild
+clutild 7174/udp # Clutild
+janus-disc 7181/udp # Janus Guidewire Enterprise Discovery Service Bus
+fodms 7200/tcp # FODMS FLIP
+fodms 7200/udp # FODMS FLIP
+dlip 7201/tcp # DLIP
+dlip 7201/udp # DLIP
+PS-Server 7215/tcp # PaperStream Server services
+PS-Capture-Pro 7216/tcp # PaperStream Capture Professional
+ramp 7227/tcp # Registry A & M Protocol
+ramp 7227/udp # Registry A $ M Protocol
+citrixupp 7228/tcp # Citrix Universal Printing Port
+citrixuppg 7229/tcp # Citrix UPP Gateway
+aspcoordination 7235/udp # ASP Coordination Protocol
+display 7236/tcp # Wi-Fi Alliance Wi-Fi Display Protocol
+pads 7237/tcp # PADS (Public Area Display System) Server
+frc-hicp 7244/tcp # FrontRow Calypso Human Interface Control Protocol
+frc-hicp-disc 7244/udp # FrontRow Calypso Human Interface Control Protocol
+cnap 7262/tcp # Calypso Network Access Protocol
+cnap 7262/udp # Calypso Network Access Protocol
+watchme-7272 7272/tcp # WatchMe Monitoring 7272
+watchme-7272 7272/udp # WatchMe Monitoring 7272
+oma-rlp 7273/tcp # OMA Roaming Location
+oma-rlp 7273/udp # OMA Roaming Location
+oma-rlp-s 7274/tcp # OMA Roaming Location SEC
+oma-rlp-s 7274/udp # OMA Roaming Location SEC
+oma-ulp 7275/tcp # OMA UserPlane Location
+oma-ulp 7275/udp # OMA UserPlane Location
+oma-ilp 7276/tcp # OMA Internal Location Protocol
+oma-ilp 7276/udp # OMA Internal Location Protocol
+oma-ilp-s 7277/tcp # OMA Internal Location Secure Protocol
+oma-ilp-s 7277/udp # OMA Internal Location Secure Protocol
+oma-dcdocbs 7278/tcp # OMA Dynamic Content Delivery over CBS
+oma-dcdocbs 7278/udp # OMA Dynamic Content Delivery over CBS
+ctxlic 7279/tcp # Citrix Licensing
+ctxlic 7279/udp # Citrix Licensing
+itactionserver1 7280/tcp # ITACTIONSERVER 1
+itactionserver1 7280/udp # ITACTIONSERVER 1
+itactionserver2 7281/tcp # ITACTIONSERVER 2
+itactionserver2 7281/udp # ITACTIONSERVER 2
+mzca-action 7282/tcp # eventACTION/ussACTION (MZCA) server
+mzca-alert 7282/udp # eventACTION/ussACTION (MZCA) alert
+genstat 7283/tcp # General Statistics Rendezvous Protocol
+lcm-server 7365/tcp # LifeKeeper Communications
+lcm-server 7365/udp # LifeKeeper Communications
+mindfilesys 7391/tcp # mind-file system server
+mindfilesys 7391/udp # mind-file system server
+mrssrendezvous 7392/tcp # mrss-rendezvous server
+mrssrendezvous 7392/udp # mrss-rendezvous server
+nfoldman 7393/tcp # nFoldMan Remote Publish
+nfoldman 7393/udp # nFoldMan Remote Publish
+fse 7394/tcp # File system export of backup images
+fse 7394/udp # File system export of backup images
+winqedit 7395/tcp # winqedit
+winqedit 7395/udp # winqedit
+hexarc 7397/tcp # Hexarc Command Language
+hexarc 7397/udp # Hexarc Command Language
+rtps-discovery 7400/tcp # RTPS Discovery
+rtps-discovery 7400/udp # RTPS Discovery
+rtps-dd-ut 7401/tcp # RTPS Data-Distribution User-Traffic
+rtps-dd-ut 7401/udp # RTPS Data-Distribution User-Traffic
+rtps-dd-mt 7402/tcp # RTPS Data-Distribution Meta-Traffic
+rtps-dd-mt 7402/udp # RTPS Data-Distribution Meta-Traffic
+ionixnetmon 7410/tcp # Ionix Network Monitor
+ionixnetmon 7410/udp # Ionix Network Monitor
+daqstream 7411/tcp # Streaming of measurement
+daqstream 7411/udp # Streaming of measurement
+mtportmon 7421/tcp # Matisse Port Monitor
+mtportmon 7421/udp # Matisse Port Monitor
+pmdmgr 7426/tcp # OpenView DM Postmaster Manager
+pmdmgr 7426/udp # OpenView DM Postmaster Manager
+oveadmgr 7427/tcp # OpenView DM Event Agent Manager
+oveadmgr 7427/udp # OpenView DM Event Agent Manager
+ovladmgr 7428/tcp # OpenView DM Log Agent Manager
+ovladmgr 7428/udp # OpenView DM Log Agent Manager
+opi-sock 7429/tcp # OpenView DM rqt communication
+opi-sock 7429/udp # OpenView DM rqt communication
+xmpv7 7430/tcp # OpenView DM xmpv7 api pipe
+xmpv7 7430/udp # OpenView DM xmpv7 api pipe
+pmd 7431/tcp # OpenView DM ovc/xmpv3 api pipe
+pmd 7431/udp # OpenView DM ovc/xmpv3 api pipe
+faximum 7437/tcp # Faximum
+faximum 7437/udp # Faximum
+oracleas-https 7443/tcp # Oracle Application Server HTTPS
+oracleas-https 7443/udp # Oracle Application Server HTTPS
+sttunnel 7471/tcp # Stateless Transport Tunneling Protocol
+rise 7473/tcp # Rise: The Vieneo Province
+rise 7473/udp # Rise: The Vieneo Province
+neo4j 7474/tcp # Neo4j Graph Database
+telops-lmd 7491/tcp # telops-lmd
+telops-lmd 7491/udp # telops-lmd
+silhouette 7500/tcp # Silhouette User
+silhouette 7500/udp # Silhouette User
+ovbus 7501/tcp # HP OpenView Bus Daemon
+ovbus 7501/udp # HP OpenView Bus Daemon
+adcp 7508/tcp # Automation Device Configuration Protocol
+acplt 7509/tcp # ACPLT - process automation service
+ovhpas 7510/tcp # HP OpenView Application Server
+ovhpas 7510/udp # HP OpenView Application Server
+pafec-lm 7511/tcp # pafec-lm
+pafec-lm 7511/udp # pafec-lm
+saratoga 7542/tcp # Saratoga Transfer Protocol
+saratoga 7542/udp # Saratoga Transfer Protocol
+atul 7543/tcp # atul server
+atul 7543/udp # atul server
+nta-ds 7544/tcp # FlowAnalyzer DisplayServer
+nta-ds 7544/udp # FlowAnalyzer DisplayServer
+nta-us 7545/tcp # FlowAnalyzer UtilityServer
+nta-us 7545/udp # FlowAnalyzer UtilityServer
+cfs 7546/tcp # Cisco Fabric service
+cfs 7546/udp # Cisco Fabric service
+cwmp 7547/tcp # DSL Forum CWMP
+cwmp 7547/udp # DSL Forum CWMP
+tidp 7548/tcp # Threat Information Distribution Protocol
+tidp 7548/udp # Threat Information Distribution Protocol
+nls-tl 7549/tcp # Network Layer Signaling Transport Layer
+nls-tl 7549/udp # Network Layer Signaling Transport Layer
+cloudsignaling 7550/udp # Cloud Signaling Service
+controlone-con 7551/tcp # ControlONE Console signaling
+sncp 7560/tcp # Sniffer Command Protocol
+sncp 7560/udp # Sniffer Command Protocol
+cfw 7563/tcp # Control Framework
+vsi-omega 7566/tcp # VSI Omega
+vsi-omega 7566/udp # VSI Omega
+dell-eql-asm 7569/tcp # Dell EqualLogic Host Group Management
+aries-kfinder 7570/tcp # Aries Kfinder
+aries-kfinder 7570/udp # Aries Kfinder
+coherence 7574/tcp # Oracle Coherence Cluster Service
+sun-lm 7588/tcp # Sun License Manager
+sun-lm 7588/udp # Sun License Manager
+mipi-debug 7606/tcp # MIPI Alliance Debug
+mipi-debug 7606/udp # MIPI Alliance Debug
+indi 7624/tcp # Instrument Neutral Distributed Interface
+indi 7624/udp # Instrument Neutral Distributed Interface
+simco 7626/tcp # SImple Middlebox COnfiguration (SIMCO) Server
+simco 7626/sctp # SImple Middlebox COnfiguration (SIMCO)
+soap-http 7627/tcp # SOAP Service Port
+soap-http 7627/udp # SOAP Service Port
+zen-pawn 7628/tcp # Primary Agent Work Notification
+zen-pawn 7628/udp # Primary Agent Work Notification
+xdas 7629/tcp # OpenXDAS Wire Protocol
+xdas 7629/udp # OpenXDAS Wire Protocol
+hawk 7630/tcp # HA Web Konsole
+tesla-sys-msg 7631/tcp # TESLA System Messaging
+pmdfmgt 7633/tcp # PMDF Management
+pmdfmgt 7633/udp # PMDF Management
+cuseeme 7648/tcp # bonjour-cuseeme
+cuseeme 7648/udp # bonjour-cuseeme
+imqstomp 7672/tcp # iMQ STOMP Server
+imqstomps 7673/tcp # iMQ STOMP Server over SSL
+imqtunnels 7674/tcp # iMQ SSL tunnel
+imqtunnels 7674/udp # iMQ SSL tunnel
+imqtunnel 7675/tcp # iMQ Tunnel
+imqtunnel 7675/udp # iMQ Tunnel
+imqbrokerd 7676/tcp # iMQ Broker Rendezvous
+imqbrokerd 7676/udp # iMQ Broker Rendezvous
+sun-user-https 7677/tcp # Sun App Server - HTTPS
+sun-user-https 7677/udp # Sun App Server - HTTPS
+pando-pub 7680/tcp # Pando Media Public Distribution
+pando-pub 7680/udp # Pando Media Public Distribution
+dmt 7683/tcp # Cleondris DMT
+collaber 7689/tcp # Collaber Network Service
+collaber 7689/udp # Collaber Network Service
+klio 7697/tcp # KLIO communications
+klio 7697/udp # KLIO communications
+em7-secom 7700/tcp # EM7 Secure Communications
+sync-em7 7707/tcp # EM7 Dynamic Updates
+sync-em7 7707/udp # EM7 Dynamic Updates
+scinet 7708/tcp # scientia.net
+scinet 7708/udp # scientia.net
+medimageportal 7720/tcp # MedImage Portal
+medimageportal 7720/udp # MedImage Portal
+nsdeepfreezectl 7724/tcp # Novell Snap-in Deep Freeze Control
+nsdeepfreezectl 7724/udp # Novell Snap-in Deep Freeze Control
+nitrogen 7725/tcp # Nitrogen Service
+nitrogen 7725/udp # Nitrogen Service
+freezexservice 7726/tcp # FreezeX Console Service
+freezexservice 7726/udp # FreezeX Console Service
+trident-data 7727/tcp # Trident Systems Data
+trident-data 7727/udp # Trident Systems Data
+osvr 7728/tcp # Open-Source Virtual Reality
+osvr 7728/udp # Open-Source Virtual Reality
+osvr 7728/sctp # Open-Source Virtual Reality
+smip 7734/tcp # Smith Protocol over IP
+smip 7734/udp # Smith Protocol over IP
+aiagent 7738/tcp # HP Enterprise Discovery Agent
+aiagent 7738/udp # HP Enterprise Discovery Agent
+scriptview 7741/tcp # ScriptView Network
+scriptview 7741/udp # ScriptView Network
+msss 7742/tcp # Mugginsoft Script Server Service
+sstp-1 7743/tcp # Sakura Script Transfer Protocol
+sstp-1 7743/udp # Sakura Script Transfer Protocol
+raqmon-pdu 7744/tcp # RAQMON PDU
+raqmon-pdu 7744/udp # RAQMON PDU
+prgp 7747/tcp # Put/Run/Get Protocol
+prgp 7747/udp # Put/Run/Get Protocol
+inetfs 7775/tcp # File System using TLS over WAN
+cbt 7777/tcp # cbt
+cbt 7777/udp # cbt
+interwise 7778/tcp # Interwise
+interwise 7778/udp # Interwise
+vstat 7779/tcp # VSTAT
+vstat 7779/udp # VSTAT
+accu-lmgr 7781/tcp # accu-lmgr
+accu-lmgr 7781/udp # accu-lmgr
+s-bfd 7784/udp # Seamless Bidirectional Forwarding Detection
+minivend 7786/tcp # MINIVEND
+minivend 7786/udp # MINIVEND
+popup-reminders 7787/tcp # Popup Reminders Receive
+popup-reminders 7787/udp # Popup Reminders Receive
+office-tools 7789/tcp # Office Tools Pro Receive
+office-tools 7789/udp # Office Tools Pro Receive
+q3ade 7794/tcp # Q3ADE Cluster Service
+q3ade 7794/udp # Q3ADE Cluster Service
+pnet-conn 7797/tcp # Propel Connector port
+pnet-conn 7797/udp # Propel Connector port
+pnet-enc 7798/tcp # Propel Encoder port
+pnet-enc 7798/udp # Propel Encoder port
+altbsdp 7799/tcp # Alternate BSDP Service
+altbsdp 7799/udp # Alternate BSDP Service
+asr 7800/tcp # Apple Software Restore
+asr 7800/udp # Apple Software Restore
+ssp-client 7801/tcp # Secure Server Protocol - client
+ssp-client 7801/udp # Secure Server Protocol - client
+vns-tp 7802/udp # Virtualized Network Services tunnel protocol
+rbt-wanopt 7810/tcp # Riverbed WAN Optimization Protocol
+rbt-wanopt 7810/udp # Riverbed WAN Optimization Protocol
+apc-7845 7845/tcp # APC 7845
+apc-7845 7845/udp # APC 7845
+apc-7846 7846/tcp # APC 7846
+apc-7846 7846/udp # APC 7846
+csoauth 7847/tcp # A product key authentication by CSO
+mobileanalyzer 7869/tcp # MobileAnalyzer& MobileMonitor
+rbt-smc 7870/tcp # Riverbed Steelhead Mobile Service
+mdm 7871/tcp # Mobile Device Management
+mipv6tls 7872/udp # TLS-based Mobile IPv6 Security
+owms 7878/tcp # Opswise Message Service
+pss 7880/tcp # Pearson
+pss 7880/udp # Pearson
+ubroker 7887/tcp # Universal Broker
+ubroker 7887/udp # Universal Broker
+mevent 7900/tcp # Multicast Event
+mevent 7900/udp # Multicast Event
+tnos-sp 7901/tcp # TNOS Service Protocol
+tnos-sp 7901/udp # TNOS Service Protocol
+tnos-dp 7902/tcp # TNOS shell Protocol
+tnos-dp 7902/udp # TNOS shell Protocol
+tnos-dps 7903/tcp # TNOS Secure DiaguardProtocol
+tnos-dps 7903/udp # TNOS Secure DiaguardProtocol
+qo-secure 7913/tcp # QuickObjects secure port
+qo-secure 7913/udp # QuickObjects secure port
+t2-drm 7932/tcp # Tier 2 Data Resource Manager
+t2-drm 7932/udp # Tier 2 Data Resource Manager
+t2-brm 7933/tcp # Tier 2 Business Rules Manager
+t2-brm 7933/udp # Tier 2 Business Rules Manager
+generalsync 7962/tcp # general-purpose synchronization protocol
+generalsync 7962/udp # general-purpose synchronization protocol
+supercell 7967/tcp # Supercell
+supercell 7967/udp # Supercell
+micromuse-ncps 7979/tcp # Micromuse-ncps
+micromuse-ncps 7979/udp # Micromuse-ncps
+quest-vista 7980/tcp # Quest Vista
+quest-vista 7980/udp # Quest Vista
+sossd-collect 7981/tcp # Spotlight on SQL Server Desktop Collect
+sossd-agent 7982/tcp # Spotlight on SQL Server Desktop Agent
+sossd-disc 7982/udp # Spotlight on SQL Server Desktop Agent Discovery
+pushns 7997/tcp # PUSH Notification Service
+usicontentpush 7998/udp # USI Content Push Service
+irdmi2 7999/tcp # iRDMI2
+irdmi2 7999/udp # iRDMI2
+irdmi 8000/tcp # iRDMI
+irdmi 8000/udp # iRDMI
+vcom-tunnel 8001/tcp # VCOM Tunnel
+vcom-tunnel 8001/udp # VCOM Tunnel
+teradataordbms 8002/tcp # Teradata ORDBMS
+teradataordbms 8002/udp # Teradata ORDBMS
+mcreport 8003/tcp # Mulberry Connect Reporting Service
+mcreport 8003/udp # Mulberry Connect Reporting Service
+mxi 8005/tcp # MXI Generation II for z/OS
+mxi 8005/udp # MXI Generation II for z/OS
+qbdb 8019/tcp # QB DB Dynamic Port
+qbdb 8019/udp # QB DB Dynamic Port
+intu-ec-svcdisc 8020/tcp # Intuit Entitlement Service and Discovery
+intu-ec-svcdisc 8020/udp # Intuit Entitlement Service and Discovery
+intu-ec-client 8021/tcp # Intuit Entitlement Client
+intu-ec-client 8021/udp # Intuit Entitlement Client
+oa-system 8022/tcp # oa-system
+oa-system 8022/udp # oa-system
+ca-audit-da 8025/tcp # CA Audit Distribution Agent
+ca-audit-da 8025/udp # CA Audit Distribution Agent
+ca-audit-ds 8026/tcp # CA Audit Distribution Server
+ca-audit-ds 8026/udp # CA Audit Distribution Server
+pro-ed 8032/tcp # ProEd
+pro-ed 8032/udp # ProEd
+mindprint 8033/tcp # MindPrint
+mindprint 8033/udp # MindPrint
+vantronix-mgmt 8034/tcp # .vantronix Management
+vantronix-mgmt 8034/udp # .vantronix Management
+ampify 8040/tcp # Ampify Messaging Protocol
+ampify 8040/udp # Ampify Messaging Protocol
+fs-agent 8042/tcp # FireScope Agent
+fs-server 8043/tcp # FireScope Server
+fs-mgmt 8044/tcp # FireScope Management Interface
+rocrail 8051/tcp # Rocrail Client Service
+senomix01 8052/tcp # Senomix Timesheets Server
+senomix01 8052/udp # Senomix Timesheets Server
+senomix02 8053/tcp # Senomix Timesheets Client [1 year assignment]
+senomix02 8053/udp # Senomix Timesheets Client [1 year assignment]
+senomix03 8054/tcp # Senomix Timesheets Server [1 year assignment]
+senomix03 8054/udp # Senomix Timesheets Server [1 year assignment]
+senomix04 8055/tcp # Senomix Timesheets Server [1 year assignment]
+senomix04 8055/udp # Senomix Timesheets Server [1 year assignment]
+senomix05 8056/tcp # Senomix Timesheets Server [1 year assignment]
+senomix05 8056/udp # Senomix Timesheets Server [1 year assignment]
+senomix06 8057/tcp # Senomix Timesheets Client [1 year assignment]
+senomix06 8057/udp # Senomix Timesheets Client [1 year assignment]
+senomix07 8058/tcp # Senomix Timesheets Client [1 year assignment]
+senomix07 8058/udp # Senomix Timesheets Client [1 year assignment]
+senomix08 8059/tcp # Senomix Timesheets Client [1 year assignment]
+senomix08 8059/udp # Senomix Timesheets Client [1 year assignment]
+aero 8060/udp # Asymmetric Extended Route Optimization (AERO)
+toad-bi-appsrvr 8066/tcp # Toad BI Application Server
+infi-async 8067/tcp # Infinidat async replication
+gadugadu 8074/tcp # Gadu-Gadu
+gadugadu 8074/udp # Gadu-Gadu
+us-cli 8082/tcp # Utilistor (Client)
+us-cli 8082/udp # Utilistor (Client)
+us-srv 8083/tcp # Utilistor (Server)
+us-srv 8083/udp # Utilistor (Server)
+d-s-n 8086/tcp # Distributed SCADA Networking Rendezvous Port
+d-s-n 8086/udp # Distributed SCADA Networking Rendezvous Port
+simplifymedia 8087/tcp # Simplify Media SPP Protocol
+simplifymedia 8087/udp # Simplify Media SPP Protocol
+radan-http 8088/tcp # Radan HTTP
+radan-http 8088/udp # Radan HTTP
+jamlink 8091/tcp # Jam Link Framework
+sac 8097/tcp # SAC Port Id
+sac 8097/udp # SAC Port Id
+xprint-server 8100/tcp # Xprint Server
+xprint-server 8100/udp # Xprint Server
+ldoms-migr 8101/tcp # Logical Domains Migration
+kz-migr 8102/tcp # Oracle Kernel zones migration server
+mtl8000-matrix 8115/tcp # MTL8000 Matrix
+mtl8000-matrix 8115/udp # MTL8000 Matrix
+cp-cluster 8116/tcp # Check Point Clustering
+cp-cluster 8116/udp # Check Point Clustering
+purityrpc 8117/tcp # Purity replication clustering and remote management
+privoxy 8118/tcp # Privoxy HTTP proxy
+privoxy 8118/udp # Privoxy HTTP proxy
+apollo-data 8121/tcp # Apollo Data Port
+apollo-data 8121/udp # Apollo Data Port
+apollo-admin 8122/tcp # Apollo Admin Port
+apollo-admin 8122/udp # Apollo Admin Port
+paycash-online 8128/tcp # PayCash Online Protocol
+paycash-online 8128/udp # PayCash Online Protocol
+paycash-wbp 8129/tcp # PayCash Wallet-Browser
+paycash-wbp 8129/udp # PayCash Wallet-Browser
+indigo-vrmi 8130/tcp # INDIGO-VRMI
+indigo-vrmi 8130/udp # INDIGO-VRMI
+indigo-vbcp 8131/tcp # INDIGO-VBCP
+indigo-vbcp 8131/udp # INDIGO-VBCP
+dbabble 8132/tcp # dbabble
+dbabble 8132/udp # dbabble
+puppet 8140/tcp # The Puppet master service
+isdd 8148/tcp # i-SDD file transfer
+isdd 8148/udp # i-SDD file transfer
+eor-game 8149/udp # Edge of Reality game data
+quantastor 8153/tcp # QuantaStor Management interface
+patrol 8160/tcp # Patrol
+patrol 8160/udp # Patrol
+patrol-snmp 8161/tcp # Patrol SNMP
+patrol-snmp 8161/udp # Patrol SNMP
+lpar2rrd 8162/tcp # LPAR2RRD client server communication
+intermapper 8181/tcp # Intermapper network management system
+vmware-fdm 8182/tcp # VMware Fault Domain Manager
+vmware-fdm 8182/udp # VMware Fault Domain Manager
+proremote 8183/tcp # ProRemote
+itach 8184/tcp # Remote iTach Connection
+itach 8184/udp # Remote iTach Connection
+gcp-rphy 8190/tcp # Generic control plane for RPHY
+limnerpressure 8191/tcp # Limner Pressure
+spytechphone 8192/tcp # SpyTech Phone Service
+spytechphone 8192/udp # SpyTech Phone Service
+blp1 8194/tcp # Bloomberg data API
+blp1 8194/udp # Bloomberg data API
+blp2 8195/tcp # Bloomberg feed
+blp2 8195/udp # Bloomberg feed
+vvr-data 8199/tcp # VVR DATA
+vvr-data 8199/udp # VVR DATA
+trivnet1 8200/tcp # TRIVNET
+trivnet1 8200/udp # TRIVNET
+trivnet2 8201/tcp # TRIVNET
+trivnet2 8201/udp # TRIVNET
+aesop 8202/udp # Audio+Ethernet Standard Open Protocol
+lm-perfworks 8204/tcp # LM Perfworks
+lm-perfworks 8204/udp # LM Perfworks
+lm-instmgr 8205/tcp # LM Instmgr
+lm-instmgr 8205/udp # LM Instmgr
+lm-dta 8206/tcp # LM Dta
+lm-dta 8206/udp # LM Dta
+lm-sserver 8207/tcp # LM SServer
+lm-sserver 8207/udp # LM SServer
+lm-webwatcher 8208/tcp # LM Webwatcher
+lm-webwatcher 8208/udp # LM Webwatcher
+rexecj 8230/tcp # RexecJ Server
+rexecj 8230/udp # RexecJ Server
+hncp-udp-port 8231/udp # HNCP
+hncp-dtls-port 8232/udp # HNCP over DTLS
+synapse-nhttps 8243/tcp # Synapse Non Blocking HTTPS
+synapse-nhttps 8243/udp # Synapse Non Blocking HTTPS
+pando-sec 8276/tcp # Pando Media Controlled Distribution
+pando-sec 8276/udp # Pando Media Controlled Distribution
+synapse-nhttp 8280/tcp # Synapse Non Blocking HTTP
+synapse-nhttp 8280/udp # Synapse Non Blocking HTTP
+libelle 8282/tcp # Libelle EnterpriseBus
+libelle-disc 8282/udp # Libelle EnterpriseBus Discovery
+blp3 8292/tcp # Bloomberg professional
+blp3 8292/udp # Bloomberg professional
+blp4 8294/tcp # Bloomberg intelligent client
+blp4 8294/udp # Bloomberg intelligent client
+hiperscan-id 8293/tcp # Hiperscan Identification Service
+tmi 8300/tcp # Transport Management Interface
+tmi 8300/udp # Transport Management Interface
+amberon 8301/tcp # Amberon PPC/PPS
+amberon 8301/udp # Amberon PPC/PPS
+hub-open-net 8313/tcp # Hub Open Network
+tnp-discover 8320/tcp # Thin(ium) Network Protocol
+tnp-discover 8320/udp # Thin(ium) Network Protocol
+tnp 8321/tcp # Thin(ium) Network Protocol
+tnp 8321/udp # Thin(ium) Network Protocol
+garmin-marine 8322/tcp # Garmin Marine
+garmin-marine 8322/udp # Garmin Marine
+server-find 8351/tcp # Server Find
+server-find 8351/udp # Server Find
+cruise-enum 8376/tcp # Cruise ENUM
+cruise-enum 8376/udp # Cruise ENUM
+cruise-swroute 8377/tcp # Cruise SWROUTE
+cruise-swroute 8377/udp # Cruise SWROUTE
+cruise-config 8378/tcp # Cruise CONFIG
+cruise-config 8378/udp # Cruise CONFIG
+cruise-diags 8379/tcp # Cruise DIAGS
+cruise-diags 8379/udp # Cruise DIAGS
+cruise-update 8380/tcp # Cruise UPDATE
+cruise-update 8380/udp # Cruise UPDATE
+m2mservices 8383/tcp # M2m Services
+m2mservices 8383/udp # M2m Services
+marathontp 8384/udp # Marathon Transport Protocol
+cvd 8400/tcp # cvd
+cvd 8400/udp # cvd
+sabarsd 8401/tcp # sabarsd
+sabarsd 8401/udp # sabarsd
+abarsd 8402/tcp # abarsd
+abarsd 8402/udp # abarsd
+admind2 8403/tcp # admind
+admind2 8403/udp # admind
+svcloud 8404/tcp # SuperVault Cloud
+svbackup 8405/tcp # SuperVault Backup
+dlpx-sp 8415/tcp # Delphix Session Protocol
+espeech 8416/tcp # eSpeech Session Protocol
+espeech 8416/udp # eSpeech Session Protocol
+espeech-rtp 8417/tcp # eSpeech RTP Protocol
+espeech-rtp 8417/udp # eSpeech RTP Protocol
+cybro-a-bus 8442/tcp # CyBro A-bus Protocol
+cybro-a-bus 8442/udp # CyBro A-bus Protocol
+pcsync-https 8443/tcp # PCsync HTTPS
+pcsync-https 8443/udp # PCsync HTTPS
+pcsync-http 8444/tcp # PCsync HTTP
+pcsync-http 8444/udp # PCsync HTTP
+copy 8445/tcp # Port for copy per sync feature
+copy-disc 8445/udp # Port for copy discovery
+npmp 8450/tcp # npmp
+npmp 8450/udp # npmp
+nexentamv 8457/tcp # Nexenta Management GUI
+cisco-avp 8470/tcp # Cisco Address Validation Protocol
+pim-port 8471/tcp # PIM over Reliable Transport
+pim-port 8471/sctp # PIM over Reliable Transport
+otv 8472/tcp # Overlay Transport Virtualization (OTV)
+otv 8472/udp # Overlay Transport Virtualization (OTV)
+vp2p 8473/tcp # Virtual Point to Point
+vp2p 8473/udp # Virtual Point to Point
+noteshare 8474/tcp # AquaMinds NoteShare
+noteshare 8474/udp # AquaMinds NoteShare
+fmtp 8500/tcp # Flight Message Transfer Protocol
+fmtp 8500/udp # Flight Message Transfer Protocol
+cmtp-mgt 8501/tcp # CYTEL Message Transfer Management
+cmtp-av 8501/udp # CYTEL Message Transfer Audio and Video
+ftnmtp 8502/tcp # FTN Message Transfer
+lsp-self-ping 8503/udp # MPLS LSP Self-Ping
+rtsp-alt 8554/tcp # RTSP Alternate (see port 554)
+rtsp-alt 8554/udp # RTSP Alternate (see port 554)
+d-fence 8555/tcp # SYMAX D-FENCE
+d-fence 8555/udp # SYMAX D-FENCE
+dof-tunnel 8567/tcp # DOF tunneling protocol
+dof-tunnel 8567/udp # DOF tunneling protocol
+asterix 8600/tcp # Surveillance Data
+asterix 8600/udp # Surveillance Data
+canon-cpp-disc 8609/udp # Canon Compact Printer Protocol Discovery
+canon-mfnp 8610/tcp # Canon MFNP Service
+canon-mfnp 8610/udp # Canon MFNP Service
+canon-bjnp1 8611/tcp # Canon BJNP Port 1
+canon-bjnp1 8611/udp # Canon BJNP Port 1
+canon-bjnp2 8612/tcp # Canon BJNP Port 2
+canon-bjnp2 8612/udp # Canon BJNP Port 2
+canon-bjnp3 8613/tcp # Canon BJNP Port 3
+canon-bjnp3 8613/udp # Canon BJNP Port 3
+canon-bjnp4 8614/tcp # Canon BJNP Port 4
+canon-bjnp4 8614/udp # Canon BJNP Port 4
+imink 8615/tcp # Imink Service Control
+monetra 8665/tcp # Monetra
+monetra-admin 8666/tcp # Monetra Administrative
+msi-cps-rm 8675/tcp # Programming Software for Radio Management Motorola Solutions Customer
+msi-cps-rm-disc 8675/udp # Programming Software for Radio Management Discovery
+sun-as-jmxrmi 8686/tcp # Sun App Server - JMX/RMI
+sun-as-jmxrmi 8686/udp # Sun App Server - JMX/RMI
+openremote-ctrl 8688/tcp # OpenRemote Controller
+vnyx 8699/tcp # VNYX Primary Port
+vnyx 8699/udp # VNYX Primary Port
+nvc 8711/tcp # Nuance Voice Control
+dtp-net 8732/udp # DASGIP Net Services
+ibus 8733/tcp # iBus
+ibus 8733/udp # iBus
+dey-keyneg 8750/tcp # DEY Storage Key Negotiation
+mc-appserver 8763/tcp # MC-APPSERVER
+mc-appserver 8763/udp # MC-APPSERVER
+openqueue 8764/tcp # OPENQUEUE
+openqueue 8764/udp # OPENQUEUE
+ultraseek-http 8765/tcp # Ultraseek HTTP
+ultraseek-http 8765/udp # Ultraseek HTTP
+amcs 8766/tcp # Agilent Connectivity Service
+amcs 8766/udp # Agilent Connectivity Service
+dpap 8770/tcp # Digital Photo Access Protocol
+dpap 8770/udp # Digital Photo Access Protocol
+uec 8778/tcp # Stonebranch Universal Enterprise Controller
+msgclnt 8786/tcp # Message Client
+msgclnt 8786/udp # Message Client
+msgsrvr 8787/tcp # Message Server
+msgsrvr 8787/udp # Message Server
+acd-pm 8793/tcp # Accedian Performance Measurement
+acd-pm 8793/udp # Accedian Performance Measurement
+sunwebadmin 8800/tcp # Sun Web Server Admin Service
+sunwebadmin 8800/udp # Sun Web Server Admin Service
+truecm 8804/tcp # truecm
+truecm 8804/udp # truecm
+dxspider 8873/tcp # dxspider linking protocol
+dxspider 8873/udp # dxspider linking protocol
+cddbp-alt 8880/tcp # CDDBP
+cddbp-alt 8880/udp # CDDBP
+galaxy4d 8881/tcp # Galaxy4D Online Game Engine
+secure-mqtt 8883/tcp # Secure MQTT
+secure-mqtt 8883/udp # Secure MQTT
+ddi-tcp-1 8888/tcp # NewsEDGE server TCP (TCP 1)
+ddi-udp-1 8888/udp # NewsEDGE server UDP (UDP 1)
+ddi-tcp-2 8889/tcp # Desktop Data TCP 1
+ddi-udp-2 8889/udp # NewsEDGE server broadcast
+ddi-tcp-3 8890/tcp # Desktop Data TCP 2
+ddi-udp-3 8890/udp # NewsEDGE client broadcast
+ddi-tcp-4 8891/tcp # Desktop Data TCP 3: NESS application
+ddi-udp-4 8891/udp # Desktop Data UDP 3: NESS application
+ddi-tcp-5 8892/tcp # Desktop Data TCP 4: FARM product
+ddi-udp-5 8892/udp # Desktop Data UDP 4: FARM product
+ddi-tcp-6 8893/tcp # Desktop Data TCP 5: NewsEDGE/Web application
+ddi-udp-6 8893/udp # Desktop Data UDP 5: NewsEDGE/Web application
+ddi-tcp-7 8894/tcp # Desktop Data TCP 6: COAL application
+ddi-udp-7 8894/udp # Desktop Data UDP 6: COAL application
+canto-roboflow 8898/tcp # Canto RoboFlow Control
+#canto-roboflow 8998/tcp # Canto RoboFlow Control
+ospf-lite 8899/tcp # ospf-lite
+ospf-lite 8899/udp # ospf-lite
+jmb-cds1 8900/tcp # JMB-CDS 1
+jmb-cds1 8900/udp # JMB-CDS 1
+jmb-cds2 8901/tcp # JMB-CDS 2
+jmb-cds2 8901/udp # JMB-CDS 2
+manyone-http 8910/tcp # manyone-http
+manyone-http 8910/udp # manyone-http
+manyone-xml 8911/tcp # manyone-xml
+manyone-xml 8911/udp # manyone-xml
+wcbackup 8912/tcp # Windows Client Backup
+wcbackup 8912/udp # Windows Client Backup
+dragonfly 8913/tcp # Dragonfly System Service
+dragonfly 8913/udp # Dragonfly System Service
+twds 8937/tcp # Transaction Warehouse Data Service
+ub-dns-control 8953/tcp # unbound dns nameserver control
+cumulus-admin 8954/tcp # Cumulus Admin Port
+cumulus-admin 8954/udp # Cumulus Admin Port
+nod-provider 8980/tcp # Network of Devices Provider
+nod-provider 8980/udp # Network of Devices Provider
+nod-client 8981/udp # Network of Devices Client
+sunwebadmins 8989/tcp # Sun Web Server SSL Admin Service
+sunwebadmins 8989/udp # Sun Web Server SSL Admin Service
+http-wmap 8990/tcp # webmail HTTP service
+http-wmap 8990/udp # webmail HTTP service
+https-wmap 8991/tcp # webmail HTTPS service
+https-wmap 8991/udp # webmail HTTPS service
+oracle-ms-ens 8997/tcp # Oracle Messaging Server Event Notification Service
+bctp 8999/tcp # Brodos Crypto Trade Protocol
+bctp 8999/udp # Brodos Crypto Trade Protocol
+cslistener 9000/tcp # CSlistener
+cslistener 9000/udp # CSlistener
+etlservicemgr 9001/tcp # ETL Service Manager
+etlservicemgr 9001/udp # ETL Service Manager
+dynamid 9002/tcp # DynamID authentication
+dynamid 9002/udp # DynamID authentication
+golem 9005/tcp # Golem Inter-System RPC
+ogs-client 9007/udp # Open Grid Services Client
+ogs-server 9008/tcp # Open Grid Services Server
+pichat 9009/tcp # Pichat Server
+pichat 9009/udp # Pichat Server
+sdr 9010/tcp # Secure Data Replicator Protocol
+tambora 9020/tcp # TAMBORA
+tambora 9020/udp # TAMBORA
+panagolin-ident 9021/tcp # Pangolin Identification
+panagolin-ident 9021/udp # Pangolin Identification
+paragent 9022/tcp # PrivateArk Remote Agent
+paragent 9022/udp # PrivateArk Remote Agent
+swa-1 9023/tcp # Secure Web Access - 1
+swa-1 9023/udp # Secure Web Access - 1
+swa-2 9024/tcp # Secure Web Access - 2
+swa-2 9024/udp # Secure Web Access - 2
+swa-3 9025/tcp # Secure Web Access - 3
+swa-3 9025/udp # Secure Web Access - 3
+swa-4 9026/tcp # Secure Web Access - 4
+swa-4 9026/udp # Secure Web Access - 4
+versiera 9050/tcp # Versiera Agent Listener
+fio-cmgmt 9051/tcp # Fusion-io Central Manager Service
+glrpc 9080/tcp # Groove GLRPC
+glrpc 9080/udp # Groove GLRPC
+lcs-ap 9082/sctp # LCS Application Protocol
+emc-pp-mgmtsvc 9083/tcp # EMC PowerPath Mgmt Service
+aurora 9084/tcp # IBM AURORA Performance Visualizer
+aurora 9084/udp # IBM AURORA Performance Visualizer
+aurora 9084/sctp # IBM AURORA Performance Visualizer
+ibm-rsyscon 9085/tcp # IBM Remote System Console
+ibm-rsyscon 9085/udp # IBM Remote System Console
+net2display 9086/tcp # Vesa Net2Display
+net2display 9086/udp # Vesa Net2Display
+classic 9087/tcp # Classic Data Server
+classic 9087/udp # Classic Data Server
+sqlexec 9088/tcp # IBM Informix SQL Interface
+sqlexec 9088/udp # IBM Informix SQL Interface
+sqlexec-ssl 9089/tcp # IBM Informix SQL Interface - Encrypted
+sqlexec-ssl 9089/udp # IBM Informix SQL Interface - Encrypted
+websm 9090/tcp # WebSM
+websm 9090/udp # WebSM
+xmltec-xmlmail 9091/tcp # xmltec-xmlmail
+xmltec-xmlmail 9091/udp # xmltec-xmlmail
+XmlIpcRegSvc 9092/tcp # Xml-Ipc Server Reg
+XmlIpcRegSvc 9092/udp # Xml-Ipc Server Reg
+copycat 9093/tcp # Copycat database replication service
+hp-pdl-datastr 9100/udp pdl-datastream # PDL Data Streaming Port
+bacula-dir 9101/tcp # Bacula Director
+bacula-dir 9101/udp # Bacula Director
+bacula-fd 9102/tcp # Bacula File Daemon
+bacula-fd 9102/udp # Bacula File Daemon
+bacula-sd 9103/tcp # Bacula Storage Daemon
+bacula-sd 9103/udp # Bacula Storage Daemon
+peerwire 9104/tcp # PeerWire
+peerwire 9104/udp # PeerWire
+xadmin 9105/tcp # Xadmin Control Service
+xadmin 9105/udp # Xadmin Control Service
+astergate 9106/tcp # Astergate Control Service
+astergate-disc 9106/udp # Astergate Discovery Service
+astergatefax 9107/tcp # AstergateFax Control Service
+mxit 9119/tcp # MXit Instant Messaging
+mxit 9119/udp # MXit Instant Messaging
+grcmp 9122/tcp # Global Relay compliant mobile IM protocol
+grcp 9123/tcp # Global Relay compliant IM protocol
+dddp 9131/tcp # Dynamic Device Discovery
+dddp 9131/udp # Dynamic Device Discovery
+apani1 9160/tcp # apani1
+apani1 9160/udp # apani1
+apani2 9161/tcp # apani2
+apani2 9161/udp # apani2
+apani3 9162/tcp # apani3
+apani3 9162/udp # apani3
+apani4 9163/tcp # apani4
+apani4 9163/udp # apani4
+apani5 9164/tcp # apani5
+apani5 9164/udp # apani5
+sun-as-jpda 9191/tcp # Sun AppSvr JPDA
+sun-as-jpda 9191/udp # Sun AppSvr JPDA
+wap-wsp 9200/tcp # WAP connectionless session service
+wap-wsp 9200/udp # WAP connectionless session service
+wap-wsp-wtp 9201/tcp # WAP session service
+wap-wsp-wtp 9201/udp # WAP session service
+wap-wsp-s 9202/tcp # WAP secure connectionless session service
+wap-wsp-s 9202/udp # WAP secure connectionless session service
+wap-wsp-wtp-s 9203/tcp # WAP secure session service
+wap-wsp-wtp-s 9203/udp # WAP secure session service
+wap-vcard 9204/tcp # WAP vCard
+wap-vcard 9204/udp # WAP vCard
+wap-vcal 9205/tcp # WAP vCal
+wap-vcal 9205/udp # WAP vCal
+wap-vcard-s 9206/tcp # WAP vCard Secure
+wap-vcard-s 9206/udp # WAP vCard Secure
+wap-vcal-s 9207/tcp # WAP vCal Secure
+wap-vcal-s 9207/udp # WAP vCal Secure
+rjcdb-vcards 9208/tcp # rjcdb vCard
+rjcdb-vcards 9208/udp # rjcdb vCard
+almobile-system 9209/tcp # ALMobile System Service
+almobile-system 9209/udp # ALMobile System Service
+oma-mlp 9210/tcp # OMA Mobile Location Protocol
+oma-mlp 9210/udp # OMA Mobile Location Protocol
+oma-mlp-s 9211/tcp # OMA Mobile Location Protocol Secure
+oma-mlp-s 9211/udp # OMA Mobile Location Protocol Secure
+serverviewdbms 9212/tcp # Server View dbms access
+serverviewdbms 9212/udp # Server View dbms access
+serverstart 9213/tcp # ServerStart RemoteControl
+serverstart 9213/udp # ServerStart RemoteControl
+ipdcesgbs 9214/tcp # IPDC ESG BootstrapService
+ipdcesgbs 9214/udp # IPDC ESG BootstrapService
+insis 9215/tcp # Integrated Setup and Install Service
+insis 9215/udp # Integrated Setup and Install Service
+acme 9216/tcp # Aionex Communication Management Engine
+acme 9216/udp # Aionex Communication Management Engine
+fsc-port 9217/tcp # FSC Communication Port
+fsc-port 9217/udp # FSC Communication Port
+teamcoherence 9222/tcp # QSC Team Coherence
+teamcoherence 9222/udp # QSC Team Coherence
+Mon 9255/tcp # Manager On Network
+Mon 9255/udp # Manager On Network
+traingpsdata 9277/udp # GPS Data transmition from train to ground network
+pegasus 9278/tcp # Pegasus GPS Platform
+pegasus 9278/udp # Pegasus GPS Platform
+pegasus-ctl 9279/tcp # Pegaus GPS System Control Interface
+pegasus-ctl 9279/udp # Pegaus GPS System Control Interface
+pgps 9280/tcp # Predicted GPS
+pgps 9280/udp # Predicted GPS
+swtp-port1 9281/tcp # SofaWare transport port 1
+swtp-port1 9281/udp # SofaWare transport port 1
+swtp-port2 9282/tcp # SofaWare transport port 2
+swtp-port2 9282/udp # SofaWare transport port 2
+callwaveiam 9283/tcp # CallWaveIAM
+callwaveiam 9283/udp # CallWaveIAM
+visd 9284/tcp # VERITAS Information Serve
+visd 9284/udp # VERITAS Information Serve
+n2h2server 9285/tcp # N2H2 Filter Service Port
+n2h2server 9285/udp # N2H2 Filter Service Port
+n2receive 9286/udp # n2 monitoring receiver
+cumulus 9287/tcp # Cumulus
+cumulus 9287/udp # Cumulus
+armtechdaemon 9292/tcp # ArmTech Daemon
+armtechdaemon 9292/udp # ArmTech Daemon
+storview 9293/tcp # StorView Client
+storview 9293/udp # StorView Client
+armcenterhttp 9294/tcp # ARMCenter http Service
+armcenterhttp 9294/udp # ARMCenter http Service
+armcenterhttps 9295/tcp # ARMCenter https Service
+armcenterhttps 9295/udp # ARMCenter https Service
+vrace 9300/tcp # Virtual Racing Service
+vrace 9300/udp # Virtual Racing Service
+sphinxql 9306/tcp # Sphinx search server (MySQL listener)
+sphinxapi 9312/tcp # Sphinx search server
+secure-ts 9318/tcp # PKIX TimeStamp over TLS
+secure-ts 9318/udp # PKIX TimeStamp over TLS
+guibase 9321/tcp # guibase
+guibase 9321/udp # guibase
+mpidcmgr 9343/tcp # MpIdcMgr
+mpidcmgr 9343/udp # MpIdcMgr
+mphlpdmc 9344/tcp # Mphlpdmc
+mphlpdmc 9344/udp # Mphlpdmc
+rancher 9345/tcp # Rancher Agent
+ctechlicensing 9346/tcp # C Tech Licensing
+ctechlicensing 9346/udp # C Tech Licensing
+fjdmimgr 9374/tcp # fjdmimgr
+fjdmimgr 9374/udp # fjdmimgr
+boxp 9380/tcp # Brivs! Open Extensible Protocol
+boxp 9380/udp # Brivs! Open Extensible Protocol
+d2dconfig 9387/tcp # D2D Configuration Service
+d2ddatatrans 9388/tcp # D2D Data Transfer Service
+adws 9389/tcp # Active Directory Web Services
+otp 9390/tcp # OpenVAS Transfer Protocol
+fjinvmgr 9396/tcp # fjinvmgr
+fjinvmgr 9396/udp # fjinvmgr
+mpidcagt 9397/tcp # MpIdcAgt
+mpidcagt 9397/udp # MpIdcAgt
+sec-t4net-srv 9400/tcp # Samsung Twain for Network Server
+sec-t4net-srv 9400/udp # Samsung Twain for Network Server
+sec-t4net-clt 9401/tcp # Samsung Twain for Network Client
+sec-t4net-clt 9401/udp # Samsung Twain for Network Client
+sec-pc2fax-srv 9402/tcp # Samsung PC2FAX for Network Server
+sec-pc2fax-srv 9402/udp # Samsung PC2FAX for Network Server
+git 9418/tcp # git pack transfer service
+git 9418/udp # git pack transfer service
+tungsten-https 9443/tcp # WSO2 Tungsten HTTPS
+tungsten-https 9443/udp # WSO2 Tungsten HTTPS
+wso2esb-console 9444/tcp # WSO2 ESB Administration Console HTTPS
+wso2esb-console 9444/udp # WSO2 ESB Administration Console HTTPS
+mindarray-ca 9445/tcp # MindArray Systems Console Agent
+sntlkeyssrvr 9450/tcp # Sentinel Keys Server
+sntlkeyssrvr 9450/udp # Sentinel Keys Server
+ismserver 9500/tcp # ismserver
+ismserver 9500/udp # ismserver
+sma-spw 9522/udp # SMA Speedwire
+mngsuite 9535/tcp # Management Suite Remote Control
+mngsuite 9535/udp # Management Suite Remote Control
+laes-bf 9536/tcp # Surveillance buffering function
+laes-bf 9536/udp # Surveillance buffering function
+trispen-sra 9555/tcp # Trispen Secure Remote Access
+trispen-sra 9555/udp # Trispen Secure Remote Access
+ldgateway 9592/tcp # LANDesk Gateway
+ldgateway 9592/udp # LANDesk Gateway
+cba8 9593/tcp # LANDesk Management Agent (cba8)
+cba8 9593/udp # LANDesk Management Agent (cba8)
+msgsys 9594/tcp # Message System
+msgsys 9594/udp # Message System
+pds 9595/tcp # Ping Discovery Service
+pds 9595/udp # Ping Discovery Service
+mercury-disc 9596/tcp # Mercury Discovery
+mercury-disc 9596/udp # Mercury Discovery
+pd-admin 9597/tcp # PD Administration
+pd-admin 9597/udp # PD Administration
+vscp 9598/tcp # Very Simple Ctrl Protocol
+vscp 9598/udp # Very Simple Ctrl Protocol
+robix 9599/tcp # Robix
+robix 9599/udp # Robix
+micromuse-ncpw 9600/tcp # MICROMUSE-NCPW
+micromuse-ncpw 9600/udp # MICROMUSE-NCPW
+streamcomm-ds 9612/tcp # StreamComm User Directory
+streamcomm-ds 9612/udp # StreamComm User Directory
+iadt-tls 9614/tcp # iADT Protocol over TLS
+erunbook_agent 9616/tcp erunbook-agent # eRunbook Agent
+erunbook_server 9617/tcp erunbook-server # eRunbook Server
+condor 9618/tcp # Condor Collector Service
+condor 9618/udp # Condor Collector Service
+odbcpathway 9628/tcp # ODBC Pathway Service
+odbcpathway 9628/udp # ODBC Pathway Service
+uniport 9629/tcp # UniPort SSO Controller
+uniport 9629/udp # UniPort SSO Controller
+peoctlr 9630/tcp # Peovica Controller
+peocoll 9631/tcp # Peovica Collector
+mc-comm 9632/udp # Mobile-C Communications
+pqsflows 9640/tcp # ProQueSys Flows Service
+zoomcp 9666/tcp # Zoom Control Panel Game Server Management
+xmms2 9667/tcp # Cross-platform Music Multiplexing System
+xmms2 9667/udp # Cross-platform Music Multiplexing System
+tec5-sdctp 9668/tcp # tec5 Spectral Device Control Protocol
+tec5-sdctp 9668/udp # tec5 Spectral Device Control Protocol
+client-wakeup 9694/tcp # T-Mobile Client Wakeup Message
+client-wakeup 9694/udp # T-Mobile Client Wakeup Message
+ccnx 9695/tcp # Content Centric Networking
+ccnx 9695/udp # Content Centric Networking
+board-roar 9700/tcp # Board M.I.T. Service
+board-roar 9700/udp # Board M.I.T. Service
+l5nas-parchan 9747/tcp # L5NAS Parallel Channel
+l5nas-parchan 9747/udp # L5NAS Parallel Channel
+board-voip 9750/tcp # Board M.I.T. Synchronous Collaboration
+board-voip 9750/udp # Board M.I.T. Synchronous Collaboration
+rasadv 9753/tcp # rasadv
+rasadv 9753/udp # rasadv
+tungsten-http 9762/tcp # WSO2 Tungsten HTTP
+tungsten-http 9762/udp # WSO2 Tungsten HTTP
+davsrc 9800/tcp # WebDav Source Port
+davsrc 9800/udp # WebDav Source Port
+sstp-2 9801/tcp # Sakura Script Transfer Protocol-2
+sstp-2 9801/udp # Sakura Script Transfer Protocol-2
+davsrcs 9802/tcp # WebDAV Source TLS/SSL
+davsrcs 9802/udp # WebDAV Source TLS/SSL
+sapv1 9875/tcp # Session Announcement v1
+sapv1 9875/udp # Session Announcement v1
+sd 9876/tcp # Session Director
+kca-service 9878/udp # Certificate Issuance
+cyborg-systems 9888/tcp # CYBORG Systems
+cyborg-systems 9888/udp # CYBORG Systems
+gt-proxy 9889/tcp # Port for Cable network related data proxy or repeater
+gt-proxy 9889/udp # Port for Cable network related data proxy or repeater
+monkeycom 9898/tcp # MonkeyCom
+monkeycom 9898/udp # MonkeyCom
+sctp-tunneling 9899/udp # SCTP TUNNELING
+iua 9900/tcp # IUA
+iua 9900/udp # IUA
+iua 9900/sctp # IUA
+enrp 9901/udp # enrp server channel
+enrp-sctp 9901/sctp # enrp server channel
+enrp-sctp-tls 9902/sctp # enrp/tls server channel
+multicast-ping 9903/tcp # Multicast Ping Protocol
+multicast-ping 9903/udp # Multicast Ping Protocol
+domaintime 9909/tcp # domaintime
+domaintime 9909/udp # domaintime
+sype-transport 9911/tcp # SYPECom Transport Protocol
+sype-transport 9911/udp # SYPECom Transport Protocol
+xybrid-cloud 9925/tcp # XYBRID Cloud
+apc-9950 9950/tcp # APC 9950
+apc-9950 9950/udp # APC 9950
+apc-9951 9951/tcp # APC 9951
+apc-9951 9951/udp # APC 9951
+apc-9952 9952/tcp # APC 9952
+apc-9952 9952/udp # APC 9952
+acis 9953/tcp # 9953
+acis 9953/udp # 9953
+hinp 9954/tcp # HaloteC Instrument Network
+alljoyn-stm 9955/tcp # Contact Port for AllJoyn
+alljoyn-mcm 9955/udp # multiplexed constrained messaging
+alljoyn 9956/udp # Alljoyn Name Service
+odnsp 9966/tcp # OKI Data Network Setting Protocol
+odnsp 9966/udp # OKI Data Network Setting Protocol
+xybrid-rt 9978/tcp # XYBRID RT Server
+dsm-scm-target 9987/tcp # DSM/SCM Target Interface
+dsm-scm-target 9987/udp # DSM/SCM Target Interface
+nsesrvr 9988/tcp # Software Essentials Secure HTTP server
+osm-appsrvr 9990/tcp # OSM Applet Server
+osm-appsrvr 9990/udp # OSM Applet Server
+osm-oev 9991/tcp # OSM Event Server
+osm-oev 9991/udp # OSM Event Server
+palace-1 9992/tcp # OnLive-1
+palace-1 9992/udp # OnLive-1
+palace-2 9993/tcp # OnLive-2
+palace-2 9993/udp # OnLive-2
+palace-3 9994/tcp # OnLive-3
+palace-3 9994/udp # OnLive-3
+palace-4 9995/tcp # Palace-4
+palace-4 9995/udp # Palace-4
+palace-5 9996/tcp # Palace-5
+palace-5 9996/udp # Palace-5
+palace-6 9997/tcp # Palace-6
+palace-6 9997/udp # Palace-6
+distinct32 9998/tcp # Distinct32
+distinct32 9998/udp # Distinct32
+distinct 9999/tcp # distinct
+distinct 9999/udp # distinct
+ndmp 10000/tcp # Network Data Management Protocol
+ndmp 10000/udp # Network Data Management Protocol
+scp-config 10001/tcp # SCP Configuration
+scp-config 10001/udp # SCP Configuration
+documentum 10002/tcp # EMC-Documentum Content Server Product
+documentum 10002/udp # EMC-Documentum Content Server Product
+documentum_s 10003/tcp documentum-s # EMC-Documentum Content Server Product
+documentum_s 10003/udp documentum-s # EMC-Documentum Content Server Product
+emcrmirccd 10004/tcp # EMC Replication Manager Client
+emcrmird 10005/tcp # EMC Replication Manager Server
+netapp-sync 10006/tcp # Sync replication protocol among different NetApp platforms
+mvs-capacity 10007/tcp # MVS Capacity
+mvs-capacity 10007/udp # MVS Capacity
+octopus 10008/tcp # Octopus Multiplexer
+octopus 10008/udp # Octopus Multiplexer
+swdtp-sv 10009/tcp # Systemwalker Desktop Patrol
+swdtp-sv 10009/udp # Systemwalker Desktop Patrol
+rxapi 10010/tcp # ooRexx rxapi services
+cefdvmp 10023/udp # Comtech EF-Data's Vipersat Management Protocol (likely typo in IANA reservation)
+zabbix-agent 10050/tcp # Zabbix Agent
+zabbix-agent 10050/udp # Zabbix Agent
+zabbix-trapper 10051/tcp # Zabbix Trapper
+zabbix-trapper 10051/udp # Zabbix Trapper
+qptlmd 10055/tcp # Quantapoint FLEXlm Licensing Service
+itap-ddtp 10100/tcp # VERITAS ITAP DDTP
+itap-ddtp 10100/udp # VERITAS ITAP DDTP
+ezmeeting-2 10101/tcp # eZmeeting
+ezmeeting-2 10101/udp # eZmeeting
+ezproxy-2 10102/tcp # eZproxy
+ezproxy-2 10102/udp # eZproxy
+ezrelay 10103/tcp # eZrelay
+ezrelay 10103/udp # eZrelay
+swdtp 10104/tcp # Systemwalker Desktop Patrol
+swdtp 10104/udp # Systemwalker Desktop Patrol
+bctp-server 10107/tcp # VERITAS BCTP, server
+bctp-server 10107/udp # VERITAS BCTP, server
+nmea-0183 10110/tcp # NMEA-0183 Navigational Data
+nmea-0183 10110/udp # NMEA-0183 Navigational Data
+nmea-onenet 10111/udp # NMEA OneNet multicast messaging
+netiq-endpoint 10113/tcp # NetIQ Endpoint
+netiq-endpoint 10113/udp # NetIQ Endpoint
+netiq-qcheck 10114/tcp # NetIQ Qcheck
+netiq-qcheck 10114/udp # NetIQ Qcheck
+netiq-endpt 10115/tcp # NetIQ Endpoint
+netiq-endpt 10115/udp # NetIQ Endpoint
+netiq-voipa 10116/tcp # NetIQ VoIP Assessor
+netiq-voipa 10116/udp # NetIQ VoIP Assessor
+iqrm 10117/tcp # NetIQ IQCResource Managament Svc
+iqrm 10117/udp # NetIQ IQCResource Managament Svc
+cimple 10125/udp # HotLink CIMple REST API
+bmc-perf-sd 10128/tcp # BMC-PERFORM-SERVICE DAEMON
+bmc-perf-sd 10128/udp # BMC-PERFORM-SERVICE DAEMON
+bmc-gms 10129/tcp # BMC General Manager Server
+qb-db-server 10160/tcp # QB Database Server
+qb-db-server 10160/udp # QB Database Server
+snmptls 10161/tcp # SNMP-TLS
+snmpdtls 10161/udp # SNMP-DTLS
+snmptls-trap 10162/tcp # SNMP-Trap-TLS
+snmpdtls-trap 10162/udp # SNMP-Trap-DTLS
+trisoap 10200/tcp # Trigence AE Soap Service
+trisoap 10200/udp # Trigence AE Soap Service
+rsms 10201/tcp # Remote Server Management Service
+rscs 10201/udp # Remote Server Control and Test Service
+apollo-relay 10252/tcp # Apollo Relay Port
+apollo-relay 10252/udp # Apollo Relay Port
+eapol-relay 10253/udp # Relay of EAPOL frames
+axis-wimp-port 10260/tcp # Axis WIMP Port
+axis-wimp-port 10260/udp # Axis WIMP Port
+blocks 10288/tcp # Blocks
+blocks 10288/udp # Blocks
+cosir 10321/tcp # Computer Op System Information Report
+bngsync 10439/udp # BalanceNG session table synchronization protocol
+hip-nat-t 10500/udp # HIP NAT-traversal
+MOS-lower 10540/tcp # MOS Media Object Metadata Port
+MOS-lower 10540/udp # MOS Media Object Metadata Port
+MOS-upper 10541/tcp # MOS Running Order Port
+MOS-upper 10541/udp # MOS Running Order Port
+MOS-aux 10542/tcp # MOS Low Priority Port
+MOS-aux 10542/udp # MOS Low Priority Port
+MOS-soap 10543/tcp # MOS SOAP Default Port
+MOS-soap 10543/udp # MOS SOAP Default Port
+MOS-soap-opt 10544/tcp # MOS SOAP Optional Port
+MOS-soap-opt 10544/udp # MOS SOAP Optional Port
+serverdocs 10548/tcp # Apple Document Sharing
+printopia 10631/tcp # administration and control of "Printopia"
+gap 10800/tcp # Gestor de Acaparamiento para Pocket PCs
+gap 10800/udp # Gestor de Acaparamiento para Pocket PCs
+lpdg 10805/tcp # LUCIA Pareja Data Group
+lpdg 10805/udp # LUCIA Pareja Data Group
+nbd 10809/tcp # Linux Network Block Device
+nmc-disc 10810/udp # Nuance Mobile Care Discovery
+helix 10860/tcp # Helix Client/Server
+helix 10860/udp # Helix Client/Server
+bveapi 10880/tcp # BVEssentials HTTP API
+bveapi 10880/udp # BVEssentials HTTP API
+octopustentacle 10933/tcp # Octopus Deploy Tentacle
+rmiaux 10990/tcp # Auxiliary RMI Port
+rmiaux 10990/udp # Auxiliary RMI Port
+irisa 11000/tcp # IRISA
+irisa 11000/udp # IRISA
+metasys 11001/tcp # Metasys
+metasys 11001/udp # Metasys
+cefd-vmp 11023/udp # Comtech EF-Data's Vipersat Management Protocol (is listed as 10023 in IANA)
+weave 11095/tcp # Nest device-to-device and device-to-service application protocol
+weave 11095/udp # Nest device-to-device and device-to-service application protocol
+origo-sync 11103/tcp # OrigoDB Server Sync
+netapp-icmgmt 11104/tcp # NetApp Intercluster Management
+netapp-icdata 11105/tcp # NetApp Intercluster Data
+sgi-lk 11106/tcp # SGI LK Licensing service
+sgi-lk 11106/udp # SGI LK Licensing service
+myq-termlink 11108/udp # Hardware Terminals Discovery protocol
+sgi-dmfmgr 11109/tcp # Data migration facility manager
+sgi-soap 11110/tcp # Data migration facility SOAP
+vce 11111/tcp # Viral Computing Environment (VCE)
+vce 11111/udp # Viral Computing Environment (VCE)
+dicom 11112/tcp # DICOM
+dicom 11112/udp # DICOM
+suncacao-snmp 11161/tcp # sun cacao snmp access point
+suncacao-snmp 11161/udp # sun cacao snmp access point
+suncacao-jmxmp 11162/tcp # sun cacao JMX-remoting access point
+suncacao-jmxmp 11162/udp # sun cacao JMX-remoting access point
+suncacao-rmi 11163/tcp # sun cacao rmi registry access point
+suncacao-rmi 11163/udp # sun cacao rmi registry access point
+suncacao-csa 11164/tcp # sun cacao command-streaming access point
+suncacao-csa 11164/udp # sun cacao command-streaming access point
+suncacao-websvc 11165/tcp # sun cacao web service access point
+suncacao-websvc 11165/udp # sun cacao web service access point
+snss 11171/udp # Surgical Notes Security Service Discovery (SNSS)
+oemcacao-jmxmp 11172/tcp # OEM cacao JMX-remoting access point
+t5-straton 11173/tcp # Straton Runtime Programing
+oemcacao-rmi 11174/tcp # OEM cacao rmi registry access point
+oemcacao-websvc 11175/tcp # OEM cacao web service access point
+smsqp 11201/tcp # smsqp
+smsqp 11201/udp # smsqp
+dcsl-backup 11202/tcp # DCSL Network Backup Services
+wifree 11208/tcp # WiFree Service
+wifree 11208/udp # WiFree Service
+memcache 11211/tcp # Memory cache service
+memcache 11211/udp # Memory cache service
+imip 11319/tcp # IMIP
+imip 11319/udp # IMIP
+imip-channels 11320/tcp # IMIP Channels Port
+imip-channels 11320/udp # IMIP Channels Port
+arena-server 11321/tcp # Arena Server Listen
+arena-server 11321/udp # Arena Server Listen
+atm-uhas 11367/tcp # ATM UHAS
+atm-uhas 11367/udp # ATM UHAS
+lsdp 11430/udp # Lenbrook Service Discovery Protocol
+tempest-port 11600/tcp # Tempest Protocol Port
+tempest-port 11600/udp # Tempest Protocol Port
+emc-xsw-dconfig 11623/tcp # EMC XtremSW distributed config
+emc-xsw-dcache 11723/tcp # EMC XtremSW distributed cache
+emc-xsw-dcache 11723/udp # EMC XtremSW distributed cache
+intrepid-ssl 11751/tcp # Intrepid SSL
+intrepid-ssl 11751/udp # Intrepid SSL
+lanschool 11796/tcp # LanSchool
+lanschool-mpt 11796/udp # Lanschool Multipoint
+xoraya 11876/tcp # X2E Xoraya Multichannel protocol
+xoraya 11876/udp # X2E Xoraya Multichannel protocol
+x2e-disc 11877/udp # X2E service discovery protocol
+sysinfo-sp 11967/tcp # SysInfo Service Protocol
+sysinfo-sp 11967/udp # SysInfo Sercice Protocol
+wmereceiving 11997/sctp # WorldMailExpress
+wmedistribution 11998/sctp # WorldMailExpress
+wmereporting 11999/sctp # WorldMailExpress
+entextxid 12000/tcp # IBM Enterprise Extender SNA XID Exchange
+entextxid 12000/udp # IBM Enterprise Extender SNA XID Exchange
+entextnetwk 12001/tcp # IBM Enterprise Extender SNA COS Network Priority
+entextnetwk 12001/udp # IBM Enterprise Extender SNA COS Network Priority
+entexthigh 12002/tcp # IBM Enterprise Extender SNA COS High Priority
+entexthigh 12002/udp # IBM Enterprise Extender SNA COS High Priority
+entextmed 12003/tcp # IBM Enterprise Extender SNA COS Medium Priority
+entextmed 12003/udp # IBM Enterprise Extender SNA COS Medium Priority
+entextlow 12004/tcp # IBM Enterprise Extender SNA COS Low Priority
+entextlow 12004/udp # IBM Enterprise Extender SNA COS Low Priority
+dbisamserver1 12005/tcp # DBISAM Database Server - Regular
+dbisamserver1 12005/udp # DBISAM Database Server - Regular
+dbisamserver2 12006/tcp # DBISAM Database Server - Admin
+dbisamserver2 12006/udp # DBISAM Database Server - Admin
+accuracer 12007/tcp # Accuracer Database System ñ Server
+accuracer 12007/udp # Accuracer Database System ñ Server
+accuracer-dbms 12008/tcp # Accuracer Database System ñ Admin
+accuracer-dbms 12008/udp # Accuracer Database System ñ Admin
+ghvpn 12009/udp # Green Hills VPN
+edbsrvr 12010/tcp # ElevateDB Server
+vipera 12012/tcp # Vipera Messaging Service
+vipera 12012/udp # Vipera Messaging Service
+vipera-ssl 12013/tcp # Vipera Messaging Service over SSL Communication
+vipera-ssl 12013/udp # Vipera Messaging Service over SSL Communication
+rets-ssl 12109/tcp # RETS over SSL
+rets-ssl 12109/udp # RETS over SSL
+nupaper-ss 12121/tcp # NuPaper Session Service
+nupaper-ss 12121/udp # NuPaper Session Service
+cawas 12168/tcp # CA Web Access Service
+cawas 12168/udp # CA Web Access Service
+hivep 12172/tcp # HiveP
+hivep 12172/udp # HiveP
+linogridengine 12300/tcp # LinoGrid Engine
+linogridengine 12300/udp # LinoGrid Engine
+rads 12302/tcp # Remote Administration Daemon
+warehouse-sss 12321/tcp # Warehouse Monitoring Syst SSS
+warehouse-sss 12321/udp # Warehouse Monitoring Syst SSS
+warehouse 12322/tcp # Warehouse Monitoring Syst
+warehouse 12322/udp # Warehouse Monitoring Syst
+italk 12345/tcp # Italk Chat System
+italk 12345/udp # Italk Chat System
+tsaf 12753/tcp # tsaf port
+tsaf 12753/udp # tsaf port
+netperf 12865/tcp # control port for netperf benchmark
+i-zipqd 13160/tcp # I-ZIPQD
+i-zipqd 13160/udp # I-ZIPQD
+bcslogc 13216/tcp # Black Crow Software application logging
+bcslogc 13216/udp # Black Crow Software application logging
+rs-pias 13217/tcp # R&S Proxy Installation Assistant Service
+rs-pias 13217/udp # R&S Proxy Installation Assistant Service
+emc-vcas-tcp 13218/tcp # EMC Virtual CAS Service
+emc-vcas-udp 13218/udp # EMV Virtual CAS Service Discovery
+powwow-client 13223/tcp # PowWow Client
+powwow-client 13223/udp # PowWow Client
+powwow-server 13224/tcp # PowWow Server
+powwow-server 13224/udp # PowWow Server
+doip-data 13400/tcp # DoIP Data
+doip-disc 13400/udp # DoIP Discovery
+nbdb 13785/tcp # NetBackup Database
+nbdb 13785/udp # NetBackup Database
+nomdb 13786/tcp # Veritas-nomdb
+nomdb 13786/udp # Veritas-nomdb
+dsmcc-config 13818/tcp # DSMCC Config
+dsmcc-config 13818/udp # DSMCC Config
+dsmcc-session 13819/tcp # DSMCC Session Messages
+dsmcc-session 13819/udp # DSMCC Session Messages
+dsmcc-passthru 13820/tcp # DSMCC Pass-Thru Messages
+dsmcc-passthru 13820/udp # DSMCC Pass-Thru Messages
+dsmcc-download 13821/tcp # DSMCC Download Protocol
+dsmcc-download 13821/udp # DSMCC Download Protocol
+dsmcc-ccp 13822/tcp # DSMCC Channel Change Protocol
+dsmcc-ccp 13822/udp # DSMCC Channel Change Protocol
+bmdss 13823/tcp # Blackmagic Design Streaming Server
+ucontrol 13894/tcp # Ultimate Control communication protocol
+ucontrol 13894/udp # Ultimate Control communication protocol
+dta-systems 13929/tcp # D-TA SYSTEMS
+dta-systems 13929/udp # D-TA SYSTEMS
+medevolve 13930/tcp # MedEvolve Port Requester
+scotty-ft 14000/tcp # SCOTTY High-Speed Filetransfer
+scotty-ft 14000/udp # SCOTTY High-Speed Filetransfer
+sua 14001/tcp # SUA
+sua 14001/udp # De-Registered (2001 June 06)
+sua 14001/sctp # SUA
+scotty-disc 14002/udp # Discovery of a SCOTTY hardware codec board
+sage-best-com1 14033/tcp # sage Best! Config Server 1
+sage-best-com1 14033/udp # sage Best! Config Server 1
+sage-best-com2 14034/tcp # sage Best! Config Server 2
+sage-best-com2 14034/udp # sage Best! Config Server 2
+vcs-app 14141/tcp # VCS Application
+vcs-app 14141/udp # VCS Application
+icpp 14142/tcp # IceWall Cert Protocol
+icpp 14142/udp # IceWall Cert Protocol
+icpps 14143/tcp # IceWall Cert Protocol over TLS
+gcm-app 14145/tcp # GCM Application
+gcm-app 14145/udp # GCM Application
+vrts-tdd 14149/tcp # Veritas Traffic Director
+vrts-tdd 14149/udp # Veritas Traffic Director
+vcscmd 14150/tcp # Veritas Cluster Server Command Server
+vad 14154/tcp # Veritas Application Director
+vad 14154/udp # Veritas Application Director
+cps 14250/tcp # Fencing Server
+cps 14250/udp # Fencing Server
+ca-web-update 14414/tcp # CA eTrust Web Update Service
+ca-web-update 14414/udp # CA eTrust Web Update Service
+hde-lcesrvr-1 14936/tcp # hde-lcesrvr-1
+hde-lcesrvr-1 14936/udp # hde-lcesrvr-1
+hde-lcesrvr-2 14937/tcp # hde-lcesrvr-2
+hde-lcesrvr-2 14937/udp # hde-lcesrvr-2
+hydap 15000/tcp # Hypack Data Aquisition
+hydap 15000/udp # Hypack Data Aquisition
+onep-tls 15001/tcp # Open Network Environment TLS
+# onep-tls 15002/tcp # Open Network Environment TLS
+v2g-secc 15118/udp # v2g Supply Equipment Communication Controller Discovery Protocol
+xpilot 15345/tcp # XPilot Contact Port
+xpilot 15345/udp # XPilot Contact Port
+3link 15363/tcp # 3Link Negotiation
+3link 15363/udp # 3Link Negotiation
+cisco-snat 15555/tcp # Cisco Stateful NAT
+cisco-snat 15555/udp # Cisco Stateful NAT
+bex-xr 15660/tcp # Backup Express Restore Server
+bex-xr 15660/udp # Backup Express Restore Server
+ptp 15740/tcp # Picture Transfer Protocol
+ptp 15740/udp # Picture Transfer Protocol
+2ping 15998/udp # 2ping Bi-Directional Ping Service
+programmar 15999/tcp # ProGrammar Enterprise
+fmsas 16000/tcp # Administration Server Access
+fmsascon 16001/tcp # Administration Server Connector
+gsms 16002/tcp # GoodSync Mediation Service
+alfin 16003/udp # Automation and Control by REGULACE.ORG
+jwpc 16020/tcp # Filemaker Java Web Publishing Core
+jwpc-bin 16021/tcp # Filemaker Java Web Publishing Core Binary
+sun-sea-port 16161/tcp # Solaris SEA Port
+sun-sea-port 16161/udp # Solaris SEA Port
+solaris-audit 16162/tcp # Solaris Audit - secure remote audit log
+etb4j 16309/tcp # etb4j
+etb4j 16309/udp # etb4j
+pduncs 16310/tcp # Policy Distribute, Update Notification
+pduncs 16310/udp # Policy Distribute, Update Notification
+pdefmns 16311/tcp # Policy definition and update management
+pdefmns 16311/udp # Policy definition and update management
+netserialext1 16360/tcp # Network Serial Extension Ports One
+netserialext1 16360/udp # Network Serial Extension Ports One
+netserialext2 16361/tcp # Network Serial Extension Ports Two
+netserialext2 16361/udp # Network Serial Extension Ports Two
+netserialext3 16367/tcp # Network Serial Extension Ports Three
+netserialext3 16367/udp # Network Serial Extension Ports Three
+netserialext4 16368/tcp # Network Serial Extension Ports Four
+netserialext4 16368/udp # Network Serial Extension Ports Four
+connected 16384/tcp # Connected Corp
+connected 16384/udp # Connected Corp
+rdgs 16385/tcp # Reliable Datagram Sockets
+xoms 16619/tcp # X509 Objects Management Service
+axon-tunnel 16665/tcp # Reliable multipath data transport for high latencies
+vtp 16666/udp # Vidder Tunnel Protocol
+cadsisvr 16789/tcp # mainframe External Security Managers
+newbay-snc-mc 16900/tcp # Newbay Mobile Client Update Service
+newbay-snc-mc 16900/udp # Newbay Mobile Client Update Service
+sgcip 16950/tcp # Simple Generic Client Interface Protocol
+sgcip 16950/udp # Simple Generic Client Interface Protocol
+intel-rci-mp 16991/tcp # INTEL-RCI-MP
+intel-rci-mp 16991/udp # INTEL-RCI-MP
+amt-soap-http 16992/tcp # Intel(R) AMT SOAP/HTTP
+amt-soap-http 16992/udp # Intel(R) AMT SOAP/HTTP
+amt-soap-https 16993/tcp # Intel(R) AMT SOAP/HTTPS
+amt-soap-https 16993/udp # Intel(R) AMT SOAP/HTTPS
+amt-redir-tcp 16994/tcp # Intel(R) AMT Redirection/TCP
+amt-redir-tcp 16994/udp # Intel(R) AMT Redirection/TCP
+amt-redir-tls 16995/tcp # Intel(R) AMT Redirection/TLS
+amt-redir-tls 16995/udp # Intel(R) AMT Redirection/TLS
+isode-dua 17007/tcp #
+isode-dua 17007/udp #
+vestasdlp 17184/tcp # Vestas Data Layer Protocol
+soundsvirtual 17185/tcp # Sounds Virtual
+soundsvirtual 17185/udp # Sounds Virtual
+chipper 17219/tcp # Chipper
+chipper 17219/udp # Chipper
+avtp 17220/tcp # IEEE 1722 Transport Protocol for Time Sensitive Applications
+avtp 17220/udp # IEEE 1722 Transport Protocol for Time Sensitive Applications
+avdecc 17221/tcp # IEEE 1722.1 AVB Discovery, Enumeration, Connection management, and Control
+avdecc 17221/udp # IEEE 1722.1 AVB Discovery, Enumeration, Connection management, and Control
+cpsp 17222/udp # Control Plane Synchronization Protocol
+isa100-gci 17223/tcp # ISA100 Wireless gateway to client
+trdp-pd 17224/udp # Train Realtime Data Protocol
+trdp-md 17225/tcp # Train Realtime Data Protocol
+trdp-md 17225/udp # Train Realtime Data Protocol
+integrius-stp 17234/tcp # Integrius Secure Tunnel Protocol
+integrius-stp 17234/udp # Integrius Secure Tunnel Protocol
+ssh-mgmt 17235/tcp # SSH Tectia Manager
+ssh-mgmt 17235/udp # SSH Tectia Manager
+db-lsp 17500/tcp # Dropbox LanSync Protocol
+db-lsp-disc 17500/udp # Dropbox LanSync Discovery
+ailith 17555/tcp # Ailith management of routers
+ea 17729/tcp # Eclipse Aviation
+ea 17729/udp # Eclipse Aviation
+zep 17754/tcp # Encap. ZigBee Packets
+zep 17754/udp # Encap. ZigBee Packets
+zigbee-ip 17755/tcp # ZigBee IP Transport Service
+zigbee-ip 17755/udp # ZigBee IP Transport Service
+zigbee-ips 17756/tcp # ZigBee IP Transport Secure Service
+zigbee-ips 17756/udp # ZigBee IP Transport Secure Service
+sw-orion 17777/tcp # SolarWinds Orion
+biimenu 18000/tcp # Beckman Instruments, Inc.
+biimenu 18000/udp # Beckman Instruments, Inc.
+radpdf 18104/tcp # RAD PDF Service
+racf 18136/tcp # z/OS Resource Access Control Facility
+opsec-cvp 18181/tcp # OPSEC CVP
+opsec-cvp 18181/udp # OPSEC CVP
+opsec-ufp 18182/tcp # OPSEC UFP
+opsec-ufp 18182/udp # OPSEC UFP
+opsec-sam 18183/tcp # OPSEC SAM
+opsec-sam 18183/udp # OPSEC SAM
+opsec-lea 18184/tcp # OPSEC LEA
+opsec-lea 18184/udp # OPSEC LEA
+opsec-omi 18185/tcp # OPSEC OMI
+opsec-omi 18185/udp # OPSEC OMI
+ohsc 18186/tcp # Occupational Health SC
+ohsc 18186/udp # Occupational Health Sc
+opsec-ela 18187/tcp # OPSEC ELA
+opsec-ela 18187/udp # OPSEC ELA
+checkpoint-rtm 18241/tcp # Check Point RTM
+checkpoint-rtm 18241/udp # Check Point RTM
+iclid 18242/tcp # Checkpoint router monitoring
+clusterxl 18243/tcp # Checkpoint router state backup
+gv-pf 18262/tcp # GV NetConfig Service
+gv-pf 18262/udp # GV NetConfig Service
+ac-cluster 18463/tcp # AC Cluster
+ac-cluster 18463/udp # AC Cluster
+rds-ib 18634/tcp # Reliable Datagram Service
+rds-ib 18634/udp # Reliable Datagram Service
+rds-ip 18635/tcp # Reliable Datagram Service over IP
+rds-ip 18635/udp # Reliable Datagram Service over IP
+ique 18769/tcp # IQue Protocol
+ique 18769/udp # IQue Protocol
+infotos 18881/tcp # Infotos
+infotos 18881/udp # Infotos
+apc-necmp 18888/tcp # APCNECMP
+apc-necmp 18888/udp # APCNECMP
+igrid 19000/tcp # iGrid Server
+igrid 19000/udp # iGrid Server
+scintilla 19007/tcp # Scintilla protocol for device services
+scintilla 19007/udp # Scintilla protocol for device services
+j-link 19020/tcp # J-Link TCP/IP Protocol
+opsec-uaa 19191/tcp # OPSEC UAA
+opsec-uaa 19191/udp # OPSEC UAA
+ua-secureagent 19194/tcp # UserAuthority SecureAgent
+ua-secureagent 19194/udp # UserAuthority SecureAgent
+keysrvr 19283/tcp # Key Server for SASSAFRAS
+keysrvr 19283/udp # Key Server for SASSAFRAS
+keyshadow 19315/tcp # Key Shadow for SASSAFRAS
+keyshadow 19315/udp # Key Shadow for SASSAFRAS
+mtrgtrans 19398/tcp # mtrgtrans
+mtrgtrans 19398/udp # mtrgtrans
+hp-sco 19410/tcp # hp-sco
+hp-sco 19410/udp # hp-sco
+hp-sca 19411/tcp # hp-sca
+hp-sca 19411/udp # hp-sca
+hp-sessmon 19412/tcp # HP-SESSMON
+hp-sessmon 19412/udp # HP-SESSMON
+fxuptp 19539/tcp # FXUPTP
+fxuptp 19539/udp # FXUPTP
+sxuptp 19540/tcp # SXUPTP
+sxuptp 19540/udp # SXUPTP
+jcp 19541/tcp # JCP Client
+jcp 19541/udp # JCP Client
+mle 19788/udp # Mesh Link Establishment
+iec-104-sec 19998/tcp # IEC 60870-5-104 process control - secure
+dnp-sec 19999/tcp # Distributed Network Protocol - Secure
+dnp-sec 19999/udp # Distributed Network Protocol - Secure
+dnp 20000/tcp # DNP
+dnp 20000/udp # DNP
+microsan 20001/tcp # MicroSAN
+microsan 20001/udp # MicroSAN
+commtact-http 20002/tcp # Commtact HTTP
+commtact-http 20002/udp # Commtact HTTP
+commtact-https 20003/tcp # Commtact HTTPS
+commtact-https 20003/udp # Commtact HTTPS
+openwebnet 20005/tcp # OpenWebNet protocol for electric network
+openwebnet 20005/udp # OpenWebNet protocol for electric network
+ss-idi-disc 20012/udp # Samsung Interdevice Interaction discovery
+ss-idi 20013/tcp # Samsung Interdevice Interaction
+opendeploy 20014/tcp # OpenDeploy Listener
+opendeploy 20014/udp # OpenDeploy Listener
+nburn_id 20034/tcp nburn-id # NetBurner ID Port
+nburn_id 20034/udp nburn-id # NetBurner ID Port
+tmophl7mts 20046/tcp # TMOP HL7 Message Transfer Service
+tmophl7mts 20046/udp # TMOP HL7 Message Transfer Service
+mountd 20048/tcp # NFS mount protocol
+mountd 20048/udp # NFS mount protocol
+nfsrdma 20049/tcp # Network File System (NFS) over RDMA
+nfsrdma 20049/udp # Network File System (NFS) over RDMA
+nfsrdma 20049/sctp # Network File System (NFS) over RDMA
+avesterra 20057/tcp # AvesTerra Hypergraph Transfer Protocol (HGTP)
+tolfab 20167/tcp # TOLfab Data Change
+tolfab 20167/udp # TOLfab Data Change
+ipdtp-port 20202/tcp # IPD Tunneling Port
+ipdtp-port 20202/udp # IPD Tunneling Port
+ipulse-ics 20222/tcp # iPulse-ICS
+ipulse-ics 20222/udp # iPulse-ICS
+emwavemsg 20480/tcp # emWave Message Service
+emwavemsg 20480/udp # emWave Message Service
+track 20670/tcp # Track
+track 20670/udp # Track
+athand-mmp 20999/tcp # At Hand MMP
+athand-mmp 20999/udp # AT Hand MMP
+irtrans 21000/tcp # IRTrans Control
+irtrans 21000/udp # IRTrans Control
+notezilla-lan 21010/tcp # Notezilla.Lan Server
+rdm-tfs 21553/tcp # Raima RDM TFS
+dfserver 21554/tcp # MineScape Design File Server
+dfserver 21554/udp # MineScape Design File Server
+vofr-gateway 21590/tcp # VoFR Gateway
+vofr-gateway 21590/udp # VoFR Gateway
+tvpm 21800/tcp # TVNC Pro Multiplexing
+tvpm 21800/udp # TVNC Pro Multiplexing
+webphone 21845/tcp # webphone
+webphone 21845/udp # webphone
+netspeak-is 21846/tcp # NetSpeak Corp. Directory Services
+netspeak-is 21846/udp # NetSpeak Corp. Directory Services
+netspeak-cs 21847/tcp # NetSpeak Corp. Connection Services
+netspeak-cs 21847/udp # NetSpeak Corp. Connection Services
+netspeak-acd 21848/tcp # NetSpeak Corp. Automatic Call Distribution
+netspeak-acd 21848/udp # NetSpeak Corp. Automatic Call Distribution
+netspeak-cps 21849/tcp # NetSpeak Corp. Credit Processing System
+netspeak-cps 21849/udp # NetSpeak Corp. Credit Processing System
+snapenetio 22000/tcp # SNAPenetIO
+snapenetio 22000/udp # SNAPenetIO
+optocontrol 22001/tcp # OptoControl
+optocontrol 22001/udp # OptoControl
+optohost002 22002/tcp # Opto Host Port 2
+optohost002 22002/udp # Opto Host Port 2
+optohost003 22003/tcp # Opto Host Port 3
+optohost003 22003/udp # Opto Host Port 3
+optohost004 22004/tcp # Opto Host Port 4
+optohost004 22004/udp # Opto Host Port 4
+optohost005 22005/tcp # Opto Host Port 5
+optohost005 22005/udp # Opto Host Port 5
+dcap 22125/tcp # dCache Access Protocol
+gsidcap 22128/tcp # GSI dCache Access Protocol
+easyengine 22222/tcp # EasyEngine is CLI tool to manage WordPress Sites on Nginx server
+cis 22305/udp # CompactIS Tunnel
+shrewd-control 22335/tcp # Initium Labs Security and Automation Control
+shrewd-stream 22335/udp # Initium Labs Security and Automation Streaming
+cis-secure 22343/tcp # CompactIS Secure Tunnel
+cis-secure 22343/udp # CompactIS Secure Tunnel
+WibuKey 22347/tcp # WibuKey Standard WkLan
+WibuKey 22347/udp # WibuKey Standard WkLan
+CodeMeter 22350/tcp # CodeMeter Standard
+CodeMeter 22350/udp # CodeMeter Standard
+codemeter-cmwan 22351/tcp # requests of copy protection software
+caldsoft-backup 22537/tcp # CaldSoft Backup server file transfer
+vocaltec-wconf 22555/tcp # Vocaltec Web Conference
+vocaltec-phone 22555/udp # Vocaltec Internet Phone
+talikaserver 22763/tcp # Talika Main Server
+talikaserver 22763/udp # Talika Main Server
+aws-brf 22800/tcp # Telerate Information Platform LAN
+aws-brf 22800/udp # Telerate Information Platform LAN
+brf-gw 22951/tcp # Telerate Information Platform WAN
+brf-gw 22951/udp # Telerate Information Platform WAN
+inovaport1 23000/tcp # Inova LightLink Server Type 1
+inovaport1 23000/udp # Inova LightLink Server Type 1
+inovaport2 23001/tcp # Inova LightLink Server Type 2
+inovaport2 23001/udp # Inova LightLink Server Type 2
+inovaport3 23002/tcp # Inova LightLink Server Type 3
+inovaport3 23002/udp # Inova LightLink Server Type 3
+inovaport4 23003/tcp # Inova LightLink Server Type 4
+inovaport4 23003/udp # Inova LightLink Server Type 4
+inovaport5 23004/tcp # Inova LightLink Server Type 5
+inovaport5 23004/udp # Inova LightLink Server Type 5
+inovaport6 23005/tcp # Inova LightLink Server Type 6
+inovaport6 23005/udp # Inova LightLink Server Type 6
+gntp 23053/tcp # Generic Notification Transport Protocol
+s102 23272/udp # S102 application
+5afe-dir 23294/tcp # 5AFE SDN Directory
+5afe-disc 23294/udp # 5AFE SDN Directory discovery
+elxmgmt 23333/tcp # Emulex HBAnyware Remote Management
+elxmgmt 23333/udp # Emulex HBAnyware Remote Management
+novar-dbase 23400/tcp # Novar Data
+novar-dbase 23400/udp # Novar Data
+novar-alarm 23401/tcp # Novar Alarm
+novar-alarm 23401/udp # Novar Alarm
+novar-global 23402/tcp # Novar Global
+novar-global 23402/udp # Novar Global
+aequus 23456/tcp # Aequus Service
+aequus-alt 23457/tcp # Aequus Service Mgmt
+areaguard-neo 23546/tcp # AreaGuard Neo - WebServer
+med-ltp 24000/tcp # med-ltp
+med-ltp 24000/udp # med-ltp
+med-fsp-rx 24001/tcp # med-fsp-rx
+med-fsp-rx 24001/udp # med-fsp-rx
+med-fsp-tx 24002/tcp # med-fsp-tx
+med-fsp-tx 24002/udp # med-fsp-tx
+med-supp 24003/tcp # med-supp
+med-supp 24003/udp # med-supp
+med-ovw 24004/tcp # med-ovw
+med-ovw 24004/udp # med-ovw
+med-ci 24005/tcp # med-ci
+med-ci 24005/udp # med-ci
+med-net-svc 24006/tcp # med-net-svc
+med-net-svc 24006/udp # med-net-svc
+filesphere 24242/tcp # fileSphere
+filesphere 24242/udp # fileSphere
+vista-4gl 24249/tcp # Vista 4GL
+vista-4gl 24249/udp # Vista 4GL
+ild 24321/tcp # Isolv Local Directory
+ild 24321/udp # Isolv Local Directory
+hid 24322/udp # Human Interface Device data streams transport
+intel_rci 24386/tcp intel-rci # Intel RCI
+intel_rci 24386/udp intel-rci # Intel RCI
+tonidods 24465/tcp # Tonido Domain Server
+tonidods 24465/udp # Tonido Domain Server
+bilobit 24577/tcp # bilobit Service
+bilobit-update 24577/udp # bilobit Service Update
+flashfiler 24677/tcp # FlashFiler
+flashfiler 24677/udp # FlashFiler
+proactivate 24678/tcp # Turbopower Proactivate
+proactivate 24678/udp # Turbopower Proactivate
+tcc-http 24680/tcp # TCC User HTTP Service
+tcc-http 24680/udp # TCC User HTTP Service
+cslg 24754/tcp # Citrix StorageLink Gateway
+assoc-disc 24850/udp # Device Association Discovery
+find 24922/tcp # Find Identification of Network Devices
+find 24922/udp # Find Identification of Network Devices
+icl-twobase1 25000/tcp # icl-twobase1
+icl-twobase1 25000/udp # icl-twobase1
+icl-twobase2 25001/tcp # icl-twobase2
+icl-twobase2 25001/udp # icl-twobase2
+icl-twobase3 25002/tcp # icl-twobase3
+icl-twobase3 25002/udp # icl-twobase3
+icl-twobase4 25003/tcp # icl-twobase4
+icl-twobase4 25003/udp # icl-twobase4
+icl-twobase5 25004/tcp # icl-twobase5
+icl-twobase5 25004/udp # icl-twobase5
+icl-twobase6 25005/tcp # icl-twobase6
+icl-twobase6 25005/udp # icl-twobase6
+icl-twobase7 25006/tcp # icl-twobase7
+icl-twobase7 25006/udp # icl-twobase7
+icl-twobase8 25007/tcp # icl-twobase8
+icl-twobase8 25007/udp # icl-twobase8
+icl-twobase9 25008/tcp # icl-twobase9
+icl-twobase9 25008/udp # icl-twobase9
+icl-twobase10 25009/tcp # icl-twobase10
+icl-twobase10 25009/udp # icl-twobase10
+rna 25471/sctp # RNSAP User Adaptation for Iurh
+sauterdongle 25576/tcp # Sauter Dongle
+idtp 25604/tcp # Identifier Tracing Protocol
+vocaltec-hos 25793/tcp # Vocaltec Address Server
+vocaltec-hos 25793/udp # Vocaltec Address Server
+tasp-net 25900/tcp # TASP Network Comm
+tasp-net 25900/udp # TASP Network Comm
+niobserver 25901/tcp # NIObserver
+niobserver 25901/udp # NIObserver
+nilinkanalyst 25902/tcp # NILinkAnalyst
+nilinkanalyst 25902/udp # NILinkAnalyst
+niprobe 25903/tcp # NIProbe
+niprobe 25903/udp # NIProbe
+bf-game 25954/udp # Bitfighter game server
+bf-master 25955/udp # Bitfighter master server
+scscp 26133/tcp # Symbolic Computation Software Composability Protocol
+scscp 26133/udp # Symbolic Computation Software Composability Protocol
+cockroach 26257/tcp # CockroachDB
+ezproxy 26260/tcp # eZproxy
+ezproxy 26260/udp # eZproxy
+ezmeeting 26261/tcp # eZmeeting
+ezmeeting 26261/udp # eZmeeting
+k3software-svr 26262/tcp # K3 Software-Server
+k3software-svr 26262/udp # K3 Software-Server
+k3software-cli 26263/tcp # K3 Software-Client
+k3software-cli 26263/udp # K3 Software-Client
+exoline-tcp 26486/tcp # EXOline-TCP
+exoline-udp 26486/udp # EXOline-UDP
+exoconfig 26487/tcp # EXOconfig
+exoconfig 26487/udp # EXOconfig
+exonet 26489/tcp # EXOnet
+exonet 26489/udp # EXOnet
+imagepump 27345/tcp # ImagePump
+imagepump 27345/udp # ImagePump
+jesmsjc 27442/tcp # Job controller service
+jesmsjc 27442/udp # Job controller service
+kopek-httphead 27504/tcp # Kopek HTTP Head Port
+kopek-httphead 27504/udp # Kopek HTTP Head Port
+ars-vista 27782/tcp # ARS VISTA Application
+ars-vista 27782/udp # ARS VISTA Application
+astrolink 27876/tcp # Astrolink Protocol
+tw-auth-key 27999/tcp # TW Authentication/Key Distribution and
+tw-auth-key 27999/udp # Attribute Certificate Services
+nxlmd 28000/tcp # NX License Manager
+nxlmd 28000/udp # NX License Manager
+pqsp 28001/tcp # PQ Service
+a27-ran-ran 28119/udp # A27 cdma2000 RAN Management
+voxelstorm 28200/tcp # VoxelStorm game server
+voxelstorm 28200/udp # VoxelStorm game server
+siemensgsm 28240/tcp # Siemens GSM
+siemensgsm 28240/udp # Siemens GSM
+bosswave 28589/tcp # Building operating system services wide area verified exchange
+sgsap 29118/sctp # SGsAP in 3GPP
+otmp 29167/tcp # ObTools Message Protocol
+otmp 29167/udp # ObTools Message Protocol
+sbcap 29168/sctp # SBcAP in 3GPP
+iuhsctpassoc 29169/sctp # HNBAP and RUA Common Association
+bingbang 29999/tcp # data exchange protocol for IEC61850 inn wind power plants
+ndmps 30000/tcp # Secure Network Data Management Protocol
+pago-services1 30001/tcp # Pago Services 1
+pago-services1 30001/udp # Pago Services 1
+pago-services2 30002/tcp # Pago Services 2
+pago-services2 30002/udp # Pago Services 2
+amicon-fpsu-ra 30003/tcp # Amicon FPSU-IP Remote Administration
+amicon-fpsu-ra 30003/udp # Amicon FPSU-IP Remote Administration
+amicon-fpsu-s 30004/udp # Amicon FPSU-IP VPN
+rwp 30100/tcp # Remote Window Protocol
+rwp 30100/sctp # Remote Window Protocol
+kingdomsonline 30260/tcp # Kingdoms Online (CraigAvenue)
+kingdomsonline 30260/udp # Kingdoms Online (CraigAvenue)
+samsung-disc 30832/udp # Samsung Convergence Discovery Protocol
+ovobs 30999/tcp # OpenView Service Desk Client
+ovobs 30999/udp # OpenView Service Desk Client
+autotrac-acp 31020/tcp # Autotrac ACP 245
+yawn 31029/udp # YaWN - Yet Another Windows Notifie
+pace-licensed 31400/tcp # PACE license server
+xqosd 31416/tcp # XQoS network monitor
+xqosd 31416/udp # XQoS network monitor
+tetrinet 31457/tcp # TetriNET Protocol
+tetrinet 31457/udp # TetriNET Protocol
+lm-mon 31620/tcp # lm mon
+lm-mon 31620/udp # lm mon
+dsx_monitor 31685/tcp dsx-monitor # DS Expert Monitor
+gamesmith-port 31765/tcp # GameSmith Port
+gamesmith-port 31765/udp # GameSmith Port
+iceedcp_tx 31948/tcp iceedcp-tx # Embedded Device Configuration Protocol TX
+iceedcp_tx 31948/udp iceedcp-tx # Embedded Device Configuration Protocol TX
+iceedcp_rx 31949/tcp iceedcp-rx # Embedded Device Configuration Protocol RX
+iceedcp_rx 31949/udp iceedcp-rx # Embedded Device Configuration Protocol RX
+iracinghelper 32034/tcp # iRacing helper service
+iracinghelper 32034/udp # iRacing helper service
+t1distproc60 32249/tcp # T1 Distributed Processor
+t1distproc60 32249/udp # T1 Distributed Processor
+plex 32400/tcp # Plex multimedia
+apm-link 32483/tcp # Access Point Manager Link
+apm-link 32483/udp # Access Point Manager Link
+sec-ntb-clnt 32635/tcp # SecureNotebook-CLNT
+sec-ntb-clnt 32635/udp # SecureNotebook-CLNT
+DMExpress 32636/tcp # DMExpress
+DMExpress 32636/udp # DMExpress
+filenet-powsrm 32767/tcp # FileNet BPM WS-ReliableMessaging Client
+filenet-powsrm 32767/udp # FileNet BPM WS-ReliableMessaging Client
+filenet-tms 32768/tcp # Filenet TMS
+filenet-tms 32768/udp # Filenet TMS
+filenet-rpc 32769/tcp # Filenet RPC
+filenet-rpc 32769/udp # Filenet RPC
+filenet-nch 32770/tcp # Filenet NCH
+filenet-nch 32770/udp # Filenet NCH
+filenet-rmi 32771/tcp # FileNET RMI
+filenet-rmi 32771/udp # FileNet RMI
+filenet-pa 32772/tcp # FileNET Process Analyzer
+filenet-pa 32772/udp # FileNET Process Analyzer
+filenet-cm 32773/tcp # FileNET Component Manager
+filenet-cm 32773/udp # FileNET Component Manager
+filenet-re 32774/tcp # FileNET Rules Engine
+filenet-re 32774/udp # FileNET Rules Engine
+filenet-pch 32775/tcp # Performance Clearinghouse
+filenet-pch 32775/udp # Performance Clearinghouse
+filenet-peior 32776/tcp # FileNET BPM IOR
+filenet-peior 32776/udp # FileNET BPM IOR
+filenet-obrok 32777/tcp # FileNet BPM CORBA
+filenet-obrok 32777/udp # FileNet BPM CORBA
+mlsn 32801/tcp # Multiple Listing Service Network
+mlsn 32801/udp # Multiple Listing Service Network
+retp 32811/tcp # Real Estate Transport Protocol
+idmgratm 32896/tcp # Attachmate ID Manager
+idmgratm 32896/udp # Attachmate ID Manager
+mysqlx 33060/tcp # MySQL Database Extended Interface
+aurora-balaena 33123/tcp # Aurora (Balaena Ltd)
+aurora-balaena 33123/udp # Aurora (Balaena Ltd)
+diamondport 33331/tcp # DiamondCentral Interface
+diamondport 33331/udp # DiamondCentral Interface
+dgi-serv 33333/tcp # Digital Gaslight Service
+speedtrace 33334/tcp # SpeedTrace TraceAgent
+speedtrace-disc 33334/udp # SpeedTrace TraceAgent Discovery
+snip-slave 33656/tcp # SNIP Slave
+snip-slave 33656/udp # SNIP Slave
+turbonote-2 34249/tcp # TurboNote Relay Server Default Port
+turbonote-2 34249/udp # TurboNote Relay Server Default Port
+p-net-local 34378/tcp # P-Net on IP local
+p-net-local 34378/udp # P-Net on IP local
+p-net-remote 34379/tcp # P-Net on IP remote
+p-net-remote 34379/udp # P-Net on IP remote
+dhanalakshmi 34567/tcp # dhanalakshmi.org EDI Service
+profinet-rt 34962/tcp # PROFInet RT Unicast
+profinet-rt 34962/udp # PROFInet RT Unicast
+profinet-rtm 34963/tcp # PROFInet RT Multicast
+profinet-rtm 34963/udp # PROFInet RT Multicast
+profinet-cm 34964/tcp # PROFInet Context Manager
+profinet-cm 34964/udp # PROFInet Context Manager
+ethercat 34980/tcp # EtherCAT Port
+ethercat 34980/udp # EhterCAT Port
+heathview 35000/tcp # HeathView
+rt-viewer 35001/tcp # ReadyTech Viewer
+rt-viewer 35001/udp # ReadyTech Viewer
+rt-sound 35002/tcp # ReadyTech Sound Server
+rt-devicemapper 35003/tcp # ReadyTech DeviceMapper
+rt-classmanager 35004/tcp # ReadyTech ClassManager
+rt-classmanager 35004/udp # ReadyTech ClassManager
+rt-labtracker 35005/tcp # ReadyTech LabTracker
+rt-helper 35006/tcp # ReadyTech Helper Service
+kitim 35354/tcp # KIT Messenger
+altova-lm 35355/tcp # Altova License Management
+altova-lm-disc 35355/udp # Altova License Management Discovery
+guttersnex 35356/tcp # Gutters Note Exchange
+openstack-id 35357/tcp # OpenStack ID Service
+allpeers 36001/tcp # AllPeers Network
+allpeers 36001/udp # AllPeers Network
+wlcp 36411/udp # Wireless LAN Control plane Protocol (WLCP)
+s1-control 36412/sctp # S1-Control Plane (3GPP)
+x2-control 36422/sctp # X2-Control Plane (3GPP)
+slmap 36423/sctp # SLm Interface Application protocol
+nq-ap 36424/sctp # Nq and Nq' Application protocol
+m2ap 36443/sctp # M2 Application Part
+m3ap 36444/sctp # M3 Application Part
+xw-control 36462/sctp # Xw-Control Plane (3GPP)
+febooti-aw 36524/tcp # Febooti Automation Workshop
+observium-agent 36602/tcp # Observium statistics collection agent
+kastenxpipe 36865/tcp # KastenX Pipe
+kastenxpipe 36865/udp # KastenX Pipe
+mapx 36700/tcp # MapX communication
+neckar 37475/tcp # science + computing's Venus Administration Port
+neckar 37475/udp # science + computing's Venus Administration Port
+eftp 37601/tcp # Epipole File Transfer
+unisys-eportal 37654/tcp # Unisys ClearPath ePortal
+unisys-eportal 37654/udp # Unisys ClearPath ePortal
+gdrive-sync 37483/tcp # Google Drive Sync
+ivs-database 38000/tcp # InfoVista Server Database
+ivs-insertion 38001/tcp # InfoVista Server Insertion
+cresco-control 38002/tcp # Cresco Controller
+crescoctrl-disc 38002/udp # Cresco Controller Discovery
+galaxy7-data 38201/tcp # Galaxy7 Data Tunnel
+galaxy7-data 38201/udp # Galaxy7 Data Tunnel
+fairview 38202/tcp # Fairview Message Service
+fairview 38202/udp # Fairview Message Service
+agpolicy 38203/tcp # AppGate Policy Server
+agpolicy 38203/udp # AppGate Policy Server
+sruth 38800/tcp # Sruth - University_Corporation_for_Atmospheric_Research
+secrmmsafecopya 38865/tcp # for use of the secRMM SafeCopy program
+turbonote-1 39681/tcp # TurboNote Default Port
+turbonote-1 39681/udp # TurboNote Default Port
+safetynetp 40000/tcp # SafetyNET p
+safetynetp 40000/udp # SafetyNET p
+k-patentssensor 40023/udp # K-PatentsSensorInformation
+z-wave-s 41230/tcp # Z-Wave Protocol over SSL/TLS
+z-wave-s 41230/udp # Z-Wave Protocol over DTLS
+sptx 40404/tcp # Simplify Printing TX
+cscp 40841/tcp # CSCP
+cscp 40841/udp # CSCP
+csccredir 40842/tcp # CSCCREDIR
+csccredir 40842/udp # CSCCREDIR
+csccfirewall 40843/tcp # CSCCFIREWALL
+csccfirewall 40843/udp # CSCCFIREWALL
+ortec-disc 40853/udp # ORTEC Service Discovery
+fs-qos 41111/tcp # Foursticks QoS Protocol
+fs-qos 41111/udp # Foursticks QoS Protocol
+tentacle 41121/tcp # Tentacle Server
+crestron-cip 41794/tcp # Crestron Control Port
+crestron-cip 41794/udp # Crestron Control Port
+crestron-ctp 41795/tcp # Crestron Terminal Port
+crestron-ctp 41795/udp # Crestron Terminal Port
+crestron-cips 41796/tcp # Crestron Secure Control Port
+crestron-ctps 41797/tcp # Crestron Secure Terminal Port
+candp 42508/tcp # Computer Associates network discovery protocol
+candp 42508/udp # Computer Associates network discovery protocol
+candrp 42509/tcp # CA discovery response
+candrp 42509/udp # CA discovery response
+caerpc 42510/tcp # CA eTrust RPC
+caerpc 42510/udp # CA eTrust RPC
+recvr-rc 43000/tcp # Receiver Remote Control
+recvr-rc-disc 43000/udp # Receiver Remote Control Discovery
+reachout 43188/tcp # REACHOUT
+reachout 43188/udp # REACHOUT
+ndm-agent-port 43189/tcp # NDM-AGENT-PORT
+ndm-agent-port 43189/udp # NDM-AGENT-PORT
+ip-provision 43190/tcp # IP-PROVISION
+ip-provision 43190/udp # IP-PROVISION
+noit-transport 43191/tcp # Reconnoiter Agent Data Transport
+shaperai 43210/tcp # Shaper Automation Server
+shaperai-disc 43210/udp # Shaper Automation Server Management Discovery
+eq3-update 43439/tcp # EQ3 firmware update
+eq3-config 43439/udp # EQ3 discovery and configuration
+ew-mgmt 43440/tcp # Cisco EnergyWise Management
+ew-disc-cmd 43440/udp # Cisco EnergyWise Discovery and Command Flooding
+ciscocsdb 43441/tcp # Cisco NetMgmt DB Ports
+ciscocsdb 43441/udp # Cisco NetMgmt DB Ports
+z-wave-tunnel 44123/tcp # Z-Wave Secure Tunnel
+pmcd 44321/tcp # PCP server (pmcd)
+pmcd 44321/udp # PCP server (pmcd)
+pmcdproxy 44322/tcp # PCP server (pmcd) proxy
+pmcdproxy 44322/udp # PCP server (pmcd) proxy
+cognex-dataman 44444/tcp # Cognex DataMan Management
+### UNAUTHORIZED USE: Ports 44515 & 44516 used by NI Device Protocol############
+domiq 44544/udp # DOMIQ Building Automation
+rbr-debug 44553/tcp # REALbasic Remote Debug
+rbr-debug 44553/udp # REALbasic Remote Debug
+asihpi 44600/udp # AudioScience HPI
+EtherNet/IP-2 44818/tcp EtherNet-IP-2 # EtherNet/IP messaging
+EtherNet/IP-2 44818/udp EtherNet-IP-2 # EtherNet/IP messaging
+m3da 44900/tcp # M3DA (efficient machine-to-machine communication)
+m3da-disc 44900/udp # M3DA Discovery (efficient machine-to-machine communication)
+asmp 45000/tcp # NSi AutoStore Status Monitoring Protocol data transfer
+asmp-mon 45000/udp # NSi AutoStore Status Monitoring Protocol device monitoring
+asmps 45001/tcp # NSi AutoStore Status Monitoring Protocol secure data transfer
+rs-status 45002/tcp # Redspeed Status Monitor
+synctest 45045/tcp # Remote application control
+invision-ag 45054/tcp # InVision AG
+invision-ag 45054/udp # InVision AG
+eba 45678/tcp # EBA PRISE
+eba 45678/udp # EBA PRISE
+dai-shell 45824/tcp # Server for the DAI family of client-server products
+qdb2service 45825/tcp # Qpuncture Data Access Service
+qdb2service 45825/udp # Qpuncture Data Access Service
+ssr-servermgr 45966/tcp # SSRServerMgr
+ssr-servermgr 45966/udp # SSRServerMgr
+inedo 46336/tcp # Listen port used for Inedo agent
+mediabox 46999/tcp # MediaBox Server
+mediabox 46999/udp # MediaBox Server
+mbus 47000/tcp # Message Bus
+mbus 47000/udp # Message Bus
+winrm 47001/tcp # Windows Remote Management Service
+jvl-mactalk 47100/udp # Configuration of motors conneced to industrial ethernet
+dbbrowse 47557/tcp # Databeam Corporation
+dbbrowse 47557/udp # Databeam Corporation
+directplaysrvr 47624/tcp # Direct Play Server
+directplaysrvr 47624/udp # Direct Play Server
+ap 47806/tcp # ALC Protocol
+ap 47806/udp # ALC Protocol
+bacnet 47808/tcp # Building Automation and Control Networks
+bacnet 47808/udp # Building Automation and Control Networks
+presonus-ucnet 47809/udp # PreSonus Universal Control Network protocol
+nimcontroller 48000/tcp # Nimbus Controller
+nimcontroller 48000/udp # Nimbus Controller
+nimspooler 48001/tcp # Nimbus Spooler
+nimspooler 48001/udp # Nimbus Spooler
+nimhub 48002/tcp # Nimbus Hub
+nimhub 48002/udp # Nimbus Hub
+nimgtw 48003/tcp # Nimbus Gateway
+nimgtw 48003/udp # Nimbus Gateway
+nimbusdb 48004/tcp # NimbusDB Connector
+nimbusdbctrl 48005/tcp # NimbusDB Control
+3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service Protocol
+weandsf 48050/tcp # WeFi Access Network Discovery and Selection
+isnetserv 48128/tcp # Image Systems Network Services
+isnetserv 48128/udp # Image Systems Network Services
+blp5 48129/tcp # Bloomberg locator
+blp5 48129/udp # Bloomberg locator
+com-bardac-dw 48556/tcp # com-bardac-dw
+com-bardac-dw 48556/udp # com-bardac-dw
+iqobject 48619/tcp # iqobject
+iqobject 48619/udp # iqobject
+robotraconteur 48653/tcp # Robot Raconteur transport
+robotraconteur 48653/udp # Robot Raconteur transport
+matahari 49000/tcp # Matahari Broker
+nusrp 49001/tcp # Nuance Unity Service Request Protocol
+nusdp-disc 49001/udp # Nuance Unity Service Discovery Protocol
+# Updated additional list from IANA with all missing services 05/05/2017 done by Karl Vogel
+mit-ml-dev 85/tcp # MIT ML Device
+mit-ml-dev 85/udp # MIT ML Device
+rap 256/tcp # Route Access Protocol
+rap 256/udp # Route Access Protocol
+pt-tls 271/tcp # IETF Network Endpoint Assessment (PT-TLS)
+meter 571/tcp # udemon
+meter 571/udp # udemon
+dlep 854/tcp # Dynamic Link Exchange Protocol (DLEP)
+dlep 854/udp # Dynamic Link Exchange Protocol (DLEP)
+accessbuilder 888/udp # AccessBuilder
+webpush 1001/tcp # HTTP Web Push
+pip 1321/tcp # PIP
+pip 1321/udp # PIP
+csdmbase 1471/tcp # csdmbase
+csdmbase 1471/udp # csdmbase
+csdm 1472/tcp # csdm
+csdm 1472/udp # csdm
+ngr-t 1528/udp # NGR transport for mobile ad-hoc networks
+nmsp 1790/tcp # Narrative Media Streaming Protocol
+nmsp 1790/udp # Narrative Media Streaming Protocol
+uma 1797/tcp # Universal Management Architecture
+uma 1797/udp # Universal Management Architecture
+raid-cd 2013/udp # raid
+dls 2047/tcp #
+dls 2047/udp #
+nvd 2329/tcp # NVD User
+nvd 2329/udp # NVD User
+swarm 2377/tcp # RPC interface for Docker Swarm
+msp 2438/tcp # Message send protocol?
+msp 2438/udp # Message send protocol?
+unicontrol 2499/tcp # UniControl
+unicontrol 2499/udp # UniControl
+nmsigport 2839/tcp # NMSigPort
+nmsigport 2839/udp # NMSigPort
+fxp 2849/tcp # FXP Communication
+fxp 2849/udp # FXP Communication
+epp 3044/tcp # EndPoint Protocol
+epp 3044/udp # EndPoint Protocol
+creativeserver 3364/tcp # Creative Server
+creativeserver 3364/udp # Creative Server
+contentserver 3365/tcp # Content Server
+contentserver 3365/udp # Content Server
+creativepartnr 3366/tcp # Creative Partner
+creativepartnr 3366/udp # Creative Partner
+udt-os 3900/tcp # Unidata UDT OS
+udt-os 3900/udp # Unidata UDT OS
+npp 4045/tcp # Network Paging Protocol
+npp 4045/udp # Network Paging Protocol
+hctl 4197/tcp # Harman HControl Protocol
+hctl 4197/udp # Harman HControl Protocol
+trinity-dist 4711/sctp # Trinity Trust Network Node Communication
+trinity-dist 4711/tcp # Trinity Trust Network Node Communication
+trinity-dist 4711/udp # Trinity Trust Network Node Communication
+intelliadm-disc 4746/udp # IntelliAdmin Discovery
+gre-in-udp 4754/udp # GRE-in-UDP Encapsulation
+gre-udp-dtls 4755/udp # GRE-in-UDP Encapsulation with DTLS
+RDCenter 4756/tcp # Reticle Decision Center
+converge 4774/tcp # Converge RPC
+mftp 5402/tcp # OmniCast MFTP
+mftp 5402/udp # OmniCast MFTP
+cbus 5550/tcp # Model Railway control using CBUS protocol
+storageos 5705/tcp # StorageOS REST API
+ricardo-lm 6148/tcp # Ricardo North America License Manager
+ricardo-lm 6148/udp # Ricardo North America License Manager
+ieee11073-20701 6464/tcp # Medical device communication
+ieee11073-20701 6464/udp # Medical device communication
+nexgen-aux 6629/tcp #
+nexgen-aux 6629/udp #
+rtimeviewer 6900/tcp # R*TIME Viewer Data Interface
+spg 7016/tcp # SPG Controls Carrier
+spg 7016/udp # SPG Controls Carrier
+grasp 7017/tcp # GeneRic Autonomic Signaling Protocol
+grasp 7017/udp # GeneRic Autonomic Signaling Protocol
+pon-ictp 7202/tcp # Inter-Channel Termination Protocol (ICTP)
+openit 7478/tcp # IT Asset Management
+coherence-disc 7574/udp # Oracle Coherence Cluster discovery service
+bolt 7687/tcp # Bolt database connection
+nfapi 7701/sctp # SCF nFAPI defining MAC/PHY split
+wpl-analytics 8006/tcp # World Programming analytics
+wpl-disc 8006/udp # World Programming analytics discovery
+ucs-isc 8070/tcp # Oracle Communication Indexed Search Converter
+mles 8077/tcp # Client-server data distribution
+opsmessaging 8090/tcp # Vehicle to station messaging
+robot-remote 8270/tcp # Robot Framework Remote Library Interface
+aritts 8423/tcp # Aristech text-to-speech server
+ssports-bcast 8808/udp # STATSports Broadcast Service
+CardWeb-IO 9060/tcp # CardWeb request-response I/O exchange
+CardWeb-RT 9060/udp # CardWeb realtime device data
+pumpkindb 9981/tcp # Event sourcing database engine/language
+abb-hw 10020/tcp # Hardware configuration and maintenance
+tile-ml 10261/tcp # Tile remote machine learning
+xpra 14500/tcp # xpra network protocol
+vdmmesh-disc 18668/udp # Manufacturing Execution Systems Mesh Comm
+vdmmesh 18668/tcp # Manufacturing Execution Systems Mesh Comm
+cora-disc 19220/udp # Discovery for Client Connection ... Service
+cora 19220/tcp # Client Connection Mgmt/Data Exchange Service
+aigairserver 21221/tcp # Services for Air Server
+ka-kdp 31016/udp # Kollective Agent Kollective Delivery
+ka-sddp 31016/tcp # Kollective Agent Secure Distributed Delivery
+edi_service 34567/udp # dhanalakshmi.org EDI Service
+axio-disc 35100/tcp # Axiomatic discovery protocol
+axio-disc 35100/udp # Axiomatic discovery protocol
+pmwebapi 44323/tcp # Performance Co-Pilot client HTTP API
+cloudcheck-ping 45514/udp # ASSIA CloudCheck WiFi Management keepalive
+cloudcheck 45514/tcp # ASSIA CloudCheck WiFi Management System
+spremotetablet 46998/tcp # Capture handwritten signatures
diff --git a/crates/rustnet-core/src/lib.rs b/crates/rustnet-core/src/lib.rs
new file mode 100644
index 0000000..63e140b
--- /dev/null
+++ b/crates/rustnet-core/src/lib.rs
@@ -0,0 +1,39 @@
+//! # rustnet-core
+//!
+//! The reusable network-analysis core of [RustNet](https://github.com/domcyrus/rustnet):
+//! packet parsing, protocol types, deep packet inspection (DPI), link-layer
+//! parsers, connection merging, and DNS / GeoIP / OUI lookups.
+//!
+//! This crate is platform-independent and capture-independent — it operates on
+//! byte slices and parsed structures, with no dependency on `libpcap`, raw
+//! sockets, or OS process tables. Raw packet capture and platform-specific
+//! process attribution live in the `rustnet` binary crate.
+//!
+//! ## Capabilities
+//!
+//! - **Packet parsing** for Ethernet, Linux SLL/SLL2, PKTAP, raw IP, and
+//! TUN/TAP link layers, plus IPv4/IPv6, TCP, UDP, ICMP, and IGMP.
+//! - **Deep packet inspection** for HTTP, HTTPS/TLS with SNI extraction,
+//! DNS, SSH, QUIC, NTP, mDNS, LLMNR, DHCP, SNMP, SSDP, NetBIOS, and more.
+//! - **Connection merging** — fold parsed packets into long-lived connection
+//! state with protocol-aware lifecycle tracking and TCP analytics.
+//! - **GeoIP** lookups against MaxMind GeoLite2 databases.
+//! - **Reverse DNS** with background async resolution and caching.
+//! - **OUI** vendor resolution and **service** name resolution (baked-in
+//! datasets).
+//!
+//! ## Layout
+//!
+//! All modules live under [`network`]. They are also re-exported at the crate
+//! root for convenience, so both `rustnet_core::network::types` and
+//! `rustnet_core::types` resolve to the same module.
+
+pub mod network;
+
+// Flat re-exports so external users can write `rustnet_core::types` instead of
+// `rustnet_core::network::types`. The `network` module remains the canonical
+// home and keeps internal `crate::network::*` paths working unchanged.
+pub use network::{
+ bogon, dns, dpi, geoip, interface_stats, link_layer, merge, oui, parser, protocol, services,
+ tracker, types,
+};
diff --git a/crates/rustnet-core/src/network/bogon.rs b/crates/rustnet-core/src/network/bogon.rs
new file mode 100644
index 0000000..6e95730
--- /dev/null
+++ b/crates/rustnet-core/src/network/bogon.rs
@@ -0,0 +1,230 @@
+//! Classification of an IP address into a routing/usage scope: globally
+//! routable ("public"), or one of the RFC-reserved ranges (private, loopback,
+//! link-local, multicast, documentation, etc.).
+//!
+//! Used to label remote endpoints in the UI so internal traffic is visually
+//! distinguishable from public-Internet traffic. Pure passive classification:
+//! no lookups, no I/O.
+//!
+//! The set of non-public categories is intentionally small but covers the
+//! ranges users care about when scanning a connection list. Stable-Rust
+//! `is_*` helpers on `Ipv4Addr` / `Ipv6Addr` only cover a subset, so the
+//! checks here are done with explicit bit-mask comparisons to avoid relying
+//! on unstable APIs.
+
+use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum Scope {
+ Public,
+ Loopback,
+ Private,
+ LinkLocal,
+ Cgnat,
+ Multicast,
+ Broadcast,
+ Documentation,
+ Benchmarking,
+ Unspecified,
+ Reserved,
+ UniqueLocal,
+ Discard,
+ Ipv4Mapped,
+}
+
+impl Scope {
+ /// Short, all-caps tag suitable for inline display in a detail panel.
+ pub fn label(self) -> &'static str {
+ match self {
+ Scope::Public => "PUBLIC",
+ Scope::Loopback => "LOOPBACK",
+ Scope::Private => "PRIVATE",
+ Scope::LinkLocal => "LINK-LOCAL",
+ Scope::Cgnat => "CGNAT",
+ Scope::Multicast => "MULTICAST",
+ Scope::Broadcast => "BROADCAST",
+ Scope::Documentation => "DOCUMENTATION",
+ Scope::Benchmarking => "BENCHMARKING",
+ Scope::Unspecified => "UNSPECIFIED",
+ Scope::Reserved => "RESERVED",
+ Scope::UniqueLocal => "UNIQUE-LOCAL",
+ Scope::Discard => "DISCARD",
+ Scope::Ipv4Mapped => "IPV4-MAPPED",
+ }
+ }
+}
+
+pub fn classify(ip: IpAddr) -> Scope {
+ match ip {
+ IpAddr::V4(v4) => classify_v4(v4),
+ IpAddr::V6(v6) => classify_v6(v6),
+ }
+}
+
+fn classify_v4(ip: Ipv4Addr) -> Scope {
+ let octets = ip.octets();
+ let [a, b, _, _] = octets;
+
+ if ip.is_unspecified() {
+ return Scope::Unspecified;
+ }
+ if a == 127 {
+ return Scope::Loopback;
+ }
+ // RFC 1918
+ if a == 10 || (a == 172 && (16..=31).contains(&b)) || (a == 192 && b == 168) {
+ return Scope::Private;
+ }
+ if a == 169 && b == 254 {
+ return Scope::LinkLocal;
+ }
+ // RFC 6598 carrier-grade NAT: 100.64.0.0/10
+ if a == 100 && (64..=127).contains(&b) {
+ return Scope::Cgnat;
+ }
+ // Documentation: 192.0.2.0/24 (TEST-NET-1), 198.51.100.0/24 (TEST-NET-2),
+ // 203.0.113.0/24 (TEST-NET-3).
+ if (a == 192 && b == 0 && octets[2] == 2)
+ || (a == 198 && b == 51 && octets[2] == 100)
+ || (a == 203 && b == 0 && octets[2] == 113)
+ {
+ return Scope::Documentation;
+ }
+ // RFC 2544 benchmarking: 198.18.0.0/15
+ if a == 198 && (b == 18 || b == 19) {
+ return Scope::Benchmarking;
+ }
+ if octets == [255, 255, 255, 255] {
+ return Scope::Broadcast;
+ }
+ // 224.0.0.0/4 multicast
+ if (224..=239).contains(&a) {
+ return Scope::Multicast;
+ }
+ // 240.0.0.0/4 reserved (excluding the 255.255.255.255 broadcast above)
+ if a >= 240 {
+ return Scope::Reserved;
+ }
+ Scope::Public
+}
+
+fn classify_v6(ip: Ipv6Addr) -> Scope {
+ if ip.is_unspecified() {
+ return Scope::Unspecified;
+ }
+ if ip.is_loopback() {
+ return Scope::Loopback;
+ }
+ let segs = ip.segments();
+ // ::ffff:0:0/96 IPv4-mapped
+ if segs[0..5] == [0, 0, 0, 0, 0] && segs[5] == 0xffff {
+ return Scope::Ipv4Mapped;
+ }
+ // 100::/64 discard prefix (RFC 6666)
+ if segs[0] == 0x0100 && segs[1] == 0 && segs[2] == 0 && segs[3] == 0 {
+ return Scope::Discard;
+ }
+ // 2001:db8::/32 documentation
+ if segs[0] == 0x2001 && segs[1] == 0x0db8 {
+ return Scope::Documentation;
+ }
+ // ff00::/8 multicast
+ if (segs[0] >> 8) == 0xff {
+ return Scope::Multicast;
+ }
+ // fe80::/10 link-local
+ if (segs[0] & 0xffc0) == 0xfe80 {
+ return Scope::LinkLocal;
+ }
+ // fc00::/7 unique-local
+ if (segs[0] & 0xfe00) == 0xfc00 {
+ return Scope::UniqueLocal;
+ }
+ Scope::Public
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn v4(s: &str) -> IpAddr {
+ IpAddr::V4(s.parse().unwrap())
+ }
+
+ fn v6(s: &str) -> IpAddr {
+ IpAddr::V6(s.parse().unwrap())
+ }
+
+ // The three cases the user explicitly asked for.
+
+ #[test]
+ fn rfc1918_10_slash_8_is_private() {
+ assert_eq!(classify(v4("10.0.0.1")), Scope::Private);
+ assert_eq!(classify(v4("10.255.255.254")), Scope::Private);
+ assert_eq!(classify(v4("10.0.0.0")), Scope::Private);
+ assert_eq!(classify(v4("10.0.0.0")).label(), "PRIVATE");
+ }
+
+ #[test]
+ fn ipv4_169_254_slash_16_is_link_local() {
+ assert_eq!(classify(v4("169.254.1.1")), Scope::LinkLocal);
+ assert_eq!(classify(v4("169.254.255.255")), Scope::LinkLocal);
+ assert_eq!(classify(v4("169.254.0.0")).label(), "LINK-LOCAL");
+ // Sibling /16 must not match.
+ assert_eq!(classify(v4("169.253.1.1")), Scope::Public);
+ }
+
+ #[test]
+ fn ipv6_fe80_slash_10_is_link_local() {
+ assert_eq!(classify(v6("fe80::1")), Scope::LinkLocal);
+ assert_eq!(
+ classify(v6("febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff")),
+ Scope::LinkLocal
+ );
+ assert_eq!(classify(v6("fe80::1")).label(), "LINK-LOCAL");
+ // fec0::/10 site-local is deprecated and outside fe80::/10.
+ assert_eq!(classify(v6("fec0::1")), Scope::Public);
+ }
+
+ // One representative per remaining category.
+
+ #[test]
+ fn ipv4_other_categories() {
+ assert_eq!(classify(v4("172.16.0.1")), Scope::Private);
+ assert_eq!(classify(v4("192.168.1.1")), Scope::Private);
+ assert_eq!(classify(v4("172.32.0.1")), Scope::Public); // outside 172.16/12
+ assert_eq!(classify(v4("127.0.0.1")), Scope::Loopback);
+ assert_eq!(classify(v4("0.0.0.0")), Scope::Unspecified);
+ assert_eq!(classify(v4("100.64.0.1")), Scope::Cgnat);
+ assert_eq!(classify(v4("100.128.0.1")), Scope::Public); // outside 100.64/10
+ assert_eq!(classify(v4("224.0.0.251")), Scope::Multicast); // mDNS
+ assert_eq!(classify(v4("239.255.255.250")), Scope::Multicast);
+ assert_eq!(classify(v4("255.255.255.255")), Scope::Broadcast);
+ assert_eq!(classify(v4("192.0.2.1")), Scope::Documentation);
+ assert_eq!(classify(v4("198.51.100.5")), Scope::Documentation);
+ assert_eq!(classify(v4("203.0.113.7")), Scope::Documentation);
+ assert_eq!(classify(v4("198.18.0.1")), Scope::Benchmarking);
+ assert_eq!(classify(v4("240.0.0.1")), Scope::Reserved);
+ }
+
+ #[test]
+ fn ipv6_other_categories() {
+ assert_eq!(classify(v6("::")), Scope::Unspecified);
+ assert_eq!(classify(v6("::1")), Scope::Loopback);
+ assert_eq!(classify(v6("ff02::1")), Scope::Multicast);
+ assert_eq!(classify(v6("fc00::1")), Scope::UniqueLocal);
+ assert_eq!(classify(v6("fd00::1")), Scope::UniqueLocal);
+ assert_eq!(classify(v6("2001:db8::1")), Scope::Documentation);
+ assert_eq!(classify(v6("100::1")), Scope::Discard);
+ assert_eq!(classify(v6("::ffff:1.2.3.4")), Scope::Ipv4Mapped);
+ }
+
+ #[test]
+ fn public_ips_are_classified_public() {
+ assert_eq!(classify(v4("1.1.1.1")), Scope::Public);
+ assert_eq!(classify(v4("8.8.8.8")), Scope::Public);
+ assert_eq!(classify(v4("93.184.216.34")), Scope::Public);
+ assert_eq!(classify(v6("2606:4700:4700::1111")), Scope::Public);
+ assert_eq!(classify(v4("1.1.1.1")).label(), "PUBLIC");
+ }
+}
diff --git a/crates/rustnet-core/src/network/dns.rs b/crates/rustnet-core/src/network/dns.rs
new file mode 100644
index 0000000..c8ec446
--- /dev/null
+++ b/crates/rustnet-core/src/network/dns.rs
@@ -0,0 +1,354 @@
+//! DNS resolver with background async resolution and caching.
+//!
+//! Provides non-blocking reverse DNS lookups with an LRU cache to avoid
+//! repeated lookups for the same IP address.
+
+use crossbeam::channel::{self, Receiver, Sender};
+use dashmap::DashMap;
+use dns_lookup::lookup_addr;
+use log::debug;
+use std::net::IpAddr;
+use std::sync::Arc;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::thread;
+use std::time::{Duration, Instant};
+
+/// Resolution state for a cached entry
+#[derive(Debug, Clone, PartialEq)]
+pub enum ResolutionState {
+ /// Resolution is in progress
+ Pending,
+ /// Resolution succeeded
+ Resolved,
+ /// Resolution failed
+ Failed,
+}
+
+/// Cached hostname entry
+#[derive(Debug, Clone)]
+pub struct CachedHostname {
+ /// The resolved hostname, if successful
+ pub hostname: Option,
+ /// When this entry was resolved
+ pub resolved_at: Instant,
+ /// Current resolution state
+ pub state: ResolutionState,
+}
+
+impl CachedHostname {
+ fn pending() -> Self {
+ Self {
+ hostname: None,
+ resolved_at: Instant::now(),
+ state: ResolutionState::Pending,
+ }
+ }
+
+ fn resolved(hostname: String) -> Self {
+ Self {
+ hostname: Some(hostname),
+ resolved_at: Instant::now(),
+ state: ResolutionState::Resolved,
+ }
+ }
+
+ fn failed() -> Self {
+ Self {
+ hostname: None,
+ resolved_at: Instant::now(),
+ state: ResolutionState::Failed,
+ }
+ }
+}
+
+/// Configuration for DNS resolver
+#[derive(Debug, Clone)]
+pub struct DnsResolverConfig {
+ /// Cache TTL for resolved hostnames (default: 5 minutes)
+ pub cache_ttl: Duration,
+ /// Cache TTL for failed lookups (default: 1 minute)
+ pub negative_cache_ttl: Duration,
+ /// Maximum cache size (default: 10000 entries)
+ pub max_cache_size: usize,
+ /// Number of resolver threads (default: 4)
+ pub resolver_threads: usize,
+}
+
+impl Default for DnsResolverConfig {
+ fn default() -> Self {
+ Self {
+ cache_ttl: Duration::from_secs(300), // 5 minutes
+ negative_cache_ttl: Duration::from_secs(60), // 1 minute
+ max_cache_size: 10000,
+ resolver_threads: 4,
+ }
+ }
+}
+
+/// Background DNS resolver with caching
+pub struct DnsResolver {
+ /// Hostname cache: IP -> CachedHostname
+ cache: Arc>,
+ /// Channel to send IPs for resolution
+ request_tx: Sender,
+ /// Control flag for shutdown
+ should_stop: Arc,
+ /// Configuration
+ config: DnsResolverConfig,
+}
+
+impl DnsResolver {
+ /// Create a new DNS resolver with the given configuration
+ pub fn new(config: DnsResolverConfig) -> Self {
+ let cache = Arc::new(DashMap::new());
+ let (request_tx, request_rx) = channel::unbounded();
+ let should_stop = Arc::new(AtomicBool::new(false));
+
+ let resolver = Self {
+ cache: Arc::clone(&cache),
+ request_tx,
+ should_stop: Arc::clone(&should_stop),
+ config: config.clone(),
+ };
+
+ // Start resolver threads
+ resolver.start_resolver_threads(request_rx, &config);
+
+ // Start cache cleanup thread
+ resolver.start_cache_cleanup_thread();
+
+ resolver
+ }
+
+ /// Create a new DNS resolver with default configuration
+ pub fn with_defaults() -> Self {
+ Self::new(DnsResolverConfig::default())
+ }
+
+ /// Start background resolver threads
+ fn start_resolver_threads(&self, request_rx: Receiver, config: &DnsResolverConfig) {
+ let num_threads = config.resolver_threads;
+
+ for i in 0..num_threads {
+ let rx = request_rx.clone();
+ let cache = Arc::clone(&self.cache);
+ let should_stop = Arc::clone(&self.should_stop);
+ let cache_ttl = config.cache_ttl;
+ let negative_cache_ttl = config.negative_cache_ttl;
+
+ thread::Builder::new()
+ .name(format!("dns-resolver-{}", i))
+ .spawn(move || {
+ debug!("DNS resolver thread {} started", i);
+
+ while !should_stop.load(Ordering::Relaxed) {
+ match rx.recv_timeout(Duration::from_millis(100)) {
+ Ok(ip) => {
+ // Skip if already resolved or pending
+ if let Some(entry) = cache.get(&ip) {
+ let age = entry.resolved_at.elapsed();
+ match entry.state {
+ ResolutionState::Pending => continue,
+ ResolutionState::Resolved if age < cache_ttl => continue,
+ ResolutionState::Failed if age < negative_cache_ttl => {
+ continue;
+ }
+ _ => {} // Expired, re-resolve
+ }
+ }
+
+ // Mark as pending
+ cache.insert(ip, CachedHostname::pending());
+
+ // Perform DNS lookup
+ match lookup_addr(&ip) {
+ Ok(hostname) => {
+ debug!("Resolved {} -> {}", ip, hostname);
+ cache.insert(ip, CachedHostname::resolved(hostname));
+ }
+ Err(e) => {
+ debug!("Failed to resolve {}: {}", ip, e);
+ cache.insert(ip, CachedHostname::failed());
+ }
+ }
+ }
+ Err(crossbeam::channel::RecvTimeoutError::Timeout) => continue,
+ Err(crossbeam::channel::RecvTimeoutError::Disconnected) => break,
+ }
+ }
+
+ debug!("DNS resolver thread {} stopping", i);
+ })
+ .expect("Failed to spawn DNS resolver thread");
+ }
+ }
+
+ /// Start cache cleanup thread to evict expired entries
+ fn start_cache_cleanup_thread(&self) {
+ let cache = Arc::clone(&self.cache);
+ let should_stop = Arc::clone(&self.should_stop);
+ let cache_ttl = self.config.cache_ttl;
+ let negative_cache_ttl = self.config.negative_cache_ttl;
+ let max_cache_size = self.config.max_cache_size;
+
+ thread::Builder::new()
+ .name("dns-cache-cleanup".to_string())
+ .spawn(move || {
+ debug!("DNS cache cleanup thread started");
+
+ while !should_stop.load(Ordering::Relaxed) {
+ thread::sleep(Duration::from_secs(30)); // Cleanup every 30 seconds
+
+ if should_stop.load(Ordering::Relaxed) {
+ break;
+ }
+
+ // Remove expired entries
+ cache.retain(|_, entry| {
+ let age = entry.resolved_at.elapsed();
+ match entry.state {
+ ResolutionState::Resolved => age < cache_ttl,
+ ResolutionState::Failed => age < negative_cache_ttl,
+ ResolutionState::Pending => age < Duration::from_secs(30), // Timeout pending
+ }
+ });
+
+ // If cache is too large, remove oldest entries
+ if cache.len() > max_cache_size {
+ let mut entries: Vec<_> =
+ cache.iter().map(|e| (*e.key(), e.resolved_at)).collect();
+ entries.sort_by_key(|(_, time)| *time);
+
+ let to_remove = cache.len() - max_cache_size;
+ for (ip, _) in entries.into_iter().take(to_remove) {
+ cache.remove(&ip);
+ }
+ }
+
+ debug!("DNS cache size: {}", cache.len());
+ }
+
+ debug!("DNS cache cleanup thread stopping");
+ })
+ .expect("Failed to spawn DNS cache cleanup thread");
+ }
+
+ /// Request resolution for an IP address (non-blocking)
+ pub fn request_resolution(&self, ip: IpAddr) {
+ // Don't resolve localhost or link-local
+ if ip.is_loopback() || is_link_local(&ip) {
+ return;
+ }
+
+ // Check if already in cache and not expired
+ if let Some(entry) = self.cache.get(&ip) {
+ let age = entry.resolved_at.elapsed();
+ match entry.state {
+ ResolutionState::Pending => return,
+ ResolutionState::Resolved if age < self.config.cache_ttl => return,
+ ResolutionState::Failed if age < self.config.negative_cache_ttl => return,
+ _ => {} // Expired
+ }
+ }
+
+ // Queue for resolution (ignore send errors - channel is unbounded)
+ let _ = self.request_tx.send(ip);
+ }
+
+ /// Get hostname for IP if resolved, otherwise return None
+ pub fn get_hostname(&self, ip: &IpAddr) -> Option {
+ // Request resolution if not in cache
+ self.request_resolution(*ip);
+
+ // Return cached hostname if available
+ self.cache.get(ip).and_then(|entry| {
+ if entry.state == ResolutionState::Resolved {
+ entry.hostname.clone()
+ } else {
+ None
+ }
+ })
+ }
+
+ /// Stop the resolver
+ pub fn stop(&self) {
+ self.should_stop.store(true, Ordering::Relaxed);
+ }
+}
+
+impl Drop for DnsResolver {
+ fn drop(&mut self) {
+ self.stop();
+ }
+}
+
+/// Check if IP is link-local
+fn is_link_local(ip: &IpAddr) -> bool {
+ match ip {
+ IpAddr::V4(v4) => v4.is_link_local(),
+ IpAddr::V6(v6) => {
+ // fe80::/10
+ let segments = v6.segments();
+ (segments[0] & 0xffc0) == 0xfe80
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_cached_hostname_states() {
+ let pending = CachedHostname::pending();
+ assert_eq!(pending.state, ResolutionState::Pending);
+ assert!(pending.hostname.is_none());
+
+ let resolved = CachedHostname::resolved("example.com".to_string());
+ assert_eq!(resolved.state, ResolutionState::Resolved);
+ assert_eq!(resolved.hostname, Some("example.com".to_string()));
+
+ let failed = CachedHostname::failed();
+ assert_eq!(failed.state, ResolutionState::Failed);
+ assert!(failed.hostname.is_none());
+ }
+
+ #[test]
+ fn test_link_local_detection() {
+ // IPv4 link-local
+ assert!(is_link_local(&"169.254.1.1".parse().unwrap()));
+ assert!(!is_link_local(&"192.168.1.1".parse().unwrap()));
+
+ // IPv6 link-local
+ assert!(is_link_local(&"fe80::1".parse().unwrap()));
+ assert!(!is_link_local(&"2001:db8::1".parse().unwrap()));
+ }
+
+ #[test]
+ fn test_loopback_skip() {
+ let config = DnsResolverConfig {
+ resolver_threads: 1,
+ ..Default::default()
+ };
+ let resolver = DnsResolver::new(config);
+
+ // Loopback should not be queued
+ resolver.request_resolution("127.0.0.1".parse().unwrap());
+ assert!(
+ resolver
+ .get_hostname(&"127.0.0.1".parse().unwrap())
+ .is_none()
+ );
+
+ resolver.stop();
+ }
+
+ #[test]
+ fn test_default_config() {
+ let config = DnsResolverConfig::default();
+ assert_eq!(config.cache_ttl, Duration::from_secs(300));
+ assert_eq!(config.negative_cache_ttl, Duration::from_secs(60));
+ assert_eq!(config.max_cache_size, 10000);
+ assert_eq!(config.resolver_threads, 4);
+ }
+}
diff --git a/crates/rustnet-core/src/network/dpi/bittorrent.rs b/crates/rustnet-core/src/network/dpi/bittorrent.rs
new file mode 100644
index 0000000..af688ea
--- /dev/null
+++ b/crates/rustnet-core/src/network/dpi/bittorrent.rs
@@ -0,0 +1,673 @@
+use crate::network::types::{BitTorrentInfo, BitTorrentType};
+use std::fmt::Write as _;
+
+/// BitTorrent protocol handshake prefix: length byte (19) + "BitTorrent protocol"
+const BT_HANDSHAKE_PREFIX: &[u8] = b"\x13BitTorrent protocol";
+
+/// Full handshake length: 1 (pstrlen) + 19 (pstr) + 8 (reserved) + 20 (info_hash) + 20 (peer_id)
+const BT_HANDSHAKE_LEN: usize = 68;
+
+/// Reserved byte bit positions for extension flags
+const DHT_BIT_BYTE: usize = 7; // Byte 7, bit 0x01
+const FAST_BIT_BYTE: usize = 7; // Byte 7, bit 0x04
+const EXTENSION_BIT_BYTE: usize = 5; // Byte 5, bit 0x10
+
+/// uTP header size (BEP 29)
+const UTP_HEADER_LEN: usize = 20;
+
+/// Maximum DHT method name length. Standard methods are short
+/// (e.g., "ping", "find_node", "get_peers", "announce_peer").
+const MAX_DHT_METHOD_LEN: usize = 64;
+
+// --- TCP: Peer Handshake ---
+
+/// Check if the payload starts with a BitTorrent peer handshake.
+pub fn is_bittorrent_handshake(payload: &[u8]) -> bool {
+ payload.starts_with(BT_HANDSHAKE_PREFIX)
+}
+
+/// Analyze a BitTorrent TCP handshake payload and extract protocol details.
+pub fn analyze_bittorrent(payload: &[u8]) -> Option {
+ if !is_bittorrent_handshake(payload) {
+ return None;
+ }
+
+ let reserved = if payload.len() >= 28 {
+ Some(&payload[20..28])
+ } else {
+ None
+ };
+
+ let supports_dht = reserved.is_some_and(|r| r[DHT_BIT_BYTE] & 0x01 != 0);
+ let supports_fast = reserved.is_some_and(|r| r[FAST_BIT_BYTE] & 0x04 != 0);
+ let supports_extension = reserved.is_some_and(|r| r[EXTENSION_BIT_BYTE] & 0x10 != 0);
+
+ let info_hash = if payload.len() >= 48 {
+ let hash_bytes = &payload[28..48];
+ Some(hex_encode(hash_bytes))
+ } else {
+ None
+ };
+
+ let client = if payload.len() >= BT_HANDSHAKE_LEN {
+ let peer_id = &payload[48..68];
+ decode_client_name(peer_id)
+ } else {
+ None
+ };
+
+ Some(BitTorrentInfo {
+ protocol_type: BitTorrentType::Peer,
+ info_hash,
+ client,
+ dht_method: None,
+ supports_dht,
+ supports_extension,
+ supports_fast,
+ })
+}
+
+// --- UDP: DHT + uTP ---
+
+/// Analyze a UDP payload for BitTorrent DHT or uTP traffic.
+/// Tries DHT first (higher confidence), then uTP.
+pub fn analyze_udp_bittorrent(payload: &[u8]) -> Option {
+ if let Some(info) = analyze_dht(payload) {
+ return Some(info);
+ }
+ analyze_utp(payload)
+}
+
+/// Analyze a UDP payload for BitTorrent DHT (bencoded dictionary messages).
+///
+/// DHT messages are bencoded dicts containing:
+/// - `y`: message type — `q` (query), `r` (response), `e` (error)
+/// - `q`: method name (for queries) — `ping`, `find_node`, `get_peers`, `announce_peer`
+/// - `t`: transaction ID
+fn analyze_dht(payload: &[u8]) -> Option {
+ // Must start with 'd' (bencoded dict) and end with 'e'
+ if payload.len() < 10 || payload[0] != b'd' || payload[payload.len() - 1] != b'e' {
+ return None;
+ }
+
+ // Must contain the message type key "1:y1:" followed by q/r/e
+ let pos = find_subsequence(payload, b"1:y1:")?;
+ if pos + 6 > payload.len() {
+ return None;
+ }
+ let msg_type_char = payload[pos + 5];
+ if msg_type_char != b'q' && msg_type_char != b'r' && msg_type_char != b'e' {
+ return None;
+ }
+
+ // Extract DHT method for queries
+ let dht_method = if msg_type_char == b'q' {
+ extract_dht_method(payload)
+ } else if msg_type_char == b'r' {
+ Some("response".to_string())
+ } else {
+ Some("error".to_string())
+ };
+
+ Some(BitTorrentInfo {
+ protocol_type: BitTorrentType::Dht,
+ info_hash: None,
+ client: None,
+ dht_method,
+ supports_dht: false,
+ supports_extension: false,
+ supports_fast: false,
+ })
+}
+
+/// Extract the DHT query method name from a bencoded payload.
+/// Looks for "1:q" followed by a bencoded string like "4:ping" or "9:find_node".
+fn extract_dht_method(payload: &[u8]) -> Option {
+ let pos = find_subsequence(payload, b"1:q")?;
+ let after = pos + 3;
+ if after >= payload.len() {
+ return None;
+ }
+
+ // Parse the bencoded string length: digits followed by ':'
+ let mut len_end = after;
+ while len_end < payload.len() && payload[len_end].is_ascii_digit() {
+ len_end += 1;
+ }
+ if len_end == after || len_end >= payload.len() || payload[len_end] != b':' {
+ return None;
+ }
+
+ let len_str = std::str::from_utf8(&payload[after..len_end]).ok()?;
+ let str_len: usize = len_str.parse().ok()?;
+ if str_len > MAX_DHT_METHOD_LEN {
+ return None;
+ }
+ let str_start = len_end + 1;
+ let str_end = str_start + str_len;
+ if str_end > payload.len() {
+ return None;
+ }
+
+ std::str::from_utf8(&payload[str_start..str_end])
+ .ok()
+ .map(String::from)
+}
+
+/// Analyze a UDP payload for BitTorrent uTP (Micro Transport Protocol, BEP 29).
+///
+/// uTP header (20 bytes):
+/// - Byte 0: type (upper 4 bits) | version (lower 4 bits, must be 1)
+/// - Byte 1: extension
+/// - Bytes 2-3: connection_id
+/// - Bytes 4-7: timestamp_microseconds
+/// - Bytes 8-11: timestamp_difference_microseconds
+/// - Bytes 12-15: wnd_size
+/// - Bytes 16-17: seq_nr
+/// - Bytes 18-19: ack_nr
+fn analyze_utp(payload: &[u8]) -> Option {
+ if payload.len() < UTP_HEADER_LEN {
+ return None;
+ }
+
+ let first_byte = payload[0];
+ let version = first_byte & 0x0F;
+ let pkt_type = (first_byte >> 4) & 0x0F;
+ let extension = payload[1];
+
+ // Version must be 1
+ if version != 1 {
+ return None;
+ }
+
+ // Type must be 0-4: ST_DATA, ST_FIN, ST_STATE, ST_RESET, ST_SYN
+ if pkt_type > 4 {
+ return None;
+ }
+
+ // Extension byte should be small (0=none, 1=selective ack, 2=extension bits)
+ if extension > 2 {
+ return None;
+ }
+
+ // Real uTP connection IDs are randomly generated, so 0 is effectively
+ // never seen — but bytes 2-3 == 0 is exactly what a WireGuard handshake
+ // initiation looks like here (type byte 0x01 followed by three reserved
+ // zero bytes). Since this check runs on every unmatched UDP packet,
+ // require a non-zero connection ID so WireGuard rekeys are not
+ // classified as BitTorrent.
+ let connection_id = u16::from_be_bytes([payload[2], payload[3]]);
+ if connection_id == 0 {
+ return None;
+ }
+
+ // Window size sanity check — 0 is valid for ST_RESET but otherwise should be non-zero
+ let wnd_size = u32::from_be_bytes([payload[12], payload[13], payload[14], payload[15]]);
+ if pkt_type != 3 && wnd_size == 0 {
+ // ST_SYN with zero window is also suspicious
+ return None;
+ }
+
+ Some(BitTorrentInfo {
+ protocol_type: BitTorrentType::Utp,
+ info_hash: None,
+ client: None,
+ dht_method: None,
+ supports_dht: false,
+ supports_extension: false,
+ supports_fast: false,
+ })
+}
+
+// --- Helpers ---
+
+/// Decode the BitTorrent client name from a 20-byte peer_id using the Azureus-style convention.
+///
+/// Azureus-style: `-XX1234-............` where XX is the client ID and 1234 is the version.
+fn decode_client_name(peer_id: &[u8]) -> Option {
+ if peer_id.len() >= 8 && peer_id[0] == b'-' && peer_id[7] == b'-' {
+ let client_id = std::str::from_utf8(&peer_id[1..3]).ok()?;
+ let version_bytes = &peer_id[3..7];
+
+ let name = match client_id {
+ "qB" => "qBittorrent",
+ "TR" => "Transmission",
+ "DE" => "Deluge",
+ "UT" => "uTorrent",
+ "lt" => "libtorrent",
+ "LT" => "libtorrent",
+ "AZ" => "Azureus",
+ "BT" => "BitTorrent",
+ "BI" => "BiglyBT",
+ "FD" => "Free Download Manager",
+ "KT" => "KTorrent",
+ "RB" => "rtorrent",
+ "WW" => "WebTorrent",
+ "FL" => "Flud",
+ "SD" => "Xunlei",
+ "TL" => "Tribler",
+ _ => client_id,
+ };
+
+ let version = format_version(version_bytes);
+ Some(format!("{name} {version}"))
+ } else if peer_id[0].is_ascii_alphanumeric() {
+ let id_char = peer_id[0] as char;
+ let name = match id_char {
+ 'M' => "Mainline",
+ 'S' => "Shadow",
+ 'T' => "BitTornado",
+ 'A' => "ABC",
+ _ => return None,
+ };
+ Some(name.to_string())
+ } else {
+ None
+ }
+}
+
+/// Format version bytes into a dotted version string.
+fn format_version(bytes: &[u8]) -> String {
+ bytes
+ .iter()
+ .filter_map(|&b| {
+ if b.is_ascii_digit() {
+ Some((b - b'0').to_string())
+ } else if b.is_ascii_alphanumeric() {
+ Some((b as char).to_string())
+ } else {
+ None
+ }
+ })
+ .collect::>()
+ .join(".")
+}
+
+/// Encode bytes as a lowercase hex string.
+fn hex_encode(bytes: &[u8]) -> String {
+ let mut out = String::with_capacity(bytes.len() * 2);
+ for b in bytes {
+ // write! to a String never fails.
+ let _ = write!(out, "{b:02x}");
+ }
+ out
+}
+
+/// Find the first occurrence of a subsequence in a byte slice.
+fn find_subsequence(haystack: &[u8], needle: &[u8]) -> Option {
+ haystack
+ .windows(needle.len())
+ .position(|window| window == needle)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ // --- TCP Handshake Tests ---
+
+ fn build_handshake(reserved: [u8; 8], info_hash: [u8; 20], peer_id: &[u8; 20]) -> Vec {
+ let mut payload = Vec::with_capacity(BT_HANDSHAKE_LEN);
+ payload.extend_from_slice(BT_HANDSHAKE_PREFIX);
+ payload.extend_from_slice(&reserved);
+ payload.extend_from_slice(&info_hash);
+ payload.extend_from_slice(peer_id);
+ payload
+ }
+
+ #[test]
+ fn test_handshake_detection() {
+ let payload = build_handshake([0; 8], [0xAB; 20], b"-qB4250-xxxxxxxxxxxx");
+ assert!(is_bittorrent_handshake(&payload));
+ }
+
+ #[test]
+ fn test_non_bittorrent_payloads() {
+ assert!(!is_bittorrent_handshake(b"GET / HTTP/1.1\r\n"));
+ assert!(!is_bittorrent_handshake(b"SSH-2.0-OpenSSH_8.9\r\n"));
+ assert!(!is_bittorrent_handshake(&[0x16, 0x03, 0x01, 0x00]));
+ assert!(!is_bittorrent_handshake(b""));
+ assert!(!is_bittorrent_handshake(&[0x13]));
+ }
+
+ #[test]
+ fn test_analyze_qbittorrent() {
+ let payload = build_handshake([0; 8], [0xDE; 20], b"-qB4250-xxxxxxxxxxxx");
+ let info = analyze_bittorrent(&payload).unwrap();
+
+ assert_eq!(info.protocol_type, BitTorrentType::Peer);
+ assert_eq!(info.client.as_deref(), Some("qBittorrent 4.2.5.0"));
+ assert_eq!(
+ info.info_hash.as_deref(),
+ Some("dededededededededededededededededededede") // 20 bytes = 40 hex chars
+ );
+ assert!(!info.supports_dht);
+ assert!(!info.supports_extension);
+ assert!(!info.supports_fast);
+ }
+
+ #[test]
+ fn test_analyze_transmission() {
+ let payload = build_handshake([0; 8], [0; 20], b"-TR3000-xxxxxxxxxxxx");
+ let info = analyze_bittorrent(&payload).unwrap();
+ assert_eq!(info.client.as_deref(), Some("Transmission 3.0.0.0"));
+ }
+
+ #[test]
+ fn test_analyze_deluge() {
+ let payload = build_handshake([0; 8], [0; 20], b"-DE0018-xxxxxxxxxxxx");
+ let info = analyze_bittorrent(&payload).unwrap();
+ assert_eq!(info.client.as_deref(), Some("Deluge 0.0.1.8"));
+ }
+
+ #[test]
+ fn test_analyze_utorrent() {
+ let payload = build_handshake([0; 8], [0; 20], b"-UT3560-xxxxxxxxxxxx");
+ let info = analyze_bittorrent(&payload).unwrap();
+ assert_eq!(info.client.as_deref(), Some("uTorrent 3.5.6.0"));
+ }
+
+ #[test]
+ fn test_analyze_libtorrent() {
+ let payload = build_handshake([0; 8], [0; 20], b"-lt0D60-xxxxxxxxxxxx");
+ let info = analyze_bittorrent(&payload).unwrap();
+ assert_eq!(info.client.as_deref(), Some("libtorrent 0.D.6.0"));
+ }
+
+ #[test]
+ fn test_dht_flag() {
+ let mut reserved = [0u8; 8];
+ reserved[7] = 0x01;
+ let payload = build_handshake(reserved, [0; 20], b"-qB4250-xxxxxxxxxxxx");
+ let info = analyze_bittorrent(&payload).unwrap();
+ assert!(info.supports_dht);
+ assert!(!info.supports_extension);
+ assert!(!info.supports_fast);
+ }
+
+ #[test]
+ fn test_extension_flag() {
+ let mut reserved = [0u8; 8];
+ reserved[5] = 0x10;
+ let payload = build_handshake(reserved, [0; 20], b"-qB4250-xxxxxxxxxxxx");
+ let info = analyze_bittorrent(&payload).unwrap();
+ assert!(!info.supports_dht);
+ assert!(info.supports_extension);
+ assert!(!info.supports_fast);
+ }
+
+ #[test]
+ fn test_fast_flag() {
+ let mut reserved = [0u8; 8];
+ reserved[7] = 0x04;
+ let payload = build_handshake(reserved, [0; 20], b"-qB4250-xxxxxxxxxxxx");
+ let info = analyze_bittorrent(&payload).unwrap();
+ assert!(!info.supports_dht);
+ assert!(!info.supports_extension);
+ assert!(info.supports_fast);
+ }
+
+ #[test]
+ fn test_all_flags() {
+ let mut reserved = [0u8; 8];
+ reserved[5] = 0x10;
+ reserved[7] = 0x05;
+ let payload = build_handshake(reserved, [0; 20], b"-qB4250-xxxxxxxxxxxx");
+ let info = analyze_bittorrent(&payload).unwrap();
+ assert!(info.supports_dht);
+ assert!(info.supports_extension);
+ assert!(info.supports_fast);
+ }
+
+ #[test]
+ fn test_partial_handshake_prefix_only() {
+ let payload = BT_HANDSHAKE_PREFIX.to_vec();
+ let info = analyze_bittorrent(&payload).unwrap();
+ assert!(info.info_hash.is_none());
+ assert!(info.client.is_none());
+ assert!(!info.supports_dht);
+ }
+
+ #[test]
+ fn test_partial_handshake_with_hash() {
+ let mut payload = Vec::new();
+ payload.extend_from_slice(BT_HANDSHAKE_PREFIX);
+ payload.extend_from_slice(&[0u8; 8]);
+ payload.extend_from_slice(&[0xAB; 20]);
+ let info = analyze_bittorrent(&payload).unwrap();
+ assert!(info.info_hash.is_some());
+ assert!(info.client.is_none());
+ }
+
+ #[test]
+ fn test_unknown_client_id() {
+ let payload = build_handshake([0; 8], [0; 20], b"-ZZ1234-xxxxxxxxxxxx");
+ let info = analyze_bittorrent(&payload).unwrap();
+ assert_eq!(info.client.as_deref(), Some("ZZ 1.2.3.4"));
+ }
+
+ #[test]
+ fn test_non_bt_returns_none() {
+ assert!(analyze_bittorrent(b"GET / HTTP/1.1\r\n").is_none());
+ assert!(analyze_bittorrent(b"SSH-2.0-OpenSSH\r\n").is_none());
+ assert!(analyze_bittorrent(b"").is_none());
+ }
+
+ #[test]
+ fn test_hex_encode() {
+ assert_eq!(hex_encode(&[0xDE, 0xAD, 0xBE, 0xEF]), "deadbeef");
+ assert_eq!(hex_encode(&[0x00, 0xFF]), "00ff");
+ }
+
+ #[test]
+ fn test_format_version() {
+ assert_eq!(format_version(b"4250"), "4.2.5.0");
+ assert_eq!(format_version(b"3000"), "3.0.0.0");
+ assert_eq!(format_version(b"0D60"), "0.D.6.0");
+ }
+
+ // --- DHT Tests ---
+
+ #[test]
+ fn test_dht_ping_query() {
+ let payload = b"d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aa1:y1:qe";
+ let info = analyze_dht(payload).unwrap();
+ assert_eq!(info.protocol_type, BitTorrentType::Dht);
+ assert_eq!(info.dht_method.as_deref(), Some("ping"));
+ }
+
+ #[test]
+ fn test_dht_find_node_query() {
+ let payload = b"d1:ad2:id20:abcdefghij01234567896:target20:mnopqrstuvwxyz123456e1:q9:find_node1:t2:aa1:y1:qe";
+ let info = analyze_dht(payload).unwrap();
+ assert_eq!(info.dht_method.as_deref(), Some("find_node"));
+ }
+
+ #[test]
+ fn test_dht_get_peers_query() {
+ let payload = b"d1:ad2:id20:abcdefghij01234567899:info_hash20:mnopqrstuvwxyz123456e1:q9:get_peers1:t2:aa1:y1:qe";
+ let info = analyze_dht(payload).unwrap();
+ assert_eq!(info.dht_method.as_deref(), Some("get_peers"));
+ }
+
+ #[test]
+ fn test_dht_announce_peer_query() {
+ let payload = b"d1:ad2:id20:abcdefghij01234567899:info_hash20:mnopqrstuvwxyz1234564:porti6881e5:token8:aoeusnthe1:q13:announce_peer1:t2:aa1:y1:qe";
+ let info = analyze_dht(payload).unwrap();
+ assert_eq!(info.dht_method.as_deref(), Some("announce_peer"));
+ }
+
+ #[test]
+ fn test_dht_response() {
+ let payload = b"d1:rd2:id20:0123456789abcdefghij5:nodes208:...e1:t2:aa1:y1:re";
+ let info = analyze_dht(payload).unwrap();
+ assert_eq!(info.protocol_type, BitTorrentType::Dht);
+ assert_eq!(info.dht_method.as_deref(), Some("response"));
+ }
+
+ #[test]
+ fn test_dht_error() {
+ let payload = b"d1:eli201e23:A Generic Error Ocurrede1:t2:aa1:y1:ee";
+ let info = analyze_dht(payload).unwrap();
+ assert_eq!(info.dht_method.as_deref(), Some("error"));
+ }
+
+ #[test]
+ fn test_dht_rejects_non_bencode() {
+ assert!(analyze_dht(b"GET / HTTP/1.1\r\n").is_none());
+ assert!(analyze_dht(b"").is_none());
+ assert!(analyze_dht(b"d").is_none());
+ // Starts with 'd' but no message type key
+ assert!(analyze_dht(b"d3:foo3:bare").is_none());
+ }
+
+ // --- uTP Tests ---
+
+ /// Build a minimal uTP header.
+ fn build_utp_header(pkt_type: u8, version: u8, extension: u8, wnd_size: u32) -> Vec {
+ let mut payload = vec![0u8; UTP_HEADER_LEN];
+ payload[0] = (pkt_type << 4) | (version & 0x0F);
+ payload[1] = extension;
+ // connection_id at bytes 2-3: always random (non-zero) in real uTP
+ payload[2..4].copy_from_slice(&0x1234u16.to_be_bytes());
+ // wnd_size at bytes 12-15
+ payload[12..16].copy_from_slice(&wnd_size.to_be_bytes());
+ payload
+ }
+
+ #[test]
+ fn test_utp_rejects_wireguard_handshake_initiation() {
+ // WireGuard handshake initiation: message type 0x01, three reserved
+ // zero bytes, then a random sender index and ephemeral key. It
+ // passes every other uTP field check (version 1, type ST_DATA,
+ // extension 0, non-zero bytes at the wnd_size position), so the
+ // zero connection-id bytes are what must reject it.
+ let mut payload = vec![0u8; 148];
+ payload[0] = 0x01; // type=1 (handshake initiation), reserved bytes 1-3 zero
+ for (i, byte) in payload.iter_mut().enumerate().skip(4) {
+ *byte = (i * 31 % 251 + 1) as u8; // arbitrary non-zero key material
+ }
+ assert!(analyze_utp(&payload).is_none());
+ assert!(analyze_udp_bittorrent(&payload).is_none());
+ }
+
+ #[test]
+ fn test_utp_data_packet() {
+ let payload = build_utp_header(0, 1, 0, 65535); // ST_DATA
+ let info = analyze_utp(&payload).unwrap();
+ assert_eq!(info.protocol_type, BitTorrentType::Utp);
+ }
+
+ #[test]
+ fn test_utp_syn_packet() {
+ let payload = build_utp_header(4, 1, 0, 65535); // ST_SYN
+ let info = analyze_utp(&payload).unwrap();
+ assert_eq!(info.protocol_type, BitTorrentType::Utp);
+ }
+
+ #[test]
+ fn test_utp_state_packet() {
+ let payload = build_utp_header(2, 1, 0, 32768); // ST_STATE (ACK)
+ let info = analyze_utp(&payload).unwrap();
+ assert_eq!(info.protocol_type, BitTorrentType::Utp);
+ }
+
+ #[test]
+ fn test_utp_fin_packet() {
+ let payload = build_utp_header(1, 1, 0, 1024); // ST_FIN
+ let info = analyze_utp(&payload).unwrap();
+ assert_eq!(info.protocol_type, BitTorrentType::Utp);
+ }
+
+ #[test]
+ fn test_utp_reset_zero_window() {
+ // ST_RESET with zero window is valid
+ let payload = build_utp_header(3, 1, 0, 0);
+ let info = analyze_utp(&payload).unwrap();
+ assert_eq!(info.protocol_type, BitTorrentType::Utp);
+ }
+
+ #[test]
+ fn test_utp_with_selective_ack() {
+ let payload = build_utp_header(0, 1, 1, 65535); // extension=1 (SACK)
+ assert!(analyze_utp(&payload).is_some());
+ }
+
+ #[test]
+ fn test_utp_rejects_wrong_version() {
+ let payload = build_utp_header(0, 0, 0, 65535);
+ assert!(analyze_utp(&payload).is_none());
+ let payload = build_utp_header(0, 2, 0, 65535);
+ assert!(analyze_utp(&payload).is_none());
+ }
+
+ #[test]
+ fn test_utp_rejects_invalid_type() {
+ let payload = build_utp_header(5, 1, 0, 65535); // type 5 is invalid
+ assert!(analyze_utp(&payload).is_none());
+ }
+
+ #[test]
+ fn test_utp_rejects_bad_extension() {
+ let payload = build_utp_header(0, 1, 10, 65535); // extension=10 is suspicious
+ assert!(analyze_utp(&payload).is_none());
+ }
+
+ #[test]
+ fn test_utp_rejects_zero_window_non_reset() {
+ let payload = build_utp_header(0, 1, 0, 0); // ST_DATA with zero window
+ assert!(analyze_utp(&payload).is_none());
+ }
+
+ #[test]
+ fn test_utp_rejects_too_short() {
+ assert!(analyze_utp(&[0x01; 10]).is_none());
+ assert!(analyze_utp(&[]).is_none());
+ }
+
+ // --- Combined UDP analyzer ---
+
+ #[test]
+ fn test_udp_prefers_dht_over_utp() {
+ // A DHT message should be detected as DHT, not uTP
+ let payload = b"d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aa1:y1:qe";
+ let info = analyze_udp_bittorrent(payload).unwrap();
+ assert_eq!(info.protocol_type, BitTorrentType::Dht);
+ }
+
+ #[test]
+ fn test_udp_falls_through_to_utp() {
+ let payload = build_utp_header(4, 1, 0, 65535);
+ let info = analyze_udp_bittorrent(&payload).unwrap();
+ assert_eq!(info.protocol_type, BitTorrentType::Utp);
+ }
+
+ #[test]
+ fn test_udp_returns_none_for_unknown() {
+ assert!(analyze_udp_bittorrent(b"GET / HTTP/1.1\r\n").is_none());
+ }
+
+ #[test]
+ fn test_hex_encode_lowercase_20_byte_info_hash() {
+ // A representative 20-byte info-hash (the size BitTorrent always uses).
+ let info_hash: [u8; 20] = [
+ 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x00, 0xff, 0x01, 0x02, 0x03, 0x04,
+ 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
+ ];
+ assert_eq!(
+ hex_encode(&info_hash),
+ "123456789abcdef000ff0102030405060708090a"
+ );
+ }
+
+ #[test]
+ fn test_hex_encode_empty_slice() {
+ assert_eq!(hex_encode(&[]), "");
+ }
+
+ #[test]
+ fn test_hex_encode_pads_single_digit_bytes() {
+ // Locks the `{:02x}` padding contract — 0x00..=0x0f stay two chars.
+ assert_eq!(hex_encode(&[0x00, 0x0a, 0x0f, 0x10]), "000a0f10");
+ }
+}
diff --git a/crates/rustnet-core/src/network/dpi/cipher_suites.rs b/crates/rustnet-core/src/network/dpi/cipher_suites.rs
new file mode 100644
index 0000000..8b32741
--- /dev/null
+++ b/crates/rustnet-core/src/network/dpi/cipher_suites.rs
@@ -0,0 +1,227 @@
+//! TLS Cipher Suite mappings
+//!
+//! This module provides mappings from cipher suite codes to their human-readable names.
+//! The mappings are based on the IANA TLS Cipher Suite Registry and include commonly
+//! used cipher suites from TLS 1.0 through TLS 1.3.
+
+use std::collections::HashMap;
+use std::sync::LazyLock;
+
+/// Static mapping of cipher suite codes to their names
+static CIPHER_SUITE_MAP: LazyLock> = LazyLock::new(|| {
+ let mut map = HashMap::new();
+
+ // TLS 1.3 Cipher Suites (RFC 8446)
+ map.insert(0x1301, "TLS_AES_128_GCM_SHA256");
+ map.insert(0x1302, "TLS_AES_256_GCM_SHA384");
+ map.insert(0x1303, "TLS_CHACHA20_POLY1305_SHA256");
+ map.insert(0x1304, "TLS_AES_128_CCM_SHA256");
+ map.insert(0x1305, "TLS_AES_128_CCM_8_SHA256");
+
+ // TLS 1.2 ECDHE Cipher Suites (RFC 5289, RFC 7905)
+ map.insert(0xc02b, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256");
+ map.insert(0xc02c, "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384");
+ map.insert(0xc02f, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256");
+ map.insert(0xc030, "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384");
+ map.insert(0xc009, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA");
+ map.insert(0xc00a, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA");
+ map.insert(0xc013, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA");
+ map.insert(0xc014, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA");
+ map.insert(0xc023, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256");
+ map.insert(0xc024, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384");
+ map.insert(0xc027, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256");
+ map.insert(0xc028, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384");
+
+ // ChaCha20-Poly1305 (RFC 7905)
+ map.insert(0xcca9, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256");
+ map.insert(0xcca8, "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256");
+ map.insert(0xccaa, "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256");
+
+ // DHE Cipher Suites
+ map.insert(0x009e, "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256");
+ map.insert(0x009f, "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384");
+ map.insert(0x0033, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA");
+ map.insert(0x0039, "TLS_DHE_RSA_WITH_AES_256_CBC_SHA");
+ map.insert(0x0067, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256");
+ map.insert(0x006b, "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256");
+
+ // RSA Cipher Suites (less preferred but still common)
+ map.insert(0x009c, "TLS_RSA_WITH_AES_128_GCM_SHA256");
+ map.insert(0x009d, "TLS_RSA_WITH_AES_256_GCM_SHA384");
+ map.insert(0x002f, "TLS_RSA_WITH_AES_128_CBC_SHA");
+ map.insert(0x0035, "TLS_RSA_WITH_AES_256_CBC_SHA");
+ map.insert(0x003c, "TLS_RSA_WITH_AES_128_CBC_SHA256");
+ map.insert(0x003d, "TLS_RSA_WITH_AES_256_CBC_SHA256");
+
+ // 3DES (Legacy)
+ map.insert(0x000a, "TLS_RSA_WITH_3DES_EDE_CBC_SHA");
+ map.insert(0x0016, "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA");
+ map.insert(0xc008, "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA");
+ map.insert(0xc012, "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA");
+
+ // RC4 (Deprecated but still seen)
+ map.insert(0x0004, "TLS_RSA_WITH_RC4_128_MD5");
+ map.insert(0x0005, "TLS_RSA_WITH_RC4_128_SHA");
+ map.insert(0xc007, "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA");
+ map.insert(0xc011, "TLS_ECDHE_RSA_WITH_RC4_128_SHA");
+
+ // PSK Cipher Suites (RFC 4279, RFC 5487)
+ map.insert(0x008c, "TLS_PSK_WITH_AES_128_CBC_SHA");
+ map.insert(0x008d, "TLS_PSK_WITH_AES_256_CBC_SHA");
+ map.insert(0x00a8, "TLS_PSK_WITH_AES_128_GCM_SHA256");
+ map.insert(0x00a9, "TLS_PSK_WITH_AES_256_GCM_SHA384");
+
+ // ECDHE-PSK
+ map.insert(0xc035, "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA");
+ map.insert(0xc036, "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA");
+ map.insert(0xc037, "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256");
+ map.insert(0xc038, "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384");
+
+ // ARIA Cipher Suites (RFC 6209)
+ map.insert(0xc03c, "TLS_RSA_WITH_ARIA_128_CBC_SHA256");
+ map.insert(0xc03d, "TLS_RSA_WITH_ARIA_256_CBC_SHA384");
+ map.insert(0xc060, "TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256");
+ map.insert(0xc061, "TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384");
+
+ // Camellia Cipher Suites (RFC 6367)
+ map.insert(0xc072, "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256");
+ map.insert(0xc073, "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384");
+ map.insert(0xc076, "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256");
+ map.insert(0xc077, "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384");
+
+ // NULL encryption (for testing/debugging)
+ map.insert(0x0001, "TLS_RSA_WITH_NULL_MD5");
+ map.insert(0x0002, "TLS_RSA_WITH_NULL_SHA");
+ map.insert(0x003b, "TLS_RSA_WITH_NULL_SHA256");
+
+ map
+});
+
+/// Get the human-readable name for a cipher suite code
+///
+/// # Arguments
+/// * `code` - The cipher suite code (u16)
+///
+/// # Returns
+/// * Some(name) if the cipher suite is known
+/// * None if the cipher suite is not in our mapping
+pub fn get_cipher_suite_name(code: u16) -> Option<&'static str> {
+ CIPHER_SUITE_MAP.get(&code).copied()
+}
+
+/// Format a cipher suite code with its name if known
+///
+/// # Arguments
+/// * `code` - The cipher suite code (u16)
+///
+/// # Returns
+/// * A formatted string like "TLS_AES_128_GCM_SHA256 (0x1301)" if known
+/// * Just the hex code like "0x1234" if unknown
+pub fn format_cipher_suite(code: u16) -> String {
+ match get_cipher_suite_name(code) {
+ Some(name) => format!("{} (0x{:04X})", name, code),
+ None => format!("0x{:04X}", code),
+ }
+}
+
+/// Check if a cipher suite is considered secure by modern standards
+///
+/// # Arguments
+/// * `code` - The cipher suite code (u16)
+///
+/// # Returns
+/// * true if the cipher suite is considered secure
+/// * false if it's deprecated or insecure
+pub fn is_secure_cipher_suite(code: u16) -> bool {
+ match code {
+ // TLS 1.3 suites are all secure
+ 0x1301..=0x1305 => true,
+ // Modern TLS 1.2 ECDHE suites with AEAD
+ 0xc02b | 0xc02c | 0xc02f | 0xc030 => true, // ECDHE + AES-GCM
+ 0xcca8..=0xccaa => true, // ChaCha20-Poly1305
+ 0x009e | 0x009f => true, // DHE-RSA with AES-GCM
+ // Everything else is either legacy or insecure
+ _ => false,
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_tls13_cipher_suites() {
+ assert_eq!(
+ get_cipher_suite_name(0x1301),
+ Some("TLS_AES_128_GCM_SHA256")
+ );
+ assert_eq!(
+ get_cipher_suite_name(0x1302),
+ Some("TLS_AES_256_GCM_SHA384")
+ );
+ assert_eq!(
+ get_cipher_suite_name(0x1303),
+ Some("TLS_CHACHA20_POLY1305_SHA256")
+ );
+ }
+
+ #[test]
+ fn test_format_cipher_suite() {
+ assert_eq!(
+ format_cipher_suite(0x1301),
+ "TLS_AES_128_GCM_SHA256 (0x1301)"
+ );
+ assert_eq!(format_cipher_suite(0x9999), "0x9999");
+ }
+
+ #[test]
+ fn test_security_classification() {
+ assert!(is_secure_cipher_suite(0x1301)); // TLS 1.3
+ assert!(is_secure_cipher_suite(0xc02f)); // ECDHE-RSA-AES128-GCM-SHA256
+ assert!(!is_secure_cipher_suite(0x0004)); // RC4
+ assert!(!is_secure_cipher_suite(0x000a)); // 3DES
+ }
+
+ #[test]
+ fn test_unknown_cipher_suite() {
+ assert_eq!(get_cipher_suite_name(0xFFFF), None);
+ assert_eq!(format_cipher_suite(0xFFFF), "0xFFFF");
+ }
+
+ #[test]
+ fn test_aria_camellia_iana_names() {
+ // 0xC03C/0xC03D are RSA + CBC per IANA / RFC 6209; the
+ // ECDHE_ECDSA ARIA GCM suites live at 0xC05C/0xC05D.
+ assert_eq!(
+ get_cipher_suite_name(0xc03c),
+ Some("TLS_RSA_WITH_ARIA_128_CBC_SHA256")
+ );
+ assert_eq!(
+ get_cipher_suite_name(0xc03d),
+ Some("TLS_RSA_WITH_ARIA_256_CBC_SHA384")
+ );
+ // 0xC060/0xC061 are ECDHE_RSA ARIA GCM (already correct).
+ assert_eq!(
+ get_cipher_suite_name(0xc060),
+ Some("TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256")
+ );
+ // 0xC072-0xC077 are CBC per IANA / RFC 6367; the matching
+ // Camellia GCM suites live at 0xC086/0xC087/0xC08A/0xC08B.
+ assert_eq!(
+ get_cipher_suite_name(0xc072),
+ Some("TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256")
+ );
+ assert_eq!(
+ get_cipher_suite_name(0xc073),
+ Some("TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384")
+ );
+ assert_eq!(
+ get_cipher_suite_name(0xc076),
+ Some("TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256")
+ );
+ assert_eq!(
+ get_cipher_suite_name(0xc077),
+ Some("TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384")
+ );
+ }
+}
diff --git a/crates/rustnet-core/src/network/dpi/dhcp.rs b/crates/rustnet-core/src/network/dpi/dhcp.rs
new file mode 100644
index 0000000..2c2c1d6
--- /dev/null
+++ b/crates/rustnet-core/src/network/dpi/dhcp.rs
@@ -0,0 +1,219 @@
+//! DHCP (Dynamic Host Configuration Protocol) Deep Packet Inspection
+//!
+//! Parses DHCP packets according to RFC 2131.
+//! DHCP uses UDP ports 67 (server) and 68 (client).
+
+use crate::network::types::{DhcpInfo, DhcpMessageType};
+
+/// Minimum DHCP packet size (fixed header + magic cookie)
+const MIN_DHCP_SIZE: usize = 240;
+
+/// DHCP magic cookie: 99.130.83.99 (0x63825363)
+const DHCP_MAGIC_COOKIE: [u8; 4] = [0x63, 0x82, 0x53, 0x63];
+
+/// DHCP option codes
+const DHCP_OPT_HOSTNAME: u8 = 12;
+const DHCP_OPT_MESSAGE_TYPE: u8 = 53;
+const DHCP_OPT_END: u8 = 255;
+
+/// Analyze a DHCP packet and extract key information.
+///
+/// Returns `None` if the packet is too small or doesn't have the DHCP magic cookie.
+pub fn analyze_dhcp(payload: &[u8]) -> Option {
+ // Early size check
+ if payload.len() < MIN_DHCP_SIZE {
+ return None;
+ }
+
+ // Verify DHCP magic cookie at offset 236-239
+ if payload[236..240] != DHCP_MAGIC_COOKIE {
+ return None;
+ }
+
+ // Extract client MAC address from bytes 28-33 (chaddr field, first 6 bytes)
+ let client_mac = format_mac(&payload[28..34]);
+
+ // Parse DHCP options starting at byte 240
+ let (message_type, hostname) = parse_dhcp_options(&payload[240..])?;
+
+ Some(DhcpInfo {
+ message_type,
+ hostname,
+ client_mac: Some(client_mac),
+ })
+}
+
+/// Format a 6-byte MAC address as a string
+fn format_mac(bytes: &[u8]) -> String {
+ format!(
+ "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
+ bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5]
+ )
+}
+
+/// Parse DHCP options and extract message type and hostname
+fn parse_dhcp_options(options: &[u8]) -> Option<(DhcpMessageType, Option)> {
+ let mut message_type = None;
+ let mut hostname = None;
+ let mut offset = 0;
+
+ while offset < options.len() {
+ let opt_code = options[offset];
+
+ // Option 255 = End
+ if opt_code == DHCP_OPT_END {
+ break;
+ }
+
+ // Option 0 = Pad (no length)
+ if opt_code == 0 {
+ offset += 1;
+ continue;
+ }
+
+ // Need at least 1 more byte for length
+ if offset + 1 >= options.len() {
+ break;
+ }
+
+ let opt_len = options[offset + 1] as usize;
+
+ // Bounds check for option data
+ if offset + 2 + opt_len > options.len() {
+ break;
+ }
+
+ let opt_data = &options[offset + 2..offset + 2 + opt_len];
+
+ match opt_code {
+ DHCP_OPT_MESSAGE_TYPE if opt_len >= 1 => {
+ message_type = Some(parse_message_type(opt_data[0]));
+ }
+ DHCP_OPT_HOSTNAME => {
+ if let Ok(name) = std::str::from_utf8(opt_data) {
+ hostname = Some(name.to_string());
+ }
+ }
+ _ => {}
+ }
+
+ offset += 2 + opt_len;
+ }
+
+ // Message type is required
+ Some((message_type?, hostname))
+}
+
+/// Parse DHCP message type from option 53 value
+fn parse_message_type(value: u8) -> DhcpMessageType {
+ match value {
+ 1 => DhcpMessageType::Discover,
+ 2 => DhcpMessageType::Offer,
+ 3 => DhcpMessageType::Request,
+ 4 => DhcpMessageType::Decline,
+ 5 => DhcpMessageType::Ack,
+ 6 => DhcpMessageType::Nak,
+ 7 => DhcpMessageType::Release,
+ 8 => DhcpMessageType::Inform,
+ other => DhcpMessageType::Unknown(other),
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn build_dhcp_packet(msg_type: u8, hostname: Option<&str>, mac: &[u8; 6]) -> Vec {
+ let mut packet = vec![0u8; 240];
+
+ // Set client MAC at offset 28
+ packet[28..34].copy_from_slice(mac);
+
+ // Set magic cookie at offset 236
+ packet[236..240].copy_from_slice(&DHCP_MAGIC_COOKIE);
+
+ // Add options
+ // Option 53: DHCP Message Type
+ packet.push(DHCP_OPT_MESSAGE_TYPE);
+ packet.push(1); // length
+ packet.push(msg_type);
+
+ // Option 12: Hostname (if provided)
+ if let Some(name) = hostname {
+ packet.push(DHCP_OPT_HOSTNAME);
+ packet.push(name.len() as u8);
+ packet.extend_from_slice(name.as_bytes());
+ }
+
+ // Option 255: End
+ packet.push(DHCP_OPT_END);
+
+ packet
+ }
+
+ #[test]
+ fn test_dhcp_discover() {
+ let mac = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff];
+ let packet = build_dhcp_packet(1, Some("myhost"), &mac);
+ let info = analyze_dhcp(&packet).expect("should parse");
+ assert_eq!(info.message_type, DhcpMessageType::Discover);
+ assert_eq!(info.hostname, Some("myhost".to_string()));
+ assert_eq!(info.client_mac, Some("aa:bb:cc:dd:ee:ff".to_string()));
+ }
+
+ #[test]
+ fn test_dhcp_offer() {
+ let mac = [0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
+ let packet = build_dhcp_packet(2, None, &mac);
+ let info = analyze_dhcp(&packet).expect("should parse");
+ assert_eq!(info.message_type, DhcpMessageType::Offer);
+ assert!(info.hostname.is_none());
+ }
+
+ #[test]
+ fn test_dhcp_request() {
+ let mac = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55];
+ let packet = build_dhcp_packet(3, Some("workstation-01"), &mac);
+ let info = analyze_dhcp(&packet).expect("should parse");
+ assert_eq!(info.message_type, DhcpMessageType::Request);
+ assert_eq!(info.hostname, Some("workstation-01".to_string()));
+ }
+
+ #[test]
+ fn test_dhcp_ack() {
+ let mac = [0x00, 0x00, 0x00, 0x00, 0x00, 0x01];
+ let packet = build_dhcp_packet(5, None, &mac);
+ let info = analyze_dhcp(&packet).expect("should parse");
+ assert_eq!(info.message_type, DhcpMessageType::Ack);
+ }
+
+ #[test]
+ fn test_dhcp_too_short() {
+ let packet = vec![0u8; 100];
+ assert!(analyze_dhcp(&packet).is_none());
+ }
+
+ #[test]
+ fn test_dhcp_bad_magic_cookie() {
+ let mut packet = vec![0u8; 250];
+ packet[236..240].copy_from_slice(&[0x00, 0x00, 0x00, 0x00]); // Wrong cookie
+ assert!(analyze_dhcp(&packet).is_none());
+ }
+
+ #[test]
+ fn test_dhcp_release() {
+ let mac = [0xde, 0xad, 0xbe, 0xef, 0x00, 0x01];
+ let packet = build_dhcp_packet(7, None, &mac);
+ let info = analyze_dhcp(&packet).expect("should parse");
+ assert_eq!(info.message_type, DhcpMessageType::Release);
+ }
+
+ #[test]
+ fn test_dhcp_inform() {
+ let mac = [0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc];
+ let packet = build_dhcp_packet(8, Some("printer"), &mac);
+ let info = analyze_dhcp(&packet).expect("should parse");
+ assert_eq!(info.message_type, DhcpMessageType::Inform);
+ assert_eq!(info.hostname, Some("printer".to_string()));
+ }
+}
diff --git a/crates/rustnet-core/src/network/dpi/dns.rs b/crates/rustnet-core/src/network/dpi/dns.rs
new file mode 100644
index 0000000..8cd4cf7
--- /dev/null
+++ b/crates/rustnet-core/src/network/dpi/dns.rs
@@ -0,0 +1,661 @@
+use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
+
+use crate::network::types::{DnsInfo, DnsQueryType};
+
+/// Maximum DNS name length per RFC 1035 section 2.3.4
+const MAX_DNS_NAME_LEN: usize = 253;
+
+/// Cap on how many answer records we will walk for a single packet. Real
+/// resolver answers are well under this; the bound is here to keep a
+/// malformed `ancount` from spinning the parser.
+const MAX_ANSWERS_TO_PARSE: usize = 64;
+
+/// Cap on response IPs we surface per packet. The UI only renders a short
+/// list anyway, and the merge layer dedups across the flow, so anything
+/// beyond this is noise we'd rather drop than allocate for.
+const MAX_RESPONSE_IPS_PER_PACKET: usize = 16;
+
+/// Cap on how many pointer indirections we follow while skipping a name in
+/// the answer section. Per RFC 1035 these must not form cycles; this cap
+/// keeps a crafted packet from looping forever.
+const MAX_NAME_POINTER_HOPS: usize = 16;
+
+/// Walk a DNS name in the answer section and return the offset of the
+/// byte immediately after the name (where TYPE / CLASS / TTL / RDLENGTH
+/// start). Compression pointers (0xC0-prefixed two-byte sequences) terminate
+/// the name in-place, so the returned offset is two past the start of the
+/// pointer. Returns `None` if the name is malformed or runs off the end of
+/// the payload.
+fn skip_dns_name(payload: &[u8], start: usize) -> Option {
+ let mut offset = start;
+ let mut hops = 0;
+ loop {
+ if offset >= payload.len() {
+ return None;
+ }
+ let label_len = payload[offset] as usize;
+ if label_len == 0 {
+ return Some(offset + 1);
+ }
+ if label_len & 0xC0 == 0xC0 {
+ // Pointer: two bytes total, name ends here at the call site.
+ if offset + 1 >= payload.len() {
+ return None;
+ }
+ hops += 1;
+ if hops > MAX_NAME_POINTER_HOPS {
+ return None;
+ }
+ return Some(offset + 2);
+ }
+ // Reject reserved length-octet top bits (0x40 / 0x80) — neither
+ // standard label nor pointer.
+ if label_len & 0xC0 != 0 {
+ return None;
+ }
+ let next = offset.checked_add(1)?.checked_add(label_len)?;
+ if next > payload.len() {
+ return None;
+ }
+ offset = next;
+ }
+}
+
+/// Parse a single question section and return `(query_name, query_type,
+/// offset_after_question)`. The offset advances past QNAME + QTYPE (2) +
+/// QCLASS (2); if QTYPE / QCLASS run off the end the offset still moves to
+/// keep skip-only callers (qdcount > 1) bounds-safe.
+fn parse_question(payload: &[u8], start: usize) -> (Option, Option, usize) {
+ let mut offset = start;
+ let mut name = String::new();
+ let mut name_over_limit = false;
+
+ // Parse domain name (label-by-label, with light pointer handling — we
+ // only need to terminate the walk, not fully resolve compressed labels).
+ while offset < payload.len() {
+ let label_len = payload[offset] as usize;
+ if label_len == 0 {
+ offset += 1;
+ break;
+ }
+
+ if label_len & 0xC0 == 0xC0 {
+ // Compressed name — skip for simplicity.
+ offset += 2;
+ break;
+ }
+
+ // Reject reserved length-octet top bits (0x40 / 0x80) — neither a
+ // standard label nor a pointer (RFC 1035 §3.3). Stop the walk so the
+ // invalid bytes are not pulled into the name, matching skip_dns_name.
+ if label_len & 0xC0 != 0 {
+ break;
+ }
+
+ if offset + 1 + label_len > payload.len() {
+ break;
+ }
+
+ if !name_over_limit {
+ if !name.is_empty() {
+ name.push('.');
+ }
+
+ if let Ok(label) = std::str::from_utf8(&payload[offset + 1..offset + 1 + label_len]) {
+ name.push_str(label);
+ }
+
+ // Enforce RFC 1035 maximum name length: stop accumulating, but
+ // keep walking the remaining labels so `offset` ends up past the
+ // whole QNAME — otherwise QTYPE/QCLASS would be read from name
+ // bytes and report a fabricated query type.
+ if name.len() > MAX_DNS_NAME_LEN {
+ name_over_limit = true;
+ }
+ }
+
+ offset += 1 + label_len;
+ }
+
+ let query_name = if name.is_empty() { None } else { Some(name) };
+
+ let mut query_type = None;
+ if offset + 2 <= payload.len() {
+ let qtype = u16::from_be_bytes([payload[offset], payload[offset + 1]]);
+ query_type = Some(match qtype {
+ 1 => DnsQueryType::A,
+ 2 => DnsQueryType::NS,
+ 5 => DnsQueryType::CNAME,
+ 6 => DnsQueryType::SOA,
+ 12 => DnsQueryType::PTR,
+ 13 => DnsQueryType::HINFO,
+ 15 => DnsQueryType::MX,
+ 16 => DnsQueryType::TXT,
+ 17 => DnsQueryType::RP,
+ 18 => DnsQueryType::AFSDB,
+ 24 => DnsQueryType::SIG,
+ 25 => DnsQueryType::KEY,
+ 28 => DnsQueryType::AAAA,
+ 29 => DnsQueryType::LOC,
+ 33 => DnsQueryType::SRV,
+ 35 => DnsQueryType::NAPTR,
+ 36 => DnsQueryType::KX,
+ 37 => DnsQueryType::CERT,
+ 39 => DnsQueryType::DNAME,
+ 42 => DnsQueryType::APL,
+ 43 => DnsQueryType::DS,
+ 44 => DnsQueryType::SSHFP,
+ 45 => DnsQueryType::IPSECKEY,
+ 46 => DnsQueryType::RRSIG,
+ 47 => DnsQueryType::NSEC,
+ 48 => DnsQueryType::DNSKEY,
+ 49 => DnsQueryType::DHCID,
+ 50 => DnsQueryType::NSEC3,
+ 51 => DnsQueryType::NSEC3PARAM,
+ 52 => DnsQueryType::TLSA,
+ 53 => DnsQueryType::SMIMEA,
+ 55 => DnsQueryType::HIP,
+ 59 => DnsQueryType::CDS,
+ 60 => DnsQueryType::CDNSKEY,
+ 61 => DnsQueryType::OPENPGPKEY,
+ 62 => DnsQueryType::CSYNC,
+ 63 => DnsQueryType::ZONEMD,
+ 64 => DnsQueryType::SVCB,
+ 65 => DnsQueryType::HTTPS,
+ 108 => DnsQueryType::EUI48,
+ 109 => DnsQueryType::EUI64,
+ 249 => DnsQueryType::TKEY,
+ 250 => DnsQueryType::TSIG,
+ 256 => DnsQueryType::URI,
+ 257 => DnsQueryType::CAA,
+ 32768 => DnsQueryType::TA,
+ 32769 => DnsQueryType::DLV,
+ other => DnsQueryType::Other(other),
+ });
+ }
+ // Advance past QTYPE (2) and QCLASS (2). If they run past the payload,
+ // downstream walks' bounds checks will short-circuit cleanly.
+ offset = offset.saturating_add(4);
+
+ (query_name, query_type, offset)
+}
+
+/// Walk `count` resource records starting at `offset`, pushing A / AAAA
+/// rdata into `ips` (subject to [`MAX_RESPONSE_IPS_PER_PACKET`]). Returns
+/// the offset of the byte immediately after the last record successfully
+/// walked, so callers can chain a second walk (e.g. ANCOUNT then ARCOUNT
+/// for mDNS).
+fn walk_a_aaaa_records(payload: &[u8], start: usize, count: usize, ips: &mut Vec) -> usize {
+ let mut offset = start;
+ let count = count.min(MAX_ANSWERS_TO_PARSE);
+ for _ in 0..count {
+ let after_name = match skip_dns_name(payload, offset) {
+ Some(o) => o,
+ None => return offset,
+ };
+ // Fixed-size RR fields: TYPE (2) + CLASS (2) + TTL (4) + RDLENGTH (2) = 10.
+ if after_name
+ .checked_add(10)
+ .map(|e| e > payload.len())
+ .unwrap_or(true)
+ {
+ return offset;
+ }
+ let atype = u16::from_be_bytes([payload[after_name], payload[after_name + 1]]);
+ let rdlength =
+ u16::from_be_bytes([payload[after_name + 8], payload[after_name + 9]]) as usize;
+ let rdata_start = after_name + 10;
+ let rdata_end = match rdata_start.checked_add(rdlength) {
+ Some(e) if e <= payload.len() => e,
+ _ => return offset,
+ };
+
+ if ips.len() < MAX_RESPONSE_IPS_PER_PACKET {
+ match (atype, rdlength) {
+ (1, 4) => {
+ let octets: [u8; 4] = payload[rdata_start..rdata_end]
+ .try_into()
+ .expect("rdlength==4 and bounds checked above");
+ ips.push(IpAddr::V4(Ipv4Addr::from(octets)));
+ }
+ (28, 16) => {
+ let octets: [u8; 16] = payload[rdata_start..rdata_end]
+ .try_into()
+ .expect("rdlength==16 and bounds checked above");
+ ips.push(IpAddr::V6(Ipv6Addr::from(octets)));
+ }
+ _ => {
+ // CNAME, NS, SOA, PTR, SRV, TXT, … — not surfaced.
+ }
+ }
+ }
+
+ offset = rdata_end;
+ }
+ offset
+}
+
+/// Parsed DNS-shaped header counts. Surfaced as a thin internal helper so
+/// the mDNS / LLMNR wrappers can also reach ARCOUNT without re-decoding.
+pub(super) struct DnsHeaderCounts {
+ pub is_response: bool,
+ pub qdcount: u16,
+ pub ancount: u16,
+ pub arcount: u16,
+}
+
+/// Decode the four 16-bit counts at the start of a DNS-shaped header.
+pub(super) fn dns_header_counts(payload: &[u8]) -> Option {
+ if payload.len() < 12 {
+ return None;
+ }
+ let flags = u16::from_be_bytes([payload[2], payload[3]]);
+ Some(DnsHeaderCounts {
+ is_response: (flags & 0x8000) != 0,
+ qdcount: u16::from_be_bytes([payload[4], payload[5]]),
+ ancount: u16::from_be_bytes([payload[6], payload[7]]),
+ arcount: u16::from_be_bytes([payload[10], payload[11]]),
+ })
+}
+
+/// Walk `qdcount` question sections starting at offset 12 and return the
+/// `(query_name, query_type, offset_after_all_questions)` triple. The name
+/// and type are taken from the **first** question (matching prior behaviour
+/// and the DNS / mDNS / LLMNR convention of one question per packet);
+/// subsequent questions are skipped only so the answer-walk starts at the
+/// correct offset (#333: multi-question packets used to leave the offset
+/// misaligned, which could surface bogus IPs from the answer walk).
+pub(super) fn parse_questions_starting_at_header(
+ payload: &[u8],
+ qdcount: u16,
+) -> (Option, Option, usize) {
+ let mut offset = 12;
+ let mut query_name = None;
+ let mut query_type = None;
+ for i in 0..qdcount {
+ let (name, qtype, next) = parse_question(payload, offset);
+ if i == 0 {
+ query_name = name;
+ query_type = qtype;
+ }
+ offset = next;
+ if offset > payload.len() {
+ break;
+ }
+ }
+ (query_name, query_type, offset)
+}
+
+pub fn analyze_dns(payload: &[u8]) -> Option {
+ let header = dns_header_counts(payload)?;
+ let (query_name, query_type, mut offset) =
+ parse_questions_starting_at_header(payload, header.qdcount);
+
+ let mut info = DnsInfo {
+ query_name,
+ query_type,
+ response_ips: Vec::new(),
+ is_response: header.is_response,
+ };
+
+ // Answer-section walk for A / AAAA records. Only runs on responses
+ // (QR bit set), since `DnsInfo.response_ips` is only meaningful for
+ // resolver answers.
+ if header.is_response {
+ offset = walk_a_aaaa_records(
+ payload,
+ offset,
+ header.ancount as usize,
+ &mut info.response_ips,
+ );
+ // `offset` is now positioned for callers that want to keep walking
+ // (e.g. mDNS's additional-records pass — see `analyze_dns_for_mdns`).
+ let _ = offset;
+ }
+
+ Some(info)
+}
+
+/// mDNS-specific variant of [`analyze_dns`]: like the DNS path but also
+/// walks the **additional records** (ARCOUNT) and tolerates packets with
+/// `qdcount == 0` (RFC 6762 §6 — typical mDNS announcements carry only
+/// answers / additionals, no questions). The DNS / LLMNR path keeps the
+/// stricter behaviour because their responses always echo the question.
+pub(super) fn analyze_dns_for_mdns(payload: &[u8]) -> Option {
+ let header = dns_header_counts(payload)?;
+ let (query_name, query_type, mut offset) =
+ parse_questions_starting_at_header(payload, header.qdcount);
+
+ let mut info = DnsInfo {
+ query_name,
+ query_type,
+ response_ips: Vec::new(),
+ is_response: header.is_response,
+ };
+
+ if header.is_response {
+ offset = walk_a_aaaa_records(
+ payload,
+ offset,
+ header.ancount as usize,
+ &mut info.response_ips,
+ );
+ // RFC 6762: mDNS responses frequently place A / AAAA in the
+ // ADDITIONAL section (NSEC / negative responses, "known-answer
+ // suppression"-related additionals, etc.). Skip NSCOUNT (the
+ // authority section) records before reaching ADDITIONAL.
+ if let Some(after_authority) = skip_records(payload, offset, header_nscount(payload)) {
+ let _ = walk_a_aaaa_records(
+ payload,
+ after_authority,
+ header.arcount as usize,
+ &mut info.response_ips,
+ );
+ }
+ }
+
+ Some(info)
+}
+
+/// Decode the NSCOUNT (authority-records count) at bytes 8-9 of the header.
+fn header_nscount(payload: &[u8]) -> u16 {
+ if payload.len() < 12 {
+ return 0;
+ }
+ u16::from_be_bytes([payload[8], payload[9]])
+}
+
+/// Skip `count` resource records starting at `offset` without collecting
+/// rdata. Returns `None` on a malformed record so callers can stop chasing
+/// trailing sections (e.g. don't walk ADDITIONAL if AUTHORITY is busted).
+fn skip_records(payload: &[u8], start: usize, count: u16) -> Option {
+ let mut offset = start;
+ let count = (count as usize).min(MAX_ANSWERS_TO_PARSE);
+ for _ in 0..count {
+ let after_name = skip_dns_name(payload, offset)?;
+ if after_name.checked_add(10)? > payload.len() {
+ return None;
+ }
+ let rdlength =
+ u16::from_be_bytes([payload[after_name + 8], payload[after_name + 9]]) as usize;
+ let rdata_end = after_name.checked_add(10)?.checked_add(rdlength)?;
+ if rdata_end > payload.len() {
+ return None;
+ }
+ offset = rdata_end;
+ }
+ Some(offset)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_empty_payload_safe() {
+ assert!(analyze_dns(&[]).is_none());
+ }
+
+ #[test]
+ fn test_short_payload_safe() {
+ assert!(analyze_dns(&[0; 5]).is_none());
+ }
+
+ #[test]
+ fn test_dns_name_length_limit() {
+ // Build a DNS packet with many 63-byte labels (exceeding 253 chars)
+ let mut payload = vec![0u8; 12]; // DNS header
+ // Set qdcount = 1
+ payload[5] = 1;
+ // Add 10 labels of 63 bytes each (630+ chars total, exceeds 253)
+ for _ in 0..10 {
+ payload.push(63); // label length
+ payload.extend_from_slice(&[b'a'; 63]);
+ }
+ payload.push(0); // null terminator
+ payload.extend_from_slice(&[0, 1, 0, 1]); // QTYPE A, QCLASS IN
+
+ let info = analyze_dns(&payload).unwrap();
+ if let Some(name) = &info.query_name {
+ // Name should be truncated near the RFC limit, not the full 630+ chars
+ assert!(name.len() <= MAX_DNS_NAME_LEN + 63 + 1);
+ }
+ // The walk must still consume the whole QNAME so QTYPE is read from
+ // the right offset — not fabricated from mid-name bytes.
+ assert_eq!(info.query_type, Some(DnsQueryType::A));
+ }
+
+ #[test]
+ fn test_normal_dns_query() {
+ // Build a simple query for "example.com"
+ let mut payload = vec![0u8; 12]; // DNS header
+ payload[5] = 1; // qdcount = 1
+ // "example" label
+ payload.push(7);
+ payload.extend_from_slice(b"example");
+ // "com" label
+ payload.push(3);
+ payload.extend_from_slice(b"com");
+ // null terminator
+ payload.push(0);
+ // QTYPE A (1), QCLASS IN (1)
+ payload.extend_from_slice(&[0, 1, 0, 1]);
+
+ let info = analyze_dns(&payload).unwrap();
+ assert_eq!(info.query_name, Some("example.com".to_string()));
+ assert_eq!(info.query_type, Some(DnsQueryType::A));
+ assert!(!info.is_response);
+ }
+
+ #[test]
+ fn test_question_rejects_reserved_label_bits() {
+ // A label octet whose top two bits are 01 (0x40) or 10 (0x80) is neither
+ // a standard label nor a compression pointer (RFC 1035 §3.3). The name
+ // walk must stop at it instead of reading it as a 64+ byte label and
+ // pulling the following bytes into query_name. skip_dns_name already
+ // rejects these; parse_question must be consistent.
+ let mut payload = vec![0u8; 12];
+ payload[5] = 1; // qdcount = 1
+ // valid "abc" label
+ payload.push(3);
+ payload.extend_from_slice(b"abc");
+ // reserved-bit label octet (0x40) followed by 64 bytes that must not be
+ // absorbed into the name
+ payload.push(0x40);
+ payload.extend_from_slice(&[b'x'; 64]);
+ // null terminator + QTYPE A / QCLASS IN
+ payload.push(0);
+ payload.extend_from_slice(&[0, 1, 0, 1]);
+
+ let info = analyze_dns(&payload).unwrap();
+ // Only the valid label before the reserved octet is kept.
+ assert_eq!(info.query_name, Some("abc".to_string()));
+ }
+
+ /// Helper: build a baseline question-section payload for `example.com / A`.
+ /// Returns the payload and the offset that points at the byte right after
+ /// the question section, where answer records start.
+ fn make_example_question(qr_bit: bool, ancount: u16) -> (Vec, usize) {
+ let mut payload = vec![0u8; 12];
+ // Flags: QR bit when this is a response
+ if qr_bit {
+ payload[2] = 0x80;
+ }
+ // qdcount = 1
+ payload[4] = 0;
+ payload[5] = 1;
+ // ancount
+ payload[6..8].copy_from_slice(&ancount.to_be_bytes());
+ // "example"
+ payload.push(7);
+ payload.extend_from_slice(b"example");
+ // "com"
+ payload.push(3);
+ payload.extend_from_slice(b"com");
+ // null terminator
+ payload.push(0);
+ // QTYPE A (1), QCLASS IN (1)
+ payload.extend_from_slice(&[0, 1, 0, 1]);
+ let answers_start = payload.len();
+ (payload, answers_start)
+ }
+
+ #[test]
+ fn test_response_with_single_a_record_populates_ip() {
+ // Response with one A record pointing to 93.184.216.34 (the public
+ // example.com address). The answer name uses a compression pointer
+ // back to the question, which is the common wire shape.
+ let (mut payload, _answers_start) = make_example_question(true, 1);
+
+ // NAME: pointer to offset 12 (start of question name).
+ payload.extend_from_slice(&[0xC0, 0x0C]);
+ // TYPE A, CLASS IN, TTL 60, RDLENGTH 4
+ payload.extend_from_slice(&[0, 1, 0, 1, 0, 0, 0, 60, 0, 4]);
+ // RDATA: 93.184.216.34
+ payload.extend_from_slice(&[93, 184, 216, 34]);
+
+ let info = analyze_dns(&payload).unwrap();
+ assert!(info.is_response);
+ assert_eq!(
+ info.response_ips,
+ vec![IpAddr::V4(Ipv4Addr::new(93, 184, 216, 34))]
+ );
+ }
+
+ #[test]
+ fn test_response_with_aaaa_record_populates_ipv6() {
+ let (mut payload, _) = make_example_question(true, 1);
+ payload.extend_from_slice(&[0xC0, 0x0C]); // pointer to question name
+ // TYPE AAAA (28), CLASS IN, TTL 60, RDLENGTH 16
+ payload.extend_from_slice(&[0, 28, 0, 1, 0, 0, 0, 60, 0, 16]);
+ payload.extend_from_slice(&[
+ 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01,
+ ]);
+
+ let info = analyze_dns(&payload).unwrap();
+ assert_eq!(info.response_ips.len(), 1);
+ assert!(matches!(info.response_ips[0], IpAddr::V6(_)));
+ }
+
+ #[test]
+ fn test_response_mixed_records_collects_a_and_aaaa_skips_cname() {
+ // Three answers: CNAME (skipped — not surfaced via response_ips),
+ // A, AAAA. Order matters; the parser must walk past CNAME's
+ // rdata correctly to reach the IP records.
+ let (mut payload, _) = make_example_question(true, 3);
+
+ // 1) CNAME record. RDATA = pointer to "example.com" at offset 12 (2 bytes).
+ payload.extend_from_slice(&[0xC0, 0x0C]); // NAME
+ payload.extend_from_slice(&[0, 5, 0, 1, 0, 0, 0, 60, 0, 2]); // TYPE CNAME (5), CLASS IN, TTL, RDLENGTH 2
+ payload.extend_from_slice(&[0xC0, 0x0C]); // RDATA: compressed name pointer
+
+ // 2) A record 1.2.3.4
+ payload.extend_from_slice(&[0xC0, 0x0C]);
+ payload.extend_from_slice(&[0, 1, 0, 1, 0, 0, 0, 60, 0, 4]);
+ payload.extend_from_slice(&[1, 2, 3, 4]);
+
+ // 3) AAAA record ::1
+ payload.extend_from_slice(&[0xC0, 0x0C]);
+ payload.extend_from_slice(&[0, 28, 0, 1, 0, 0, 0, 60, 0, 16]);
+ payload.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
+
+ let info = analyze_dns(&payload).unwrap();
+ assert_eq!(
+ info.response_ips,
+ vec![
+ IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)),
+ IpAddr::V6(Ipv6Addr::LOCALHOST),
+ ]
+ );
+ }
+
+ #[test]
+ fn test_query_packet_leaves_response_ips_empty() {
+ // QR bit clear ⇒ this is a question, not a response. Even if a
+ // (malformed) packet stuffs answer-shaped bytes after the question,
+ // the parser must not surface any IPs because the field is only
+ // meaningful for resolver answers.
+ let (mut payload, _) = make_example_question(false, 1);
+ payload.extend_from_slice(&[0xC0, 0x0C]);
+ payload.extend_from_slice(&[0, 1, 0, 1, 0, 0, 0, 60, 0, 4]);
+ payload.extend_from_slice(&[8, 8, 8, 8]);
+
+ let info = analyze_dns(&payload).unwrap();
+ assert!(!info.is_response);
+ assert!(info.response_ips.is_empty());
+ }
+
+ #[test]
+ fn test_truncated_rdata_does_not_panic() {
+ // Claims RDLENGTH 4 but only supplies 2 bytes of rdata. The walk
+ // must stop cleanly, not panic on the slice access.
+ let (mut payload, _) = make_example_question(true, 1);
+ payload.extend_from_slice(&[0xC0, 0x0C]);
+ payload.extend_from_slice(&[0, 1, 0, 1, 0, 0, 0, 60, 0, 4]);
+ payload.extend_from_slice(&[1, 2]); // only 2 bytes of rdata
+
+ let info = analyze_dns(&payload).unwrap();
+ assert!(info.response_ips.is_empty());
+ }
+
+ #[test]
+ fn test_multi_question_offset_lands_on_first_answer() {
+ // Two questions, one answer (A 1.2.3.4) for question 1.
+ // Pre-#333 only the first question was skipped, so the answer-walk
+ // offset would land inside the second question's bytes — usually
+ // either bailing out via skip_dns_name or, worse, surfacing bogus
+ // IPs. With multi-question skipping the parser must land on the
+ // real answer and return exactly one IP.
+ let mut payload = vec![0u8; 12];
+ payload[2] = 0x80; // QR bit
+ payload[5] = 2; // qdcount = 2
+ payload[7] = 1; // ancount = 1
+ // Q1: "example.com" / A
+ payload.push(7);
+ payload.extend_from_slice(b"example");
+ payload.push(3);
+ payload.extend_from_slice(b"com");
+ payload.push(0);
+ payload.extend_from_slice(&[0, 1, 0, 1]);
+ // Q2: "test.net" / AAAA
+ payload.push(4);
+ payload.extend_from_slice(b"test");
+ payload.push(3);
+ payload.extend_from_slice(b"net");
+ payload.push(0);
+ payload.extend_from_slice(&[0, 28, 0, 1]);
+ // Answer: pointer to Q1 name, A, IN, TTL 60, RDLENGTH 4, 1.2.3.4.
+ payload.extend_from_slice(&[0xC0, 0x0C]);
+ payload.extend_from_slice(&[0, 1, 0, 1, 0, 0, 0, 60, 0, 4]);
+ payload.extend_from_slice(&[1, 2, 3, 4]);
+
+ let info = analyze_dns(&payload).unwrap();
+ // First question wins for query_name / query_type — matches the
+ // single-question convention. The two-question hardening is purely
+ // about answer-walk offset correctness.
+ assert_eq!(info.query_name, Some("example.com".to_string()));
+ assert_eq!(info.query_type, Some(DnsQueryType::A));
+ assert_eq!(
+ info.response_ips,
+ vec![IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4))]
+ );
+ }
+
+ #[test]
+ fn test_response_ips_capped_per_packet() {
+ // Build a response with more A records than the per-packet cap.
+ // The parser must surface at most MAX_RESPONSE_IPS_PER_PACKET IPs
+ // even when ancount and the wire payload are both larger.
+ let n: u16 = (MAX_RESPONSE_IPS_PER_PACKET as u16) + 4;
+ let (mut payload, _) = make_example_question(true, n);
+ for i in 0..n {
+ payload.extend_from_slice(&[0xC0, 0x0C]);
+ payload.extend_from_slice(&[0, 1, 0, 1, 0, 0, 0, 60, 0, 4]);
+ payload.extend_from_slice(&[10, 0, 0, (i & 0xFF) as u8]);
+ }
+
+ let info = analyze_dns(&payload).unwrap();
+ assert_eq!(info.response_ips.len(), MAX_RESPONSE_IPS_PER_PACKET);
+ }
+}
diff --git a/crates/rustnet-core/src/network/dpi/ftp.rs b/crates/rustnet-core/src/network/dpi/ftp.rs
new file mode 100644
index 0000000..ae3ca29
--- /dev/null
+++ b/crates/rustnet-core/src/network/dpi/ftp.rs
@@ -0,0 +1,367 @@
+//! FTP (File Transfer Protocol) Deep Packet Inspection
+//!
+//! Parses the plaintext FTP control channel (RFC 959, RFC 2389, RFC 2428).
+//! Detection is keyed off port 21 plus a cheap start-line signature so non-
+//! standard ports are still caught. The data channel (port 20 / passive) is
+//! deliberately not inspected — payloads are arbitrary file bytes.
+
+use crate::network::types::{FtpInfo, FtpMessageType};
+
+/// Maximum bytes we ever scan to decide whether a payload looks like FTP.
+const MAX_SNIFF_BYTES: usize = 1024;
+
+/// Commands defined by RFC 959 / 2389 / 2428 / 4217. Matched case-insensitively
+/// against the first whitespace-delimited token on the first line of the
+/// payload.
+const FTP_COMMANDS: &[&str] = &[
+ // RFC 959 access control
+ "USER", "PASS", "ACCT", "CWD", "CDUP", "SMNT", "QUIT", "REIN",
+ // RFC 959 transfer parameters
+ "PORT", "PASV", "TYPE", "STRU", "MODE", // RFC 959 service
+ "RETR", "STOR", "STOU", "APPE", "ALLO", "REST", "RNFR", "RNTO", "ABOR", "DELE", "RMD", "MKD",
+ "PWD", "LIST", "NLST", "SITE", "SYST", "STAT", "HELP", "NOOP",
+ // RFC 2389 (FEAT/OPTS)
+ "FEAT", "OPTS", // RFC 2428 (extended passive / port for IPv6)
+ "EPSV", "EPRT", // RFC 4217 (FTP over TLS)
+ "AUTH", "PBSZ", "PROT", "CCC", // RFC 3659 (size/mdtm/mlsd)
+ "SIZE", "MDTM", "MLSD", "MLST",
+];
+
+/// Commands accepted by the port-independent signature check. This is the
+/// subset of [`FTP_COMMANDS`] that no other common line-based protocol
+/// shares: SMTP claims `AUTH`/`QUIT`/`NOOP`/`HELP`, POP3 claims
+/// `USER`/`PASS`/`STAT`/`LIST`/`RETR`/`DELE`, and NNTP claims `MODE`, so
+/// matching those off port 21 would label mail and news flows as FTP.
+const FTP_DISTINCTIVE_COMMANDS: &[&str] = &[
+ "ACCT", "CWD", "CDUP", "SMNT", "REIN", "PORT", "PASV", "TYPE", "STRU", "STOR", "STOU", "APPE",
+ "ALLO", "REST", "RNFR", "RNTO", "ABOR", "RMD", "MKD", "PWD", "NLST", "SITE", "SYST", "FEAT",
+ "OPTS", "EPSV", "EPRT", "PBSZ", "PROT", "CCC", "SIZE", "MDTM", "MLSD", "MLST",
+];
+
+/// Cheap heuristic that returns `true` when a payload's first line plausibly
+/// belongs to the FTP control channel. Used so non-standard-port flows can
+/// still be classified.
+///
+/// Only distinctively-FTP commands count as a signature. Server replies
+/// (`220 `) are deliberately NOT matched here: the 3-digit reply
+/// grammar is shared verbatim by SMTP, POP3-era protocols, and NNTP, so
+/// off port 21 a reply line carries no FTP signal. Replies are still parsed
+/// by [`analyze_ftp`] on the port-21 path.
+pub fn is_ftp(payload: &[u8]) -> bool {
+ let line = first_line(payload);
+ if line.is_empty() {
+ return false;
+ }
+ let upper = first_token_upper(line);
+ if upper.is_empty() {
+ return false;
+ }
+ FTP_DISTINCTIVE_COMMANDS
+ .iter()
+ .any(|cmd| cmd.as_bytes() == upper)
+}
+
+/// Parse an FTP control-channel payload. Returns `None` when the payload does
+/// not look like FTP.
+pub fn analyze_ftp(payload: &[u8]) -> Option {
+ let line = first_line(payload);
+ if line.is_empty() {
+ return None;
+ }
+
+ // Server response branch: `CCC ` or `CCC-` where CCC is a
+ // 3-digit reply code. A trailing `-` is the RFC 959 §4.2 continuation
+ // marker, signalling that the payload is multi-line.
+ if line.len() >= 4
+ && line[0].is_ascii_digit()
+ && line[1].is_ascii_digit()
+ && line[2].is_ascii_digit()
+ && (line[3] == b' ' || line[3] == b'-')
+ {
+ let code = std::str::from_utf8(&line[0..3]).ok()?.parse::().ok()?;
+ // RFC 959 §4.2: the first digit is 1-5 and the second 0-5. Anything
+ // else ("999 hi") is not an FTP reply.
+ if !(1..=5).contains(&(code / 100)) || (code / 10) % 10 > 5 {
+ return None;
+ }
+ let is_continuation = line[3] == b'-';
+ let message = std::str::from_utf8(&line[4..])
+ .ok()
+ .map(|s| s.trim().to_string());
+
+ // Software / system-type extraction is skipped on continuation lines
+ // (`220-Welcome to the FTP service.\r\n220 ProFTPD ...\r\n`) because
+ // the first line is human-greeting prose. vsftpd, ProFTPD, and
+ // Pure-FTPd all emit multi-line greetings by default, so honouring
+ // the continuation marker is critical — without it we tag
+ // `server_software = "Welcome"` on most real servers.
+ let server_software = if code == 220 && !is_continuation {
+ // RFC 959 §5.4 — service-ready greetings typically embed the
+ // FTP server software in the first whitespace-delimited token
+ // (`220 ProFTPD 1.3.7 ...`).
+ message.as_deref().map(extract_software_token)
+ } else {
+ None
+ };
+ // RFC 959 §4.2 — code 215 carries the system / OS name (`UNIX`,
+ // `Windows_NT`), NOT the FTP server software. Keeping the two
+ // separate avoids labelling "UNIX" under "Server Software" in the
+ // TUI.
+ let system_type = if code == 215 && !is_continuation {
+ message.as_deref().map(extract_software_token)
+ } else {
+ None
+ };
+ return Some(FtpInfo {
+ message_type: FtpMessageType::Response,
+ command: None,
+ args: None,
+ response_code: Some(code),
+ response_message: message,
+ username: None,
+ server_software,
+ system_type,
+ });
+ }
+
+ // Client request branch.
+ let upper = first_token_upper(line);
+ if upper.is_empty() {
+ return None;
+ }
+ let is_command = FTP_COMMANDS.iter().any(|cmd| cmd.as_bytes() == upper);
+ if !is_command {
+ return None;
+ }
+ // `first_token_upper` already proved every byte is ASCII alphabetic, so
+ // the UTF-8 validation inside `String::from_utf8` is a fast linear walk
+ // that always succeeds — but the `?` stays as a defensive guard against
+ // a future change that widens the accepted byte range. Taking the `Vec`
+ // by value avoids the redundant `.to_string()` copy that the previous
+ // `std::str::from_utf8(&upper).ok()?.to_string()` shape required.
+ let command = String::from_utf8(upper).ok()?;
+ // Trim leading command + whitespace to expose the argument.
+ let args = std::str::from_utf8(line)
+ .ok()
+ .map(|s| s.trim())
+ .and_then(|s| {
+ s.split_once(char::is_whitespace)
+ .map(|(_, rest)| rest.trim())
+ })
+ .filter(|s| !s.is_empty())
+ .map(|s| s.to_string());
+ // RFC 959 §5.4: `USER` carries the login name, useful as a per-flow
+ // identity hint (plaintext anyway — FTP-AUTH/TLS encrypts later).
+ let username = if command == "USER" {
+ args.clone()
+ } else {
+ None
+ };
+
+ Some(FtpInfo {
+ message_type: FtpMessageType::Request,
+ command: Some(command),
+ args,
+ response_code: None,
+ response_message: None,
+ username,
+ server_software: None,
+ system_type: None,
+ })
+}
+
+fn first_line(payload: &[u8]) -> &[u8] {
+ let sniff = &payload[..payload.len().min(MAX_SNIFF_BYTES)];
+ match sniff.iter().position(|&b| b == b'\n') {
+ Some(end) => {
+ // Strip trailing CR if present.
+ if end > 0 && sniff[end - 1] == b'\r' {
+ &sniff[..end - 1]
+ } else {
+ &sniff[..end]
+ }
+ }
+ None => sniff,
+ }
+}
+
+fn first_token_upper(line: &[u8]) -> Vec {
+ let token_end = line
+ .iter()
+ .position(|&b| b == b' ' || b == b'\t' || b == b'\r')
+ .unwrap_or(line.len());
+ let token = &line[..token_end];
+ if token.is_empty() || token.len() > 6 {
+ // FTP commands are 3-4 letters; cap at 6 to keep the cost of
+ // upper-casing tight on hostile traffic.
+ return Vec::new();
+ }
+ if !token.iter().all(|b| b.is_ascii_alphabetic()) {
+ return Vec::new();
+ }
+ token.iter().map(|b| b.to_ascii_uppercase()).collect()
+}
+
+fn extract_software_token(message: &str) -> String {
+ // The greeting line is free-form. Heuristic: take the first whitespace-
+ // delimited token that contains a letter, strip surrounding punctuation.
+ // Falls back to the full message when no clean token is found.
+ for token in message.split_whitespace() {
+ let trimmed = token.trim_matches(|c: char| !c.is_ascii_alphanumeric());
+ if trimmed.chars().any(|c| c.is_ascii_alphabetic()) {
+ return trimmed.to_string();
+ }
+ }
+ message.to_string()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn detects_server_greeting() {
+ // Replies are parsed on the port-21 path but are NOT a signature:
+ // the 3-digit grammar is shared with SMTP/POP3/NNTP.
+ let payload = b"220 ProFTPD 1.3.7 Server (Example) [::ffff:10.0.0.1]\r\n";
+ assert!(!is_ftp(payload));
+ let info = analyze_ftp(payload).expect("should parse");
+ assert!(matches!(info.message_type, FtpMessageType::Response));
+ assert_eq!(info.response_code, Some(220));
+ assert_eq!(info.server_software.as_deref(), Some("ProFTPD"));
+ }
+
+ #[test]
+ fn detects_continuation_response() {
+ let payload = b"220-Welcome to the FTP service.\r\n220 Ready.\r\n";
+ assert!(!is_ftp(payload));
+ let info = analyze_ftp(payload).expect("should parse");
+ assert_eq!(info.response_code, Some(220));
+ }
+
+ #[test]
+ fn signature_ignores_verbs_shared_with_mail_protocols() {
+ // SMTP traffic must not be classified as FTP off port 21.
+ assert!(!is_ftp(b"220 smtp.gmail.com ESMTP x1234\r\n"));
+ assert!(!is_ftp(b"EHLO mail.example.com\r\n"));
+ assert!(!is_ftp(b"AUTH LOGIN\r\n"));
+ // POP3 shares USER/PASS/RETR/LIST/DELE with FTP.
+ assert!(!is_ftp(b"USER alice\r\n"));
+ assert!(!is_ftp(b"RETR 1\r\n"));
+ // Distinctively-FTP commands still trigger the signature.
+ assert!(is_ftp(b"PASV\r\n"));
+ assert!(is_ftp(b"TYPE I\r\n"));
+ assert!(is_ftp(b"cwd /pub\r\n"));
+ }
+
+ #[test]
+ fn rejects_out_of_range_reply_codes() {
+ // RFC 959 reply codes are 1xx-5xx with second digit 0-5.
+ assert!(analyze_ftp(b"999 hello\r\n").is_none());
+ assert!(analyze_ftp(b"090 hello\r\n").is_none());
+ assert!(analyze_ftp(b"260-x\r\n").is_none());
+ }
+
+ #[test]
+ fn skips_software_extraction_on_220_continuation() {
+ // Default greeting on vsftpd / ProFTPD / Pure-FTPd is multi-line:
+ // the first line is `220-` continuation prose ("Welcome to..."),
+ // followed by `220 ` on a later line. We only see the
+ // first line at the DPI layer, so we must not pull "Welcome" out of
+ // it and label it as server software.
+ let payload = b"220-Welcome to the FTP service.\r\n220 ProFTPD 1.3.7\r\n";
+ let info = analyze_ftp(payload).expect("should parse");
+ assert_eq!(info.response_code, Some(220));
+ assert!(
+ info.server_software.is_none(),
+ "continuation lines must not populate server_software, got {:?}",
+ info.server_software
+ );
+ }
+
+ #[test]
+ fn parses_user_request() {
+ let payload = b"USER alice\r\n";
+ let info = analyze_ftp(payload).expect("should parse");
+ assert!(matches!(info.message_type, FtpMessageType::Request));
+ assert_eq!(info.command.as_deref(), Some("USER"));
+ assert_eq!(info.username.as_deref(), Some("alice"));
+ assert_eq!(info.args.as_deref(), Some("alice"));
+ }
+
+ #[test]
+ fn parses_retr_request() {
+ let payload = b"RETR /pub/file.iso\r\n";
+ let info = analyze_ftp(payload).expect("should parse");
+ assert_eq!(info.command.as_deref(), Some("RETR"));
+ assert_eq!(info.args.as_deref(), Some("/pub/file.iso"));
+ assert!(info.username.is_none());
+ }
+
+ #[test]
+ fn parses_noop_without_args() {
+ let payload = b"NOOP\r\n";
+ let info = analyze_ftp(payload).expect("should parse");
+ assert_eq!(info.command.as_deref(), Some("NOOP"));
+ assert!(info.args.is_none());
+ }
+
+ #[test]
+ fn parses_lowercase_command() {
+ let payload = b"quit\r\n";
+ let info = analyze_ftp(payload).expect("should parse");
+ assert_eq!(info.command.as_deref(), Some("QUIT"));
+ }
+
+ #[test]
+ fn parses_system_type_response() {
+ // RFC 959 §4.2: a `215` reply returns the OS / system type, NOT the
+ // FTP server software. Previously we tagged "UNIX" under
+ // `server_software`, which surfaced under the "Server Software"
+ // column in the TUI alongside greetings like "ProFTPD". They are
+ // different things; route 215 to a dedicated `system_type` field
+ // and confirm `server_software` stays unset for this reply.
+ let payload = b"215 UNIX Type: L8\r\n";
+ let info = analyze_ftp(payload).expect("should parse");
+ assert_eq!(info.response_code, Some(215));
+ assert!(
+ info.server_software.is_none(),
+ "215 must not populate server_software"
+ );
+ assert_eq!(info.system_type.as_deref(), Some("UNIX"));
+ }
+
+ #[test]
+ fn rejects_unknown_request() {
+ assert!(!is_ftp(b"FOOBAR baz\r\n"));
+ assert!(analyze_ftp(b"FOOBAR baz\r\n").is_none());
+ }
+
+ #[test]
+ fn rejects_http_payload() {
+ // OPTIONS is a real HTTP method; its argument must be a path, not a
+ // 3-digit token. The FTP detector should reject it.
+ let payload = b"GET /index.html HTTP/1.1\r\nHost: example.com\r\n\r\n";
+ assert!(!is_ftp(payload));
+ assert!(analyze_ftp(payload).is_none());
+ }
+
+ #[test]
+ fn rejects_short_response() {
+ assert!(!is_ftp(b"220"));
+ assert!(analyze_ftp(b"220").is_none());
+ }
+
+ #[test]
+ fn rejects_non_digit_prefix() {
+ // Looks like response prefix but first char is alphabetic.
+ assert!(!is_ftp(b"2X0 hello\r\n"));
+ }
+
+ #[test]
+ fn parses_epsv_extended_passive() {
+ let payload = b"EPSV\r\n";
+ let info = analyze_ftp(payload).expect("should parse");
+ assert_eq!(info.command.as_deref(), Some("EPSV"));
+ }
+}
diff --git a/crates/rustnet-core/src/network/dpi/http.rs b/crates/rustnet-core/src/network/dpi/http.rs
new file mode 100644
index 0000000..b26d052
--- /dev/null
+++ b/crates/rustnet-core/src/network/dpi/http.rs
@@ -0,0 +1,218 @@
+use crate::network::types::{HttpInfo, HttpVersion};
+
+/// Analyze payload for HTTP protocol
+pub fn analyze_http(payload: &[u8]) -> Option {
+ if !is_likely_http(payload) {
+ return None;
+ }
+
+ let mut info = HttpInfo {
+ version: HttpVersion::Http11,
+ method: None,
+ host: None,
+ path: None,
+ status_code: None,
+ user_agent: None,
+ };
+
+ // Safe string conversion for HTTP parsing
+ let text = String::from_utf8_lossy(payload);
+ // Drive the line iterator directly: the request/status line is the first
+ // element and headers are everything after it. Collecting into a `Vec<&str>`
+ // just to index `[0]` and `skip(1)` allocates one heap slice per parse.
+ let mut lines = text.lines();
+ let first_line = lines.next()?;
+ // Consume the first-line tokens lazily via iterator to avoid the
+ // `split_whitespace().collect::>()` heap allocation. Requests
+ // have exactly 3 SP-delimited tokens; responses have 2 or 3 since the
+ // reason phrase is optional (RFC 9112 §4).
+ let mut tokens = first_line.split_whitespace();
+ let (tok0, tok1) = match (tokens.next(), tokens.next()) {
+ (Some(a), Some(b)) => (a, b),
+ _ => return None,
+ };
+
+ if first_line.starts_with("HTTP/") {
+ // Response line: HTTP/1.1 200 OK — the reason phrase may be empty,
+ // but the status code must be a 3-digit number.
+ info.version = parse_http_version(tok0)?;
+ info.status_code = Some(
+ tok1.parse::()
+ .ok()
+ .filter(|c| (100..=599).contains(c))?,
+ );
+ } else if is_http_method(tok0) {
+ // Request line: GET /path HTTP/1.1. The version token is required:
+ // SIP and RTSP share the same verbs ("OPTIONS sip:bob@example.com
+ // SIP/2.0"), so a method match alone is not HTTP.
+ info.version = parse_http_version(tokens.next()?)?;
+ info.method = Some(tok0.to_string());
+ info.path = Some(tok1.to_string());
+ } else {
+ return None; // Not valid HTTP
+ }
+
+ // Parse headers. HTTP field-names are case-insensitive (RFC 7230 §3.2),
+ // so compare in place with `eq_ignore_ascii_case` instead of allocating a
+ // lowercased copy of every header name just to match two ASCII literals.
+ for line in lines {
+ if line.is_empty() {
+ break; // End of headers
+ }
+
+ if let Some((key, value)) = line.split_once(':') {
+ let key = key.trim();
+ let value = value.trim();
+
+ if key.eq_ignore_ascii_case("host") {
+ info.host = Some(value.to_string());
+ } else if key.eq_ignore_ascii_case("user-agent") {
+ info.user_agent = Some(value.to_string());
+ }
+ }
+ }
+
+ Some(info)
+}
+
+/// Quick check if payload might be HTTP
+fn is_likely_http(payload: &[u8]) -> bool {
+ if payload.len() < 4 {
+ return false;
+ }
+
+ // HTTP request methods
+ payload.starts_with(b"GET ") ||
+ payload.starts_with(b"POST ") ||
+ payload.starts_with(b"PUT ") ||
+ payload.starts_with(b"DELETE ") ||
+ payload.starts_with(b"HEAD ") ||
+ payload.starts_with(b"OPTIONS ") ||
+ payload.starts_with(b"CONNECT ") ||
+ payload.starts_with(b"TRACE ") ||
+ payload.starts_with(b"PATCH ") ||
+ // HTTP responses
+ payload.starts_with(b"HTTP/1.0 ") ||
+ payload.starts_with(b"HTTP/1.1 ") ||
+ payload.starts_with(b"HTTP/2 ")
+}
+
+fn is_http_method(s: &str) -> bool {
+ matches!(
+ s,
+ "GET" | "POST" | "PUT" | "DELETE" | "HEAD" | "OPTIONS" | "CONNECT" | "TRACE" | "PATCH"
+ )
+}
+
+fn parse_http_version(s: &str) -> Option {
+ match s {
+ "HTTP/1.0" => Some(HttpVersion::Http10),
+ "HTTP/1.1" => Some(HttpVersion::Http11),
+ "HTTP/2" => Some(HttpVersion::Http2),
+ _ => None,
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_http_request() {
+ let payload = b"GET /index.html HTTP/1.1\r\nHost: example.com\r\n\r\n";
+ let info = analyze_http(payload).unwrap();
+
+ assert_eq!(info.method.as_deref(), Some("GET"));
+ assert_eq!(info.path.as_deref(), Some("/index.html"));
+ assert_eq!(info.host.as_deref(), Some("example.com"));
+ }
+
+ #[test]
+ fn test_http_response() {
+ let payload = b"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n";
+ let info = analyze_http(payload).unwrap();
+
+ assert_eq!(info.status_code, Some(200));
+ assert!(info.method.is_none());
+ }
+
+ #[test]
+ fn test_http_start_line_token_extraction() {
+ // Lock the lazy-iterator token extraction: all three tokens from a
+ // well-formed request line must be parsed without a Vec allocation.
+ let req = b"POST /api/v1/upload HTTP/1.1\r\nHost: api.example.com\r\n\r\n";
+ let info = analyze_http(req).expect("POST request should parse");
+ assert_eq!(info.method.as_deref(), Some("POST"));
+ assert_eq!(info.path.as_deref(), Some("/api/v1/upload"));
+ assert_eq!(info.host.as_deref(), Some("api.example.com"));
+
+ // A truncated request line must be rejected: requests need all
+ // three tokens (method, target, version).
+ let truncated: [&[u8]; 2] = [
+ b"GET /index.html\r\n\r\n", // only 2 tokens
+ b"GET\r\n\r\n", // only 1 token
+ ];
+ for payload in &truncated {
+ assert!(
+ analyze_http(payload).is_none(),
+ "truncated start line should not parse: {:?}",
+ std::str::from_utf8(payload).unwrap_or("")
+ );
+ }
+ }
+
+ #[test]
+ fn test_http_response_without_reason_phrase() {
+ // RFC 9112 §4: the reason phrase is optional. Real servers emit
+ // `HTTP/1.1 200 \r\n` (empty phrase) and some omit the trailing
+ // space entirely; both are valid responses.
+ for payload in [b"HTTP/1.1 200\r\n\r\n".as_slice(), b"HTTP/1.1 200 \r\n\r\n"] {
+ let info = analyze_http(payload).expect("reason phrase is optional");
+ assert_eq!(info.status_code, Some(200));
+ }
+ }
+
+ #[test]
+ fn test_non_http_text_protocols_rejected() {
+ // SIP and RTSP share request verbs with HTTP; the version token
+ // must be validated so they are not misclassified.
+ let sip = b"OPTIONS sip:bob@example.com SIP/2.0\r\nVia: SIP/2.0/TCP host\r\n\r\n";
+ assert!(analyze_http(sip).is_none());
+
+ let rtsp = b"OPTIONS rtsp://cam.local/stream RTSP/1.0\r\nCSeq: 1\r\n\r\n";
+ assert!(analyze_http(rtsp).is_none());
+
+ // A response-shaped line with a non-numeric status is not HTTP.
+ assert!(analyze_http(b"HTTP/1.1 OK 200\r\n\r\n").is_none());
+ }
+
+ #[test]
+ fn test_http_mixed_case_host_and_user_agent_headers() {
+ // HTTP/1.1 §3.2 makes field-names case-insensitive. Real-world traffic
+ // varies in capitalisation (`Host`, `HOST`, `host`, etc.) and the
+ // `eq_ignore_ascii_case` refactor relies on this invariant — lock it
+ // here so a future change that drops the case-insensitive compare
+ // fails this test instead of silently regressing.
+ let host_variants: [&[u8]; 4] = [
+ b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n",
+ b"GET / HTTP/1.1\r\nhost: example.com\r\n\r\n",
+ b"GET / HTTP/1.1\r\nHOST: example.com\r\n\r\n",
+ b"GET / HTTP/1.1\r\nhOsT: example.com\r\n\r\n",
+ ];
+ for payload in &host_variants {
+ let info = analyze_http(payload).expect("should parse");
+ assert_eq!(info.host.as_deref(), Some("example.com"));
+ }
+
+ let ua_variants: [&[u8]; 4] = [
+ b"GET / HTTP/1.1\r\nUser-Agent: curl/8.5\r\n\r\n",
+ b"GET / HTTP/1.1\r\nuser-agent: curl/8.5\r\n\r\n",
+ b"GET / HTTP/1.1\r\nUSER-AGENT: curl/8.5\r\n\r\n",
+ b"GET / HTTP/1.1\r\nUser-AGENT: curl/8.5\r\n\r\n",
+ ];
+ for payload in &ua_variants {
+ let info = analyze_http(payload).expect("should parse");
+ assert_eq!(info.user_agent.as_deref(), Some("curl/8.5"));
+ }
+ }
+}
diff --git a/crates/rustnet-core/src/network/dpi/https.rs b/crates/rustnet-core/src/network/dpi/https.rs
new file mode 100644
index 0000000..e57b9a5
--- /dev/null
+++ b/crates/rustnet-core/src/network/dpi/https.rs
@@ -0,0 +1,483 @@
+use crate::network::types::{HttpsInfo, TlsInfo, TlsVersion};
+use log::debug;
+
+/// Maximum TLS extensions to parse. Defense-in-depth against crafted packets
+/// with many zero-length extensions designed to waste CPU.
+const MAX_TLS_EXTENSIONS: usize = 100;
+
+pub fn is_tls_handshake(payload: &[u8]) -> bool {
+ if payload.len() < 5 {
+ return false;
+ }
+
+ // TLS record header:
+ // - Content type (1 byte): 0x16 for handshake
+ // - Version (2 bytes): 0x0301-0x0304 for TLS 1.0-1.3
+ // - Length (2 bytes)
+ payload[0] == 0x16 && // Handshake content type
+ payload[1] == 0x03 && // Major version 3
+ (payload[2] >= 0x01 && payload[2] <= 0x04) // Minor version 1-4
+}
+
+pub fn analyze_https(payload: &[u8]) -> Option {
+ // Need at least 5 bytes for the TLS record header
+ if payload.len() < 5 {
+ return None;
+ }
+
+ let mut info = TlsInfo::new();
+
+ // Check content type
+ let content_type = payload[0];
+
+ if content_type != 0x16 {
+ // Not a handshake record - still extract version
+ let record_version = version_from_bytes(payload[1], payload[2]);
+ info.version = record_version;
+ return Some(HttpsInfo {
+ tls_info: Some(info),
+ });
+ }
+
+ // Record layer version
+ let record_version = version_from_bytes(payload[1], payload[2]);
+ info.version = record_version;
+
+ // Get record length
+ let record_length = u16::from_be_bytes([payload[3], payload[4]]) as usize;
+
+ // Sanity check
+ if record_length > 16384 + 2048 {
+ return Some(HttpsInfo {
+ tls_info: Some(info),
+ });
+ }
+
+ // Calculate available data (handle fragmentation gracefully)
+ let available_data = (payload.len() - 5).min(record_length);
+
+ if available_data < 4 {
+ return Some(HttpsInfo {
+ tls_info: Some(info),
+ });
+ }
+
+ // Skip TLS record header (5 bytes)
+ let handshake_data = &payload[5..5 + available_data];
+
+ let handshake_type = handshake_data[0];
+
+ // Quick validation
+ if !matches!(handshake_type, 0x00..=0x18 | 0xfe) {
+ return Some(HttpsInfo {
+ tls_info: Some(info),
+ });
+ }
+
+ let handshake_length =
+ u32::from_be_bytes([0, handshake_data[1], handshake_data[2], handshake_data[3]]) as usize;
+
+ // Sanity check
+ if handshake_length > 16384 {
+ return Some(HttpsInfo {
+ tls_info: Some(info),
+ });
+ }
+
+ // Calculate how much handshake data we actually have
+ let handshake_available = (handshake_data.len() - 4).min(handshake_length);
+
+ if handshake_available == 0 {
+ return Some(HttpsInfo {
+ tls_info: Some(info),
+ });
+ }
+
+ match handshake_type {
+ 0x01 => {
+ // Client Hello - this is where SNI and ALPN are
+ parse_client_hello(
+ &handshake_data[4..4 + handshake_available],
+ &mut info,
+ record_version,
+ );
+ }
+ 0x02 => {
+ // Server Hello
+ parse_server_hello(&handshake_data[4..4 + handshake_available], &mut info);
+ }
+ _ => {
+ // Other handshake types we don't parse
+ }
+ }
+
+ if info.sni.is_some() || !info.alpn.is_empty() {
+ debug!("TLS: Found SNI={:?}, ALPN={:?}", info.sni, info.alpn);
+ }
+ Some(HttpsInfo {
+ tls_info: Some(info),
+ })
+}
+
+fn version_from_bytes(major: u8, minor: u8) -> Option {
+ match (major, minor) {
+ (0x03, 0x01) => Some(TlsVersion::Tls10),
+ (0x03, 0x02) => Some(TlsVersion::Tls11),
+ (0x03, 0x03) => Some(TlsVersion::Tls12),
+ (0x03, 0x04) => Some(TlsVersion::Tls13),
+ _ => None,
+ }
+}
+
+fn parse_client_hello(data: &[u8], info: &mut TlsInfo, record_version: Option) {
+ // Need at least 2 bytes for version
+ if data.len() < 2 {
+ return;
+ }
+
+ // Client version
+ let client_version = version_from_bytes(data[0], data[1]);
+ info.version = client_version.or(record_version);
+
+ // Need at least 34 bytes for version + random
+ if data.len() < 34 {
+ return;
+ }
+
+ // Skip random (32 bytes)
+ let mut offset = 34;
+
+ // Session ID - be lenient with bounds
+ if offset >= data.len() {
+ return;
+ }
+ let session_id_len = data[offset] as usize;
+ offset += 1 + session_id_len;
+
+ if offset >= data.len() {
+ return;
+ }
+
+ // Cipher suites
+ if offset + 2 > data.len() {
+ return;
+ }
+ let cipher_suites_len = u16::from_be_bytes([data[offset], data[offset + 1]]) as usize;
+ offset += 2 + cipher_suites_len;
+
+ if offset >= data.len() {
+ return;
+ }
+
+ // Compression methods
+ if offset >= data.len() {
+ return;
+ }
+ let compression_len = data[offset] as usize;
+ offset += 1 + compression_len;
+
+ if offset >= data.len() {
+ return;
+ }
+
+ // Extensions - this is what we really want
+ if offset + 2 > data.len() {
+ return;
+ }
+ let extensions_len = u16::from_be_bytes([data[offset], data[offset + 1]]) as usize;
+ offset += 2;
+
+ // Parse whatever extension data we have
+ if offset < data.len() {
+ let available_ext_data = &data[offset..data.len().min(offset + extensions_len)];
+ if !available_ext_data.is_empty() {
+ parse_extensions(available_ext_data, info, true);
+ }
+ }
+}
+
+fn parse_server_hello(data: &[u8], info: &mut TlsInfo) {
+ if data.len() < 2 {
+ return;
+ }
+
+ // Server version
+ let server_version = version_from_bytes(data[0], data[1]);
+ info.version = server_version;
+
+ if data.len() < 34 {
+ return;
+ }
+
+ // Skip random (32 bytes)
+ let mut offset = 34;
+
+ // Session ID length
+ if offset >= data.len() {
+ return;
+ }
+ let session_id_len = data[offset] as usize;
+ offset += 1 + session_id_len;
+
+ if offset >= data.len() {
+ return;
+ }
+
+ // Cipher suite (2 bytes)
+ if offset + 2 > data.len() {
+ return;
+ }
+ let cipher = u16::from_be_bytes([data[offset], data[offset + 1]]);
+ info.cipher_suite = Some(cipher);
+ offset += 2;
+
+ // Compression method (1 byte)
+ if offset >= data.len() {
+ return;
+ }
+ offset += 1;
+
+ // Extensions (optional)
+ if offset + 2 > data.len() {
+ return;
+ }
+
+ let extensions_len = u16::from_be_bytes([data[offset], data[offset + 1]]) as usize;
+ offset += 2;
+
+ // Parse whatever extension data we have
+ if offset < data.len() {
+ let available_ext_data = &data[offset..data.len().min(offset + extensions_len)];
+ if !available_ext_data.is_empty() {
+ parse_extensions(available_ext_data, info, false);
+ }
+ }
+}
+
+fn parse_extensions(data: &[u8], info: &mut TlsInfo, is_client_hello: bool) {
+ let mut offset = 0;
+ let mut extensions_parsed = 0;
+
+ while offset + 4 <= data.len() {
+ extensions_parsed += 1;
+ if extensions_parsed > MAX_TLS_EXTENSIONS {
+ break;
+ }
+ let ext_type = u16::from_be_bytes([data[offset], data[offset + 1]]);
+ let ext_len = u16::from_be_bytes([data[offset + 2], data[offset + 3]]) as usize;
+
+ // Calculate how much extension data we actually have
+ let available_ext_len = data.len().saturating_sub(offset + 4);
+ let ext_data_len = ext_len.min(available_ext_len);
+
+ if ext_data_len > 0 {
+ let ext_data = &data[offset + 4..offset + 4 + ext_data_len];
+
+ match ext_type {
+ 0x0000 if is_client_hello => {
+ // SNI (Server Name Indication)
+ if let Some(sni) = parse_sni_extension_resilient(ext_data) {
+ info.sni = Some(sni);
+ }
+ }
+ 0x0010 => {
+ // ALPN (Application-Layer Protocol Negotiation)
+ if let Some(alpn) = parse_alpn_extension_resilient(ext_data)
+ && !alpn.is_empty()
+ {
+ info.alpn = alpn;
+ }
+ }
+ 0x002b => {
+ // Supported Versions
+ if let Some(version) =
+ parse_supported_versions_resilient(ext_data, is_client_hello)
+ {
+ info.version = Some(version);
+ }
+ }
+ _ => {
+ // Skip unknown extensions
+ }
+ }
+ }
+
+ // Move to next extension (use declared length, not actual)
+ offset += 4 + ext_len;
+
+ // But stop if we've gone past available data
+ if offset > data.len() {
+ break;
+ }
+ }
+}
+
+fn parse_sni_extension_resilient(data: &[u8]) -> Option {
+ if data.len() < 5 {
+ return None;
+ }
+
+ // Server name list length (2 bytes)
+ let _list_len = u16::from_be_bytes([data[0], data[1]]) as usize;
+
+ // Check name type (should be 0x00 for hostname)
+ if data[2] != 0x00 {
+ return None;
+ }
+
+ // Name length
+ let name_len = u16::from_be_bytes([data[3], data[4]]) as usize;
+
+ // Extract whatever hostname data we have
+ let available_len = data.len().saturating_sub(5);
+ let actual_len = name_len.min(available_len);
+
+ if actual_len > 0 {
+ let hostname_bytes = &data[5..5 + actual_len];
+
+ // Try to parse as UTF-8
+ if let Ok(hostname) = std::str::from_utf8(hostname_bytes) {
+ // Basic validation - at least check it looks like a hostname
+ if hostname.chars().all(|c| c.is_ascii_graphic() || c == '.') {
+ let result = if actual_len < name_len {
+ format!("{}[PARTIAL]", hostname)
+ } else {
+ hostname.to_string()
+ };
+ return Some(result);
+ }
+ }
+ }
+
+ None
+}
+
+fn parse_alpn_extension_resilient(data: &[u8]) -> Option> {
+ if data.len() < 2 {
+ return None;
+ }
+
+ let mut protocols = Vec::new();
+
+ // ALPN list length
+ let alpn_len = u16::from_be_bytes([data[0], data[1]]) as usize;
+
+ let mut offset = 2;
+ let list_end = 2 + alpn_len.min(data.len() - 2);
+
+ while offset < list_end && offset < data.len() {
+ let proto_len = data[offset] as usize;
+ offset += 1;
+
+ let available_len = list_end
+ .saturating_sub(offset)
+ .min(data.len().saturating_sub(offset));
+ let actual_len = proto_len.min(available_len);
+
+ if actual_len > 0
+ && let Ok(proto) = std::str::from_utf8(&data[offset..offset + actual_len])
+ {
+ if actual_len < proto_len {
+ protocols.push(format!("{}[PARTIAL]", proto));
+ } else {
+ protocols.push(proto.to_string());
+ }
+ }
+
+ offset += proto_len;
+
+ if offset >= data.len() {
+ break;
+ }
+ }
+
+ if protocols.is_empty() {
+ None
+ } else {
+ Some(protocols)
+ }
+}
+
+fn parse_supported_versions_resilient(data: &[u8], is_client_hello: bool) -> Option {
+ if is_client_hello {
+ // Client sends a list of supported versions
+ if data.is_empty() {
+ return None;
+ }
+
+ let list_len = data[0] as usize;
+ let mut offset = 1;
+ let mut best_version: Option = None;
+
+ while offset + 1 < data.len() && offset < 1 + list_len {
+ if let Some(version) = version_from_bytes(data[offset], data[offset + 1]) {
+ best_version = match (best_version, version) {
+ (None, v) => Some(v),
+ (Some(v1), v2) => {
+ // Simple comparison - return the higher version
+ if version_to_priority(v2) > version_to_priority(v1) {
+ Some(v2)
+ } else {
+ Some(v1)
+ }
+ }
+ };
+ }
+ offset += 2;
+ }
+
+ best_version
+ } else {
+ // Server sends a single selected version
+ if data.len() < 2 {
+ return None;
+ }
+ version_from_bytes(data[0], data[1])
+ }
+}
+
+fn version_to_priority(version: TlsVersion) -> u8 {
+ match version {
+ TlsVersion::Tls10 => 1,
+ TlsVersion::Tls11 => 2,
+ TlsVersion::Tls12 => 3,
+ TlsVersion::Tls13 => 4,
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_partial_sni_extraction() {
+ // Simulate a truncated SNI extension
+ let partial_sni = vec![
+ 0x00, 0x10, // List length: 16
+ 0x00, // Name type: host_name
+ 0x00, 0x0d, // Name length: 13
+ b'e', b'x', b'a', b'm', b'p', // Only 5 bytes of "example.com"
+ ];
+
+ let result = parse_sni_extension_resilient(&partial_sni);
+ assert!(result.is_some());
+ let sni = result.unwrap();
+ assert!(sni.starts_with("examp"));
+ assert!(sni.contains("PARTIAL"));
+ }
+
+ #[test]
+ fn test_partial_alpn_extraction() {
+ // Simulate a truncated ALPN extension
+ let partial_alpn = vec![
+ 0x00, 0x0e, // List length: 14
+ 0x08, b'h', b't', b't', b'p', // Only partial "http/1.1"
+ ];
+
+ let result = parse_alpn_extension_resilient(&partial_alpn);
+ assert!(result.is_some());
+ let protocols = result.unwrap();
+ assert!(!protocols.is_empty());
+ assert!(protocols[0].contains("PARTIAL"));
+ }
+}
diff --git a/crates/rustnet-core/src/network/dpi/llmnr.rs b/crates/rustnet-core/src/network/dpi/llmnr.rs
new file mode 100644
index 0000000..716b855
--- /dev/null
+++ b/crates/rustnet-core/src/network/dpi/llmnr.rs
@@ -0,0 +1,146 @@
+//! LLMNR (Link-Local Multicast Name Resolution) Deep Packet Inspection
+//!
+//! Parses LLMNR packets according to RFC 4795.
+//! LLMNR uses UDP port 5355 and shares the same wire format as DNS.
+
+use crate::network::types::LlmnrInfo;
+
+use super::dns;
+
+/// Analyze an LLMNR packet and extract key information.
+///
+/// LLMNR uses the same packet format as DNS, so we reuse the DNS parser.
+/// Returns `None` if the packet cannot be parsed as DNS.
+pub fn analyze_llmnr(payload: &[u8]) -> Option {
+ // Reuse DNS parser - LLMNR has the same wire format
+ let dns_info = dns::analyze_dns(payload)?;
+
+ Some(LlmnrInfo {
+ query_name: dns_info.query_name,
+ query_type: dns_info.query_type,
+ is_response: dns_info.is_response,
+ response_ips: dns_info.response_ips,
+ })
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::network::types::DnsQueryType;
+
+ fn build_llmnr_query(name: &str, qtype: u16) -> Vec {
+ let mut packet = Vec::new();
+
+ // DNS header (12 bytes)
+ packet.extend_from_slice(&[0x00, 0x01]); // Transaction ID
+ packet.extend_from_slice(&[0x00, 0x00]); // Flags (query)
+ packet.extend_from_slice(&[0x00, 0x01]); // Questions: 1
+ packet.extend_from_slice(&[0x00, 0x00]); // Answer RRs: 0
+ packet.extend_from_slice(&[0x00, 0x00]); // Authority RRs: 0
+ packet.extend_from_slice(&[0x00, 0x00]); // Additional RRs: 0
+
+ // Question section - encode name (single label for LLMNR)
+ packet.push(name.len() as u8);
+ packet.extend_from_slice(name.as_bytes());
+ packet.push(0x00); // Null terminator
+
+ // Query type and class
+ packet.extend_from_slice(&qtype.to_be_bytes());
+ packet.extend_from_slice(&[0x00, 0x01]); // Class: IN
+
+ packet
+ }
+
+ fn build_llmnr_response(name: &str, qtype: u16) -> Vec {
+ let mut packet = Vec::new();
+
+ // DNS header (12 bytes)
+ packet.extend_from_slice(&[0x00, 0x01]); // Transaction ID
+ packet.extend_from_slice(&[0x80, 0x00]); // Flags (response)
+ packet.extend_from_slice(&[0x00, 0x01]); // Questions: 1
+ packet.extend_from_slice(&[0x00, 0x00]); // Answer RRs: 0
+ packet.extend_from_slice(&[0x00, 0x00]); // Authority RRs: 0
+ packet.extend_from_slice(&[0x00, 0x00]); // Additional RRs: 0
+
+ // Question section
+ packet.push(name.len() as u8);
+ packet.extend_from_slice(name.as_bytes());
+ packet.push(0x00);
+ packet.extend_from_slice(&qtype.to_be_bytes());
+ packet.extend_from_slice(&[0x00, 0x01]);
+
+ packet
+ }
+
+ #[test]
+ fn test_llmnr_query() {
+ let packet = build_llmnr_query("workstation", 1); // A query
+ let info = analyze_llmnr(&packet).expect("should parse");
+ assert_eq!(info.query_name, Some("workstation".to_string()));
+ assert_eq!(info.query_type, Some(DnsQueryType::A));
+ assert!(!info.is_response);
+ }
+
+ #[test]
+ fn test_llmnr_response() {
+ let packet = build_llmnr_response("fileserver", 1);
+ let info = analyze_llmnr(&packet).expect("should parse");
+ assert_eq!(info.query_name, Some("fileserver".to_string()));
+ assert!(info.is_response);
+ }
+
+ #[test]
+ fn test_llmnr_aaaa_query() {
+ let packet = build_llmnr_query("mypc", 28); // AAAA query
+ let info = analyze_llmnr(&packet).expect("should parse");
+ assert_eq!(info.query_type, Some(DnsQueryType::AAAA));
+ }
+
+ #[test]
+ fn test_llmnr_too_short() {
+ let packet = [0u8; 8];
+ assert!(analyze_llmnr(&packet).is_none());
+ }
+
+ /// Build an LLMNR response that echoes the question and supplies an A
+ /// record. RFC 4795 §2.1: LLMNR responses re-include the question.
+ fn build_llmnr_response_with_a(name: &str, ip: [u8; 4]) -> Vec {
+ let mut packet = Vec::new();
+ // Header: txid 1, flags response, qdcount=1, ancount=1, ns=0, ar=0.
+ packet.extend_from_slice(&[0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x00, 0x01]);
+ packet.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
+ // Question (single label).
+ packet.push(name.len() as u8);
+ packet.extend_from_slice(name.as_bytes());
+ packet.push(0x00);
+ packet.extend_from_slice(&[0x00, 0x01, 0x00, 0x01]); // QTYPE A, QCLASS IN
+ // Answer: NAME pointer back to offset 12, TYPE A, CLASS IN, TTL, RDLENGTH 4, RDATA.
+ packet.extend_from_slice(&[
+ 0xC0, 0x0C, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x04,
+ ]);
+ packet.extend_from_slice(&ip);
+ packet
+ }
+
+ #[test]
+ fn test_llmnr_response_populates_response_ips() {
+ let packet = build_llmnr_response_with_a("workstation", [192, 168, 1, 42]);
+ let info = analyze_llmnr(&packet).expect("should parse");
+ assert!(info.is_response);
+ assert_eq!(info.query_name, Some("workstation".to_string()));
+ assert_eq!(
+ info.response_ips,
+ vec![std::net::IpAddr::V4(std::net::Ipv4Addr::new(
+ 192, 168, 1, 42
+ ))]
+ );
+ }
+
+ #[test]
+ fn test_llmnr_query_leaves_response_ips_empty() {
+ let packet = build_llmnr_query("workstation", 1);
+ let info = analyze_llmnr(&packet).expect("should parse");
+ assert!(!info.is_response);
+ assert!(info.response_ips.is_empty());
+ }
+}
diff --git a/crates/rustnet-core/src/network/dpi/mdns.rs b/crates/rustnet-core/src/network/dpi/mdns.rs
new file mode 100644
index 0000000..610ecb2
--- /dev/null
+++ b/crates/rustnet-core/src/network/dpi/mdns.rs
@@ -0,0 +1,195 @@
+//! mDNS (Multicast DNS) Deep Packet Inspection
+//!
+//! Parses mDNS packets according to RFC 6762.
+//! mDNS uses UDP port 5353 and shares the same wire format as DNS.
+
+use crate::network::types::MdnsInfo;
+
+use super::dns;
+
+/// Analyze an mDNS packet and extract key information.
+///
+/// mDNS uses the same packet format as DNS, so we reuse the DNS parser.
+/// We route through the mDNS-aware variant so the answer walk also runs
+/// when `qdcount == 0` (RFC 6762 §6) and so the parser also walks
+/// ADDITIONAL records, where mDNS frequently carries A / AAAA rdata.
+///
+/// Returns `None` if the packet cannot be parsed as DNS.
+pub fn analyze_mdns(payload: &[u8]) -> Option {
+ let dns_info = dns::analyze_dns_for_mdns(payload)?;
+
+ Some(MdnsInfo {
+ query_name: dns_info.query_name,
+ query_type: dns_info.query_type,
+ is_response: dns_info.is_response,
+ response_ips: dns_info.response_ips,
+ })
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::network::types::DnsQueryType;
+
+ fn build_mdns_query(name: &str, qtype: u16) -> Vec {
+ let mut packet = Vec::new();
+
+ // DNS header (12 bytes)
+ packet.extend_from_slice(&[0x00, 0x00]); // Transaction ID
+ packet.extend_from_slice(&[0x00, 0x00]); // Flags (query)
+ packet.extend_from_slice(&[0x00, 0x01]); // Questions: 1
+ packet.extend_from_slice(&[0x00, 0x00]); // Answer RRs: 0
+ packet.extend_from_slice(&[0x00, 0x00]); // Authority RRs: 0
+ packet.extend_from_slice(&[0x00, 0x00]); // Additional RRs: 0
+
+ // Question section - encode domain name
+ for label in name.split('.') {
+ packet.push(label.len() as u8);
+ packet.extend_from_slice(label.as_bytes());
+ }
+ packet.push(0x00); // Null terminator
+
+ // Query type and class
+ packet.extend_from_slice(&qtype.to_be_bytes());
+ packet.extend_from_slice(&[0x00, 0x01]); // Class: IN
+
+ packet
+ }
+
+ fn build_mdns_response(name: &str, qtype: u16) -> Vec {
+ let mut packet = Vec::new();
+
+ // DNS header (12 bytes)
+ packet.extend_from_slice(&[0x00, 0x00]); // Transaction ID
+ packet.extend_from_slice(&[0x84, 0x00]); // Flags (response, authoritative)
+ packet.extend_from_slice(&[0x00, 0x01]); // Questions: 1
+ packet.extend_from_slice(&[0x00, 0x00]); // Answer RRs: 0
+ packet.extend_from_slice(&[0x00, 0x00]); // Authority RRs: 0
+ packet.extend_from_slice(&[0x00, 0x00]); // Additional RRs: 0
+
+ // Question section
+ for label in name.split('.') {
+ packet.push(label.len() as u8);
+ packet.extend_from_slice(label.as_bytes());
+ }
+ packet.push(0x00);
+ packet.extend_from_slice(&qtype.to_be_bytes());
+ packet.extend_from_slice(&[0x00, 0x01]);
+
+ packet
+ }
+
+ #[test]
+ fn test_mdns_query() {
+ let packet = build_mdns_query("_http._tcp.local", 12); // PTR query
+ let info = analyze_mdns(&packet).expect("should parse");
+ assert_eq!(info.query_name, Some("_http._tcp.local".to_string()));
+ assert_eq!(info.query_type, Some(DnsQueryType::PTR));
+ assert!(!info.is_response);
+ }
+
+ #[test]
+ fn test_mdns_response() {
+ let packet = build_mdns_response("mydevice.local", 1); // A record
+ let info = analyze_mdns(&packet).expect("should parse");
+ assert_eq!(info.query_name, Some("mydevice.local".to_string()));
+ assert_eq!(info.query_type, Some(DnsQueryType::A));
+ assert!(info.is_response);
+ }
+
+ #[test]
+ fn test_mdns_too_short() {
+ let packet = [0u8; 5];
+ assert!(analyze_mdns(&packet).is_none());
+ }
+
+ #[test]
+ fn test_mdns_srv_query() {
+ let packet = build_mdns_query("_printer._tcp.local", 33); // SRV query
+ let info = analyze_mdns(&packet).expect("should parse");
+ assert_eq!(info.query_name, Some("_printer._tcp.local".to_string()));
+ assert_eq!(info.query_type, Some(DnsQueryType::SRV));
+ }
+
+ /// Build an mDNS announcement (RFC 6762 §6: qdcount=0, ancount>=1).
+ /// `name` is encoded uncompressed inside each answer record.
+ fn build_mdns_announcement_a(name: &str, ip: [u8; 4]) -> Vec {
+ let mut packet = Vec::new();
+ // Header: txid 0, flags response, qdcount=0, ancount=1, nscount=0, arcount=0.
+ packet.extend_from_slice(&[0x00, 0x00, 0x84, 0x00, 0x00, 0x00, 0x00, 0x01]);
+ packet.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
+ // Answer: NAME uncompressed.
+ for label in name.split('.') {
+ packet.push(label.len() as u8);
+ packet.extend_from_slice(label.as_bytes());
+ }
+ packet.push(0x00);
+ // TYPE A, CLASS IN (cache-flush bit cleared), TTL 120, RDLENGTH 4, RDATA.
+ packet.extend_from_slice(&[0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x04]);
+ packet.extend_from_slice(&ip);
+ packet
+ }
+
+ #[test]
+ fn test_mdns_announcement_with_qdcount_zero_collects_a_record() {
+ // RFC 6762 §6: typical mDNS announcement has qdcount=0 and ancount>=1.
+ // Pre-#333 the answer walk lived inside `qdcount > 0`, so this
+ // packet returned an empty `response_ips`.
+ let packet = build_mdns_announcement_a("printer.local", [192, 168, 1, 50]);
+ let info = analyze_mdns(&packet).expect("should parse");
+ assert!(info.is_response);
+ assert!(info.query_name.is_none());
+ assert_eq!(
+ info.response_ips,
+ vec![std::net::IpAddr::V4(std::net::Ipv4Addr::new(
+ 192, 168, 1, 50
+ ))]
+ );
+ }
+
+ #[test]
+ fn test_mdns_collects_a_record_from_additional_section() {
+ // mDNS often carries A / AAAA in the ADDITIONAL section (arcount)
+ // rather than answers — e.g. when responding to a PTR with the
+ // SRV target's address records. Build: qdcount=0, ancount=1
+ // (PTR), nscount=0, arcount=1 (A).
+ let mut packet = Vec::new();
+ packet.extend_from_slice(&[0x00, 0x00, 0x84, 0x00, 0x00, 0x00, 0x00, 0x01]);
+ packet.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
+ // ANSWER: PTR "_http._tcp.local" → "mybox._http._tcp.local"
+ let ptr_name = "_http._tcp.local";
+ for label in ptr_name.split('.') {
+ packet.push(label.len() as u8);
+ packet.extend_from_slice(label.as_bytes());
+ }
+ packet.push(0x00);
+ // TYPE PTR (12), CLASS IN, TTL, RDLENGTH 2 (compressed pointer back).
+ packet.extend_from_slice(&[0x00, 0x0C, 0x00, 0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x02]);
+ packet.extend_from_slice(&[0xC0, 0x0C]);
+ // ADDITIONAL: A record for "host.local" → 10.0.0.5.
+ let a_name = "host.local";
+ for label in a_name.split('.') {
+ packet.push(label.len() as u8);
+ packet.extend_from_slice(label.as_bytes());
+ }
+ packet.push(0x00);
+ packet.extend_from_slice(&[0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x04]);
+ packet.extend_from_slice(&[10, 0, 0, 5]);
+
+ let info = analyze_mdns(&packet).expect("should parse");
+ assert_eq!(
+ info.response_ips,
+ vec![std::net::IpAddr::V4(std::net::Ipv4Addr::new(10, 0, 0, 5))]
+ );
+ }
+
+ #[test]
+ fn test_mdns_query_packet_response_ips_empty() {
+ // Even if the wire has bytes after the question that look
+ // answer-shaped, a query (QR bit clear) must not surface IPs.
+ let packet = build_mdns_query("_http._tcp.local", 12);
+ let info = analyze_mdns(&packet).expect("should parse");
+ assert!(!info.is_response);
+ assert!(info.response_ips.is_empty());
+ }
+}
diff --git a/crates/rustnet-core/src/network/dpi/mod.rs b/crates/rustnet-core/src/network/dpi/mod.rs
new file mode 100644
index 0000000..f84470f
--- /dev/null
+++ b/crates/rustnet-core/src/network/dpi/mod.rs
@@ -0,0 +1,266 @@
+use crate::network::types::{ApplicationProtocol, QuicInfo};
+use log::{debug, warn};
+
+mod bittorrent;
+mod cipher_suites;
+mod dhcp;
+mod dns;
+mod ftp;
+mod http;
+mod https;
+mod llmnr;
+mod mdns;
+mod mqtt;
+mod netbios;
+mod ntp;
+mod quic;
+mod snmp;
+mod ssdp;
+mod ssh;
+mod stun;
+
+pub use cipher_suites::{format_cipher_suite, is_secure_cipher_suite};
+pub use quic::{is_partial_sni, try_extract_tls_from_reassembler};
+
+// Well-known port numbers used for DPI protocol detection.
+const PORT_SSH: u16 = 22;
+const PORT_DNS: u16 = 53;
+const PORT_FTP: u16 = 21;
+const PORT_DHCP_SERVER: u16 = 67;
+const PORT_DHCP_CLIENT: u16 = 68;
+const PORT_NTP: u16 = 123;
+const PORT_NETBIOS_NS: u16 = 137;
+const PORT_NETBIOS_DGM: u16 = 138;
+const PORT_SNMP: u16 = 161;
+const PORT_SNMP_TRAP: u16 = 162;
+const PORT_HTTPS: u16 = 443;
+const PORT_MQTT: u16 = 1883;
+const PORT_SSDP: u16 = 1900;
+const PORT_MDNS: u16 = 5353;
+const PORT_STUN: u16 = 3478;
+const PORT_STUN_TLS: u16 = 5349;
+const PORT_LLMNR: u16 = 5355;
+
+/// Result of DPI analysis
+#[derive(Debug, Clone)]
+pub struct DpiResult {
+ pub application: ApplicationProtocol,
+}
+
+/// Analyze a TCP packet payload
+pub fn analyze_tcp_packet(
+ payload: &[u8],
+ local_port: u16,
+ remote_port: u16,
+ _is_outgoing: bool,
+) -> Option {
+ if payload.is_empty() {
+ return None;
+ }
+
+ // Try protocols in order of likelihood/speed
+
+ // 1. Check for HTTP (fast string matching)
+ if let Some(http_result) = http::analyze_http(payload) {
+ return Some(DpiResult {
+ application: ApplicationProtocol::Http(http_result),
+ });
+ }
+
+ // 2. Check for TLS/HTTPS (port 443 or TLS handshake)
+ if (local_port == PORT_HTTPS || remote_port == PORT_HTTPS || https::is_tls_handshake(payload))
+ && let Some(tls_result) = https::analyze_https(payload)
+ {
+ return Some(DpiResult {
+ application: ApplicationProtocol::Https(tls_result),
+ });
+ }
+
+ // 3. Check for BitTorrent (handshake signature \x13BitTorrent protocol)
+ if bittorrent::is_bittorrent_handshake(payload)
+ && let Some(bt_result) = bittorrent::analyze_bittorrent(payload)
+ {
+ return Some(DpiResult {
+ application: ApplicationProtocol::BitTorrent(bt_result),
+ });
+ }
+
+ // 4. Check for MQTT (port 1883 or MQTT signature)
+ if (local_port == PORT_MQTT || remote_port == PORT_MQTT || mqtt::is_mqtt_packet(payload))
+ && let Some(mqtt_result) = mqtt::analyze_mqtt(payload)
+ {
+ return Some(DpiResult {
+ application: ApplicationProtocol::Mqtt(mqtt_result),
+ });
+ }
+
+ // 5. Check for SSH (port 22 or SSH banner)
+ if (local_port == PORT_SSH || remote_port == PORT_SSH || ssh::is_likely_ssh(payload))
+ && let Some(ssh_result) = ssh::analyze_ssh(payload, _is_outgoing)
+ {
+ return Some(DpiResult {
+ application: ApplicationProtocol::Ssh(ssh_result),
+ });
+ }
+
+ // 6. Check for FTP control channel (port 21 plaintext / AUTH TLS upgrade,
+ // or signature). Off port 21 only distinctively-FTP commands count as
+ // a signature; 3-digit reply lines are shared with SMTP/POP3/NNTP and
+ // are only classified on port 21.
+ //
+ // Implicit FTPS on port 990 is intentionally NOT routed here: that
+ // flow is TLS from the very first byte and falls through to the
+ // HTTPS/TLS branch. AUTH TLS on port 21 is captured via the `AUTH`
+ // command in the plaintext control channel before the upgrade.
+ if (local_port == PORT_FTP || remote_port == PORT_FTP || ftp::is_ftp(payload))
+ && let Some(ftp_result) = ftp::analyze_ftp(payload)
+ {
+ return Some(DpiResult {
+ application: ApplicationProtocol::Ftp(ftp_result),
+ });
+ }
+
+ // More protocols here...
+
+ None
+}
+
+/// Analyze a UDP packet payload
+pub fn analyze_udp_packet(
+ payload: &[u8],
+ local_port: u16,
+ remote_port: u16,
+ _is_outgoing: bool,
+) -> Option {
+ if payload.is_empty() {
+ return None;
+ }
+
+ // 1. DNS (port 53)
+ if (local_port == PORT_DNS || remote_port == PORT_DNS)
+ && let Some(dns_result) = dns::analyze_dns(payload)
+ {
+ return Some(DpiResult {
+ application: ApplicationProtocol::Dns(dns_result),
+ });
+ }
+
+ // 2. QUIC/HTTP3 (port 443)
+ if (local_port == PORT_HTTPS || remote_port == PORT_HTTPS) && quic::is_quic_packet(payload) {
+ let quic_info = quic::parse_quic_packet(payload);
+ if let Some(quic_info) = quic_info {
+ debug!("QUIC packet detected: {:?}", quic_info);
+ return Some(DpiResult {
+ application: ApplicationProtocol::Quic(Box::new(quic_info)),
+ });
+ } else {
+ warn!("Failed to parse QUIC packet");
+ let empty_quic_info = QuicInfo::new(0);
+
+ return Some(DpiResult {
+ application: ApplicationProtocol::Quic(Box::new(empty_quic_info)),
+ });
+ }
+ }
+
+ // 3. mDNS (port 5353)
+ if (local_port == PORT_MDNS || remote_port == PORT_MDNS)
+ && let Some(mdns_result) = mdns::analyze_mdns(payload)
+ {
+ return Some(DpiResult {
+ application: ApplicationProtocol::Mdns(mdns_result),
+ });
+ }
+
+ // 4. DHCP (ports 67-68)
+ if matches!(
+ (local_port, remote_port),
+ (PORT_DHCP_SERVER, _)
+ | (PORT_DHCP_CLIENT, _)
+ | (_, PORT_DHCP_SERVER)
+ | (_, PORT_DHCP_CLIENT)
+ ) && let Some(dhcp_result) = dhcp::analyze_dhcp(payload)
+ {
+ return Some(DpiResult {
+ application: ApplicationProtocol::Dhcp(dhcp_result),
+ });
+ }
+
+ // 5. NTP (port 123)
+ if (local_port == PORT_NTP || remote_port == PORT_NTP)
+ && let Some(ntp_result) = ntp::analyze_ntp(payload)
+ {
+ return Some(DpiResult {
+ application: ApplicationProtocol::Ntp(ntp_result),
+ });
+ }
+
+ // 6. LLMNR (port 5355)
+ if (local_port == PORT_LLMNR || remote_port == PORT_LLMNR)
+ && let Some(llmnr_result) = llmnr::analyze_llmnr(payload)
+ {
+ return Some(DpiResult {
+ application: ApplicationProtocol::Llmnr(llmnr_result),
+ });
+ }
+
+ // 7. SSDP (port 1900)
+ if (local_port == PORT_SSDP || remote_port == PORT_SSDP)
+ && let Some(ssdp_result) = ssdp::analyze_ssdp(payload)
+ {
+ return Some(DpiResult {
+ application: ApplicationProtocol::Ssdp(ssdp_result),
+ });
+ }
+
+ // 8. NetBIOS-NS (port 137)
+ if (local_port == PORT_NETBIOS_NS || remote_port == PORT_NETBIOS_NS)
+ && let Some(netbios_result) = netbios::analyze_netbios_ns(payload)
+ {
+ return Some(DpiResult {
+ application: ApplicationProtocol::NetBios(netbios_result),
+ });
+ }
+
+ // 9. NetBIOS-DGM (port 138)
+ if (local_port == PORT_NETBIOS_DGM || remote_port == PORT_NETBIOS_DGM)
+ && let Some(netbios_result) = netbios::analyze_netbios_dgm(payload)
+ {
+ return Some(DpiResult {
+ application: ApplicationProtocol::NetBios(netbios_result),
+ });
+ }
+
+ // 10. SNMP (ports 161-162)
+ if matches!(
+ (local_port, remote_port),
+ (PORT_SNMP, _) | (PORT_SNMP_TRAP, _) | (_, PORT_SNMP) | (_, PORT_SNMP_TRAP)
+ ) && let Some(snmp_result) = snmp::analyze_snmp(payload)
+ {
+ return Some(DpiResult {
+ application: ApplicationProtocol::Snmp(snmp_result),
+ });
+ }
+
+ // 11. STUN (port 3478/5349 or magic cookie detection for non-standard ports)
+ if (local_port == PORT_STUN
+ || remote_port == PORT_STUN
+ || local_port == PORT_STUN_TLS
+ || remote_port == PORT_STUN_TLS
+ || stun::is_likely_stun(payload))
+ && let Some(stun_result) = stun::analyze_stun(payload)
+ {
+ return Some(DpiResult {
+ application: ApplicationProtocol::Stun(stun_result),
+ });
+ }
+
+ // 12. BitTorrent DHT / uTP (no port gating — signature-based)
+ if let Some(bt_result) = bittorrent::analyze_udp_bittorrent(payload) {
+ return Some(DpiResult {
+ application: ApplicationProtocol::BitTorrent(bt_result),
+ });
+ }
+
+ None
+}
diff --git a/crates/rustnet-core/src/network/dpi/mqtt.rs b/crates/rustnet-core/src/network/dpi/mqtt.rs
new file mode 100644
index 0000000..3ecde73
--- /dev/null
+++ b/crates/rustnet-core/src/network/dpi/mqtt.rs
@@ -0,0 +1,544 @@
+use crate::network::types::{MqttInfo, MqttPacketType, MqttVersion};
+use log::debug;
+
+/// Quick check if payload looks like an MQTT packet.
+pub fn is_mqtt_packet(payload: &[u8]) -> bool {
+ if payload.len() < 2 {
+ return false;
+ }
+
+ let packet_type = payload[0] >> 4;
+ let flags = payload[0] & 0x0F;
+
+ // Valid MQTT packet types are 1-15 (AUTH=15 was added in MQTT 5)
+ if !(1..=15).contains(&packet_type) {
+ return false;
+ }
+
+ // Validate flags for packet types with fixed flag requirements (MQTT
+ // spec §2.1.2): every type except PUBLISH(3), whose flags carry
+ // DUP/QoS/RETAIN, has reserved flags — 0x02 for PUBREL(6),
+ // SUBSCRIBE(8), and UNSUBSCRIBE(10), 0x00 for the rest.
+ if packet_type != 3 {
+ let expected = match packet_type {
+ 6 | 8 | 10 => 0x02,
+ _ => 0x00,
+ };
+ if flags != expected {
+ return false;
+ }
+ }
+
+ // Validate remaining length encoding and check it's plausible
+ let Some((remaining_len, header_bytes)) = decode_remaining_length(payload, 1) else {
+ return false;
+ };
+
+ // The total packet size should match what we see
+ let total = 1 + header_bytes + remaining_len;
+ if total > payload.len() + 4 {
+ // Allow a small overshoot since we may have a partial payload,
+ // but reject wildly wrong lengths (e.g. HTTP text decoded as huge length)
+ return false;
+ }
+
+ // Only CONNECT packets have a strong enough signature for port-independent detection
+ // (they contain "MQTT" or "MQIsdp" protocol name string).
+ // All other types (PINGREQ, UNSUBSCRIBE, etc.) have weak 2-byte signatures that
+ // easily match random binary data (e.g. BitTorrent file transfers).
+ // Those types are still detected via port-based matching (port 1883) in the caller.
+ packet_type == 1 && has_mqtt_protocol_name(payload)
+}
+
+/// Full MQTT packet analysis.
+pub fn analyze_mqtt(payload: &[u8]) -> Option {
+ if payload.len() < 2 {
+ return None;
+ }
+
+ let type_byte = payload[0] >> 4;
+ let flags = payload[0] & 0x0F;
+
+ let (remaining_len, header_len) = decode_remaining_length(payload, 1)?;
+
+ let packet_type = match type_byte {
+ 1 => MqttPacketType::Connect,
+ 2 => MqttPacketType::Connack,
+ 3 => MqttPacketType::Publish,
+ 4 => MqttPacketType::Puback,
+ 5 => MqttPacketType::Pubrec,
+ 6 => MqttPacketType::Pubrel,
+ 7 => MqttPacketType::Pubcomp,
+ 8 => MqttPacketType::Subscribe,
+ 9 => MqttPacketType::Suback,
+ 10 => MqttPacketType::Unsubscribe,
+ 11 => MqttPacketType::Unsuback,
+ 12 => MqttPacketType::Pingreq,
+ 13 => MqttPacketType::Pingresp,
+ 14 => MqttPacketType::Disconnect,
+ 15 => MqttPacketType::Auth,
+ _ => return None,
+ };
+
+ let var_start = 1 + header_len;
+ let packet_end = var_start + remaining_len;
+
+ // Clamp to actual payload size
+ let available = &payload[..payload.len().min(packet_end)];
+
+ let mut info = MqttInfo {
+ version: None,
+ packet_type,
+ client_id: None,
+ topic: None,
+ qos: None,
+ };
+
+ match packet_type {
+ MqttPacketType::Connect => {
+ parse_connect(available, var_start, &mut info);
+ }
+ MqttPacketType::Publish => {
+ let qos = (flags >> 1) & 0x03;
+ info.qos = Some(qos);
+ parse_publish_topic(available, var_start, &mut info);
+ }
+ MqttPacketType::Subscribe => {
+ info.qos = Some(1); // SUBSCRIBE always uses QoS 1
+ }
+ MqttPacketType::Pingreq | MqttPacketType::Pingresp => {
+ // No variable header or payload
+ }
+ _ => {}
+ }
+
+ debug!("MQTT analysis result: {:?}", info);
+ Some(info)
+}
+
+/// Decode MQTT variable-length encoding starting at `offset`.
+/// Returns (decoded_length, bytes_consumed).
+fn decode_remaining_length(payload: &[u8], offset: usize) -> Option<(usize, usize)> {
+ let mut multiplier: usize = 1;
+ let mut value: usize = 0;
+
+ for i in 0..4 {
+ let idx = offset + i;
+ if idx >= payload.len() {
+ return None;
+ }
+ let byte = payload[idx] as usize;
+ value += (byte & 0x7F) * multiplier;
+ multiplier *= 128;
+ if byte & 0x80 == 0 {
+ return Some((value, i + 1));
+ }
+ }
+ None // More than 4 bytes is invalid
+}
+
+/// Check if payload contains "MQTT" or "MQIsdp" protocol name in a CONNECT packet.
+fn has_mqtt_protocol_name(payload: &[u8]) -> bool {
+ if payload.len() < 8 {
+ return false;
+ }
+
+ let (_, header_len) = decode_remaining_length(payload, 1).unwrap_or((0, 1));
+ let var_start = 1 + header_len;
+
+ // Protocol name is a length-prefixed string
+ if var_start + 2 > payload.len() {
+ return false;
+ }
+
+ let name_len = u16::from_be_bytes([payload[var_start], payload[var_start + 1]]) as usize;
+ let name_start = var_start + 2;
+ let name_end = name_start + name_len;
+
+ if name_end > payload.len() {
+ return false;
+ }
+
+ let name = &payload[name_start..name_end];
+ name == b"MQTT" || name == b"MQIsdp"
+}
+
+/// Parse a CONNECT packet to extract version, client ID.
+fn parse_connect(payload: &[u8], var_start: usize, info: &mut MqttInfo) {
+ // Protocol Name (length-prefixed string)
+ if var_start + 2 > payload.len() {
+ return;
+ }
+ let name_len = u16::from_be_bytes([payload[var_start], payload[var_start + 1]]) as usize;
+ let after_name = var_start + 2 + name_len;
+
+ // Protocol Level byte
+ if after_name >= payload.len() {
+ return;
+ }
+ let level = payload[after_name];
+ info.version = match level {
+ 3 => Some(MqttVersion::V31),
+ 4 => Some(MqttVersion::V311),
+ 5 => Some(MqttVersion::V50),
+ _ => None,
+ };
+
+ // Connect Flags (1 byte) + Keep Alive (2 bytes) = 3 bytes after level
+ let payload_start = after_name + 4; // level(1) + flags(1) + keepalive(2)
+
+ // For MQTT v5, skip properties length
+ let payload_start = if level == 5 {
+ skip_mqtt5_properties(payload, payload_start)
+ } else {
+ payload_start
+ };
+
+ // Client ID (length-prefixed string)
+ if payload_start + 2 > payload.len() {
+ return;
+ }
+ let client_id_len =
+ u16::from_be_bytes([payload[payload_start], payload[payload_start + 1]]) as usize;
+ let client_id_start = payload_start + 2;
+ let client_id_end = client_id_start + client_id_len;
+
+ if client_id_end > payload.len() {
+ return;
+ }
+
+ if let Ok(client_id) = std::str::from_utf8(&payload[client_id_start..client_id_end])
+ && !client_id.is_empty()
+ {
+ info.client_id = Some(client_id.to_string());
+ }
+}
+
+/// Parse the topic from a PUBLISH packet.
+fn parse_publish_topic(payload: &[u8], var_start: usize, info: &mut MqttInfo) {
+ if var_start + 2 > payload.len() {
+ return;
+ }
+
+ let topic_len = u16::from_be_bytes([payload[var_start], payload[var_start + 1]]) as usize;
+ let topic_start = var_start + 2;
+ let topic_end = topic_start + topic_len;
+
+ if topic_end > payload.len() {
+ return;
+ }
+
+ if let Ok(topic) = std::str::from_utf8(&payload[topic_start..topic_end])
+ && !topic.is_empty()
+ {
+ info.topic = Some(topic.to_string());
+ }
+}
+
+/// Skip MQTT v5 properties section, returning the new offset.
+fn skip_mqtt5_properties(payload: &[u8], offset: usize) -> usize {
+ if let Some((prop_len, consumed)) = decode_remaining_length(payload, offset) {
+ offset + consumed + prop_len
+ } else {
+ offset
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ /// Helper: build a CONNECT packet.
+ fn build_connect(protocol_name: &[u8], level: u8, client_id: &str) -> Vec {
+ let name_len = protocol_name.len();
+ // variable header: name_len(2) + name + level(1) + flags(1) + keepalive(2) + client_id_len(2) + client_id
+ let var_len = 2 + name_len + 1 + 1 + 2 + 2 + client_id.len();
+
+ let mut pkt = Vec::new();
+ pkt.push(0x10); // CONNECT type (1 << 4)
+ // Remaining length (simple single-byte for test payloads < 128)
+ pkt.push(var_len as u8);
+ // Protocol name
+ pkt.extend_from_slice(&(name_len as u16).to_be_bytes());
+ pkt.extend_from_slice(protocol_name);
+ // Protocol level
+ pkt.push(level);
+ // Connect flags (clean session)
+ pkt.push(0x02);
+ // Keep alive
+ pkt.extend_from_slice(&60u16.to_be_bytes());
+ // Client ID
+ pkt.extend_from_slice(&(client_id.len() as u16).to_be_bytes());
+ pkt.extend_from_slice(client_id.as_bytes());
+ pkt
+ }
+
+ /// Helper: build a PUBLISH packet.
+ fn build_publish(topic: &str, qos: u8, payload_data: &[u8]) -> Vec {
+ let flags = (qos & 0x03) << 1;
+ let packet_id_len = if qos > 0 { 2 } else { 0 };
+ let var_len = 2 + topic.len() + packet_id_len + payload_data.len();
+
+ let mut pkt = Vec::new();
+ pkt.push(0x30 | flags); // PUBLISH type (3 << 4) | flags
+ pkt.push(var_len as u8);
+ // Topic
+ pkt.extend_from_slice(&(topic.len() as u16).to_be_bytes());
+ pkt.extend_from_slice(topic.as_bytes());
+ // Packet ID (for QoS > 0)
+ if qos > 0 {
+ pkt.extend_from_slice(&1u16.to_be_bytes());
+ }
+ pkt.extend_from_slice(payload_data);
+ pkt
+ }
+
+ #[test]
+ fn test_empty_payload_safe() {
+ // This simulates a potential DoS attack with an empty packet
+ // Should return None, not panic
+ assert!(analyze_mqtt(&[]).is_none());
+ }
+
+ #[test]
+ fn test_connect_v311() {
+ let pkt = build_connect(b"MQTT", 4, "my-client");
+ assert!(is_mqtt_packet(&pkt));
+
+ let info = analyze_mqtt(&pkt).unwrap();
+ assert_eq!(info.packet_type, MqttPacketType::Connect);
+ assert_eq!(info.version, Some(MqttVersion::V311));
+ assert_eq!(info.client_id.as_deref(), Some("my-client"));
+ }
+
+ #[test]
+ fn test_connect_v31_mqisdp() {
+ let pkt = build_connect(b"MQIsdp", 3, "old-device");
+ assert!(is_mqtt_packet(&pkt));
+
+ let info = analyze_mqtt(&pkt).unwrap();
+ assert_eq!(info.packet_type, MqttPacketType::Connect);
+ assert_eq!(info.version, Some(MqttVersion::V31));
+ assert_eq!(info.client_id.as_deref(), Some("old-device"));
+ }
+
+ #[test]
+ fn test_connect_v50() {
+ // Build a v5 CONNECT with properties length = 0
+ let mut pkt = Vec::new();
+ pkt.push(0x10); // CONNECT
+ // We'll fill remaining length at the end
+ let var_header_start = pkt.len();
+ pkt.push(0); // placeholder
+
+ // Protocol name
+ pkt.extend_from_slice(&4u16.to_be_bytes());
+ pkt.extend_from_slice(b"MQTT");
+ // Level 5
+ pkt.push(5);
+ // Flags
+ pkt.push(0x02);
+ // Keep alive
+ pkt.extend_from_slice(&60u16.to_be_bytes());
+ // Properties length = 0
+ pkt.push(0);
+ // Client ID
+ let client = "v5-client";
+ pkt.extend_from_slice(&(client.len() as u16).to_be_bytes());
+ pkt.extend_from_slice(client.as_bytes());
+
+ // Fix remaining length
+ pkt[var_header_start] = (pkt.len() - var_header_start - 1) as u8;
+
+ let info = analyze_mqtt(&pkt).unwrap();
+ assert_eq!(info.version, Some(MqttVersion::V50));
+ assert_eq!(info.client_id.as_deref(), Some("v5-client"));
+ }
+
+ #[test]
+ fn test_connack() {
+ // CONNACK: type 2, remaining length 2, session present=0, return code=0
+ // Not detected by is_mqtt_packet (only CONNECT has strong signature),
+ // but analyze_mqtt still parses it (used via port-based detection path)
+ let pkt = vec![0x20, 0x02, 0x00, 0x00];
+ assert!(!is_mqtt_packet(&pkt));
+
+ let info = analyze_mqtt(&pkt).unwrap();
+ assert_eq!(info.packet_type, MqttPacketType::Connack);
+ }
+
+ #[test]
+ fn test_publish_qos0() {
+ let pkt = build_publish("home/temp", 0, b"22.5");
+
+ let info = analyze_mqtt(&pkt).unwrap();
+ assert_eq!(info.packet_type, MqttPacketType::Publish);
+ assert_eq!(info.topic.as_deref(), Some("home/temp"));
+ assert_eq!(info.qos, Some(0));
+ }
+
+ #[test]
+ fn test_publish_qos1() {
+ let pkt = build_publish("sensors/humidity", 1, b"65");
+
+ let info = analyze_mqtt(&pkt).unwrap();
+ assert_eq!(info.packet_type, MqttPacketType::Publish);
+ assert_eq!(info.topic.as_deref(), Some("sensors/humidity"));
+ assert_eq!(info.qos, Some(1));
+ }
+
+ #[test]
+ fn test_subscribe() {
+ // SUBSCRIBE: type 8, flags 0x02, packet id, topic filter
+ let topic = "home/#";
+ let var_len = 2 + 2 + topic.len() + 1; // packet_id + topic_len + topic + qos
+ let mut pkt = vec![0x82, var_len as u8];
+ pkt.extend_from_slice(&1u16.to_be_bytes()); // packet ID
+ pkt.extend_from_slice(&(topic.len() as u16).to_be_bytes());
+ pkt.extend_from_slice(topic.as_bytes());
+ pkt.push(0x01); // QoS 1
+
+ let info = analyze_mqtt(&pkt).unwrap();
+ assert_eq!(info.packet_type, MqttPacketType::Subscribe);
+ assert_eq!(info.qos, Some(1));
+ }
+
+ #[test]
+ fn test_pingreq() {
+ let pkt = vec![0xC0, 0x00];
+ assert!(!is_mqtt_packet(&pkt));
+
+ let info = analyze_mqtt(&pkt).unwrap();
+ assert_eq!(info.packet_type, MqttPacketType::Pingreq);
+ }
+
+ #[test]
+ fn test_pingresp() {
+ let pkt = vec![0xD0, 0x00];
+ assert!(!is_mqtt_packet(&pkt));
+
+ let info = analyze_mqtt(&pkt).unwrap();
+ assert_eq!(info.packet_type, MqttPacketType::Pingresp);
+ }
+
+ #[test]
+ fn test_disconnect() {
+ let pkt = vec![0xE0, 0x00];
+ let info = analyze_mqtt(&pkt).unwrap();
+ assert_eq!(info.packet_type, MqttPacketType::Disconnect);
+ }
+
+ #[test]
+ fn test_invalid_type() {
+ // Type 0 is reserved/invalid
+ let pkt = vec![0x00, 0x00];
+ assert!(!is_mqtt_packet(&pkt));
+ assert!(analyze_mqtt(&pkt).is_none());
+ }
+
+ #[test]
+ fn test_type_15_auth() {
+ // Type 15 is AUTH in MQTT 5. Not signature-detected (only CONNECT
+ // is), but the port-based path must classify it.
+ let pkt = vec![0xF0, 0x00];
+ assert!(!is_mqtt_packet(&pkt));
+ let info = analyze_mqtt(&pkt).unwrap();
+ assert_eq!(info.packet_type, MqttPacketType::Auth);
+ }
+
+ #[test]
+ fn test_qos2_flow_packets() {
+ // PUBREC / PUBREL / PUBCOMP make up the QoS 2 delivery flow;
+ // PUBREL carries the reserved flags 0x02 (MQTT spec §2.1.2).
+ let pubrec = vec![0x50, 0x02, 0x00, 0x01];
+ assert_eq!(
+ analyze_mqtt(&pubrec).unwrap().packet_type,
+ MqttPacketType::Pubrec
+ );
+
+ let pubrel = vec![0x62, 0x02, 0x00, 0x01];
+ assert_eq!(
+ analyze_mqtt(&pubrel).unwrap().packet_type,
+ MqttPacketType::Pubrel
+ );
+
+ let pubcomp = vec![0x70, 0x02, 0x00, 0x01];
+ assert_eq!(
+ analyze_mqtt(&pubcomp).unwrap().packet_type,
+ MqttPacketType::Pubcomp
+ );
+ }
+
+ #[test]
+ fn test_too_short() {
+ assert!(!is_mqtt_packet(&[]));
+ assert!(!is_mqtt_packet(&[0x10]));
+ assert!(analyze_mqtt(&[]).is_none());
+ }
+
+ #[test]
+ fn test_connect_without_mqtt_name_rejected() {
+ // CONNECT type but garbage protocol name
+ let pkt = vec![0x10, 0x08, 0x00, 0x04, b'X', b'Y', b'Z', b'W', 4, 0];
+ assert!(!is_mqtt_packet(&pkt));
+ }
+
+ #[test]
+ fn test_non_connect_not_detected_by_signature() {
+ // Non-CONNECT packets are not detected by is_mqtt_packet (signature-based).
+ // They are only detected via port 1883 matching in the caller.
+ let pkt = vec![0x20, 0x02, 0x00, 0x00]; // CONNACK
+ assert!(!is_mqtt_packet(&pkt));
+ }
+
+ #[test]
+ fn test_multibyte_remaining_length() {
+ // Non-CONNECT with multi-byte length should NOT be detected by signature
+ let mut pkt = vec![0xC0, 0xC8, 0x01]; // PINGREQ with 200-byte remaining
+ pkt.extend(vec![0; 200]);
+ assert!(!is_mqtt_packet(&pkt));
+
+ // Multi-byte remaining length decoding is still exercised via analyze_mqtt
+ let info = analyze_mqtt(&pkt).unwrap();
+ assert_eq!(info.packet_type, MqttPacketType::Pingreq);
+ }
+
+ #[test]
+ fn test_invalid_remaining_length() {
+ // All continuation bits set (> 4 bytes) — invalid
+ let pkt = vec![0x20, 0x80, 0x80, 0x80, 0x80, 0x01];
+ assert!(!is_mqtt_packet(&pkt));
+ }
+
+ #[test]
+ fn test_connect_empty_client_id() {
+ let pkt = build_connect(b"MQTT", 4, "");
+ let info = analyze_mqtt(&pkt).unwrap();
+ assert_eq!(info.packet_type, MqttPacketType::Connect);
+ assert_eq!(info.client_id, None); // empty string stored as None
+ }
+
+ #[test]
+ fn test_http_not_mqtt() {
+ let payload = b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n";
+ assert!(!is_mqtt_packet(payload));
+ }
+
+ #[test]
+ fn test_binary_data_not_false_positive_mqtt() {
+ // Bytes resembling MQTT PINGREQ from random BitTorrent file transfer data
+ assert!(!is_mqtt_packet(&[0xC0, 0x00]));
+ assert!(!is_mqtt_packet(&[0xC0, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]));
+
+ // Bytes resembling MQTT UNSUBSCRIBE
+ assert!(!is_mqtt_packet(&[0xA2, 0x05, 0x00, 0x01, 0x00, 0x01, 0x00]));
+
+ // Bytes resembling MQTT PUBLISH
+ assert!(!is_mqtt_packet(&[
+ 0x30, 0x0A, 0x00, 0x04, b't', b'e', b's', b't'
+ ]));
+
+ // Bytes resembling MQTT DISCONNECT
+ assert!(!is_mqtt_packet(&[0xE0, 0x00]));
+ }
+}
diff --git a/crates/rustnet-core/src/network/dpi/netbios.rs b/crates/rustnet-core/src/network/dpi/netbios.rs
new file mode 100644
index 0000000..a46a1c0
--- /dev/null
+++ b/crates/rustnet-core/src/network/dpi/netbios.rs
@@ -0,0 +1,337 @@
+//! NetBIOS Deep Packet Inspection
+//!
+//! Parses NetBIOS Name Service (UDP 137) and Datagram Service (UDP 138) packets.
+
+use crate::network::types::{NetBiosInfo, NetBiosOpcode, NetBiosService};
+
+/// Minimum NetBIOS Name Service packet size
+const MIN_NBNS_SIZE: usize = 12;
+
+/// Datagram Service header sizes per RFC 1002 §4.4: DIRECT_UNIQUE /
+/// DIRECT_GROUP / BROADCAST messages have a 14-byte header (through
+/// PACKET_OFFSET), QUERY REQUEST/RESPONSE messages a 10-byte header
+/// (through SOURCE_PORT), and a DATAGRAM ERROR is 11 bytes (10-byte
+/// header plus the error code).
+const NBDGM_DIRECT_HEADER: usize = 14;
+const NBDGM_QUERY_HEADER: usize = 10;
+const NBDGM_ERROR_SIZE: usize = 11;
+
+/// Analyze a NetBIOS Name Service packet (UDP port 137).
+///
+/// Returns `None` if the packet is too small or invalid.
+pub fn analyze_netbios_ns(payload: &[u8]) -> Option {
+ if payload.len() < MIN_NBNS_SIZE {
+ return None;
+ }
+
+ // Parse header flags at bytes 2-3
+ let flags = u16::from_be_bytes([payload[2], payload[3]]);
+
+ // Extract opcode from flags (bits 11-14)
+ let opcode_value = ((flags >> 11) & 0x0F) as u8;
+ let is_response = (flags & 0x8000) != 0;
+
+ let opcode = if is_response {
+ NetBiosOpcode::Response
+ } else {
+ parse_opcode(opcode_value)
+ };
+
+ // Try to decode NetBIOS name if present
+ let name = if payload.len() > 12 {
+ decode_netbios_name(&payload[12..])
+ } else {
+ None
+ };
+
+ Some(NetBiosInfo {
+ service: NetBiosService::NameService,
+ opcode,
+ name,
+ })
+}
+
+/// Analyze a NetBIOS Datagram Service packet (UDP port 138).
+///
+/// Returns `None` if the packet is too small or invalid.
+pub fn analyze_netbios_dgm(payload: &[u8]) -> Option {
+ // Message type at byte 0
+ let msg_type = *payload.first()?;
+
+ // RFC 1002 §4.4: header size — and therefore where the encoded name
+ // starts — depends on the message type.
+ let (opcode, min_size, name_offset) = match msg_type {
+ // §4.4.1 DIRECT_UNIQUE / DIRECT_GROUP / BROADCAST: message
+ // delivery, SOURCE_NAME follows the 14-byte header
+ 0x10..=0x12 => (
+ NetBiosOpcode::Datagram,
+ NBDGM_DIRECT_HEADER,
+ Some(NBDGM_DIRECT_HEADER),
+ ),
+ // §4.4.2 DATAGRAM ERROR: header + error code, no name
+ 0x13 => (NetBiosOpcode::Error, NBDGM_ERROR_SIZE, None),
+ // §4.4.3 DATAGRAM QUERY REQUEST: DESTINATION_NAME follows the
+ // 10-byte header
+ 0x14 => (
+ NetBiosOpcode::Query,
+ NBDGM_QUERY_HEADER,
+ Some(NBDGM_QUERY_HEADER),
+ ),
+ // §4.4.3 POSITIVE / NEGATIVE QUERY RESPONSE
+ 0x15 | 0x16 => (
+ NetBiosOpcode::Response,
+ NBDGM_QUERY_HEADER,
+ Some(NBDGM_QUERY_HEADER),
+ ),
+ other => (NetBiosOpcode::Unknown(other), NBDGM_QUERY_HEADER, None),
+ };
+
+ if payload.len() < min_size {
+ return None;
+ }
+
+ let name = name_offset
+ .and_then(|off| payload.get(off..))
+ .and_then(decode_netbios_name);
+
+ Some(NetBiosInfo {
+ service: NetBiosService::DatagramService,
+ opcode,
+ name,
+ })
+}
+
+/// Parse NetBIOS opcode from the flags field
+fn parse_opcode(value: u8) -> NetBiosOpcode {
+ match value {
+ 0 => NetBiosOpcode::Query,
+ 5 => NetBiosOpcode::Registration,
+ 6 => NetBiosOpcode::Release,
+ 7 => NetBiosOpcode::Wack,
+ 8 => NetBiosOpcode::Refresh,
+ other => NetBiosOpcode::Unknown(other),
+ }
+}
+
+/// Decode a NetBIOS "first-level" encoded name.
+///
+/// NetBIOS names are encoded as 32 bytes representing 16 characters:
+/// - Each character is split into two nibbles
+/// - Each nibble is encoded as 'A' + nibble_value
+fn decode_netbios_name(data: &[u8]) -> Option {
+ // Need at least length byte + 32 encoded bytes
+ if data.is_empty() {
+ return None;
+ }
+
+ let name_len = data[0] as usize;
+
+ // Standard NetBIOS encoded name is 32 bytes
+ if name_len != 32 || data.len() < 33 {
+ return None;
+ }
+
+ let mut name = String::with_capacity(16);
+
+ for i in 0..16 {
+ let idx = 1 + i * 2;
+ if idx + 1 >= data.len() {
+ break;
+ }
+
+ let hi = data[idx];
+ let lo = data[idx + 1];
+
+ // Validate encoding (should be 'A'-'P' range)
+ if !(b'A'..=b'P').contains(&hi) || !(b'A'..=b'P').contains(&lo) {
+ return None;
+ }
+
+ let hi_nibble = (hi - b'A') << 4;
+ let lo_nibble = lo - b'A';
+ let c = hi_nibble | lo_nibble;
+
+ // Keep only printable ASCII: this naturally drops the trailing
+ // service-type suffix (0x00/0x1B/etc.) and padding spaces (0x20),
+ // since `is_ascii_graphic` covers 0x21..=0x7E exclusively.
+ if c.is_ascii_graphic() {
+ name.push(c as char);
+ }
+ }
+
+ if name.is_empty() { None } else { Some(name) }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn encode_netbios_name(name: &str) -> Vec {
+ let mut encoded = Vec::with_capacity(33);
+ encoded.push(32); // Length byte
+
+ // Pad name to 15 chars + suffix byte
+ let padded: Vec = name
+ .bytes()
+ .chain(std::iter::repeat(0x20))
+ .take(16)
+ .collect();
+
+ for &b in &padded {
+ encoded.push(b'A' + ((b >> 4) & 0x0F));
+ encoded.push(b'A' + (b & 0x0F));
+ }
+
+ encoded
+ }
+
+ fn build_nbns_query(name: &str) -> Vec {
+ let mut packet = Vec::new();
+
+ // Transaction ID
+ packet.extend_from_slice(&[0x00, 0x01]);
+ // Flags: Query (opcode 0)
+ packet.extend_from_slice(&[0x01, 0x10]);
+ // Question count: 1
+ packet.extend_from_slice(&[0x00, 0x01]);
+ // Answer, Authority, Additional counts: 0
+ packet.extend_from_slice(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
+
+ // Encoded name
+ packet.extend_from_slice(&encode_netbios_name(name));
+
+ // Null terminator and QTYPE/QCLASS
+ packet.push(0x00);
+ packet.extend_from_slice(&[0x00, 0x20, 0x00, 0x01]);
+
+ packet
+ }
+
+ fn build_nbns_response(name: &str) -> Vec {
+ let mut packet = Vec::new();
+
+ // Transaction ID
+ packet.extend_from_slice(&[0x00, 0x01]);
+ // Flags: Response
+ packet.extend_from_slice(&[0x85, 0x00]);
+ // Counts
+ packet.extend_from_slice(&[0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00]);
+
+ // Encoded name
+ packet.extend_from_slice(&encode_netbios_name(name));
+
+ packet
+ }
+
+ #[test]
+ fn test_nbns_query() {
+ let packet = build_nbns_query("WORKSTATION");
+ let info = analyze_netbios_ns(&packet).expect("should parse");
+ assert_eq!(info.service, NetBiosService::NameService);
+ assert_eq!(info.opcode, NetBiosOpcode::Query);
+ assert_eq!(info.name, Some("WORKSTATION".to_string()));
+ }
+
+ #[test]
+ fn test_nbns_response() {
+ let packet = build_nbns_response("FILESERVER");
+ let info = analyze_netbios_ns(&packet).expect("should parse");
+ assert_eq!(info.service, NetBiosService::NameService);
+ assert_eq!(info.opcode, NetBiosOpcode::Response);
+ assert_eq!(info.name, Some("FILESERVER".to_string()));
+ }
+
+ #[test]
+ fn test_nbns_too_short() {
+ let packet = [0u8; 5];
+ assert!(analyze_netbios_ns(&packet).is_none());
+ }
+
+ #[test]
+ fn test_nbdgm_direct() {
+ let mut packet = vec![0u8; 14];
+ packet[0] = 0x10; // Direct unique datagram
+ packet.extend_from_slice(&encode_netbios_name("SENDERPC")); // SOURCE_NAME
+ let info = analyze_netbios_dgm(&packet).expect("should parse");
+ assert_eq!(info.service, NetBiosService::DatagramService);
+ assert_eq!(info.opcode, NetBiosOpcode::Datagram);
+ assert_eq!(info.name, Some("SENDERPC".to_string()));
+ }
+
+ #[test]
+ fn test_nbdgm_broadcast_is_datagram_not_registration() {
+ // 0x12 is a broadcast datagram (RFC 1002 §4.4.1) — it has nothing
+ // to do with NBNS name registration.
+ let mut packet = vec![0u8; 14];
+ packet[0] = 0x12;
+ let info = analyze_netbios_dgm(&packet).expect("should parse");
+ assert_eq!(info.opcode, NetBiosOpcode::Datagram);
+ }
+
+ #[test]
+ fn test_nbdgm_query_request_name_at_offset_10() {
+ // §4.4.3 messages have a 10-byte header; DESTINATION_NAME starts at
+ // offset 10, not 14.
+ let mut packet = vec![0u8; 10];
+ packet[0] = 0x14; // DATAGRAM QUERY REQUEST
+ packet.extend_from_slice(&encode_netbios_name("FILESERVER"));
+ let info = analyze_netbios_dgm(&packet).expect("should parse");
+ assert_eq!(info.opcode, NetBiosOpcode::Query);
+ assert_eq!(info.name, Some("FILESERVER".to_string()));
+ }
+
+ #[test]
+ fn test_nbdgm_error_datagram() {
+ // A spec-conformant DATAGRAM ERROR is exactly 11 bytes
+ // (§4.4.2: 10-byte header + 1-byte error code).
+ let mut packet = vec![0u8; 11];
+ packet[0] = 0x13;
+ packet[10] = 0x82; // ERR_SOURCE_NAME_BAD_FORMAT
+ let info = analyze_netbios_dgm(&packet).expect("should parse");
+ assert_eq!(info.opcode, NetBiosOpcode::Error);
+ assert_eq!(info.name, None);
+ }
+
+ #[test]
+ fn test_nbdgm_too_short() {
+ let packet = [0u8; 5];
+ assert!(analyze_netbios_dgm(&packet).is_none());
+ }
+
+ #[test]
+ fn test_decode_netbios_name() {
+ // Encode "TEST"
+ let encoded = encode_netbios_name("TEST");
+ let name = decode_netbios_name(&encoded).expect("should decode");
+ assert_eq!(name, "TEST");
+ }
+
+ #[test]
+ fn test_decode_netbios_name_with_padding() {
+ // Encode "PC" - should trim padding
+ let encoded = encode_netbios_name("PC");
+ let name = decode_netbios_name(&encoded).expect("should decode");
+ assert_eq!(name, "PC");
+ }
+
+ #[test]
+ fn test_decode_netbios_name_strips_service_type_suffix() {
+ // Real NetBIOS names embed a service-type byte at the 16th position
+ // (workstation=0x00, file_server=0x20, master_browser=0x1D, etc.).
+ // The non-printable suffix must NOT appear in the decoded name —
+ // decoding only keeps `is_ascii_graphic` characters (0x21..=0x7E).
+ let mut encoded = Vec::with_capacity(33);
+ encoded.push(32);
+ let padded = b"WORKSTATION "; // 15 chars
+ for &b in padded {
+ encoded.push(b'A' + ((b >> 4) & 0x0F));
+ encoded.push(b'A' + (b & 0x0F));
+ }
+ // 16th byte = service-type 0x00 (workstation)
+ encoded.push(b'A');
+ encoded.push(b'A');
+
+ let decoded = decode_netbios_name(&encoded).expect("should decode");
+ assert_eq!(decoded, "WORKSTATION");
+ }
+}
diff --git a/crates/rustnet-core/src/network/dpi/ntp.rs b/crates/rustnet-core/src/network/dpi/ntp.rs
new file mode 100644
index 0000000..1246688
--- /dev/null
+++ b/crates/rustnet-core/src/network/dpi/ntp.rs
@@ -0,0 +1,108 @@
+//! NTP (Network Time Protocol) Deep Packet Inspection
+//!
+//! Parses NTP packets according to RFC 5905.
+//! NTP uses UDP port 123.
+
+use crate::network::types::{NtpInfo, NtpMode};
+
+/// Minimum NTP packet size (48 bytes for NTPv3/v4)
+const MIN_NTP_PACKET_SIZE: usize = 48;
+
+/// Analyze an NTP packet and extract key information.
+///
+/// Returns `None` if the packet is too small or has invalid version.
+pub fn analyze_ntp(payload: &[u8]) -> Option {
+ // Early size check - NTP packets are at least 48 bytes
+ if payload.len() < MIN_NTP_PACKET_SIZE {
+ return None;
+ }
+
+ // First byte contains: LI (2 bits) | VN (3 bits) | Mode (3 bits)
+ let first_byte = payload[0];
+ let version = (first_byte >> 3) & 0x07;
+ let mode = first_byte & 0x07;
+ let stratum = payload[1];
+
+ // Validate version (1-4 are valid, 3 and 4 are common)
+ if !(1..=4).contains(&version) {
+ return None;
+ }
+
+ Some(NtpInfo {
+ version,
+ mode: NtpMode::from(mode),
+ stratum,
+ })
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn build_ntp_packet(version: u8, mode: u8, stratum: u8) -> [u8; 48] {
+ let mut packet = [0u8; 48];
+ // LI=0 (2 bits), VN=version (3 bits), Mode=mode (3 bits)
+ packet[0] = (version << 3) | mode;
+ packet[1] = stratum;
+ packet
+ }
+
+ #[test]
+ fn test_ntp_client_request() {
+ let packet = build_ntp_packet(4, 3, 0); // v4, client mode, stratum 0
+ let info = analyze_ntp(&packet).expect("should parse");
+ assert_eq!(info.version, 4);
+ assert_eq!(info.mode, NtpMode::Client);
+ assert_eq!(info.stratum, 0);
+ }
+
+ #[test]
+ fn test_ntp_server_response() {
+ let packet = build_ntp_packet(4, 4, 2); // v4, server mode, stratum 2
+ let info = analyze_ntp(&packet).expect("should parse");
+ assert_eq!(info.version, 4);
+ assert_eq!(info.mode, NtpMode::Server);
+ assert_eq!(info.stratum, 2);
+ }
+
+ #[test]
+ fn test_ntp_v3_broadcast() {
+ let packet = build_ntp_packet(3, 5, 1); // v3, broadcast mode, stratum 1
+ let info = analyze_ntp(&packet).expect("should parse");
+ assert_eq!(info.version, 3);
+ assert_eq!(info.mode, NtpMode::Broadcast);
+ assert_eq!(info.stratum, 1);
+ }
+
+ #[test]
+ fn test_ntp_too_short() {
+ let packet = [0x23; 10]; // Too short
+ assert!(analyze_ntp(&packet).is_none());
+ }
+
+ #[test]
+ fn test_ntp_invalid_version_zero() {
+ let packet = build_ntp_packet(0, 3, 0); // Invalid version 0
+ assert!(analyze_ntp(&packet).is_none());
+ }
+
+ #[test]
+ fn test_ntp_invalid_version_high() {
+ let packet = build_ntp_packet(7, 3, 0); // Invalid version 7
+ assert!(analyze_ntp(&packet).is_none());
+ }
+
+ #[test]
+ fn test_ntp_symmetric_active() {
+ let packet = build_ntp_packet(4, 1, 3);
+ let info = analyze_ntp(&packet).expect("should parse");
+ assert_eq!(info.mode, NtpMode::SymmetricActive);
+ }
+
+ #[test]
+ fn test_ntp_symmetric_passive() {
+ let packet = build_ntp_packet(4, 2, 3);
+ let info = analyze_ntp(&packet).expect("should parse");
+ assert_eq!(info.mode, NtpMode::SymmetricPassive);
+ }
+}
diff --git a/crates/rustnet-core/src/network/dpi/quic.rs b/crates/rustnet-core/src/network/dpi/quic.rs
new file mode 100644
index 0000000..09b94eb
--- /dev/null
+++ b/crates/rustnet-core/src/network/dpi/quic.rs
@@ -0,0 +1,2940 @@
+use crate::network::types::{
+ CryptoFrameReassembler, QuicConnectionState, QuicInfo, QuicPacketType, TlsInfo, TlsVersion,
+};
+use aes::Aes128;
+use aes::cipher::{BlockCipherEncrypt, KeyInit};
+use log::{debug, warn};
+use ring::aead::{Aad, LessSafeKey, Nonce, UnboundKey};
+use ring::{aead, hkdf};
+
+// QUIC v1 Initial salt (from RFC 9001)
+const INITIAL_SALT_V1: &[u8] = &[
+ 0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad,
+ 0xcc, 0xbb, 0x7f, 0x0a,
+];
+
+// QUIC v2 Initial salt
+const INITIAL_SALT_V2: &[u8] = &[
+ 0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, 0x81, 0xbe, 0x6e, 0x26, 0x9d, 0xcb,
+ 0xf9, 0xbd, 0x2e, 0xd9,
+];
+
+// Initial salt for IETF drafts 29-32 (and drafts 33-34 reused the v1 salt)
+const INITIAL_SALT_DRAFT_29: &[u8] = &[
+ 0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0,
+ 0x43, 0x90, 0xa8, 0x99,
+];
+
+// Initial salt for IETF drafts 23-28 (also used by Facebook mvfst 0xfaceb002 = draft-27)
+const INITIAL_SALT_DRAFT_23: &[u8] = &[
+ 0xc3, 0xee, 0xf7, 0x12, 0xc7, 0x2e, 0xbb, 0x5a, 0x11, 0xa7, 0xd2, 0x43, 0x2b, 0xb4, 0x63, 0x65,
+ 0xbe, 0xf9, 0xf5, 0x02,
+];
+
+/// Select the Initial salt for a QUIC version (RFC 9001 §5.2, RFC 9369 §3.3.1,
+/// and the corresponding draft revisions).
+fn initial_salt_for_version(version: u32) -> &'static [u8] {
+ if is_quic_v2(version) {
+ return INITIAL_SALT_V2;
+ }
+ match version {
+ 0xff00_001d..=0xff00_0020 => INITIAL_SALT_DRAFT_29, // drafts 29-32
+ 0xff00_0017..=0xff00_001c | 0xface_b002 => INITIAL_SALT_DRAFT_23, // drafts 23-28, mvfst
+ _ => INITIAL_SALT_V1, // v1, drafts 33-34, and unknown versions
+ }
+}
+
+// ============================================================================
+// SNI Validation and Parsing Helpers
+// ============================================================================
+
+/// Minimum length for partial SNI extraction
+const PARTIAL_SNI_MIN_LENGTH: usize = 3;
+
+/// Marker suffix for partial SNI values
+const PARTIAL_SNI_MARKER: &str = "[PARTIAL]";
+
+/// Validate if a string looks like a valid complete hostname
+///
+/// This is the unified hostname validation function used across all SNI extraction methods.
+/// Rules:
+/// - Length between 4 and 253 characters
+/// - Contains at least one '.'
+/// - Only ASCII alphanumeric, '.', and '-' characters
+/// - Doesn't start or end with '.' or '-'
+/// - No consecutive dots '..'
+/// - Has at least one alphabetic character
+/// - Each label is non-empty and at most 63 characters
+fn is_valid_hostname(hostname: &str) -> bool {
+ // Length check
+ if hostname.len() < 4 || hostname.len() > 253 {
+ return false;
+ }
+
+ // Must contain at least one dot
+ if !hostname.contains('.') {
+ return false;
+ }
+
+ // Check for valid hostname characters only
+ if !hostname
+ .chars()
+ .all(|c| c.is_ascii_alphanumeric() || c == '.' || c == '-')
+ {
+ return false;
+ }
+
+ // Must not start or end with a dot or hyphen
+ if hostname.starts_with('.')
+ || hostname.ends_with('.')
+ || hostname.starts_with('-')
+ || hostname.ends_with('-')
+ {
+ return false;
+ }
+
+ // Must not contain consecutive dots
+ if hostname.contains("..") {
+ return false;
+ }
+
+ // Must have at least one alphabetic character (not just numbers and dots)
+ if !hostname.chars().any(|c| c.is_ascii_alphabetic()) {
+ return false;
+ }
+
+ // Each label must be non-empty and at most 63 characters
+ let parts: Vec<&str> = hostname.split('.').collect();
+ if parts.len() < 2 {
+ return false;
+ }
+ if !parts
+ .iter()
+ .all(|part| !part.is_empty() && part.len() <= 63)
+ {
+ return false;
+ }
+
+ true
+}
+
+/// Validate if a string looks like a valid partial hostname
+///
+/// Partial hostnames have relaxed rules since they may be truncated:
+/// - Minimum length (PARTIAL_SNI_MIN_LENGTH)
+/// - Only ASCII alphanumeric, '.', and '-' characters
+/// - Has at least one alphabetic character
+/// - Doesn't start with '.' or '-'
+fn is_valid_partial_hostname(hostname: &str) -> bool {
+ if hostname.len() < PARTIAL_SNI_MIN_LENGTH {
+ return false;
+ }
+
+ // Check for valid hostname characters only
+ if !hostname
+ .chars()
+ .all(|c| c.is_ascii_alphanumeric() || c == '.' || c == '-')
+ {
+ return false;
+ }
+
+ // Must have at least one alphabetic character
+ if !hostname.chars().any(|c| c.is_ascii_alphabetic()) {
+ return false;
+ }
+
+ // Must not start with '.' or '-'
+ if hostname.starts_with('.') || hostname.starts_with('-') {
+ return false;
+ }
+
+ true
+}
+
+/// Mark an SNI value as partial by appending the marker
+fn mark_partial_sni(hostname: &str) -> String {
+ format!("{}{}", hostname, PARTIAL_SNI_MARKER)
+}
+
+/// Check if an SNI value is marked as partial
+pub fn is_partial_sni(sni: &str) -> bool {
+ sni.ends_with(PARTIAL_SNI_MARKER)
+}
+
+/// Parsed SNI extension header
+struct SniHeader {
+ /// Server name list length
+ list_len: u16,
+ /// Hostname length
+ name_len: u16,
+}
+
+/// Parse the SNI extension header from raw data
+///
+/// Expects data starting at the SNI extension content (after extension type and length):
+/// - 2 bytes: server name list length
+/// - 1 byte: name type (0x00 = hostname)
+/// - 2 bytes: hostname length
+///
+/// Returns None if data is too short or name type is not hostname
+fn parse_sni_header(data: &[u8]) -> Option {
+ if data.len() < 5 {
+ return None;
+ }
+
+ let list_len = u16::from_be_bytes([data[0], data[1]]);
+ let name_type = data[2];
+ let name_len = u16::from_be_bytes([data[3], data[4]]);
+
+ // Name type must be 0x00 (hostname)
+ if name_type != 0x00 {
+ return None;
+ }
+
+ // Validate hostname length is reasonable
+ if name_len == 0 || name_len > 253 {
+ return None;
+ }
+
+ Some(SniHeader { list_len, name_len })
+}
+
+// ============================================================================
+
+/// Main entry point for QUIC packet parsing
+/// Handles coalesced packets - multiple QUIC packets in a single UDP datagram
+pub fn parse_quic_packet(payload: &[u8]) -> Option {
+ if payload.is_empty() {
+ debug!("QUIC: Empty payload");
+ return None;
+ }
+
+ let mut combined_info: Option = None;
+ let mut offset = 0;
+ let mut packet_count = 0;
+
+ // Process all coalesced packets in the UDP datagram
+ while offset < payload.len() {
+ let remaining = &payload[offset..];
+ if remaining.is_empty() {
+ break;
+ }
+
+ let first_byte = remaining[0];
+ let is_long_header = (first_byte & 0x80) != 0;
+
+ debug!(
+ "QUIC: Parsing packet {} at offset {} - first_byte=0x{:02x}, is_long_header={}, remaining_len={}",
+ packet_count + 1,
+ offset,
+ first_byte,
+ is_long_header,
+ remaining.len()
+ );
+
+ let (packet_info, packet_len) = if is_long_header {
+ parse_long_header_packet_with_length(remaining)
+ } else {
+ // Short header packet - consumes rest of datagram (no length field)
+ let info = parse_short_header_packet(remaining);
+ (info, remaining.len())
+ };
+
+ if let Some(info) = packet_info {
+ combined_info = Some(merge_quic_packet_info(combined_info, info));
+ }
+
+ // Move to next packet
+ if packet_len == 0 {
+ // Couldn't determine length, stop processing
+ break;
+ }
+ offset += packet_len;
+ packet_count += 1;
+
+ // Safety limit - don't process more than 10 coalesced packets
+ if packet_count >= 10 {
+ debug!("QUIC: Reached coalesced packet limit (10), stopping");
+ break;
+ }
+ }
+
+ if packet_count > 1 {
+ debug!(
+ "QUIC: Processed {} coalesced packets in UDP datagram",
+ packet_count
+ );
+ }
+
+ combined_info
+}
+
+/// Merge QUIC info from multiple coalesced packets
+/// Prefers more complete information (SNI without `[PARTIAL]`, higher connection state, etc.)
+fn merge_quic_packet_info(existing: Option, new: QuicInfo) -> QuicInfo {
+ match existing {
+ None => new,
+ Some(mut existing) => {
+ // Prefer higher connection state
+ if new.connection_state.priority() > existing.connection_state.priority() {
+ existing.connection_state = new.connection_state;
+ }
+
+ // Merge TLS info - prefer complete SNI over partial
+ match (&existing.tls_info, &new.tls_info) {
+ (None, Some(new_tls)) => {
+ existing.tls_info = Some(new_tls.clone());
+ }
+ (Some(old_tls), Some(new_tls)) => {
+ let old_is_partial = old_tls
+ .sni
+ .as_ref()
+ .map(|s| is_partial_sni(s))
+ .unwrap_or(true);
+ let new_is_partial = new_tls
+ .sni
+ .as_ref()
+ .map(|s| is_partial_sni(s))
+ .unwrap_or(true);
+
+ // Prefer complete SNI over partial, or any SNI over none
+ if (old_is_partial && !new_is_partial)
+ || (old_tls.sni.is_none() && new_tls.sni.is_some())
+ {
+ existing.tls_info = Some(new_tls.clone());
+ }
+ }
+ _ => {}
+ }
+
+ // Update connection ID if we have a better one
+ if existing.connection_id.is_empty() && !new.connection_id.is_empty() {
+ existing.connection_id = new.connection_id;
+ existing.connection_id_hex = new.connection_id_hex;
+ }
+
+ // Update version if we didn't have it
+ if existing.version_string.is_none() && new.version_string.is_some() {
+ existing.version_string = new.version_string;
+ }
+
+ // Merge crypto reassemblers. Both packets can carry CRYPTO frames:
+ // a large ClientHello (e.g. with post-quantum key shares) is split
+ // across two Initial packets that are often coalesced into one
+ // datagram. Dropping the second packet's fragments would lose the
+ // tail of the ClientHello and with it the SNI.
+ match (&mut existing.crypto_reassembler, new.crypto_reassembler) {
+ (None, Some(new_reassembler)) => {
+ existing.crypto_reassembler = Some(new_reassembler);
+ }
+ (Some(existing_reassembler), Some(new_reassembler)) => {
+ for (&frag_offset, data) in new_reassembler.get_fragments() {
+ if let Err(e) = existing_reassembler.add_fragment(frag_offset, data.clone())
+ {
+ warn!("QUIC: Failed to merge coalesced CRYPTO fragment: {}", e);
+ }
+ }
+
+ // Re-extract if the merged fragments can improve on what we have
+ let sni_missing_or_partial = existing
+ .tls_info
+ .as_ref()
+ .and_then(|tls| tls.sni.as_ref())
+ .is_none_or(|s| is_partial_sni(s));
+ if sni_missing_or_partial
+ && let Some(tls_info) =
+ try_extract_tls_from_reassembler(existing_reassembler, false)
+ {
+ existing.tls_info = Some(tls_info);
+ }
+ }
+ _ => {}
+ }
+
+ existing
+ }
+ }
+}
+
+/// Parse a QUIC long header packet and return both the info and the packet length
+/// This is needed for coalesced packet handling
+fn parse_long_header_packet_with_length(payload: &[u8]) -> (Option, usize) {
+ if payload.len() < 6 {
+ return (None, 0);
+ }
+
+ let first_byte = payload[0];
+ let version = u32::from_be_bytes([payload[1], payload[2], payload[3], payload[4]]);
+
+ // Create QuicInfo with version
+ let mut quic_info = QuicInfo::new(version);
+
+ // Determine packet type
+ let packet_type = if version == 0 {
+ QuicPacketType::VersionNegotiation
+ } else {
+ get_long_packet_type(first_byte, version)
+ };
+ quic_info.packet_type = packet_type;
+
+ // Parse connection IDs
+ let mut offset = 5;
+
+ // Destination Connection ID
+ if offset >= payload.len() {
+ debug!(
+ "QUIC: Payload too short to read DCID length at offset {}",
+ offset
+ );
+ return (None, 0);
+ }
+ let dcid_len = payload[offset] as usize;
+ offset += 1;
+
+ debug!(
+ "QUIC: Parsing long header packet - version=0x{:08x}, DCID length={}",
+ version, dcid_len
+ );
+
+ if offset + dcid_len > payload.len() {
+ debug!(
+ "QUIC: Payload too short for DCID, need {} bytes, have {}",
+ offset + dcid_len,
+ payload.len()
+ );
+ return (None, 0);
+ }
+ // Remember the DCID byte-range so we can reborrow `payload` later instead
+ // of allocating a separate owned copy. The short-header path got the same
+ // fix in #317; the long-header path kept doing `to_vec().clone()` which
+ // allocated the DCID twice per packet.
+ let dcid_range = offset..offset + dcid_len;
+ quic_info.connection_id = payload[dcid_range.clone()].to_vec();
+ quic_info.connection_id_hex = None;
+ offset += dcid_len;
+
+ // Source Connection ID
+ if offset >= payload.len() {
+ debug!(
+ "QUIC: Payload too short for SCID length at offset {}",
+ offset
+ );
+ return (None, 0);
+ }
+ let scid_len = payload[offset] as usize;
+ offset += 1;
+
+ if offset + scid_len > payload.len() {
+ debug!(
+ "QUIC: Payload too short for SCID, need {} bytes, have {}",
+ offset + scid_len,
+ payload.len()
+ );
+ return (None, 0);
+ }
+ offset += scid_len;
+
+ // Retry and Version Negotiation packets have no Length field: the retry
+ // token / supported-version list simply extends to the end of the
+ // datagram (RFC 9000 §17.2.5, RFC 8999 §6). Parsing a Length varint here
+ // would read garbage and misinterpret the trailing bytes as coalesced
+ // packets, so consume the rest of the datagram instead.
+ if matches!(
+ packet_type,
+ QuicPacketType::Retry | QuicPacketType::VersionNegotiation
+ ) {
+ extract_tls_from_long_header_packet(
+ payload,
+ &mut quic_info,
+ &payload[dcid_range],
+ version,
+ packet_type,
+ );
+ return (Some(quic_info), payload.len());
+ }
+
+ // For Initial packets, parse token length
+ if packet_type == QuicPacketType::Initial {
+ if let Some((token_len, bytes_read)) = parse_variable_length_int(&payload[offset..]) {
+ offset += bytes_read;
+ // Guard: a malformed/adversarial varint may decode to a value far
+ // larger than the remaining payload. Treat that as unparseable
+ // rather than panicking on the next slice access.
+ match (token_len as usize).checked_add(offset) {
+ Some(new_offset) if new_offset <= payload.len() => offset = new_offset,
+ _ => return (Some(quic_info), payload.len()),
+ }
+ } else {
+ return (Some(quic_info), payload.len()); // Can't parse, assume rest of datagram
+ }
+ }
+
+ // Parse packet length (variable-length integer)
+ if offset >= payload.len() {
+ return (Some(quic_info), payload.len());
+ }
+ let packet_length =
+ if let Some((pkt_len, bytes_read)) = parse_variable_length_int(&payload[offset..]) {
+ offset += bytes_read;
+ pkt_len as usize
+ } else {
+ // Can't parse packet length, assume rest of datagram
+ return (Some(quic_info), payload.len());
+ };
+
+ // Total packet size = header (offset) + packet_length (includes pkt num + payload).
+ // Use checked_add so an adversarial varint length can't overflow usize.
+ let total_packet_size = offset.checked_add(packet_length).unwrap_or(payload.len());
+
+ debug!(
+ "QUIC: Long header packet - header_len={}, packet_length={}, total={}",
+ offset, packet_length, total_packet_size
+ );
+
+ // Now do the actual TLS extraction on this packet
+ let packet_data = if total_packet_size <= payload.len() {
+ &payload[..total_packet_size]
+ } else {
+ payload // Use what we have if packet extends beyond datagram
+ };
+
+ // Extract TLS info from this packet. The DCID lives inside `payload`, so
+ // pass a borrowed slice instead of cloning the owned `connection_id` Vec.
+ extract_tls_from_long_header_packet(
+ packet_data,
+ &mut quic_info,
+ &payload[dcid_range],
+ version,
+ packet_type,
+ );
+
+ (Some(quic_info), total_packet_size.min(payload.len()))
+}
+
+/// Extract TLS information from a long header packet
+fn extract_tls_from_long_header_packet(
+ payload: &[u8],
+ quic_info: &mut QuicInfo,
+ dcid: &[u8],
+ version: u32,
+ packet_type: QuicPacketType,
+) {
+ let dcid_len = dcid.len();
+
+ // Set connection state based on packet type
+ quic_info.connection_state = match packet_type {
+ QuicPacketType::Initial => QuicConnectionState::Initial,
+ QuicPacketType::Handshake => QuicConnectionState::Handshaking,
+ QuicPacketType::Retry => QuicConnectionState::Initial,
+ QuicPacketType::VersionNegotiation => QuicConnectionState::Initial,
+ QuicPacketType::ZeroRtt => QuicConnectionState::Handshaking,
+ _ => QuicConnectionState::Unknown,
+ };
+
+ // NOTE: QUIC Initial and Handshake packets are ENCRYPTED using keys derived from the DCID.
+ // We must decrypt them first before extracting TLS information.
+ // Do NOT try to pattern-match on encrypted payload - it will give garbage results.
+
+ // For Initial and Handshake packets, try to decrypt and extract TLS information
+ // Focus on Client packets as they contain the SNI information
+ match packet_type {
+ QuicPacketType::Initial if dcid_len > 0 => {
+ debug!("QUIC: Processing Initial packet with DCID len={}", dcid_len);
+ // Only client Initial packets are decryptable here: Initial keys
+ // are derived from the client's original DCID (RFC 9001 §5.2),
+ // and only client first-flight packets carry that value in their
+ // own DCID field. A server Initial's DCID is the client's SCID,
+ // so deriving keys from it can never succeed without
+ // connection-level state that remembers the original DCID.
+ if let Some(decrypted_payload) = decrypt_client_initial_packet(payload, dcid, version) {
+ debug!("QUIC: Successfully decrypted Client Initial packet");
+ // Extract TLS info from decrypted payload using reassembly
+ if let Some(tls_info) =
+ process_crypto_frames_in_packet(&decrypted_payload, quic_info)
+ {
+ quic_info.tls_info = Some(tls_info);
+ // This is a Client Initial packet with crypto frames - mark it for connection tracking
+ quic_info.connection_id_hex = Some(connection_id_to_hex(dcid));
+ debug!(
+ "QUIC: Marking Client Initial packet with DCID {} for connection tracking",
+ connection_id_to_hex(dcid)
+ );
+ }
+ } else {
+ debug!(
+ "QUIC: Failed to decrypt Initial packet as client first flight - DCID={:02x?}, version=0x{:08x}, payload_len={}",
+ dcid,
+ version,
+ payload.len()
+ );
+ // Cannot extract TLS info from encrypted payload - don't try pattern matching
+ }
+ }
+ QuicPacketType::Handshake if dcid_len > 0 => {
+ // Handshake packets are encrypted with handshake keys derived from the TLS handshake
+ // We cannot decrypt these without the handshake secrets, so we skip TLS extraction
+ debug!("QUIC: Processing Handshake packet - encrypted, cannot extract TLS info");
+ }
+ QuicPacketType::Initial => {
+ debug!("QUIC: Initial packet has zero-length DCID - cannot derive decryption keys");
+ debug!(
+ "QUIC: Packet details - version=0x{:08x}, payload_len={}, packet_type={:?}",
+ version,
+ payload.len(),
+ packet_type
+ );
+ // Cannot decrypt without DCID - don't try pattern matching on encrypted data
+ }
+ _ => {
+ debug!(
+ "QUIC: Packet type {:?} not processed for TLS extraction",
+ packet_type
+ );
+ }
+ }
+}
+
+/// Parse a QUIC short header packet
+fn parse_short_header_packet(payload: &[u8]) -> Option {
+ if payload.is_empty() {
+ return None;
+ }
+
+ // A 1-RTT packet must have the fixed bit set (first byte 01xxxxxx,
+ // RFC 9000 §17.3.1). This also stops trailing garbage after a misparsed
+ // coalesced packet from being claimed as a Connected 1-RTT packet.
+ if (payload[0] & 0xc0) != 0x40 {
+ return None;
+ }
+
+ // For short header, we don't have version info
+ let mut quic_info = QuicInfo::new(0);
+ quic_info.packet_type = QuicPacketType::OneRtt;
+ quic_info.connection_state = QuicConnectionState::Connected;
+
+ // For short header, connection ID length is not in the packet — use a
+ // common 8-byte size as a heuristic. Move the slice straight into
+ // `connection_id`; the long-header path keeps a local `dcid` because it
+ // re-borrows for TLS decryption, but here nothing else reads it.
+ quic_info.connection_id = if payload.len() >= 9 {
+ payload[1..9].to_vec()
+ } else {
+ payload[1..].to_vec()
+ };
+ // Short header packets are data packets - don't use for connection tracking
+ quic_info.connection_id_hex = None;
+
+ Some(quic_info)
+}
+
+/// Decrypt a QUIC Client Initial packet (prioritized for SNI extraction)
+fn decrypt_client_initial_packet(packet: &[u8], dcid: &[u8], version: u32) -> Option> {
+ // Derive initial secret using HKDF
+ let salt = hkdf::Salt::new(hkdf::HKDF_SHA256, initial_salt_for_version(version));
+ let initial_secret = salt.extract(dcid);
+
+ // Derive client initial secret
+ let mut client_secret = [0u8; 32];
+ if !derive_secret(&initial_secret, b"client in", &mut client_secret) {
+ debug!("QUIC: Failed to derive client initial secret");
+ return None;
+ }
+
+ debug!(
+ "QUIC: Attempting client Initial decryption with DCID len={}",
+ dcid.len()
+ );
+
+ // Try to decrypt as a client Initial packet
+ let result = try_decrypt_initial_with_secret(packet, &client_secret, version);
+ if result.is_none() {
+ debug!("QUIC: Client Initial decryption failed");
+ }
+ result
+}
+
+/// Try to decrypt an Initial packet with a specific secret
+fn try_decrypt_initial_with_secret(packet: &[u8], secret: &[u8], version: u32) -> Option> {
+ // Derive key and IV for packet protection
+ let mut key = [0u8; 16];
+ let mut iv = [0u8; 12];
+ let mut hp_key = [0u8; 16];
+
+ if !derive_packet_protection_key(secret, &mut key, version)
+ || !derive_packet_protection_iv(secret, &mut iv, version)
+ || !derive_header_protection_key(secret, &mut hp_key, version)
+ {
+ debug!("QUIC: Failed to derive keys from secret");
+ return None;
+ }
+
+ // Parse packet structure to find packet number offset
+ let mut offset = 5; // Skip first byte and version
+
+ // Skip DCID
+ if offset >= packet.len() {
+ debug!("QUIC: Packet too short for DCID length field");
+ return None;
+ }
+ let dcid_len = packet[offset] as usize;
+ offset += 1 + dcid_len;
+
+ if offset >= packet.len() {
+ debug!("QUIC: Packet too short after DCID");
+ return None;
+ }
+
+ // Skip SCID
+ let scid_len = packet[offset] as usize;
+ offset += 1 + scid_len;
+
+ if offset >= packet.len() {
+ debug!("QUIC: Packet too short after SCID");
+ return None;
+ }
+
+ debug!(
+ "QUIC: Parsed connection IDs - DCID len={}, SCID len={}, offset now={}",
+ dcid_len, scid_len, offset
+ );
+
+ // Parse token length (for Initial packets)
+ let (token_len, bytes_read) = parse_variable_length_int(&packet[offset..])?;
+ // QUIC variable-length ints go up to 2^62 — guard against overflow on
+ // 32-bit targets and against crafted token lengths that exceed the packet.
+ let token_len_usize = usize::try_from(token_len).ok()?;
+ offset = offset
+ .checked_add(bytes_read)?
+ .checked_add(token_len_usize)?;
+ if offset > packet.len() {
+ debug!("QUIC: token_len pushed offset past end of packet");
+ return None;
+ }
+
+ // Parse packet length
+ let (packet_payload_length, bytes_read) = parse_variable_length_int(&packet[offset..])?;
+ offset += bytes_read;
+
+ // Now offset points to the packet number field
+ let pn_offset = offset;
+
+ // Sample is taken 4 bytes after the packet number offset
+ let sample_offset = pn_offset + 4;
+ if sample_offset + 16 > packet.len() {
+ debug!("QUIC: Not enough data for header protection sample");
+ return None;
+ }
+
+ // Remove header protection to get packet number
+ let sample = &packet[sample_offset..sample_offset + 16];
+ let mask = aes_ecb_encrypt(&hp_key, sample)?;
+
+ // Unmask the first byte to get packet number length
+ let mut first_byte = packet[0];
+ first_byte ^= mask[0] & 0x0f; // Only lower 4 bits for long header
+ let pn_length = ((first_byte & 0x03) + 1) as usize;
+
+ // Unmask and extract packet number
+ let mut packet_number = 0u64;
+ for i in 0..pn_length {
+ let unmasked = packet[pn_offset + i] ^ mask[1 + i];
+ packet_number = (packet_number << 8) | (unmasked as u64);
+ }
+
+ // Prepare for AEAD decryption
+ let aead_key = LessSafeKey::new(UnboundKey::new(&aead::AES_128_GCM, &key).ok()?);
+
+ // Calculate nonce
+ let mut nonce_bytes = iv;
+ for i in 0..8 {
+ nonce_bytes[11 - i] ^= ((packet_number >> (i * 8)) & 0xff) as u8;
+ }
+ let nonce = Nonce::assume_unique_for_key(nonce_bytes);
+
+ // Create AAD (authenticated header up to and including packet number)
+ let mut aad = Vec::new();
+ aad.push(first_byte); // Unmasked first byte
+ aad.extend_from_slice(&packet[1..pn_offset]); // Rest of header
+ for i in 0..pn_length {
+ aad.push(packet[pn_offset + i] ^ mask[1 + i]); // Unmasked packet number
+ }
+
+ // Decrypt the payload
+ let ciphertext_offset = pn_offset + pn_length;
+ // `packet_payload_length` is an attacker-controlled varint; if it is
+ // smaller than the packet-number length the subtraction would underflow
+ // (panic in debug, wrap to a huge value in release that then slips past
+ // the bounds check below and panics on the slice). Reject instead.
+ let ciphertext_len = (packet_payload_length as usize).checked_sub(pn_length)?;
+
+ if ciphertext_offset + ciphertext_len > packet.len() {
+ debug!("QUIC: Ciphertext extends beyond packet");
+ return None;
+ }
+
+ // The ciphertext includes the authentication tag (last 16 bytes)
+ if ciphertext_len < 16 {
+ debug!("QUIC: Ciphertext too short for auth tag");
+ return None;
+ }
+
+ let mut plaintext = packet[ciphertext_offset..ciphertext_offset + ciphertext_len].to_vec();
+
+ match aead_key.open_in_place(nonce, Aad::from(&aad), &mut plaintext) {
+ Ok(decrypted) => {
+ let decrypted_len = decrypted.len();
+ plaintext.truncate(decrypted_len);
+ Some(plaintext)
+ }
+ Err(e) => {
+ debug!("QUIC: AEAD decryption failed: {:?}", e);
+ None
+ }
+ }
+}
+
+/// Process all frames in a decrypted QUIC packet payload and extract CRYPTO frames
+pub fn process_crypto_frames_in_packet(
+ payload: &[u8],
+ quic_info: &mut QuicInfo,
+) -> Option {
+ // Ensure we have a reassembler
+ quic_info.ensure_reassembler();
+
+ let mut found_crypto_frames = false;
+
+ // Even if the frame walk stops early on a malformed or truncated frame,
+ // CRYPTO fragments collected before that point are kept in the
+ // reassembler, so still attempt TLS extraction below.
+ if scan_packet_frames(payload, quic_info, &mut found_crypto_frames).is_none() {
+ debug!("QUIC: Frame walk stopped early on malformed or truncated frame");
+ }
+
+ if found_crypto_frames
+ && let Some(reassembler) = &mut quic_info.crypto_reassembler
+ && let Some(tls_info) = try_extract_tls_from_reassembler(reassembler, false)
+ {
+ debug!(
+ "QUIC: Successfully extracted TLS info: SNI={:?}",
+ tls_info.sni
+ );
+ quic_info.tls_info = Some(tls_info.clone());
+ return Some(tls_info);
+ }
+
+ None
+}
+
+/// Walk all frames in a decrypted packet payload, feeding CRYPTO frames into
+/// the reassembler. Returns `None` if a malformed or truncated frame forced
+/// the walk to stop before the end of the payload.
+fn scan_packet_frames(
+ payload: &[u8],
+ quic_info: &mut QuicInfo,
+ found_crypto_frames: &mut bool,
+) -> Option<()> {
+ let mut offset = 0;
+
+ while offset < payload.len() {
+ let frame_type_byte = payload[offset];
+ offset += 1;
+
+ match frame_type_byte {
+ 0x00 => {
+ // PADDING frame
+ while offset < payload.len() && payload[offset] == 0x00 {
+ offset += 1;
+ }
+ }
+
+ 0x01 => {
+ // PING frame
+ debug!("QUIC: Found PING frame");
+ }
+
+ 0x02 | 0x03 => {
+ // ACK or ACK_ECN frame
+ debug!("QUIC: Found ACK frame");
+
+ // Parse and skip ACK frame fields
+ let (_, bytes_read) = parse_variable_length_int(&payload[offset..])?;
+ offset += bytes_read;
+
+ let (_, bytes_read) = parse_variable_length_int(&payload[offset..])?;
+ offset += bytes_read;
+
+ let (ack_range_count, bytes_read) = parse_variable_length_int(&payload[offset..])?;
+ offset += bytes_read;
+
+ let (_, bytes_read) = parse_variable_length_int(&payload[offset..])?;
+ offset += bytes_read;
+
+ for _ in 0..ack_range_count {
+ let (_, bytes_read) = parse_variable_length_int(&payload[offset..])?;
+ offset += bytes_read;
+ let (_, bytes_read) = parse_variable_length_int(&payload[offset..])?;
+ offset += bytes_read;
+ }
+
+ if frame_type_byte == 0x03 {
+ // ECN counts
+ for _ in 0..3 {
+ let (_, bytes_read) = parse_variable_length_int(&payload[offset..])?;
+ offset += bytes_read;
+ }
+ }
+ }
+
+ 0x04 => {
+ // RESET_STREAM frame
+ for _ in 0..3 {
+ let (_, bytes_read) = parse_variable_length_int(&payload[offset..])?;
+ offset += bytes_read;
+ }
+ }
+
+ 0x05 => {
+ // STOP_SENDING frame
+ for _ in 0..2 {
+ let (_, bytes_read) = parse_variable_length_int(&payload[offset..])?;
+ offset += bytes_read;
+ }
+ }
+
+ 0x06 => {
+ // CRYPTO frame - this is what we're looking for!
+ debug!("QUIC: Found CRYPTO frame");
+ *found_crypto_frames = true;
+ quic_info.has_crypto_frame = true;
+
+ let (crypto_offset, bytes_read) = parse_variable_length_int(&payload[offset..])?;
+ offset += bytes_read;
+
+ let (crypto_length, bytes_read) = parse_variable_length_int(&payload[offset..])?;
+ offset += bytes_read;
+
+ debug!(
+ "QUIC: CRYPTO frame - offset={}, length={}",
+ crypto_offset, crypto_length
+ );
+
+ let crypto_len = crypto_length as usize;
+ let available = (payload.len() - offset).min(crypto_len);
+
+ if available > 0 {
+ let crypto_data = payload[offset..offset + available].to_vec();
+
+ if let Some(reassembler) = &mut quic_info.crypto_reassembler
+ && let Err(e) = reassembler.add_fragment(crypto_offset, crypto_data)
+ {
+ warn!("QUIC: Failed to add CRYPTO fragment: {}", e);
+ }
+ }
+
+ offset += available;
+ }
+
+ 0x07 => {
+ // NEW_TOKEN frame
+ let (token_length, bytes_read) = parse_variable_length_int(&payload[offset..])?;
+ offset = offset
+ .checked_add(bytes_read)?
+ .checked_add(token_length as usize)?;
+ if offset > payload.len() {
+ break;
+ }
+ }
+
+ 0x08..=0x0f => {
+ // STREAM frames
+ let has_offset = (frame_type_byte & 0x04) != 0;
+ let has_length = (frame_type_byte & 0x02) != 0;
+
+ let (_, bytes_read) = parse_variable_length_int(&payload[offset..])?;
+ offset += bytes_read;
+
+ if has_offset {
+ let (_, bytes_read) = parse_variable_length_int(&payload[offset..])?;
+ offset += bytes_read;
+ }
+
+ let stream_data_len = if has_length {
+ let (length, bytes_read) = parse_variable_length_int(&payload[offset..])?;
+ offset += bytes_read;
+ length as usize
+ } else {
+ payload.len() - offset
+ };
+
+ offset = offset.checked_add(stream_data_len)?;
+ if offset > payload.len() {
+ break;
+ }
+ }
+
+ 0x10..=0x17 => {
+ // Various MAX_DATA, MAX_STREAM_DATA, MAX_STREAMS, DATA_BLOCKED frames
+ let num_vars = match frame_type_byte {
+ 0x10 | 0x12 | 0x13 | 0x14 | 0x16 | 0x17 => 1,
+ 0x11 | 0x15 => 2,
+ _ => 0,
+ };
+
+ for _ in 0..num_vars {
+ let (_, bytes_read) = parse_variable_length_int(&payload[offset..])?;
+ offset += bytes_read;
+ }
+ }
+
+ 0x18 => {
+ // NEW_CONNECTION_ID frame
+ let (_, bytes_read) = parse_variable_length_int(&payload[offset..])?;
+ offset += bytes_read;
+
+ let (_, bytes_read) = parse_variable_length_int(&payload[offset..])?;
+ offset += bytes_read;
+
+ if offset >= payload.len() {
+ break;
+ }
+ let cid_length = payload[offset] as usize;
+ offset = offset.checked_add(1 + cid_length + 16)?; // CID + stateless reset token
+ if offset > payload.len() {
+ break;
+ }
+ }
+
+ 0x19 => {
+ // RETIRE_CONNECTION_ID frame
+ let (_, bytes_read) = parse_variable_length_int(&payload[offset..])?;
+ offset += bytes_read;
+ }
+
+ 0x1a | 0x1b => {
+ // PATH_CHALLENGE or PATH_RESPONSE frame
+ offset += 8;
+ }
+
+ 0x1c | 0x1d => {
+ // CONNECTION_CLOSE frame - extract detailed information
+ let (error_code, bytes_read) = parse_variable_length_int(&payload[offset..])?;
+ offset += bytes_read;
+
+ // 0x1c has an additional frame type field, 0x1d does not
+ if frame_type_byte == 0x1c {
+ let (_, bytes_read) = parse_variable_length_int(&payload[offset..])?;
+ offset += bytes_read;
+ }
+
+ // Extract reason phrase if present
+ let (reason_length, bytes_read) = parse_variable_length_int(&payload[offset..])?;
+ offset += bytes_read;
+
+ let reason_len = reason_length as usize;
+ let reason_end = offset.checked_add(reason_len)?;
+ let reason = if reason_length > 0 && reason_end <= payload.len() {
+ let reason_bytes = &payload[offset..reason_end];
+ String::from_utf8(reason_bytes.to_vec()).ok()
+ } else {
+ None
+ };
+
+ // Store CONNECTION_CLOSE information in quic_info. This must
+ // happen before the truncation check below: the error code
+ // and frame type were parsed validly even when the reason
+ // phrase is cut off, and dropping them would lose the
+ // Draining/Closed state transition.
+ quic_info.connection_close = Some(crate::network::types::QuicCloseInfo {
+ frame_type: frame_type_byte,
+ error_code,
+ });
+
+ // Update connection state based on close frame
+ quic_info.connection_state = if error_code == 0 {
+ // NO_ERROR - graceful close, enter draining
+ crate::network::types::QuicConnectionState::Draining
+ } else {
+ // Error close - connection is closed
+ crate::network::types::QuicConnectionState::Closed
+ };
+
+ debug!(
+ "QUIC: Detected CONNECTION_CLOSE frame type 0x{:02x}, error_code: {}, reason: {:?}",
+ frame_type_byte, error_code, reason
+ );
+
+ offset = reason_end;
+ if offset > payload.len() {
+ break;
+ }
+ }
+
+ 0x1e => {
+ // HANDSHAKE_DONE frame
+ debug!("QUIC: Found HANDSHAKE_DONE frame");
+ }
+
+ _ => {
+ warn!(
+ "QUIC: Unknown frame type 0x{:02x}, stopping",
+ frame_type_byte
+ );
+ break;
+ }
+ }
+
+ if offset > payload.len() {
+ warn!("QUIC: Frame parsing exceeded payload length");
+ break;
+ }
+ }
+
+ Some(())
+}
+
+/// Check if SNI is complete (not partial)
+fn is_complete_sni(sni: &Option) -> bool {
+ match sni {
+ Some(s) => !is_partial_sni(s),
+ None => false,
+ }
+}
+
+/// Strategy 1: Try to extract TLS info from contiguous reassembled data
+fn try_extract_from_contiguous(
+ reassembler: &CryptoFrameReassembler,
+ allow_partial: bool,
+) -> Option {
+ let reassembled = reassembler.get_contiguous_data()?;
+
+ debug!(
+ "QUIC: Attempting to parse {} bytes of contiguous crypto data (allow_partial={})",
+ reassembled.len(),
+ allow_partial
+ );
+
+ // Only attempt to parse if we have enough data for a reasonable ClientHello
+ // Use lower threshold (50 bytes) when allowing partial extraction
+ let threshold = if allow_partial { 50 } else { 100 };
+ if reassembled.len() < threshold {
+ debug!(
+ "QUIC: Only {} contiguous bytes available, waiting for more data before parsing",
+ reassembled.len()
+ );
+ return None;
+ }
+
+ let tls_info = parse_partial_tls_handshake(&reassembled, allow_partial)?;
+
+ // Check if we have the essential info (SNI and ALPN)
+ if tls_info.sni.is_none() && tls_info.alpn.is_empty() {
+ return None;
+ }
+
+ let sni_is_complete = is_complete_sni(&tls_info.sni);
+ debug!(
+ "QUIC: Found TLS info from contiguous data (complete={})",
+ sni_is_complete
+ );
+ Some(tls_info)
+}
+
+/// Strategy 2: Try to parse individual fragments with proper TLS headers
+fn try_extract_from_fragments(
+ reassembler: &CryptoFrameReassembler,
+ allow_partial: bool,
+) -> Option {
+ debug!("QUIC: Trying to parse individual crypto fragments with proper TLS headers");
+
+ for (&offset, fragment_data) in reassembler.get_fragments() {
+ debug!(
+ "QUIC: Trying fragment at offset {} with {} bytes",
+ offset,
+ fragment_data.len()
+ );
+
+ // Only try to parse fragments that look like they contain complete TLS structures
+ // Check if fragment starts with TLS handshake header (0x01 for ClientHello)
+ if fragment_data.len() >= 4
+ && fragment_data[0] == 0x01
+ && let Some(tls_info) = parse_partial_tls_handshake(fragment_data, allow_partial)
+ && (tls_info.sni.is_some() || !tls_info.alpn.is_empty())
+ {
+ let sni_is_complete = is_complete_sni(&tls_info.sni);
+ debug!(
+ "QUIC: Found TLS info from individual fragment at offset {} (complete={})",
+ offset, sni_is_complete
+ );
+ return Some(tls_info);
+ }
+
+ // Also try direct TLS pattern matching, but only for fragments that look like TLS records
+ if fragment_data.len() >= 6
+ && fragment_data[0] == 0x16
+ && let Some(tls_info) = try_parse_unencrypted_crypto_frames(fragment_data)
+ && (tls_info.sni.is_some() || !tls_info.alpn.is_empty())
+ {
+ let sni_is_complete = is_complete_sni(&tls_info.sni);
+ debug!(
+ "QUIC: Found TLS info from pattern matching in fragment at offset {} (complete={})",
+ offset, sni_is_complete
+ );
+ return Some(tls_info);
+ }
+
+ debug!(
+ "QUIC: Skipping fragment at offset {} - doesn't start with TLS header",
+ offset
+ );
+ }
+
+ None
+}
+
+/// Strategy 3: Try greedy SNI extraction from all fragments and contiguous data
+fn try_extract_greedy_from_reassembler(reassembler: &CryptoFrameReassembler) -> Option {
+ debug!("QUIC: Attempting greedy SNI extraction as final fallback");
+
+ // Try greedy extraction on fragments
+ for fragment_data in reassembler.get_fragments().values() {
+ if let Some(sni) = try_extract_sni_greedy(fragment_data, true) {
+ let mut tls_info = TlsInfo::new();
+ tls_info.sni = Some(sni);
+ debug!("QUIC: Greedy extraction succeeded from fragment");
+ return Some(tls_info);
+ }
+ }
+
+ // Also try on contiguous data if available
+ if let Some(contiguous) = reassembler.get_contiguous_data()
+ && let Some(sni) = try_extract_sni_greedy(&contiguous, true)
+ {
+ let mut tls_info = TlsInfo::new();
+ tls_info.sni = Some(sni);
+ debug!("QUIC: Greedy extraction succeeded from contiguous data");
+ return Some(tls_info);
+ }
+
+ None
+}
+
+/// Try to extract TLS information from reassembled fragments
+///
+/// The `allow_partial` parameter controls whether partial SNI extraction is allowed:
+/// - `false`: Only return complete SNI (used during initial packet parsing)
+/// - `true`: Return partial SNI as fallback (used during merge/re-extraction)
+///
+/// This function orchestrates multiple extraction strategies in order of preference:
+/// 1. Check cache for complete SNI
+/// 2. Parse contiguous data
+/// 3. Parse individual fragments with TLS headers
+/// 4. Reconstruct SNI from fragmented data
+/// 5. Greedy fallback extraction
+pub fn try_extract_tls_from_reassembler(
+ reassembler: &mut CryptoFrameReassembler,
+ allow_partial: bool,
+) -> Option {
+ // Strategy 0: Check cache for complete SNI
+ if let Some(tls_info) = reassembler.get_cached_tls_info() {
+ if is_complete_sni(&tls_info.sni) {
+ return Some(tls_info.clone());
+ }
+ debug!("QUIC: Cached SNI is partial, attempting to find complete SNI");
+ }
+
+ // Strategy 1: Try to parse contiguous data
+ if let Some(tls_info) = try_extract_from_contiguous(reassembler, allow_partial) {
+ if is_complete_sni(&tls_info.sni) {
+ reassembler.set_complete_tls_info(tls_info.clone());
+ }
+ return Some(tls_info);
+ }
+
+ // Strategy 2: Try parsing individual fragments with TLS headers
+ if let Some(tls_info) = try_extract_from_fragments(reassembler, allow_partial) {
+ if is_complete_sni(&tls_info.sni) {
+ reassembler.set_complete_tls_info(tls_info.clone());
+ }
+ return Some(tls_info);
+ }
+
+ // Strategy 3: Try fragment reconstruction (requires reasonable data amount)
+ let total_fragment_size: usize = reassembler.get_fragments().values().map(|v| v.len()).sum();
+ if total_fragment_size >= 100 {
+ debug!(
+ "QUIC: Have {} total bytes in fragments, attempting reconstruction",
+ total_fragment_size
+ );
+ if let Some(sni) = try_reconstruct_sni_from_fragments(reassembler) {
+ let mut tls_info = TlsInfo::new();
+ let sni_is_complete = !is_partial_sni(&sni);
+ tls_info.sni = Some(sni);
+ debug!(
+ "QUIC: Reconstructed SNI from fragmented data (complete={})",
+ sni_is_complete
+ );
+ if sni_is_complete {
+ reassembler.set_complete_tls_info(tls_info.clone());
+ }
+ return Some(tls_info);
+ }
+ } else {
+ debug!(
+ "QUIC: Only {} total bytes in fragments, not enough for reliable SNI extraction",
+ total_fragment_size
+ );
+ }
+
+ // Strategy 4: Greedy fallback extraction
+ if let Some(tls_info) = try_extract_greedy_from_reassembler(reassembler) {
+ reassembler.set_complete_tls_info(tls_info.clone());
+ return Some(tls_info);
+ }
+
+ debug!("QUIC: No TLS info could be extracted from reassembler");
+ None
+}
+
+/// Parse a TLS handshake from reassembled data
+fn parse_partial_tls_handshake(data: &[u8], allow_partial: bool) -> Option {
+ if data.len() < 4 {
+ debug!("QUIC: TLS handshake data too short: {} bytes", data.len());
+ return None;
+ }
+
+ let handshake_type = data[0];
+ let handshake_length = u32::from_be_bytes([0, data[1], data[2], data[3]]) as usize;
+
+ debug!(
+ "QUIC: TLS handshake type=0x{:02x}, declared_length={}, available_data={}, allow_partial={}",
+ handshake_type,
+ handshake_length,
+ data.len() - 4,
+ allow_partial
+ );
+
+ let mut info = TlsInfo::new();
+
+ let available_data = &data[4..];
+ let parse_length = handshake_length.min(available_data.len());
+
+ // Sanity check the handshake length
+ if handshake_length > 65536 {
+ debug!(
+ "QUIC: Handshake length {} seems too large, skipping",
+ handshake_length
+ );
+ return None;
+ }
+
+ match handshake_type {
+ 0x01 => {
+ // Client Hello
+ debug!("QUIC: Parsing ClientHello with {} bytes", parse_length);
+ parse_partial_client_hello(&available_data[..parse_length], &mut info, allow_partial);
+ }
+ 0x02 => {
+ // Server Hello
+ debug!("QUIC: Parsing ServerHello with {} bytes", parse_length);
+ parse_partial_server_hello(&available_data[..parse_length], &mut info);
+ }
+ _ => {
+ debug!(
+ "QUIC: Unknown/unsupported handshake type: 0x{:02x}",
+ handshake_type
+ );
+ return None;
+ }
+ }
+
+ debug!(
+ "QUIC: Parsed TLS info - SNI={:?}, ALPN={:?}, version={:?}",
+ info.sni, info.alpn, info.version
+ );
+
+ if info.sni.is_some() || !info.alpn.is_empty() || info.version.is_some() {
+ Some(info)
+ } else {
+ debug!("QUIC: No useful TLS info extracted");
+ None
+ }
+}
+
+/// Parse a partial Client Hello
+fn parse_partial_client_hello(data: &[u8], info: &mut TlsInfo, allow_partial: bool) {
+ debug!(
+ "QUIC: Parsing ClientHello with {} bytes (allow_partial={})",
+ data.len(),
+ allow_partial
+ );
+
+ if data.len() < 34 {
+ debug!(
+ "QUIC: ClientHello too short: {} bytes (need at least 34)",
+ data.len()
+ );
+ return;
+ }
+
+ // Skip version (2) + random (32)
+ let mut offset = 34;
+ debug!(
+ "QUIC: ClientHello - skipping version and random, offset now={}",
+ offset
+ );
+
+ // Session ID
+ if offset >= data.len() {
+ return;
+ }
+ let session_id_len = data[offset] as usize;
+ offset += 1 + session_id_len;
+
+ if offset + 2 > data.len() {
+ return;
+ }
+
+ // Cipher suites
+ let cipher_suites_len = u16::from_be_bytes([data[offset], data[offset + 1]]) as usize;
+ offset += 2 + cipher_suites_len;
+
+ if offset >= data.len() {
+ return;
+ }
+
+ // Compression methods
+ let compression_len = data[offset] as usize;
+ offset += 1 + compression_len;
+
+ if offset + 2 > data.len() {
+ return;
+ }
+
+ // Extensions
+ let extensions_len = u16::from_be_bytes([data[offset], data[offset + 1]]) as usize;
+ offset += 2;
+
+ debug!(
+ "QUIC: ClientHello extensions - declared_len={}, available_data={}",
+ extensions_len,
+ data.len() - offset
+ );
+
+ let available_ext_len = (data.len() - offset).min(extensions_len);
+ if available_ext_len > 0 {
+ debug!("QUIC: Parsing {} bytes of extensions", available_ext_len);
+ parse_tls_extensions(
+ &data[offset..offset + available_ext_len],
+ info,
+ true,
+ allow_partial,
+ );
+ } else {
+ debug!("QUIC: No extensions data available");
+ }
+}
+
+/// Parse a partial Server Hello
+fn parse_partial_server_hello(data: &[u8], info: &mut TlsInfo) {
+ if data.len() < 34 {
+ return;
+ }
+
+ // Skip version (2) + random (32)
+ let mut offset = 34;
+
+ // Session ID
+ if offset >= data.len() {
+ return;
+ }
+ let session_id_len = data[offset] as usize;
+ offset += 1 + session_id_len;
+
+ if offset + 2 > data.len() {
+ return;
+ }
+
+ // Cipher suite
+ let cipher = u16::from_be_bytes([data[offset], data[offset + 1]]);
+ info.cipher_suite = Some(cipher);
+ offset += 2;
+
+ // Compression method
+ if offset >= data.len() {
+ return;
+ }
+ offset += 1;
+
+ // Extensions
+ if offset + 2 > data.len() {
+ return;
+ }
+
+ let extensions_len = u16::from_be_bytes([data[offset], data[offset + 1]]) as usize;
+ offset += 2;
+
+ let available_ext_len = (data.len() - offset).min(extensions_len);
+ if available_ext_len > 0 {
+ parse_tls_extensions(
+ &data[offset..offset + available_ext_len],
+ info,
+ false,
+ false,
+ );
+ }
+}
+
+/// Parse TLS extensions
+fn parse_tls_extensions(data: &[u8], info: &mut TlsInfo, is_client: bool, allow_partial: bool) {
+ let mut offset = 0;
+ debug!(
+ "QUIC: Parsing {} bytes of TLS extensions (is_client={}, allow_partial={})",
+ data.len(),
+ is_client,
+ allow_partial
+ );
+
+ while offset + 4 <= data.len() {
+ let ext_type = u16::from_be_bytes([data[offset], data[offset + 1]]);
+ let ext_len = u16::from_be_bytes([data[offset + 2], data[offset + 3]]) as usize;
+
+ debug!(
+ "QUIC: Extension type=0x{:04x}, length={}",
+ ext_type, ext_len
+ );
+
+ if offset + 4 + ext_len > data.len() {
+ // Extension data is incomplete
+ if allow_partial && ext_type == 0x0000 && is_client {
+ // Try to extract partial SNI as fallback
+ let available_ext_len = data.len() - offset - 4;
+ if available_ext_len > 5 {
+ debug!(
+ "QUIC: SNI extension is incomplete (need {} bytes, have {}), attempting partial extraction",
+ ext_len, available_ext_len
+ );
+ let ext_data = &data[offset + 4..];
+ if let Some(sni) = parse_sni_extension(ext_data, true) {
+ debug!("QUIC: Extracted partial SNI as fallback: {}", sni);
+ info.sni = Some(sni);
+ }
+ }
+ } else {
+ debug!(
+ "QUIC: Extension 0x{:04x} is incomplete (need {} bytes, have {}), waiting for more data",
+ ext_type,
+ ext_len,
+ data.len() - offset - 4
+ );
+ }
+ break;
+ }
+
+ let ext_data = &data[offset + 4..offset + 4 + ext_len];
+
+ match ext_type {
+ 0x0000 if is_client => {
+ // SNI
+ debug!("QUIC: Found SNI extension with {} bytes", ext_len);
+ if let Some(sni) = parse_sni_extension(ext_data, allow_partial) {
+ debug!("QUIC: Successfully parsed SNI: {}", sni);
+ info.sni = Some(sni);
+ } else {
+ debug!("QUIC: Failed to parse SNI extension");
+ }
+ }
+ 0x0010 => {
+ // ALPN
+ debug!("QUIC: Found ALPN extension with {} bytes", ext_len);
+ if let Some(alpn) = parse_alpn_extension(ext_data) {
+ debug!("QUIC: Successfully parsed ALPN: {:?}", alpn);
+ info.alpn = alpn;
+ } else {
+ debug!("QUIC: Failed to parse ALPN extension");
+ }
+ }
+ 0x002b => {
+ // Supported Versions
+ debug!(
+ "QUIC: Found Supported Versions extension with {} bytes",
+ ext_len
+ );
+ if let Some(version) = parse_supported_versions(ext_data, is_client) {
+ debug!("QUIC: Successfully parsed version: {:?}", version);
+ info.version = Some(version);
+ }
+ }
+ _ => {
+ debug!("QUIC: Skipping unknown extension type 0x{:04x}", ext_type);
+ }
+ }
+
+ offset += 4 + ext_len;
+ }
+
+ debug!(
+ "QUIC: Finished parsing extensions - found SNI={:?}, ALPN={:?}",
+ info.sni, info.alpn
+ );
+}
+
+/// Parse SNI extension
+fn parse_sni_extension(data: &[u8], allow_partial: bool) -> Option {
+ debug!(
+ "QUIC: Parsing SNI extension with {} bytes (allow_partial={}): {:02x?}",
+ data.len(),
+ allow_partial,
+ &data[..data.len().min(20)]
+ );
+
+ // Parse the SNI header using the unified helper
+ let header = match parse_sni_header(data) {
+ Some(h) => h,
+ None => {
+ debug!(
+ "QUIC: Failed to parse SNI header (data len: {})",
+ data.len()
+ );
+ return None;
+ }
+ };
+
+ debug!(
+ "QUIC: SNI header - list_len: {}, name_len: {}",
+ header.list_len, header.name_len
+ );
+
+ let name_len = header.name_len as usize;
+ let hostname_start = 5; // After header (2 + 1 + 2 bytes)
+
+ if hostname_start + name_len <= data.len() {
+ // Full hostname available
+ let sni_data = &data[hostname_start..hostname_start + name_len];
+ debug!("QUIC: SNI data: {:02x?}", sni_data);
+
+ match std::str::from_utf8(sni_data) {
+ Ok(sni) => {
+ if is_valid_hostname(sni) {
+ debug!("QUIC: Successfully parsed complete SNI: {}", sni);
+ Some(sni.to_string())
+ } else {
+ debug!("QUIC: SNI doesn't look like a valid hostname: {}", sni);
+ None
+ }
+ }
+ Err(e) => {
+ debug!("QUIC: SNI data is not valid UTF-8: {}", e);
+ None
+ }
+ }
+ } else if allow_partial && data.len() > hostname_start {
+ // Extract partial SNI as fallback when allowed
+ let available = &data[hostname_start..];
+ debug!(
+ "QUIC: SNI name extends beyond available data (need {}, have {}), extracting partial",
+ hostname_start + name_len,
+ data.len()
+ );
+
+ if let Ok(partial) = std::str::from_utf8(available)
+ && is_valid_partial_hostname(partial)
+ {
+ debug!("QUIC: Extracted partial SNI: {}", mark_partial_sni(partial));
+ return Some(mark_partial_sni(partial));
+ }
+ None
+ } else {
+ // SNI data is incomplete - don't extract partial, wait for more data
+ debug!(
+ "QUIC: SNI name extends beyond available data (need {}, have {}), waiting for more data",
+ hostname_start + name_len,
+ data.len()
+ );
+ None
+ }
+}
+
+/// Parse ALPN extension
+fn parse_alpn_extension(data: &[u8]) -> Option> {
+ if data.len() < 2 {
+ return None;
+ }
+
+ let mut protocols = Vec::new();
+ let alpn_len = u16::from_be_bytes([data[0], data[1]]) as usize;
+
+ let mut offset = 2;
+ let list_end = 2 + alpn_len.min(data.len() - 2);
+
+ while offset < list_end && offset < data.len() {
+ let proto_len = data[offset] as usize;
+ offset += 1;
+
+ if offset + proto_len <= data.len()
+ && let Ok(proto) = std::str::from_utf8(&data[offset..offset + proto_len])
+ {
+ protocols.push(proto.to_string());
+ }
+
+ offset += proto_len;
+ }
+
+ if protocols.is_empty() {
+ None
+ } else {
+ Some(protocols)
+ }
+}
+
+/// Greedy SNI extraction - scans raw data for SNI extension pattern
+/// This works even when full ClientHello parsing fails due to incomplete data
+///
+/// The `allow_partial` parameter controls whether partial SNI extraction is allowed:
+/// - `false`: Only return complete SNI
+/// - `true`: Return partial SNI as fallback when full hostname is truncated
+fn try_extract_sni_greedy(data: &[u8], allow_partial: bool) -> Option {
+ // SNI extension structure:
+ // 0x00 0x00 - extension type (SNI)
+ // 2 bytes - extension length
+ // 2 bytes - server name list length
+ // 0x00 - name type (hostname)
+ // 2 bytes - hostname length
+ // N bytes - hostname
+
+ if data.len() < 9 {
+ return None;
+ }
+
+ // Scan for SNI extension type pattern (0x00 0x00)
+ for i in 0..data.len().saturating_sub(9) {
+ // Look for SNI extension type marker
+ if data[i] == 0x00 && data[i + 1] == 0x00 {
+ // Read extension length
+ if i + 4 > data.len() {
+ continue;
+ }
+ let ext_len = u16::from_be_bytes([data[i + 2], data[i + 3]]) as usize;
+
+ // Sanity check extension length (5-300 bytes is reasonable for SNI)
+ if !(5..=300).contains(&ext_len) {
+ continue;
+ }
+
+ // Parse SNI header using unified helper
+ let sni_data = &data[i + 4..];
+ let header = match parse_sni_header(sni_data) {
+ Some(h) => h,
+ None => continue,
+ };
+
+ // List length should be <= ext_len - 2
+ if header.list_len as usize > ext_len {
+ continue;
+ }
+
+ let name_len = header.name_len as usize;
+ let hostname_start = i + 9; // After: ext_type(2) + ext_len(2) + list_len(2) + name_type(1) + name_len(2)
+ let hostname_end = hostname_start + name_len;
+
+ if hostname_end <= data.len() {
+ // Full hostname available
+ if let Ok(hostname) = std::str::from_utf8(&data[hostname_start..hostname_end])
+ && is_valid_hostname(hostname)
+ {
+ debug!("QUIC: Greedy SNI extraction found: {}", hostname);
+ return Some(hostname.to_string());
+ }
+ } else if allow_partial && hostname_start < data.len() {
+ // Partial hostname available - extract what we have (only if allowed)
+ if let Ok(partial) = std::str::from_utf8(&data[hostname_start..])
+ && is_valid_partial_hostname(partial)
+ {
+ debug!(
+ "QUIC: Greedy SNI extraction found partial: {}",
+ mark_partial_sni(partial)
+ );
+ return Some(mark_partial_sni(partial));
+ }
+ }
+ }
+ }
+
+ None
+}
+
+/// Parse supported versions extension
+fn parse_supported_versions(data: &[u8], is_client: bool) -> Option {
+ if is_client {
+ if data.is_empty() {
+ return None;
+ }
+
+ let list_len = data[0] as usize;
+ let mut offset = 1;
+
+ while offset + 1 < data.len() && offset < 1 + list_len {
+ if data[offset] == 0x03 && data[offset + 1] == 0x04 {
+ return Some(TlsVersion::Tls13);
+ }
+ offset += 2;
+ }
+ } else if data.len() >= 2 && data[0] == 0x03 && data[1] == 0x04 {
+ return Some(TlsVersion::Tls13);
+ }
+
+ // QUIC always uses TLS 1.3
+ Some(TlsVersion::Tls13)
+}
+
+/// Parse a variable-length integer (QUIC encoding)
+fn parse_variable_length_int(data: &[u8]) -> Option<(u64, usize)> {
+ if data.is_empty() {
+ return None;
+ }
+
+ let first_byte = data[0];
+ let len = match first_byte >> 6 {
+ 0 => 1,
+ 1 => 2,
+ 2 => 4,
+ 3 => 8,
+ _ => return None,
+ };
+
+ if data.len() < len {
+ return None;
+ }
+
+ let value = match len {
+ 1 => (first_byte & 0x3f) as u64,
+ 2 => {
+ let val = u16::from_be_bytes([data[0] & 0x3f, data[1]]);
+ val as u64
+ }
+ 4 => {
+ let val = u32::from_be_bytes([data[0] & 0x3f, data[1], data[2], data[3]]);
+ val as u64
+ }
+ 8 => u64::from_be_bytes([
+ data[0] & 0x3f,
+ data[1],
+ data[2],
+ data[3],
+ data[4],
+ data[5],
+ data[6],
+ data[7],
+ ]),
+ _ => return None,
+ };
+
+ Some((value, len))
+}
+
+/// Get QUIC packet type from long header
+fn get_long_packet_type(first_byte: u8, version: u32) -> QuicPacketType {
+ let type_bits = (first_byte & 0x30) >> 4;
+
+ if is_quic_v2(version) {
+ // QUIC v2 has different type mappings
+ match type_bits {
+ 0 => QuicPacketType::Retry,
+ 1 => QuicPacketType::Initial,
+ 2 => QuicPacketType::ZeroRtt,
+ 3 => QuicPacketType::Handshake,
+ _ => QuicPacketType::Unknown,
+ }
+ } else {
+ // QUIC v1 and drafts
+ match type_bits {
+ 0 => QuicPacketType::Initial,
+ 1 => QuicPacketType::ZeroRtt,
+ 2 => QuicPacketType::Handshake,
+ 3 => QuicPacketType::Retry,
+ _ => QuicPacketType::Unknown,
+ }
+ }
+}
+
+/// Check if version is QUIC v2
+fn is_quic_v2(version: u32) -> bool {
+ version == 0x6b3343cf
+}
+
+/// Derive a secret using HKDF
+fn derive_secret(prk: &hkdf::Prk, label: &[u8], out: &mut [u8]) -> bool {
+ let info = build_hkdf_label(label, &[], out.len());
+
+ prk.expand(&[&info], ArbitraryOutputLen(out.len()))
+ .and_then(|okm| okm.fill(out))
+ .is_ok()
+}
+
+/// Derive packet protection key
+fn derive_packet_protection_key(secret: &[u8], out: &mut [u8], version: u32) -> bool {
+ let prk = hkdf::Prk::new_less_safe(hkdf::HKDF_SHA256, secret);
+ let label: &[u8] = if is_quic_v2(version) {
+ b"quicv2 key"
+ } else {
+ b"quic key"
+ };
+ derive_secret(&prk, label, out)
+}
+
+/// Derive packet protection IV
+fn derive_packet_protection_iv(secret: &[u8], out: &mut [u8], version: u32) -> bool {
+ let prk = hkdf::Prk::new_less_safe(hkdf::HKDF_SHA256, secret);
+ let label: &[u8] = if is_quic_v2(version) {
+ b"quicv2 iv"
+ } else {
+ b"quic iv"
+ };
+ derive_secret(&prk, label, out)
+}
+
+/// Derive header protection key
+fn derive_header_protection_key(secret: &[u8], out: &mut [u8], version: u32) -> bool {
+ let prk = hkdf::Prk::new_less_safe(hkdf::HKDF_SHA256, secret);
+ let label: &[u8] = if is_quic_v2(version) {
+ b"quicv2 hp"
+ } else {
+ b"quic hp"
+ };
+ derive_secret(&prk, label, out)
+}
+
+/// Build HKDF label
+fn build_hkdf_label(label: &[u8], context: &[u8], length: usize) -> Vec {
+ let mut info = Vec::new();
+
+ // Length (2 bytes)
+ info.push((length >> 8) as u8);
+ info.push((length & 0xff) as u8);
+
+ // Label with "tls13 " prefix
+ let full_label = [b"tls13 ", label].concat();
+ info.push(full_label.len() as u8);
+ info.extend_from_slice(&full_label);
+
+ // Context
+ info.push(context.len() as u8);
+ info.extend_from_slice(context);
+
+ info
+}
+
+/// AES-ECB encryption for header protection
+fn aes_ecb_encrypt(key: &[u8], block: &[u8]) -> Option<[u8; 16]> {
+ let cipher = Aes128::new(key.try_into().ok()?);
+ let mut output: aes::cipher::Array = block.try_into().ok()?;
+ cipher.encrypt_block(&mut output);
+ Some(output.into())
+}
+
+/// Convert connection ID to hex string
+fn connection_id_to_hex(id: &[u8]) -> String {
+ id.iter()
+ .map(|b| format!("{:02x}", b))
+ .collect::>()
+ .join(":")
+}
+
+/// Check if a packet is likely a QUIC packet
+pub fn is_quic_packet(payload: &[u8]) -> bool {
+ if payload.len() < 5 {
+ return false;
+ }
+
+ let first_byte = payload[0];
+
+ // Check for QUIC long header (bit 7 set)
+ if (first_byte & 0x80) != 0 {
+ // Check version
+ let version = u32::from_be_bytes([payload[1], payload[2], payload[3], payload[4]]);
+
+ // Check for known QUIC versions
+ let known_versions = [
+ 0x00000001, // QUIC v1 (RFC 9000)
+ 0x6b3343cf, // QUIC v2
+ 0xff00001d, // draft-29
+ 0xff00001c, // draft-28
+ 0xff00001b, // draft-27
+ 0x51303530, // Google QUIC Q050
+ 0x51303433, // Google QUIC Q043
+ 0x54303530, // Google T050
+ 0xfaceb001, // Facebook mvfst draft-22
+ 0xfaceb002, // Facebook mvfst draft-27
+ 0, // Version negotiation
+ ];
+
+ if known_versions.contains(&version) {
+ return true;
+ }
+
+ // Check for IETF draft versions (0xff0000XX)
+ if (version >> 8) == 0xff0000 {
+ return true;
+ }
+
+ // Check for forcing version negotiation pattern
+ if (version & 0x0F0F0F0F) == 0x0a0a0a0a {
+ return true;
+ }
+ }
+
+ // Short header packet detection
+ // Bit 7 is 0, bit 6 is 1 for short header (fixed bit)
+ if (first_byte & 0xc0) == 0x40 {
+ // Minimum plausible 1-RTT packet: first byte + connection ID +
+ // packet number + AEAD tag. No upper bound: GSO/GRO capture can
+ // deliver datagrams well beyond the usual 1500-byte MTU.
+ return payload.len() >= 20;
+ }
+
+ false
+}
+
+/// Try to extract TLS information from unencrypted parts of QUIC packets
+/// Some QUIC implementations may have plaintext or partially encrypted data
+fn try_parse_unencrypted_crypto_frames(payload: &[u8]) -> Option {
+ // This is a best-effort attempt to find TLS ClientHello in the packet
+ // Look for TLS handshake patterns in the payload
+
+ debug!(
+ "QUIC: Searching for unencrypted TLS data in {} byte payload",
+ payload.len()
+ );
+
+ let mut offset = 0;
+ while offset + 10 < payload.len() {
+ // Need at least 10 bytes for meaningful TLS data
+ // Look for TLS handshake record header (0x16 0x03 0x01-0x04)
+ if payload[offset] == 0x16 && offset + 5 < payload.len() {
+ let tls_version_major = payload[offset + 1];
+ let tls_version_minor = payload[offset + 2];
+
+ // Check for reasonable TLS version (3.1 = TLS 1.0, 3.2 = TLS 1.1, 3.3 = TLS 1.2, 3.4 = TLS 1.3)
+ if tls_version_major == 0x03 && (0x01..=0x04).contains(&tls_version_minor) {
+ let record_length =
+ u16::from_be_bytes([payload[offset + 3], payload[offset + 4]]) as usize;
+
+ debug!(
+ "QUIC: Found TLS record at offset {} with length {}",
+ offset, record_length
+ );
+
+ if offset + 5 + record_length <= payload.len() {
+ let handshake_data = &payload[offset + 5..offset + 5 + record_length];
+
+ // Check if this is a ClientHello (handshake type 0x01)
+ if !handshake_data.is_empty() && handshake_data[0] == 0x01 {
+ debug!("QUIC: Found potential TLS ClientHello at offset {}", offset);
+
+ if let Some(tls_info) = parse_partial_tls_handshake(handshake_data, false) {
+ debug!(
+ "QUIC: Successfully parsed TLS from unencrypted data - SNI={:?}",
+ tls_info.sni
+ );
+ return Some(tls_info);
+ }
+ }
+ }
+ }
+ }
+
+ // Also try looking for direct handshake data (without TLS record wrapper)
+ if payload[offset] == 0x01 && offset + 4 < payload.len() {
+ // Direct handshake message starting with ClientHello (0x01)
+ let handshake_length = u32::from_be_bytes([
+ 0,
+ payload[offset + 1],
+ payload[offset + 2],
+ payload[offset + 3],
+ ]) as usize;
+
+ // Sanity check the length
+ if handshake_length > 0
+ && handshake_length < 65536
+ && offset + 4 + handshake_length <= payload.len()
+ {
+ debug!(
+ "QUIC: Found potential direct handshake at offset {} with length {}",
+ offset, handshake_length
+ );
+
+ if let Some(tls_info) = parse_partial_tls_handshake(
+ &payload[offset..offset + 4 + handshake_length],
+ false,
+ ) {
+ debug!(
+ "QUIC: Successfully parsed TLS from direct handshake - SNI={:?}",
+ tls_info.sni
+ );
+ return Some(tls_info);
+ }
+ }
+ }
+
+ // Look for SNI extension pattern directly (0x00 0x00 for SNI type)
+ // Add strict validation to reduce false positives from encrypted data
+ if offset + 20 < payload.len() && payload[offset] == 0x00 && payload[offset + 1] == 0x00 {
+ let ext_len = u16::from_be_bytes([payload[offset + 2], payload[offset + 3]]) as usize;
+
+ // Strict validation: reasonable extension length
+ if (5..=300).contains(&ext_len) && offset + 4 + ext_len <= payload.len() {
+ let ext_data = &payload[offset + 4..offset + 4 + ext_len];
+
+ // Pre-validate SNI structure before parsing
+ if ext_data.len() >= 5 {
+ let list_len = u16::from_be_bytes([ext_data[0], ext_data[1]]) as usize;
+ let sni_type = ext_data[2];
+ let name_len = u16::from_be_bytes([ext_data[3], ext_data[4]]) as usize;
+
+ // Only parse if structure looks valid
+ if sni_type == 0x00
+ && name_len > 0
+ && name_len <= 253
+ && (3..=256).contains(&list_len)
+ && list_len == name_len + 3
+ && let Some(sni) = parse_sni_extension(ext_data, false)
+ {
+ debug!("QUIC: Found SNI directly in packet: {}", sni);
+ let mut tls_info = TlsInfo::new();
+ tls_info.sni = Some(sni);
+ return Some(tls_info);
+ }
+ }
+ }
+ }
+
+ // Look for ALPN extension pattern (0x00 0x10 for ALPN type)
+ if offset + 10 < payload.len() && payload[offset] == 0x00 && payload[offset + 1] == 0x10 {
+ let ext_len = u16::from_be_bytes([payload[offset + 2], payload[offset + 3]]) as usize;
+ if ext_len > 2 && offset + 4 + ext_len <= payload.len() {
+ let ext_data = &payload[offset + 4..offset + 4 + ext_len];
+ if let Some(alpn) = parse_alpn_extension(ext_data) {
+ debug!("QUIC: Found ALPN directly in packet: {:?}", alpn);
+ let mut tls_info = TlsInfo::new();
+ tls_info.alpn = alpn;
+ return Some(tls_info);
+ }
+ }
+ }
+
+ offset += 1;
+ }
+
+ debug!("QUIC: No unencrypted TLS data found in packet");
+ None
+}
+
+/// Wrapper for HKDF expand with arbitrary output length
+struct ArbitraryOutputLen(usize);
+
+impl hkdf::KeyType for ArbitraryOutputLen {
+ fn len(&self) -> usize {
+ self.0
+ }
+}
+
+/// Try to reconstruct SNI from fragmented crypto data
+/// This looks for hostname patterns across fragment boundaries
+fn try_reconstruct_sni_from_fragments(reassembler: &CryptoFrameReassembler) -> Option {
+ debug!("QUIC: Attempting SNI reconstruction from fragments");
+
+ let fragments = reassembler.get_fragments();
+ let mut sorted_offsets: Vec<_> = fragments.keys().collect();
+ sorted_offsets.sort();
+
+ // First try: look for SNI extension patterns in individual fragments and reconstruct
+ // IMPORTANT: Only look in fragments that include the beginning of the ClientHello
+ // Otherwise we might find partial SNI data that's cut off at fragment boundaries
+ for &offset in &sorted_offsets {
+ // Skip fragments that don't start near the beginning of the ClientHello
+ // The SNI extension typically appears after ~70-150 bytes in the ClientHello
+ if *offset > 200 {
+ debug!(
+ "QUIC: Skipping fragment at offset {} - too far from ClientHello start",
+ offset
+ );
+ continue;
+ }
+
+ if let Some(data) = fragments.get(offset) {
+ debug!(
+ "QUIC: Scanning fragment at offset {} ({} bytes) for SNI patterns",
+ offset,
+ data.len()
+ );
+
+ // Look for SNI extension header patterns in this fragment
+ // Be more restrictive to avoid false positives from encrypted data
+ let mut i = 0;
+ while i + 10 < data.len() {
+ // Look for SNI extension: 0x00 0x00 (extension type) followed by length
+ if data[i] == 0x00 && data[i + 1] == 0x00 {
+ let ext_len = u16::from_be_bytes([data[i + 2], data[i + 3]]) as usize;
+
+ // SNI extension length should be reasonable (5-300 bytes typically)
+ if (5..=300).contains(&ext_len) {
+ // Check if we have full extension data or partial
+ let available_ext_data = if i + 4 + ext_len <= data.len() {
+ &data[i + 4..i + 4 + ext_len]
+ } else {
+ &data[i + 4..]
+ };
+
+ // Parse SNI header using unified helper
+ if let Some(header) = parse_sni_header(available_ext_data) {
+ // Additional validation: list_len should be reasonable
+ if (3..=256).contains(&(header.list_len as usize)) {
+ // Try to parse with partial extraction allowed
+ if let Some(sni) = parse_sni_extension(available_ext_data, true) {
+ if is_partial_sni(&sni) {
+ debug!("QUIC: Found partial SNI in fragment: {}", sni);
+ } else {
+ debug!("QUIC: Found complete SNI in fragment: {}", sni);
+ }
+ return Some(sni);
+ }
+ }
+ }
+ }
+ }
+ i += 1;
+ }
+ }
+ }
+
+ // Second try: smart fragment combination - try to fill gaps and maintain order
+ debug!("QUIC: Smart combining fragments for hostname pattern search");
+
+ // Check if we have fragments that include the ClientHello beginning
+ // We need at least one fragment starting at or very close to offset 0
+ let has_beginning = sorted_offsets.iter().any(|&offset| *offset <= 10);
+
+ if !has_beginning {
+ debug!(
+ "QUIC: No fragment near offset 0 (first at {:?}) - missing ClientHello beginning, skipping SNI extraction",
+ sorted_offsets.first()
+ );
+ return None;
+ }
+
+ let mut all_data = Vec::new();
+ let mut expected_offset = 0u64;
+ let mut has_significant_gaps = false;
+
+ for &offset in &sorted_offsets {
+ if let Some(data) = fragments.get(offset) {
+ debug!(
+ "QUIC: Processing fragment at offset {} ({} bytes), expected offset was {}",
+ offset,
+ data.len(),
+ expected_offset
+ );
+
+ // If there's a gap, be more careful about continuing
+ if *offset > expected_offset {
+ let gap_size = *offset - expected_offset;
+ debug!("QUIC: Gap detected of {} bytes between fragments", gap_size);
+
+ // Gaps in the first 100 bytes are critical as they likely contain SNI
+ // The SNI extension typically appears between bytes 70-200 of the ClientHello
+ // Relaxed threshold from 20 to 50 bytes to be less aggressive
+ if expected_offset < 100 && gap_size > 50 {
+ has_significant_gaps = true;
+ debug!("QUIC: Gap in critical ClientHello region - SNI might be incomplete");
+ }
+
+ // Large gaps anywhere might indicate missing data
+ // Relaxed threshold from 200 to 300 bytes
+ if gap_size > 300 {
+ has_significant_gaps = true;
+ debug!(
+ "QUIC: Large gap detected ({} bytes) - data might be incomplete",
+ gap_size
+ );
+ }
+
+ // For smaller gaps, add minimal padding to maintain data alignment
+ if gap_size <= 100 && !all_data.is_empty() {
+ // Add minimal padding to maintain structure
+ all_data.resize(all_data.len() + gap_size as usize, 0);
+ debug!("QUIC: Added {} bytes of padding for small gap", gap_size);
+ }
+ }
+
+ all_data.extend_from_slice(data);
+ expected_offset = *offset + data.len() as u64;
+ }
+ }
+
+ if all_data.len() < 10 {
+ debug!(
+ "QUIC: Not enough data for SNI reconstruction ({} bytes)",
+ all_data.len()
+ );
+ return None;
+ }
+
+ debug!(
+ "QUIC: Searching for hostname patterns in {} bytes of combined data",
+ all_data.len()
+ );
+ let candidates = find_hostname_candidates(&all_data);
+
+ // Process candidates to detect truncation and mark incomplete ones
+ let mut processed_candidates = Vec::new();
+
+ // If we have significant gaps (missing fragments), don't trust ANY hostname candidates
+ // as they are likely incomplete or corrupted
+ if has_significant_gaps {
+ debug!("QUIC: Not returning any hostname candidates due to significant gaps in fragments");
+ // We could still look for very long, complete-looking hostnames, but it's safer to wait
+ for candidate in &candidates {
+ // Only accept very long, complete-looking hostnames when gaps exist
+ if candidate.len() >= 15 && candidate.matches('.').count() >= 2 {
+ debug!(
+ "QUIC: Accepting long candidate '{}' despite gaps",
+ candidate
+ );
+ if is_valid_hostname(candidate) {
+ processed_candidates.push(candidate.clone());
+ }
+ } else {
+ debug!(
+ "QUIC: Rejecting candidate '{}' due to fragment gaps",
+ candidate
+ );
+ }
+ }
+ } else {
+ // No significant gaps - process normally
+ // Only accept complete valid hostnames
+ for candidate in candidates {
+ if is_valid_hostname(&candidate) {
+ processed_candidates.push(candidate);
+ }
+ // Don't try to mark truncated hostnames - wait for complete data
+ }
+ }
+
+ // Sort by length (longer first) to prefer complete hostnames, but prioritize unmarked ones
+ processed_candidates.sort_by(|a, b| {
+ let a_is_partial = is_partial_sni(a) || a.contains("...");
+ let b_is_partial = is_partial_sni(b) || b.contains("...");
+
+ // Prefer complete hostnames over truncated/partial ones
+ match (a_is_partial, b_is_partial) {
+ (false, true) => std::cmp::Ordering::Less, // a is complete, prefer it
+ (true, false) => std::cmp::Ordering::Greater, // b is complete, prefer it
+ _ => b.len().cmp(&a.len()), // both same type, prefer longer
+ }
+ });
+
+ if let Some(candidate) = processed_candidates.first() {
+ debug!("QUIC: Found hostname candidate: {}", candidate);
+ return Some(candidate.clone());
+ }
+
+ None
+}
+
+/// Find potential hostname strings in binary data
+fn find_hostname_candidates(data: &[u8]) -> Vec {
+ let mut candidates = Vec::new();
+
+ let mut i = 0;
+ while i < data.len() {
+ // Look for sequences that might be hostnames
+ if data[i].is_ascii_alphanumeric() {
+ let mut end = i;
+ let mut has_dot = false;
+ let mut dot_count = 0;
+
+ // Extend while we have valid hostname characters
+ while end < data.len()
+ && (data[end].is_ascii_alphanumeric() || data[end] == b'.' || data[end] == b'-')
+ {
+ if data[end] == b'.' {
+ has_dot = true;
+ dot_count += 1;
+ }
+ end += 1;
+ }
+
+ // Extract potential hostname if it looks reasonable
+ if end > i + 3 && has_dot && dot_count <= 10 {
+ // At least 4 chars with a dot, max 10 dots
+ if let Ok(candidate) = String::from_utf8(data[i..end].to_vec()) {
+ // Clean up the candidate
+ let cleaned = candidate
+ .trim_matches(|c: char| !c.is_ascii_alphanumeric() && c != '.' && c != '-');
+
+ // Additional validation: check for reasonable hostname structure
+ if !cleaned.is_empty()
+ && !cleaned.starts_with('.')
+ && !cleaned.ends_with('.')
+ && !cleaned.contains("..")
+ {
+ debug!("QUIC: Found hostname candidate: {}", cleaned);
+ candidates.push(cleaned.to_string());
+
+ // Also look for sub-patterns within longer strings
+ // This helps catch cases where we have "prefix.hostname.suffix"
+ let parts: Vec<&str> = cleaned.split('.').collect();
+ if parts.len() > 2 {
+ // Try combinations of consecutive parts
+ for start_idx in 0..parts.len() {
+ for end_idx in (start_idx + 2)..=parts.len() {
+ let sub_candidate = parts[start_idx..end_idx].join(".");
+ if sub_candidate != cleaned && sub_candidate.len() >= 4 {
+ debug!(
+ "QUIC: Found sub-hostname candidate: {}",
+ sub_candidate
+ );
+ candidates.push(sub_candidate);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ i = end;
+ } else {
+ i += 1;
+ }
+ }
+
+ // Remove duplicates while preserving order
+ let mut unique_candidates = Vec::new();
+ for candidate in candidates {
+ if !unique_candidates.contains(&candidate) {
+ unique_candidates.push(candidate);
+ }
+ }
+
+ unique_candidates
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ /// Build a minimal SNI extension structure
+ fn build_sni_extension(hostname: &str) -> Vec {
+ let name_bytes = hostname.as_bytes();
+ let name_len = name_bytes.len() as u16;
+ let list_len = name_len + 3; // name_type (1) + name_len (2)
+ let ext_len = list_len + 2; // list_len (2)
+
+ let mut data = Vec::new();
+ // Extension type: SNI (0x0000)
+ data.push(0x00);
+ data.push(0x00);
+ // Extension length
+ data.extend_from_slice(&ext_len.to_be_bytes());
+ // Server name list length
+ data.extend_from_slice(&list_len.to_be_bytes());
+ // Name type: hostname (0x00)
+ data.push(0x00);
+ // Name length
+ data.extend_from_slice(&name_len.to_be_bytes());
+ // Hostname
+ data.extend_from_slice(name_bytes);
+
+ data
+ }
+
+ #[test]
+ fn test_greedy_sni_extraction_complete() {
+ let sni_ext = build_sni_extension("www.example.com");
+ // allow_partial doesn't matter when full hostname is available
+ let result = try_extract_sni_greedy(&sni_ext, false);
+ assert_eq!(result, Some("www.example.com".to_string()));
+ }
+
+ #[test]
+ fn test_greedy_sni_extraction_with_prefix() {
+ // Add some random bytes before the SNI extension
+ let mut data = vec![0x01, 0x02, 0x03, 0x04, 0x05];
+ data.extend(build_sni_extension("api.google.com"));
+ let result = try_extract_sni_greedy(&data, false);
+ assert_eq!(result, Some("api.google.com".to_string()));
+ }
+
+ #[test]
+ fn test_greedy_sni_extraction_partial() {
+ // Build partial SNI extension (hostname truncated)
+ // With fragmented QUIC packets, we need to extract partial SNI
+ let mut data = Vec::new();
+ data.push(0x00); // ext type
+ data.push(0x00);
+ data.extend_from_slice(&20u16.to_be_bytes()); // ext_len (full would be 20)
+ data.extend_from_slice(&18u16.to_be_bytes()); // list_len
+ data.push(0x00); // name type
+ data.extend_from_slice(&15u16.to_be_bytes()); // name_len (15 chars)
+ data.extend_from_slice(b"www.examp"); // only 9 chars provided
+
+ // With allow_partial=false, returns None
+ let result = try_extract_sni_greedy(&data, false);
+ assert_eq!(result, None);
+
+ // With allow_partial=true, returns partial SNI
+ let result = try_extract_sni_greedy(&data, true);
+ assert_eq!(result, Some("www.examp[PARTIAL]".to_string()));
+ }
+
+ #[test]
+ fn test_parse_sni_extension_complete() {
+ // Build SNI extension data (without the extension type/length header)
+ let hostname = "test.example.org";
+ let name_bytes = hostname.as_bytes();
+ let name_len = name_bytes.len() as u16;
+ let list_len = name_len + 3;
+
+ let mut data = Vec::new();
+ data.extend_from_slice(&list_len.to_be_bytes());
+ data.push(0x00); // name type
+ data.extend_from_slice(&name_len.to_be_bytes());
+ data.extend_from_slice(name_bytes);
+
+ let result = parse_sni_extension(&data, false);
+ assert_eq!(result, Some("test.example.org".to_string()));
+ }
+
+ #[test]
+ fn test_parse_sni_extension_partial() {
+ // Build partial SNI extension data
+ let mut data = Vec::new();
+ data.extend_from_slice(&20u16.to_be_bytes()); // list_len
+ data.push(0x00); // name type
+ data.extend_from_slice(&15u16.to_be_bytes()); // declared name_len
+ data.extend_from_slice(b"example.co"); // only 10 chars
+
+ // With allow_partial=false, returns None
+ let result = parse_sni_extension(&data, false);
+ assert_eq!(result, None);
+
+ // With allow_partial=true, returns partial SNI
+ let result = parse_sni_extension(&data, true);
+ assert_eq!(result, Some("example.co[PARTIAL]".to_string()));
+ }
+
+ #[test]
+ fn test_initial_packet_oversized_token_len_does_not_panic() {
+ // Crafted Initial packet whose declared token length pushes the parse
+ // offset past the end of the packet. Pre-fix this panicked when
+ // slicing &packet[offset..] in try_decrypt_initial_with_secret.
+ let mut packet = Vec::new();
+ packet.push(0xC0); // long header, type=Initial
+ packet.extend_from_slice(&1u32.to_be_bytes()); // version 1
+ packet.push(0); // DCID len = 0
+ packet.push(0); // SCID len = 0
+ // Token length: 2-byte QUIC varint encoding 1000 (top 2 bits = 01).
+ let token_varint: u16 = 1000 | 0x4000;
+ packet.extend_from_slice(&token_varint.to_be_bytes());
+ // Intentionally no token bytes follow — declared length far exceeds packet.
+
+ let secret = [0u8; 32];
+ let result = try_decrypt_initial_with_secret(&packet, &secret, 1);
+ assert!(result.is_none());
+ }
+
+ #[test]
+ fn test_initial_packet_short_length_does_not_underflow() {
+ // Regression: a crafted Initial packet whose declared payload `Length`
+ // varint is smaller than the packet-number length made
+ // `packet_payload_length - pn_length` underflow. In debug that panicked
+ // outright; in release it wrapped to a huge value that slipped past the
+ // bounds check and panicked on the ciphertext slice (start > end).
+ // It must now bail out via checked_sub and return None.
+ let mut packet = Vec::new();
+ packet.push(0xC0); // long header, type = Initial
+ packet.extend_from_slice(&1u32.to_be_bytes()); // version 1
+ packet.push(0); // DCID len = 0
+ packet.push(0); // SCID len = 0
+ packet.push(0); // token length varint = 0
+ packet.push(0); // packet length varint = 0 (< any pn_length of 1..=4)
+ // Enough trailing bytes for the header-protection sample
+ // (sample_offset + 16 must be within the packet).
+ packet.extend(std::iter::repeat_n(0u8, 24));
+
+ let secret = [0u8; 32];
+ // Must not panic in either debug or release.
+ let result = try_decrypt_initial_with_secret(&packet, &secret, 1);
+ assert!(result.is_none());
+ }
+
+ #[test]
+ fn test_parse_alpn_extension() {
+ // Build ALPN extension data (without the extension type/length header)
+ let mut data = Vec::new();
+ let protocols = vec!["h3", "h2"];
+
+ let mut proto_list = Vec::new();
+ for proto in &protocols {
+ proto_list.push(proto.len() as u8);
+ proto_list.extend_from_slice(proto.as_bytes());
+ }
+
+ data.extend_from_slice(&(proto_list.len() as u16).to_be_bytes());
+ data.extend_from_slice(&proto_list);
+
+ let result = parse_alpn_extension(&data);
+ assert_eq!(result, Some(vec!["h3".to_string(), "h2".to_string()]));
+ }
+
+ #[test]
+ fn test_is_valid_hostname() {
+ assert!(is_valid_hostname("example.com"));
+ assert!(is_valid_hostname("www.example.com"));
+ assert!(is_valid_hostname("sub.domain.example.org"));
+ assert!(is_valid_hostname("my-site.io"));
+
+ // Invalid hostnames
+ assert!(!is_valid_hostname("com")); // No dot
+ assert!(!is_valid_hostname(".example.com")); // Starts with dot
+ assert!(!is_valid_hostname("example.com.")); // Ends with dot
+ assert!(!is_valid_hostname("-example.com")); // Starts with hyphen
+ assert!(!is_valid_hostname("example..com")); // Consecutive dots
+ assert!(!is_valid_hostname("ab")); // Too short
+ }
+
+ #[test]
+ fn test_greedy_extraction_ignores_invalid_patterns() {
+ // Data with 0x00 0x00 but invalid SNI structure
+ let data = vec![0x00, 0x00, 0x00, 0x01, 0x00]; // ext_len = 1 (too short)
+ let result = try_extract_sni_greedy(&data, true);
+ assert_eq!(result, None);
+ }
+
+ #[test]
+ fn test_greedy_extraction_multiple_zeros() {
+ // Data with multiple 0x00 0x00 sequences, only one valid
+ let mut data = vec![0x00, 0x00, 0x00, 0x02, 0xFF]; // Invalid SNI
+ data.extend(build_sni_extension("valid.example.com"));
+ let result = try_extract_sni_greedy(&data, false);
+ assert_eq!(result, Some("valid.example.com".to_string()));
+ }
+
+ /// Regression test for a panic in parse_long_header_packet_with_length:
+ /// a malformed Initial packet whose token_length varint decodes to a
+ /// value far larger than the payload used to push `offset` past
+ /// `payload.len()`, causing the next slice access at line 408 to panic
+ /// with "range start index ... out of range for slice of length ...".
+ #[test]
+ fn test_long_header_huge_token_length_does_not_panic() {
+ // Build a QUIC v1 Initial long-header packet.
+ // first byte: 0xC0 (long, Initial, pkt num len irrelevant here)
+ // version: 0x00000001
+ // DCID len: 0
+ // SCID len: 0
+ // token len varint: 8-byte form with a huge value (0xC8 8C ...)
+ // That varint decodes (with the top 2 bits masked) to
+ // 0x08c8c8c8c8c8ca67 — the exact value from the observed panic.
+ let mut pkt = Vec::new();
+ pkt.push(0xC0);
+ pkt.extend_from_slice(&0x0000_0001u32.to_be_bytes());
+ pkt.push(0); // DCID len
+ pkt.push(0); // SCID len
+ pkt.extend_from_slice(&[0xC8, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0xCA, 0x67]);
+ // Some trailing bytes to make the payload look plausible.
+ pkt.extend(std::iter::repeat_n(0u8, 64));
+
+ // Must not panic.
+ let (info, consumed) = parse_long_header_packet_with_length(&pkt);
+ assert!(info.is_some(), "should still surface a QuicInfo");
+ assert_eq!(
+ consumed,
+ pkt.len(),
+ "unparseable token length should cause us to treat the rest of the datagram as consumed"
+ );
+ }
+
+ /// Decode a hex string, ignoring whitespace
+ fn from_hex(s: &str) -> Vec {
+ let cleaned: Vec = s.bytes().filter(u8::is_ascii_hexdigit).collect();
+ cleaned
+ .chunks(2)
+ .map(|pair| u8::from_str_radix(std::str::from_utf8(pair).unwrap(), 16).unwrap())
+ .collect()
+ }
+
+ /// RFC 9001 Appendix A.2: the complete protected client Initial packet
+ /// (DCID 0x8394c8f03e515708, packet number 2, 1200 bytes).
+ const RFC9001_CLIENT_INITIAL: &str = "
+ c000000001088394c8f03e5157080000449e7b9aec34d1b1c98dd7689fb8ec11
+ d242b123dc9bd8bab936b47d92ec356c0bab7df5976d27cd449f63300099f399
+ 1c260ec4c60d17b31f8429157bb35a1282a643a8d2262cad67500cadb8e7378c
+ 8eb7539ec4d4905fed1bee1fc8aafba17c750e2c7ace01e6005f80fcb7df6212
+ 30c83711b39343fa028cea7f7fb5ff89eac2308249a02252155e2347b63d58c5
+ 457afd84d05dfffdb20392844ae812154682e9cf012f9021a6f0be17ddd0c208
+ 4dce25ff9b06cde535d0f920a2db1bf362c23e596d11a4f5a6cf3948838a3aec
+ 4e15daf8500a6ef69ec4e3feb6b1d98e610ac8b7ec3faf6ad760b7bad1db4ba3
+ 485e8a94dc250ae3fdb41ed15fb6a8e5eba0fc3dd60bc8e30c5c4287e53805db
+ 059ae0648db2f64264ed5e39be2e20d82df566da8dd5998ccabdae053060ae6c
+ 7b4378e846d29f37ed7b4ea9ec5d82e7961b7f25a9323851f681d582363aa5f8
+ 9937f5a67258bf63ad6f1a0b1d96dbd4faddfcefc5266ba6611722395c906556
+ be52afe3f565636ad1b17d508b73d8743eeb524be22b3dcbc2c7468d54119c74
+ 68449a13d8e3b95811a198f3491de3e7fe942b330407abf82a4ed7c1b311663a
+ c69890f4157015853d91e923037c227a33cdd5ec281ca3f79c44546b9d90ca00
+ f064c99e3dd97911d39fe9c5d0b23a229a234cb36186c4819e8b9c5927726632
+ 291d6a418211cc2962e20fe47feb3edf330f2c603a9d48c0fcb5699dbfe58964
+ 25c5bac4aee82e57a85aaf4e2513e4f05796b07ba2ee47d80506f8d2c25e50fd
+ 14de71e6c418559302f939b0e1abd576f279c4b2e0feb85c1f28ff18f58891ff
+ ef132eef2fa09346aee33c28eb130ff28f5b766953334113211996d20011a198
+ e3fc433f9f2541010ae17c1bf202580f6047472fb36857fe843b19f5984009dd
+ c324044e847a4f4a0ab34f719595de37252d6235365e9b84392b061085349d73
+ 203a4a13e96f5432ec0fd4a1ee65accdd5e3904df54c1da510b0ff20dcc0c77f
+ cb2c0e0eb605cb0504db87632cf3d8b4dae6e705769d1de354270123cb11450e
+ fc60ac47683d7b8d0f811365565fd98c4c8eb936bcab8d069fc33bd801b03ade
+ a2e1fbc5aa463d08ca19896d2bf59a071b851e6c239052172f296bfb5e724047
+ 90a2181014f3b94a4e97d117b438130368cc39dbb2d198065ae3986547926cd2
+ 162f40a29f0c3c8745c0f50fba3852e566d44575c29d39a03f0cda721984b6f4
+ 40591f355e12d439ff150aab7613499dbd49adabc8676eef023b15b65bfc5ca0
+ 6948109f23f350db82123535eb8a7433bdabcb909271a6ecbcb58b936a88cd4e
+ 8f2e6ff5800175f113253d8fa9ca8885c2f552e657dc603f252e1a8e308f76f0
+ be79e2fb8f5d5fbbe2e30ecadd220723c8c0aea8078cdfcb3868263ff8f09400
+ 54da48781893a7e49ad5aff4af300cd804a6b6279ab3ff3afb64491c85194aab
+ 760d58a606654f9f4400e8b38591356fbf6425aca26dc85244259ff2b19c41b9
+ f96f3ca9ec1dde434da7d2d392b905ddf3d1f9af93d1af5950bd493f5aa731b4
+ 056df31bd267b6b90a079831aaf579be0a39013137aac6d404f518cfd4684064
+ 7e78bfe706ca4cf5e9c5453e9f7cfd2b8b4c8d169a44e55c88d4a9a7f9474241
+ e221af44860018ab0856972e194cd934";
+
+ /// RFC 9001 Appendix A.2: the CRYPTO frame carried by the client Initial
+ /// (frame header 06 00 40 f1 followed by a 241-byte ClientHello with
+ /// SNI example.com and ALPN "alpn").
+ const RFC9001_CLIENT_CRYPTO_FRAME: &str = "
+ 060040f1010000ed0303ebf8fa56f12939b9584a3896472ec40bb863cfd3e868
+ 04fe3a47f06a2b69484c00000413011302010000c000000010000e00000b6578
+ 616d706c652e636f6dff01000100000a00080006001d0017001800100007000504616c706e
+ 0005000501000000000033002600
+ 24001d00209370b2c9caa47fbabaf4559fedba753de171fa71f50f1ce15d43e9
+ 94ec74d748002b0003020304000d0010000e04030503060302030804080508
+ 06002d00020101001c00024001003900320408ffffffffffffffff0504800
+ 0ffff07048000ffff0801100104800075300901100f088394c8f03e5157080
+ 6048000ffff";
+
+ /// RFC 9001 Appendix A.3: the protected server Initial packet
+ /// (zero-length DCID, SCID 0xf067a5502a4262b5).
+ const RFC9001_SERVER_INITIAL: &str = "
+ cf000000010008f067a5502a4262b5004075c0d95a482cd0991cd25b0aac406a
+ 5816b6394100f37a1c69797554780bb38cc5a99f5ede4cf73c3ec2493a1839b3
+ dbcba3f6ea46c5b7684df3548e7ddeb9c3bf9c73cc3f3bded74b562bfb19fb84
+ 022f8ef4cdd93795d77d06edbb7aaf2f58891850abbdca3d20398c276456cbc4
+ 2158407dd074ee";
+
+ /// RFC 9001 Appendix A.4: a Retry packet responding to the client Initial.
+ const RFC9001_RETRY: &str =
+ "ff000000010008f067a5502a4262b5746f6b656e04a265ba2eff4d829058fb3f0f2496ba";
+
+ #[test]
+ fn test_rfc9001_a1_initial_key_derivation() {
+ let dcid = from_hex("8394c8f03e515708");
+ let salt = hkdf::Salt::new(hkdf::HKDF_SHA256, initial_salt_for_version(1));
+ let initial_secret = salt.extract(&dcid);
+
+ let mut client_secret = [0u8; 32];
+ assert!(derive_secret(
+ &initial_secret,
+ b"client in",
+ &mut client_secret
+ ));
+ assert_eq!(
+ client_secret.to_vec(),
+ from_hex("c00cf151ca5be075ed0ebfb5c80323c42d6b7db67881289af4008f1f6c357aea")
+ );
+
+ let mut key = [0u8; 16];
+ let mut iv = [0u8; 12];
+ let mut hp = [0u8; 16];
+ assert!(derive_packet_protection_key(&client_secret, &mut key, 1));
+ assert!(derive_packet_protection_iv(&client_secret, &mut iv, 1));
+ assert!(derive_header_protection_key(&client_secret, &mut hp, 1));
+ assert_eq!(key.to_vec(), from_hex("1f369613dd76d5467730efcbe3b1a22d"));
+ assert_eq!(iv.to_vec(), from_hex("fa044b2f42a3fd3b46fb255c"));
+ assert_eq!(hp.to_vec(), from_hex("9f50449e04a0e810283a1e9933adedd2"));
+ }
+
+ #[test]
+ fn test_rfc9001_a2_client_initial_end_to_end() {
+ let packet = from_hex(RFC9001_CLIENT_INITIAL);
+ assert_eq!(packet.len(), 1200);
+
+ let info = parse_quic_packet(&packet).expect("client Initial should parse");
+ assert_eq!(info.packet_type, QuicPacketType::Initial);
+ assert_eq!(info.connection_state, QuicConnectionState::Initial);
+ assert_eq!(info.connection_id, from_hex("8394c8f03e515708"));
+ assert!(
+ info.connection_id_hex.is_some(),
+ "decrypted client Initial should be marked for connection tracking"
+ );
+
+ let tls = info
+ .tls_info
+ .expect("decryption should yield ClientHello TLS info");
+ assert_eq!(tls.sni.as_deref(), Some("example.com"));
+ assert_eq!(tls.alpn, vec!["alpn".to_string()]);
+ assert_eq!(tls.version, Some(TlsVersion::Tls13));
+ }
+
+ #[test]
+ fn test_rfc9001_a3_server_initial_parses_without_tls() {
+ let packet = from_hex(RFC9001_SERVER_INITIAL);
+
+ let info = parse_quic_packet(&packet).expect("server Initial should parse");
+ assert_eq!(info.packet_type, QuicPacketType::Initial);
+ // The server Initial has a zero-length DCID; Initial keys derive from
+ // the client's original DCID, which this packet does not carry, so no
+ // TLS info can be extracted.
+ assert!(info.connection_id.is_empty());
+ assert!(info.tls_info.is_none());
+ }
+
+ #[test]
+ fn test_retry_packet_consumes_rest_of_datagram() {
+ let retry = from_hex(RFC9001_RETRY);
+
+ let (info, consumed) = parse_long_header_packet_with_length(&retry);
+ let info = info.expect("Retry packet should parse");
+ assert_eq!(info.packet_type, QuicPacketType::Retry);
+ assert_eq!(
+ consumed,
+ retry.len(),
+ "Retry has no Length field, must consume the whole datagram"
+ );
+
+ // Retry token bytes must not be misparsed as coalesced packets. A
+ // token byte with the short-header bit pattern previously flipped the
+ // connection state to Connected.
+ let mut datagram = retry.clone();
+ datagram.extend_from_slice(&[0x42; 32]);
+ let info = parse_quic_packet(&datagram).expect("datagram should parse");
+ assert_eq!(info.packet_type, QuicPacketType::Retry);
+ assert_eq!(info.connection_state, QuicConnectionState::Initial);
+ }
+
+ #[test]
+ fn test_version_negotiation_consumes_rest_of_datagram() {
+ let mut pkt = vec![0x80u8]; // long header form, no fixed bit required
+ pkt.extend_from_slice(&0u32.to_be_bytes()); // version 0 = negotiation
+ pkt.push(8);
+ pkt.extend_from_slice(&[0xaa; 8]); // DCID
+ pkt.push(0); // SCID len
+ // Supported versions list - must not be misread as a Length field
+ pkt.extend_from_slice(&1u32.to_be_bytes());
+ pkt.extend_from_slice(&0x6b3343cfu32.to_be_bytes());
+
+ let (info, consumed) = parse_long_header_packet_with_length(&pkt);
+ let info = info.expect("Version Negotiation packet should parse");
+ assert_eq!(info.packet_type, QuicPacketType::VersionNegotiation);
+ assert_eq!(consumed, pkt.len());
+ }
+
+ #[test]
+ fn test_short_header_requires_fixed_bit() {
+ assert!(parse_short_header_packet(&[0x00; 30]).is_none());
+ assert!(parse_short_header_packet(&[0x41; 30]).is_some());
+ }
+
+ #[test]
+ fn test_initial_salt_for_version_mapping() {
+ assert_eq!(initial_salt_for_version(0x00000001), INITIAL_SALT_V1);
+ assert_eq!(initial_salt_for_version(0x6b3343cf), INITIAL_SALT_V2);
+ assert_eq!(initial_salt_for_version(0xff00001d), INITIAL_SALT_DRAFT_29); // draft-29
+ assert_eq!(initial_salt_for_version(0xff000020), INITIAL_SALT_DRAFT_29); // draft-32
+ assert_eq!(initial_salt_for_version(0xff00001b), INITIAL_SALT_DRAFT_23); // draft-27
+ assert_eq!(initial_salt_for_version(0xfaceb002), INITIAL_SALT_DRAFT_23); // mvfst
+ assert_eq!(initial_salt_for_version(0xff000022), INITIAL_SALT_V1); // draft-34
+ }
+
+ /// Protect (encrypt) a client Initial packet with a 4-byte packet number
+ /// the way a real client would, so tests can exercise decryption end to
+ /// end (RFC 9001 §5.3, §5.4).
+ fn protect_client_initial(dcid: &[u8], packet_number: u32, frames: &[u8]) -> Vec {
+ let salt = hkdf::Salt::new(hkdf::HKDF_SHA256, INITIAL_SALT_V1);
+ let initial_secret = salt.extract(dcid);
+ let mut client_secret = [0u8; 32];
+ assert!(derive_secret(
+ &initial_secret,
+ b"client in",
+ &mut client_secret
+ ));
+ let mut key = [0u8; 16];
+ let mut iv = [0u8; 12];
+ let mut hp = [0u8; 16];
+ assert!(derive_packet_protection_key(&client_secret, &mut key, 1));
+ assert!(derive_packet_protection_iv(&client_secret, &mut iv, 1));
+ assert!(derive_header_protection_key(&client_secret, &mut hp, 1));
+
+ let mut header = vec![0xc3]; // long header, Initial, pn_len = 4
+ header.extend_from_slice(&1u32.to_be_bytes());
+ header.push(dcid.len() as u8);
+ header.extend_from_slice(dcid);
+ header.push(0); // SCID len = 0
+ header.push(0); // token length = 0
+ let length = (4 + frames.len() + 16) as u16; // pn + frames + AEAD tag
+ header.extend_from_slice(&(0x4000u16 | length).to_be_bytes());
+ let pn_offset = header.len();
+ header.extend_from_slice(&packet_number.to_be_bytes());
+
+ let aead_key = LessSafeKey::new(UnboundKey::new(&aead::AES_128_GCM, &key).unwrap());
+ let mut nonce_bytes = iv;
+ for i in 0..4 {
+ nonce_bytes[11 - i] ^= ((u64::from(packet_number) >> (i * 8)) & 0xff) as u8;
+ }
+ let nonce = Nonce::assume_unique_for_key(nonce_bytes);
+ let mut ciphertext = frames.to_vec();
+ aead_key
+ .seal_in_place_append_tag(nonce, Aad::from(&header), &mut ciphertext)
+ .unwrap();
+
+ // With a 4-byte packet number the header protection sample is the
+ // first 16 bytes of the ciphertext.
+ let mask = aes_ecb_encrypt(&hp, &ciphertext[..16]).unwrap();
+ let mut packet = header;
+ packet[0] ^= mask[0] & 0x0f;
+ for i in 0..4 {
+ packet[pn_offset + i] ^= mask[1 + i];
+ }
+ packet.extend_from_slice(&ciphertext);
+ packet
+ }
+
+ /// A large ClientHello (e.g. with post-quantum key shares) is split
+ /// across multiple Initial packets that are coalesced into one datagram.
+ /// The CRYPTO fragments of every coalesced packet must be merged before
+ /// extracting the SNI - neither packet alone contains the full hostname.
+ #[test]
+ fn test_coalesced_initials_with_split_crypto_yield_sni() {
+ let crypto_frame = from_hex(RFC9001_CLIENT_CRYPTO_FRAME);
+ let client_hello = &crypto_frame[4..]; // strip frame header 06 00 40 f1
+
+ // Split inside the SNI hostname so packet 1 alone cannot yield it
+ let split = 60;
+ let (part1, part2) = client_hello.split_at(split);
+
+ let mut frames1 = vec![0x06, 0x00, part1.len() as u8]; // CRYPTO, offset 0
+ frames1.extend_from_slice(part1);
+ let mut frames2 = vec![0x06, split as u8]; // CRYPTO, offset 60
+ frames2.extend_from_slice(&(0x4000u16 | part2.len() as u16).to_be_bytes());
+ frames2.extend_from_slice(part2);
+
+ let dcid = from_hex("8394c8f03e515708");
+ let mut datagram = protect_client_initial(&dcid, 0, &frames1);
+ datagram.extend_from_slice(&protect_client_initial(&dcid, 1, &frames2));
+
+ let info = parse_quic_packet(&datagram).expect("coalesced datagram should parse");
+ assert_eq!(info.packet_type, QuicPacketType::Initial);
+ let tls = info
+ .tls_info
+ .expect("merged CRYPTO fragments should yield TLS info");
+ assert_eq!(tls.sni.as_deref(), Some("example.com"));
+ assert_eq!(tls.alpn, vec!["alpn".to_string()]);
+ }
+}
diff --git a/crates/rustnet-core/src/network/dpi/snmp.rs b/crates/rustnet-core/src/network/dpi/snmp.rs
new file mode 100644
index 0000000..c212721
--- /dev/null
+++ b/crates/rustnet-core/src/network/dpi/snmp.rs
@@ -0,0 +1,424 @@
+//! SNMP (Simple Network Management Protocol) Deep Packet Inspection
+//!
+//! Parses SNMP packets (v1, v2c, v3) using simplified BER decoding.
+//! SNMP uses UDP ports 161 (agent) and 162 (trap).
+
+use crate::network::types::{SnmpInfo, SnmpPduType, SnmpVersion};
+
+/// Minimum SNMP packet size
+const MIN_SNMP_SIZE: usize = 10;
+
+/// BER tag types
+const BER_SEQUENCE: u8 = 0x30;
+const BER_INTEGER: u8 = 0x02;
+const BER_OCTET_STRING: u8 = 0x04;
+
+/// SNMP PDU types (context-specific tags)
+const PDU_GET_REQUEST: u8 = 0xA0;
+const PDU_GET_NEXT_REQUEST: u8 = 0xA1;
+const PDU_GET_RESPONSE: u8 = 0xA2;
+const PDU_SET_REQUEST: u8 = 0xA3;
+const PDU_TRAP_V1: u8 = 0xA4;
+const PDU_GET_BULK_REQUEST: u8 = 0xA5;
+const PDU_INFORM_REQUEST: u8 = 0xA6;
+const PDU_TRAP_V2: u8 = 0xA7;
+const PDU_REPORT: u8 = 0xA8;
+
+/// Analyze an SNMP packet and extract key information.
+///
+/// Returns `None` if the packet is not valid SNMP.
+pub fn analyze_snmp(payload: &[u8]) -> Option {
+ if payload.len() < MIN_SNMP_SIZE {
+ return None;
+ }
+
+ // SNMP message is wrapped in a SEQUENCE
+ if payload[0] != BER_SEQUENCE {
+ return None;
+ }
+
+ // Parse SEQUENCE length
+ let (seq_len, mut offset) = parse_ber_length(&payload[1..])?;
+ offset += 1; // Account for SEQUENCE tag
+
+ // Check we have enough data
+ if offset + seq_len > payload.len() {
+ return None;
+ }
+
+ // Parse version (INTEGER)
+ if offset >= payload.len() || payload[offset] != BER_INTEGER {
+ return None;
+ }
+ offset += 1;
+
+ let (version_len, len_bytes) = parse_ber_length(&payload[offset..])?;
+ offset += len_bytes;
+
+ if offset + version_len > payload.len() {
+ return None;
+ }
+
+ let version = parse_version(&payload[offset..offset + version_len])?;
+ offset += version_len;
+
+ // Parse community string for v1/v2c (OCTET STRING). It is mandatory in
+ // both versions (RFC 1157 §4, RFC 3416), so its absence means the
+ // payload is not SNMP.
+ let (community, pdu_type) = match version {
+ SnmpVersion::V1 | SnmpVersion::V2c => {
+ if offset >= payload.len() || payload[offset] != BER_OCTET_STRING {
+ return None;
+ }
+ offset += 1;
+ let (comm_len, len_bytes) = parse_ber_length(&payload[offset..])?;
+ offset += len_bytes;
+
+ if offset + comm_len > payload.len() {
+ return None;
+ }
+ let community_bytes = &payload[offset..offset + comm_len];
+ offset += comm_len;
+ let community = std::str::from_utf8(community_bytes)
+ .ok()
+ .map(|s| s.to_string());
+
+ // The PDU tag directly follows the community string.
+ (community, pdu_type_at(payload, offset)?)
+ }
+ SnmpVersion::V3 => (None, v3_pdu_type(payload, offset)?),
+ };
+
+ Some(SnmpInfo {
+ version,
+ community,
+ pdu_type,
+ })
+}
+
+/// Parse BER length encoding.
+/// Returns (length, bytes consumed).
+fn parse_ber_length(data: &[u8]) -> Option<(usize, usize)> {
+ if data.is_empty() {
+ return None;
+ }
+
+ let first = data[0];
+
+ if first < 0x80 {
+ // Short form: length is in first byte
+ Some((first as usize, 1))
+ } else if first == 0x80 {
+ // Indefinite form - not supported
+ None
+ } else {
+ // Long form: first byte tells how many bytes encode the length
+ let num_bytes = (first & 0x7F) as usize;
+ if num_bytes > 4 || data.len() < 1 + num_bytes {
+ return None;
+ }
+
+ let mut length: usize = 0;
+ for i in 0..num_bytes {
+ length = (length << 8) | (data[1 + i] as usize);
+ }
+
+ Some((length, 1 + num_bytes))
+ }
+}
+
+/// Parse SNMP version from INTEGER value
+fn parse_version(data: &[u8]) -> Option {
+ if data.is_empty() {
+ return None;
+ }
+
+ // Version is encoded as 0=v1, 1=v2c, 3=v3
+ match data[data.len() - 1] {
+ 0 => Some(SnmpVersion::V1),
+ 1 => Some(SnmpVersion::V2c),
+ 3 => Some(SnmpVersion::V3),
+ _ => None,
+ }
+}
+
+/// Read the PDU tag expected at exactly `offset`. Structural — never scans:
+/// a byte in the 0xA0-0xA8 range inside a length or INTEGER value (e.g. a
+/// request-id) must not be mistaken for a PDU tag.
+fn pdu_type_at(payload: &[u8], offset: usize) -> Option {
+ let pdu_type = match *payload.get(offset)? {
+ PDU_GET_REQUEST => SnmpPduType::GetRequest,
+ PDU_GET_NEXT_REQUEST => SnmpPduType::GetNextRequest,
+ PDU_GET_RESPONSE => SnmpPduType::GetResponse,
+ PDU_SET_REQUEST => SnmpPduType::SetRequest,
+ PDU_TRAP_V1 => SnmpPduType::Trap,
+ PDU_GET_BULK_REQUEST => SnmpPduType::GetBulkRequest,
+ PDU_INFORM_REQUEST => SnmpPduType::InformRequest,
+ PDU_TRAP_V2 => SnmpPduType::TrapV2,
+ PDU_REPORT => SnmpPduType::Report,
+ _ => return None,
+ };
+ Some(pdu_type)
+}
+
+/// Walk the SNMPv3 message structure (RFC 3412 §6) to the PDU tag:
+/// msgGlobalData (SEQUENCE) and msgSecurityParameters (OCTET STRING) are
+/// skipped whole, then msgData is either a plaintext ScopedPDU — a SEQUENCE
+/// holding contextEngineID, contextName, and the PDU — or an encrypted
+/// OCTET STRING, in which case the PDU type is not visible.
+fn v3_pdu_type(payload: &[u8], offset: usize) -> Option {
+ let offset = skip_tlv(payload, offset, BER_SEQUENCE)?; // msgGlobalData
+ let offset = skip_tlv(payload, offset, BER_OCTET_STRING)?; // msgSecurityParameters
+
+ match *payload.get(offset)? {
+ BER_SEQUENCE => {
+ // Plaintext ScopedPDU: enter the SEQUENCE, then skip
+ // contextEngineID and contextName.
+ let mut offset = offset + 1;
+ let (_, len_bytes) = parse_ber_length(&payload[offset..])?;
+ offset += len_bytes;
+ let offset = skip_tlv(payload, offset, BER_OCTET_STRING)?; // contextEngineID
+ let offset = skip_tlv(payload, offset, BER_OCTET_STRING)?; // contextName
+ pdu_type_at(payload, offset)
+ }
+ BER_OCTET_STRING => Some(SnmpPduType::Encrypted),
+ _ => None,
+ }
+}
+
+/// Skip one TLV with the expected tag, returning the offset just past it.
+fn skip_tlv(payload: &[u8], offset: usize, expected_tag: u8) -> Option {
+ if *payload.get(offset)? != expected_tag {
+ return None;
+ }
+ let offset = offset + 1;
+ let (len, len_bytes) = parse_ber_length(&payload[offset..])?;
+ let end = offset.checked_add(len_bytes)?.checked_add(len)?;
+ (end <= payload.len()).then_some(end)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn build_snmp_v1_get(community: &str) -> Vec {
+ let mut packet = Vec::new();
+
+ // SEQUENCE
+ packet.push(BER_SEQUENCE);
+ // We'll update length later
+ let len_pos = packet.len();
+ packet.push(0x00); // Placeholder
+
+ // Version: INTEGER 0 (v1)
+ packet.push(BER_INTEGER);
+ packet.push(0x01);
+ packet.push(0x00);
+
+ // Community: OCTET STRING
+ packet.push(BER_OCTET_STRING);
+ packet.push(community.len() as u8);
+ packet.extend_from_slice(community.as_bytes());
+
+ // GetRequest PDU
+ packet.push(PDU_GET_REQUEST);
+ packet.push(0x10); // PDU length
+ // Request ID
+ packet.push(BER_INTEGER);
+ packet.push(0x04);
+ packet.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
+ // Error status
+ packet.push(BER_INTEGER);
+ packet.push(0x01);
+ packet.push(0x00);
+ // Error index
+ packet.push(BER_INTEGER);
+ packet.push(0x01);
+ packet.push(0x00);
+ // Variable bindings (empty)
+ packet.push(BER_SEQUENCE);
+ packet.push(0x00);
+
+ // Update length
+ packet[len_pos] = (packet.len() - len_pos - 1) as u8;
+
+ packet
+ }
+
+ fn build_snmp_v2c_response(community: &str) -> Vec {
+ let mut packet = Vec::new();
+
+ packet.push(BER_SEQUENCE);
+ let len_pos = packet.len();
+ packet.push(0x00);
+
+ // Version: 1 (v2c)
+ packet.push(BER_INTEGER);
+ packet.push(0x01);
+ packet.push(0x01);
+
+ // Community
+ packet.push(BER_OCTET_STRING);
+ packet.push(community.len() as u8);
+ packet.extend_from_slice(community.as_bytes());
+
+ // GetResponse PDU
+ packet.push(PDU_GET_RESPONSE);
+ packet.push(0x10);
+ packet.push(BER_INTEGER);
+ packet.push(0x04);
+ packet.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
+ packet.push(BER_INTEGER);
+ packet.push(0x01);
+ packet.push(0x00);
+ packet.push(BER_INTEGER);
+ packet.push(0x01);
+ packet.push(0x00);
+ packet.push(BER_SEQUENCE);
+ packet.push(0x00);
+
+ packet[len_pos] = (packet.len() - len_pos - 1) as u8;
+
+ packet
+ }
+
+ #[test]
+ fn test_snmp_v1_get() {
+ let packet = build_snmp_v1_get("public");
+ let info = analyze_snmp(&packet).expect("should parse");
+ assert_eq!(info.version, SnmpVersion::V1);
+ assert_eq!(info.community, Some("public".to_string()));
+ assert_eq!(info.pdu_type, SnmpPduType::GetRequest);
+ }
+
+ #[test]
+ fn test_snmp_v2c_response() {
+ let packet = build_snmp_v2c_response("private");
+ let info = analyze_snmp(&packet).expect("should parse");
+ assert_eq!(info.version, SnmpVersion::V2c);
+ assert_eq!(info.community, Some("private".to_string()));
+ assert_eq!(info.pdu_type, SnmpPduType::GetResponse);
+ }
+
+ #[test]
+ fn test_snmp_too_short() {
+ let packet = [0x30, 0x05, 0x02, 0x01, 0x00];
+ assert!(analyze_snmp(&packet).is_none());
+ }
+
+ #[test]
+ fn test_snmp_not_sequence() {
+ let packet = [
+ 0x31, 0x10, 0x02, 0x01, 0x00, 0x04, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63,
+ ];
+ assert!(analyze_snmp(&packet).is_none());
+ }
+
+ #[test]
+ fn test_ber_length_short() {
+ let data = [0x10];
+ let (len, bytes) = parse_ber_length(&data).unwrap();
+ assert_eq!(len, 16);
+ assert_eq!(bytes, 1);
+ }
+
+ #[test]
+ fn test_ber_length_long() {
+ let data = [0x82, 0x01, 0x00]; // 256
+ let (len, bytes) = parse_ber_length(&data).unwrap();
+ assert_eq!(len, 256);
+ assert_eq!(bytes, 3);
+ }
+
+ /// Build an SNMPv3 message. `scoped_pdu_plaintext` selects between a
+ /// plaintext ScopedPDU (carrying a GetRequest) and an encrypted one.
+ fn build_snmp_v3(scoped_pdu_plaintext: bool) -> Vec {
+ let mut packet = Vec::new();
+
+ packet.push(BER_SEQUENCE);
+ let len_pos = packet.len();
+ packet.push(0x00);
+
+ // Version: INTEGER 3 (v3)
+ packet.push(BER_INTEGER);
+ packet.push(0x01);
+ packet.push(0x03);
+
+ // msgGlobalData SEQUENCE: msgID, msgMaxSize, msgFlags, msgSecurityModel.
+ // The msgID value 0x12A34456 deliberately contains a 0xA3 byte — the
+ // old scanning detector misread it as a SetRequest PDU tag.
+ packet.push(BER_SEQUENCE);
+ packet.push(0x10);
+ packet.push(BER_INTEGER);
+ packet.push(0x04);
+ packet.extend_from_slice(&[0x12, 0xA3, 0x44, 0x56]); // msgID
+ packet.push(BER_INTEGER);
+ packet.push(0x02);
+ packet.extend_from_slice(&[0x05, 0xDC]); // msgMaxSize 1500
+ packet.push(BER_OCTET_STRING);
+ packet.push(0x01);
+ packet.push(0x04); // msgFlags: reportable
+ packet.push(BER_INTEGER);
+ packet.push(0x01);
+ packet.push(0x03); // msgSecurityModel: USM
+
+ // msgSecurityParameters: opaque OCTET STRING
+ packet.push(BER_OCTET_STRING);
+ packet.push(0x02);
+ packet.extend_from_slice(&[0x30, 0x00]);
+
+ if scoped_pdu_plaintext {
+ // ScopedPDU SEQUENCE: contextEngineID, contextName, PDU
+ packet.push(BER_SEQUENCE);
+ packet.push(0x0D);
+ packet.push(BER_OCTET_STRING);
+ packet.push(0x02);
+ packet.extend_from_slice(&[0x80, 0x01]); // contextEngineID
+ packet.push(BER_OCTET_STRING);
+ packet.push(0x00); // contextName (empty)
+ packet.push(PDU_GET_REQUEST);
+ packet.push(0x05);
+ packet.push(BER_INTEGER);
+ packet.push(0x01);
+ packet.push(0x01); // request-id
+ packet.push(BER_SEQUENCE);
+ packet.push(0x00); // varbinds (empty)
+ } else {
+ // Encrypted ScopedPDU: opaque OCTET STRING
+ packet.push(BER_OCTET_STRING);
+ packet.push(0x04);
+ packet.extend_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]);
+ }
+
+ packet[len_pos] = (packet.len() - len_pos - 1) as u8;
+ packet
+ }
+
+ #[test]
+ fn test_snmp_v3_plaintext_scoped_pdu() {
+ let packet = build_snmp_v3(true);
+ let info = analyze_snmp(&packet).expect("should parse");
+ assert_eq!(info.version, SnmpVersion::V3);
+ assert_eq!(info.community, None);
+ assert_eq!(info.pdu_type, SnmpPduType::GetRequest);
+ }
+
+ #[test]
+ fn test_snmp_v3_encrypted_scoped_pdu() {
+ let packet = build_snmp_v3(false);
+ let info = analyze_snmp(&packet).expect("should parse");
+ assert_eq!(info.version, SnmpVersion::V3);
+ assert_eq!(info.pdu_type, SnmpPduType::Encrypted);
+ }
+
+ #[test]
+ fn test_pdu_tag_not_scanned_from_value_bytes() {
+ // A v1 message whose community is followed by a non-PDU byte must be
+ // rejected — the detector must not scan ahead for a 0xA0-0xA8 byte.
+ let mut packet = build_snmp_v1_get("public");
+ // Overwrite the PDU tag with an INTEGER tag: still not SNMP.
+ let pdu_pos = packet.iter().position(|&b| b == PDU_GET_REQUEST).unwrap();
+ packet[pdu_pos] = BER_INTEGER;
+ assert!(analyze_snmp(&packet).is_none());
+ }
+}
diff --git a/crates/rustnet-core/src/network/dpi/ssdp.rs b/crates/rustnet-core/src/network/dpi/ssdp.rs
new file mode 100644
index 0000000..0324eb8
--- /dev/null
+++ b/crates/rustnet-core/src/network/dpi/ssdp.rs
@@ -0,0 +1,169 @@
+//! SSDP (Simple Service Discovery Protocol) Deep Packet Inspection
+//!
+//! Parses SSDP packets used for UPnP device discovery.
+//! SSDP uses UDP port 1900 and has an HTTP-like text format.
+
+use crate::network::types::{SsdpInfo, SsdpMethod};
+
+/// Minimum SSDP packet size for detection
+const MIN_SSDP_SIZE: usize = 10;
+
+/// Analyze an SSDP packet and extract key information.
+///
+/// SSDP uses HTTP-like text format with methods like M-SEARCH and NOTIFY.
+/// Returns `None` if the packet is not valid SSDP.
+pub fn analyze_ssdp(payload: &[u8]) -> Option {
+ if payload.len() < MIN_SSDP_SIZE {
+ return None;
+ }
+
+ // SSDP is ASCII text - check if it looks like text
+ if !payload.iter().take(20).all(|&b| b.is_ascii()) {
+ return None;
+ }
+
+ // Convert to string for parsing
+ let text = std::str::from_utf8(payload).ok()?;
+ let first_line = text.lines().next()?;
+
+ // Detect method from first line
+ let method = if first_line.starts_with("M-SEARCH") {
+ SsdpMethod::MSearch
+ } else if first_line.starts_with("NOTIFY") {
+ SsdpMethod::Notify
+ } else if first_line.starts_with("HTTP/1.1 200") || first_line.starts_with("HTTP/1.0 200") {
+ SsdpMethod::Response
+ } else {
+ return None;
+ };
+
+ // Extract service type from ST or NT header. Compare the 3-byte prefix
+ // with `eq_ignore_ascii_case` so we don't allocate a lowercased copy
+ // of every header line just to check two ASCII names.
+ let mut service_type = None;
+ for line in text.lines().skip(1) {
+ let is_st_or_nt = line.get(..3).is_some_and(|prefix| {
+ prefix.eq_ignore_ascii_case("st:") || prefix.eq_ignore_ascii_case("nt:")
+ });
+ if is_st_or_nt {
+ // Extract value after the colon
+ if let Some(value) = line.get(3..) {
+ let trimmed = value.trim();
+ if !trimmed.is_empty() {
+ service_type = Some(trimmed.to_string());
+ break;
+ }
+ }
+ }
+ }
+
+ Some(SsdpInfo {
+ method,
+ service_type,
+ })
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_ssdp_msearch() {
+ let packet = b"M-SEARCH * HTTP/1.1\r\n\
+ HOST: 239.255.255.250:1900\r\n\
+ MAN: \"ssdp:discover\"\r\n\
+ MX: 3\r\n\
+ ST: ssdp:all\r\n\
+ \r\n";
+ let info = analyze_ssdp(packet).expect("should parse");
+ assert_eq!(info.method, SsdpMethod::MSearch);
+ assert_eq!(info.service_type, Some("ssdp:all".to_string()));
+ }
+
+ #[test]
+ fn test_ssdp_notify() {
+ let packet = b"NOTIFY * HTTP/1.1\r\n\
+ HOST: 239.255.255.250:1900\r\n\
+ CACHE-CONTROL: max-age=1800\r\n\
+ LOCATION: http://192.168.1.1:80/desc.xml\r\n\
+ NT: upnp:rootdevice\r\n\
+ NTS: ssdp:alive\r\n\
+ \r\n";
+ let info = analyze_ssdp(packet).expect("should parse");
+ assert_eq!(info.method, SsdpMethod::Notify);
+ assert_eq!(info.service_type, Some("upnp:rootdevice".to_string()));
+ }
+
+ #[test]
+ fn test_ssdp_response() {
+ let packet = b"HTTP/1.1 200 OK\r\n\
+ CACHE-CONTROL: max-age=1800\r\n\
+ ST: urn:schemas-upnp-org:device:MediaRenderer:1\r\n\
+ LOCATION: http://192.168.1.100:8080/desc.xml\r\n\
+ \r\n";
+ let info = analyze_ssdp(packet).expect("should parse");
+ assert_eq!(info.method, SsdpMethod::Response);
+ assert_eq!(
+ info.service_type,
+ Some("urn:schemas-upnp-org:device:MediaRenderer:1".to_string())
+ );
+ }
+
+ #[test]
+ fn test_ssdp_no_service_type() {
+ let packet = b"M-SEARCH * HTTP/1.1\r\n\
+ HOST: 239.255.255.250:1900\r\n\
+ MAN: \"ssdp:discover\"\r\n\
+ \r\n";
+ let info = analyze_ssdp(packet).expect("should parse");
+ assert_eq!(info.method, SsdpMethod::MSearch);
+ assert!(info.service_type.is_none());
+ }
+
+ #[test]
+ fn test_ssdp_too_short() {
+ let packet = b"M-SEA";
+ assert!(analyze_ssdp(packet).is_none());
+ }
+
+ #[test]
+ fn test_ssdp_not_text() {
+ let packet = [0x00, 0x01, 0x02, 0x03, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00];
+ assert!(analyze_ssdp(&packet).is_none());
+ }
+
+ #[test]
+ fn test_ssdp_not_ssdp() {
+ let packet = b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n";
+ assert!(analyze_ssdp(packet).is_none());
+ }
+
+ #[test]
+ fn test_ssdp_mixed_case_st_and_nt_headers() {
+ // SSDP / UPnP servers in the wild use varied capitalisation for
+ // header names (HTTP §3.2 lets the field-name be case-insensitive).
+ // Lock the invariant the `eq_ignore_ascii_case` refactor relies on:
+ // any case mix on the 2-byte field-name still extracts the value.
+ let st_variants = [
+ b"M-SEARCH * HTTP/1.1\r\nST: ssdp:all\r\n\r\n".to_vec(),
+ b"M-SEARCH * HTTP/1.1\r\nSt: ssdp:all\r\n\r\n".to_vec(),
+ b"M-SEARCH * HTTP/1.1\r\nsT: ssdp:all\r\n\r\n".to_vec(),
+ b"M-SEARCH * HTTP/1.1\r\nst: ssdp:all\r\n\r\n".to_vec(),
+ ];
+ for packet in &st_variants {
+ let info = analyze_ssdp(packet).expect("should parse");
+ assert_eq!(info.service_type, Some("ssdp:all".to_string()));
+ }
+
+ let nt_variants = [
+ b"NOTIFY * HTTP/1.1\r\nNT: upnp:rootdevice\r\n\r\n".to_vec(),
+ b"NOTIFY * HTTP/1.1\r\nNt: upnp:rootdevice\r\n\r\n".to_vec(),
+ b"NOTIFY * HTTP/1.1\r\nnT: upnp:rootdevice\r\n\r\n".to_vec(),
+ b"NOTIFY * HTTP/1.1\r\nnt: upnp:rootdevice\r\n\r\n".to_vec(),
+ ];
+ for packet in &nt_variants {
+ let info = analyze_ssdp(packet).expect("should parse");
+ assert_eq!(info.service_type, Some("upnp:rootdevice".to_string()));
+ }
+ }
+}
diff --git a/crates/rustnet-core/src/network/dpi/ssh.rs b/crates/rustnet-core/src/network/dpi/ssh.rs
new file mode 100644
index 0000000..8b38ce5
--- /dev/null
+++ b/crates/rustnet-core/src/network/dpi/ssh.rs
@@ -0,0 +1,540 @@
+use crate::network::types::{SshConnectionState, SshInfo, SshVersion};
+use log::debug;
+
+/// Analyze payload for SSH protocol
+/// is_outgoing: true if this packet is from client to server
+pub fn analyze_ssh(payload: &[u8], is_outgoing: bool) -> Option {
+ if !is_likely_ssh(payload) {
+ return None;
+ }
+
+ let mut info = SshInfo {
+ version: None,
+ client_software: None,
+ server_software: None,
+ connection_state: SshConnectionState::Banner,
+ algorithms: Vec::new(),
+ auth_method: None,
+ };
+
+ // Convert payload to string for banner analysis. Drive the line iterator
+ // directly — the loop below is the only consumer, so materializing every
+ // line into a `Vec<&str>` first just wastes a heap slice per parse. The
+ // empty-payload case falls through to the loop and is a no-op.
+ let text = String::from_utf8_lossy(payload);
+
+ // Parse SSH banner(s) and assign based on packet direction
+ for line in text.lines() {
+ if let Some(banner_info) = parse_ssh_banner(line) {
+ // Use packet direction to distinguish client vs server
+ if is_outgoing {
+ // Outgoing packet: client to server, so this banner is from client
+ if info.client_software.is_none() {
+ info.client_software = Some(banner_info.software);
+ info.version = Some(banner_info.version);
+ }
+ } else {
+ // Incoming packet: server to client, so this banner is from server
+ if info.server_software.is_none() {
+ info.server_software = Some(banner_info.software);
+ info.version = Some(banner_info.version);
+ }
+ }
+ }
+ }
+
+ // Detect SSH message types for connection state
+ // Look for SSH packet structures throughout the payload. A packet
+ // signature needs 6 bytes, so the last valid start offset is
+ // `len - 6`; the range end must therefore be `len - 5` (exclusive).
+ // Using `saturating_sub(6)` stopped at `len - 7` and never inspected
+ // the final 6-byte window, so a packet whose signature sat at the very
+ // end of the payload (including a payload that is exactly 6 bytes) was
+ // missed.
+ let mut found_packet_state = false;
+ for i in 0..payload.len().saturating_sub(5) {
+ if payload.len() >= i + 6 {
+ // Validate this looks like a real SSH packet structure
+ if is_valid_ssh_packet_at_offset(payload, i) {
+ let msg_type = payload[i + 5];
+
+ match msg_type {
+ 20 => {
+ info.connection_state = SshConnectionState::KeyExchange;
+ debug!("SSH: Detected KEXINIT message at offset {}", i);
+ found_packet_state = true;
+ break;
+ }
+ 21 => {
+ info.connection_state = SshConnectionState::KeyExchange;
+ debug!("SSH: Detected NEWKEYS message at offset {}", i);
+ found_packet_state = true;
+ break;
+ }
+ 50 => {
+ info.connection_state = SshConnectionState::Authentication;
+ debug!("SSH: Detected USERAUTH_REQUEST message at offset {}", i);
+ found_packet_state = true;
+ break;
+ }
+ 51 => {
+ info.connection_state = SshConnectionState::Authentication;
+ debug!("SSH: Detected USERAUTH_FAILURE message at offset {}", i);
+ found_packet_state = true;
+ break;
+ }
+ 52 => {
+ info.connection_state = SshConnectionState::Established;
+ debug!("SSH: Detected USERAUTH_SUCCESS message at offset {}", i);
+ found_packet_state = true;
+ break;
+ }
+ 90..=127 => {
+ info.connection_state = SshConnectionState::Established;
+ debug!("SSH: Detected connection protocol message at offset {}", i);
+ found_packet_state = true;
+ break;
+ }
+ _ => {
+ // Continue searching
+ }
+ }
+ }
+ }
+ }
+
+ // If we didn't find a packet state and we have banner info, default to Banner state
+ if !found_packet_state && (info.server_software.is_some() || info.client_software.is_some()) {
+ info.connection_state = SshConnectionState::Banner;
+ }
+
+ // Try to extract algorithm information. `parse_kexinit_algorithms` is a
+ // substring scan, so it works equally on a real KEXINIT message (msg
+ // type 20) and on free-form banner/text content — there's no need to
+ // branch on whether the payload looks like a KEXINIT.
+ if let Some(algorithms) = parse_kexinit_algorithms(payload) {
+ info.algorithms = algorithms;
+ }
+
+ debug!("SSH analysis result: {:?}", info);
+ Some(info)
+}
+
+/// Check if payload might be SSH
+pub fn is_likely_ssh(payload: &[u8]) -> bool {
+ if payload.len() < 4 {
+ return false;
+ }
+
+ // SSH banner identification string
+ payload.starts_with(b"SSH-1.") ||
+ payload.starts_with(b"SSH-2.") ||
+ // Sometimes we might see SSH packets without banners
+ is_ssh_packet_structure(payload)
+}
+
+/// Parse SSH banner line
+fn parse_ssh_banner(line: &str) -> Option {
+ if !line.starts_with("SSH-") {
+ return None;
+ }
+
+ let parts: Vec<&str> = line.splitn(3, '-').collect();
+ if parts.len() < 2 {
+ return None;
+ }
+
+ let version = match parts[1] {
+ "1.99" | "2.0" => SshVersion::V2,
+ v if v.starts_with("1.") => SshVersion::V1,
+ _ => SshVersion::V2, // Default to V2 for unknown versions
+ };
+
+ let software = if parts.len() >= 3 {
+ parts[2].trim().to_string()
+ } else {
+ "Unknown".to_string()
+ };
+
+ Some(BannerInfo { version, software })
+}
+
+/// Check if payload has SSH packet structure
+fn is_ssh_packet_structure(payload: &[u8]) -> bool {
+ if payload.len() < 6 {
+ return false;
+ }
+
+ is_valid_ssh_packet_at_offset(payload, 0)
+}
+
+/// Check if there's a valid SSH packet structure at the given offset
+fn is_valid_ssh_packet_at_offset(payload: &[u8], offset: usize) -> bool {
+ if payload.len() < offset + 6 {
+ return false;
+ }
+
+ // SSH packet format:
+ // 4 bytes: packet length
+ // 1 byte: padding length
+ // 1+ bytes: payload (message type + data)
+ // N bytes: padding
+
+ let packet_length = u32::from_be_bytes([
+ payload[offset],
+ payload[offset + 1],
+ payload[offset + 2],
+ payload[offset + 3],
+ ]);
+ let padding_length = payload[offset + 4] as u32;
+ let msg_type = payload[offset + 5];
+
+ // 1. Realistic packet size (min 8 bytes for smallest valid packet)
+ if !(8..=35000).contains(&packet_length) {
+ return false;
+ }
+
+ // 2. RFC 4253 requires minimum 4 bytes padding
+ if padding_length < 4 {
+ return false;
+ }
+
+ // 3. Padding must fit within packet (padding + 1 for msg_type < packet_length)
+ if padding_length + 1 >= packet_length {
+ return false;
+ }
+
+ // 4. Only known SSH message types (not the full 1..=127 range)
+ matches!(
+ msg_type,
+ 1..=4 | // transport: disconnect, ignore, unimplemented, debug
+ 20..=21 | // kex: kexinit, newkeys
+ 50..=52 | // auth: userauth request/failure/success
+ 80 | // connection: global request
+ 90..=100 // channel: open/confirm/data/eof/close/request/success/failure
+ )
+}
+
+/// Parse algorithms from KEXINIT message
+fn parse_kexinit_algorithms(payload: &[u8]) -> Option> {
+ // This is a simplified version - full KEXINIT parsing is quite complex
+ // We'll just try to extract some common algorithm names
+ let text = String::from_utf8_lossy(payload);
+ let mut algorithms = Vec::new();
+
+ // Look for common SSH algorithms
+ let common_algos = [
+ "diffie-hellman-group14-sha256",
+ "ecdh-sha2-nistp256",
+ "aes128-ctr",
+ "aes256-ctr",
+ "aes128-gcm",
+ "aes256-gcm",
+ "ssh-rsa",
+ "ssh-ed25519",
+ "ecdsa-sha2-nistp256",
+ "hmac-sha2-256",
+ "hmac-sha2-512",
+ ];
+
+ for algo in &common_algos {
+ if text.contains(algo) {
+ algorithms.push(algo.to_string());
+ }
+ }
+
+ if algorithms.is_empty() {
+ None
+ } else {
+ Some(algorithms)
+ }
+}
+
+/// Helper struct for banner parsing
+struct BannerInfo {
+ version: SshVersion,
+ software: String,
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_openssh_banner() {
+ let payload = b"SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.1\r\n";
+ let info = analyze_ssh(payload, false).unwrap();
+
+ assert_eq!(info.version, Some(SshVersion::V2));
+ assert_eq!(
+ info.server_software.as_deref(),
+ Some("OpenSSH_8.9p1 Ubuntu-3ubuntu0.1")
+ );
+ assert_eq!(info.connection_state, SshConnectionState::Banner);
+ }
+
+ #[test]
+ fn test_putty_banner() {
+ let payload = b"SSH-2.0-PuTTY_Release_0.76\r\n";
+ let info = analyze_ssh(payload, false).unwrap();
+
+ assert_eq!(info.version, Some(SshVersion::V2));
+ assert_eq!(info.server_software.as_deref(), Some("PuTTY_Release_0.76"));
+ }
+
+ #[test]
+ fn test_ssh1_banner() {
+ let payload = b"SSH-1.99-libssh_0.8.9\r\n";
+ let info = analyze_ssh(payload, false).unwrap();
+
+ assert_eq!(info.version, Some(SshVersion::V2)); // 1.99 maps to V2
+ assert_eq!(info.server_software.as_deref(), Some("libssh_0.8.9"));
+ }
+
+ #[test]
+ fn test_kexinit_detection() {
+ // Simplified KEXINIT packet structure
+ let mut payload = vec![0, 0, 0, 100]; // packet length
+ payload.push(10); // padding length
+ payload.push(20); // SSH_MSG_KEXINIT
+ payload.extend_from_slice(&[0; 94]); // rest of packet
+
+ let info = analyze_ssh(&payload, false).unwrap();
+ assert_eq!(info.connection_state, SshConnectionState::KeyExchange);
+ }
+
+ #[test]
+ fn test_userauth_success() {
+ let mut payload = vec![0, 0, 0, 20]; // packet length
+ payload.push(5); // padding length
+ payload.push(52); // SSH_MSG_USERAUTH_SUCCESS
+ payload.extend_from_slice(&[0; 14]); // padding
+
+ let info = analyze_ssh(&payload, false).unwrap();
+ assert_eq!(info.connection_state, SshConnectionState::Established);
+ }
+
+ #[test]
+ fn test_non_ssh_payload() {
+ let payload = b"HTTP/1.1 200 OK\r\n";
+ assert!(analyze_ssh(payload, false).is_none());
+ }
+
+ #[test]
+ fn test_partial_ssh_banner() {
+ let payload = b"SSH-2.0-Open";
+ let info = analyze_ssh(payload, false).unwrap();
+ assert_eq!(info.version, Some(SshVersion::V2));
+ }
+
+ #[test]
+ fn test_ssh_banner_parsing() {
+ assert!(parse_ssh_banner("SSH-2.0-OpenSSH_8.9").is_some());
+ assert!(parse_ssh_banner("SSH-1.5-oldversion").is_some());
+ assert!(parse_ssh_banner("HTTP/1.1 200 OK").is_none());
+ assert!(parse_ssh_banner("").is_none());
+ }
+
+ #[test]
+ fn test_is_likely_ssh() {
+ assert!(is_likely_ssh(b"SSH-2.0-OpenSSH"));
+ assert!(is_likely_ssh(b"SSH-1.99-libssh"));
+ assert!(!is_likely_ssh(b"HTTP/1.1"));
+ assert!(!is_likely_ssh(b"GET /"));
+ assert!(!is_likely_ssh(b""));
+ }
+
+ #[test]
+ fn test_ssh_packet_structure() {
+ // Valid SSH packet: packet_len=20, padding_len=5, msg_type=50 (USERAUTH_REQUEST)
+ let valid_packet = vec![0, 0, 0, 20, 5, 50, 0, 0, 0, 0];
+ assert!(is_ssh_packet_structure(&valid_packet));
+
+ // Valid: msg_type=20 (KEXINIT)
+ let kexinit_packet = vec![0, 0, 0, 20, 5, 20, 0, 0, 0, 0];
+ assert!(is_ssh_packet_structure(&kexinit_packet));
+
+ // Valid: msg_type=90 (channel open)
+ let channel_packet = vec![0, 0, 0, 20, 5, 90, 0, 0, 0, 0];
+ assert!(is_ssh_packet_structure(&channel_packet));
+
+ // Invalid: padding too small (< 4, RFC 4253 requires min 4)
+ let bad_padding = vec![0, 0, 0, 20, 2, 50, 0, 0, 0, 0];
+ assert!(!is_ssh_packet_structure(&bad_padding));
+
+ // Invalid: unknown message type (70 is not a known SSH message)
+ let bad_msg_type = vec![0, 0, 0, 20, 5, 70, 0, 0, 0, 0];
+ assert!(!is_ssh_packet_structure(&bad_msg_type));
+
+ // Invalid: packet too small (< 8)
+ let too_small = vec![0, 0, 0, 4, 4, 50, 0, 0, 0, 0];
+ assert!(!is_ssh_packet_structure(&too_small));
+
+ // Invalid: padding larger than packet allows
+ let bad_ratio = vec![0, 0, 0, 10, 10, 50, 0, 0, 0, 0];
+ assert!(!is_ssh_packet_structure(&bad_ratio));
+
+ // Invalid: unrealistic lengths
+ let invalid_packet = vec![255, 255, 255, 255, 255, 255];
+ assert!(!is_ssh_packet_structure(&invalid_packet));
+ }
+
+ #[test]
+ fn test_various_ssh_implementations() {
+ // Test different SSH software banners
+ let test_cases = vec![
+ ("SSH-2.0-OpenSSH_7.4", SshVersion::V2, "OpenSSH_7.4"),
+ ("SSH-2.0-libssh2_1.9.0", SshVersion::V2, "libssh2_1.9.0"),
+ (
+ "SSH-2.0-WinSCP_release_5.19.6",
+ SshVersion::V2,
+ "WinSCP_release_5.19.6",
+ ),
+ ("SSH-2.0-Paramiko_2.8.0", SshVersion::V2, "Paramiko_2.8.0"),
+ ("SSH-1.99-Cisco-1.25", SshVersion::V2, "Cisco-1.25"), // 1.99 maps to V2
+ ("SSH-1.5-1.2.27", SshVersion::V1, "1.2.27"),
+ ];
+
+ for (banner, expected_version, expected_software) in test_cases {
+ let payload = format!("{}\r\n", banner).into_bytes();
+ let info = analyze_ssh(&payload, false).unwrap();
+
+ assert_eq!(
+ info.version,
+ Some(expected_version),
+ "Failed for banner: {}",
+ banner
+ );
+ assert_eq!(
+ info.server_software.as_deref(),
+ Some(expected_software),
+ "Failed for banner: {}",
+ banner
+ );
+ assert_eq!(info.connection_state, SshConnectionState::Banner);
+ }
+ }
+
+ #[test]
+ fn test_ssh_connection_states() {
+ // Test KEXINIT detection
+ let mut kexinit_packet = vec![0, 0, 0, 100, 10, 20]; // packet_len, padding_len, SSH_MSG_KEXINIT
+ kexinit_packet.extend(vec![0; 94]); // fill the packet
+ let info = analyze_ssh(&kexinit_packet, false).unwrap();
+ assert_eq!(info.connection_state, SshConnectionState::KeyExchange);
+
+ // Test USERAUTH_REQUEST
+ let mut userauth_packet = vec![0, 0, 0, 50, 8, 50]; // SSH_MSG_USERAUTH_REQUEST
+ userauth_packet.extend(vec![0; 44]);
+ let info = analyze_ssh(&userauth_packet, false).unwrap();
+ assert_eq!(info.connection_state, SshConnectionState::Authentication);
+
+ // Test USERAUTH_SUCCESS
+ let mut success_packet = vec![0, 0, 0, 20, 5, 52]; // SSH_MSG_USERAUTH_SUCCESS
+ success_packet.extend(vec![0; 14]);
+ let info = analyze_ssh(&success_packet, false).unwrap();
+ assert_eq!(info.connection_state, SshConnectionState::Established);
+
+ // Test connection protocol message
+ let mut conn_packet = vec![0, 0, 0, 30, 6, 95]; // Some connection protocol message
+ conn_packet.extend(vec![0; 24]);
+ let info = analyze_ssh(&conn_packet, false).unwrap();
+ assert_eq!(info.connection_state, SshConnectionState::Established);
+ }
+
+ #[test]
+ fn test_malformed_ssh_packets() {
+ // Empty payload
+ assert!(analyze_ssh(&[], false).is_none());
+
+ // Too short payload
+ assert!(analyze_ssh(&[1, 2, 3], false).is_none());
+
+ // Invalid SSH banner
+ let invalid_banner = b"HTTP/1.1 200 OK\r\n";
+ assert!(analyze_ssh(invalid_banner, false).is_none());
+
+ // Malformed SSH banner (missing parts)
+ let malformed_banner = b"SSH-\r\n";
+ assert!(analyze_ssh(malformed_banner, false).is_none());
+ }
+
+ #[test]
+ fn test_algorithm_detection() {
+ // Create a payload that contains some SSH algorithms in the text
+ let payload_with_algos =
+ b"SSH-2.0-test\r\nsome data aes128-ctr ssh-ed25519 hmac-sha2-256 more data";
+ let info = analyze_ssh(payload_with_algos, false).unwrap();
+
+ assert!(!info.algorithms.is_empty());
+ // Should contain some of the algorithms we look for
+ assert!(info.algorithms.iter().any(|a| a.contains("aes128-ctr")));
+ }
+
+ #[test]
+ fn test_edge_cases() {
+ // Banner with no software info
+ let minimal_banner = b"SSH-2.0\r\n";
+ let info = analyze_ssh(minimal_banner, false);
+ // Should still parse successfully but with minimal info
+ assert!(info.is_some());
+
+ // Very long banner (should still work)
+ let long_banner = format!("SSH-2.0-{}\r\n", "x".repeat(200)).into_bytes();
+ let info = analyze_ssh(&long_banner, false);
+ assert!(info.is_some());
+
+ // Banner with special characters
+ let special_banner = b"SSH-2.0-OpenSSH_8.9p1-Ubuntu-3~20.04.3\r\n";
+ let info = analyze_ssh(special_banner, false).unwrap();
+ assert_eq!(info.version, Some(SshVersion::V2));
+ }
+
+ #[test]
+ fn test_client_server_software_distinction() {
+ // Test server banner (incoming packet)
+ let server_banner = b"SSH-2.0-OpenSSH_9.9\r\n";
+ let server_info = analyze_ssh(server_banner, false).unwrap();
+ assert!(server_info.server_software.is_some());
+ assert!(server_info.client_software.is_none());
+ assert_eq!(server_info.server_software.as_ref().unwrap(), "OpenSSH_9.9");
+
+ // Test client banner (outgoing packet)
+ let client_banner = b"SSH-2.0-OpenSSH_9.8\r\n";
+ let client_info = analyze_ssh(client_banner, true).unwrap();
+ assert!(client_info.client_software.is_some());
+ assert!(client_info.server_software.is_none());
+ assert_eq!(client_info.client_software.as_ref().unwrap(), "OpenSSH_9.8");
+ }
+
+ #[test]
+ fn test_mixed_content() {
+ // Test payload that has both banner and packet data
+ // Banner: "SSH-2.0-OpenSSH_8.9\r\n" (21 bytes)
+ // Packet: \x00\x00\x00\x14\x05\x32 (packet_len=20, padding_len=5, msg_type=50/0x32)
+ let mixed_payload = b"SSH-2.0-OpenSSH_8.9\r\n\x00\x00\x00\x14\x05\x32additional data here";
+ let info = analyze_ssh(mixed_payload, false).unwrap();
+
+ assert_eq!(info.version, Some(SshVersion::V2));
+ assert!(info.server_software.is_some());
+ // The packet structure starts at offset 21, so message type is at offset 26
+ // Should detect the SSH_MSG_USERAUTH_REQUEST (50/0x32) in the packet data
+ assert_eq!(info.connection_state, SshConnectionState::Authentication);
+ }
+
+ #[test]
+ fn test_packet_state_at_last_window() {
+ // Banner (14 bytes) followed by a valid KEXINIT packet signature
+ // occupying exactly the final 6 bytes of the payload:
+ // packet_len=12 (0x0000000C), padding_len=4 (0x04), msg_type=20 (0x14)
+ // The signature starts at offset len-6, i.e. the last valid start
+ // offset. The previous `saturating_sub(6)` loop bound stopped one
+ // offset short and never inspected this window, leaving the state at
+ // Banner; the scan must now reach it and report KeyExchange.
+ let payload = b"SSH-2.0-Test\r\n\x00\x00\x00\x0c\x04\x14";
+ assert_eq!(payload.len(), 20);
+ let info = analyze_ssh(payload, false).unwrap();
+ assert!(info.server_software.is_some());
+ assert_eq!(info.connection_state, SshConnectionState::KeyExchange);
+ }
+}
diff --git a/crates/rustnet-core/src/network/dpi/stun.rs b/crates/rustnet-core/src/network/dpi/stun.rs
new file mode 100644
index 0000000..d353bd2
--- /dev/null
+++ b/crates/rustnet-core/src/network/dpi/stun.rs
@@ -0,0 +1,314 @@
+//! STUN (Session Traversal Utilities for NAT) Deep Packet Inspection
+//!
+//! Parses STUN packets according to RFC 5389 / RFC 8489.
+//! STUN uses UDP port 3478 (and 5349 for STUN over TLS).
+
+use crate::network::types::{StunInfo, StunMessageClass, StunMethod};
+
+/// STUN header is exactly 20 bytes
+const STUN_HEADER_SIZE: usize = 20;
+
+/// Magic cookie value (RFC 5389 section 6)
+const STUN_MAGIC_COOKIE: u32 = 0x2112_A442;
+
+/// STUN attribute type: SOFTWARE (0x8022)
+const ATTR_SOFTWARE: u16 = 0x8022;
+
+/// Maximum SOFTWARE attribute length to prevent allocating huge strings
+const MAX_SOFTWARE_LEN: usize = 128;
+
+/// Analyze a STUN packet and extract key information.
+///
+/// Returns `None` if the packet is too small, lacks the magic cookie,
+/// or has invalid structural properties.
+pub fn analyze_stun(payload: &[u8]) -> Option {
+ if payload.len() < STUN_HEADER_SIZE {
+ return None;
+ }
+
+ // First two bits must be 0b00 (distinguishes STUN from DTLS/RTP/RTCP)
+ if payload[0] & 0xC0 != 0x00 {
+ return None;
+ }
+
+ // Verify magic cookie at bytes 4-7
+ let cookie = u32::from_be_bytes([payload[4], payload[5], payload[6], payload[7]]);
+ if cookie != STUN_MAGIC_COOKIE {
+ return None;
+ }
+
+ // Message length (bytes 2-3) must be multiple of 4
+ let message_length = u16::from_be_bytes([payload[2], payload[3]]) as usize;
+ if !message_length.is_multiple_of(4) {
+ return None;
+ }
+
+ // Verify we have enough payload for the declared length
+ if payload.len() < STUN_HEADER_SIZE + message_length {
+ return None;
+ }
+
+ // Decode message type (bytes 0-1, 14 bits after masking top 2 bits)
+ let msg_type = u16::from_be_bytes([payload[0] & 0x3F, payload[1]]);
+
+ // Extract class bits: C1 is bit 8, C0 is bit 4 (RFC 5389 section 6).
+ // Both extractions mask to a single bit, so `class_bits` is bounded
+ // to 0..=3 by construction — the 0b11 arm covers the only remaining
+ // value, which lets us drop the `unreachable!()` catch-all.
+ let c0 = ((msg_type >> 4) & 0x1) as u8;
+ let c1 = ((msg_type >> 8) & 0x1) as u8;
+ let class_bits = (c1 << 1) | c0;
+
+ let message_class = match class_bits {
+ 0b00 => StunMessageClass::Request,
+ 0b01 => StunMessageClass::Indication,
+ 0b10 => StunMessageClass::SuccessResponse,
+ _ => StunMessageClass::ErrorResponse, // 0b11; class_bits ∈ {0,1,2,3}
+ };
+
+ // Extract method: remove the class bits from msg_type
+ let m0_3 = msg_type & 0x000F;
+ let m4_6 = (msg_type >> 1) & 0x0070;
+ let m7_11 = (msg_type >> 2) & 0x0F80;
+ let method_value = m7_11 | m4_6 | m0_3;
+
+ let method = match method_value {
+ 0x0001 => StunMethod::Binding,
+ other => StunMethod::Unknown(other),
+ };
+
+ // Extract 96-bit transaction ID (bytes 8-19)
+ let mut transaction_id = [0u8; 12];
+ transaction_id.copy_from_slice(&payload[8..20]);
+
+ // Best-effort attribute parsing for SOFTWARE
+ let software = parse_software_attribute(payload, message_length);
+
+ Some(StunInfo {
+ message_class,
+ method,
+ transaction_id,
+ software,
+ })
+}
+
+/// Check if a packet looks like STUN without full parsing.
+/// Used for non-standard port detection where we want a quick probe.
+pub fn is_likely_stun(payload: &[u8]) -> bool {
+ if payload.len() < STUN_HEADER_SIZE {
+ return false;
+ }
+ if payload[0] & 0xC0 != 0x00 {
+ return false;
+ }
+ let cookie = u32::from_be_bytes([payload[4], payload[5], payload[6], payload[7]]);
+ cookie == STUN_MAGIC_COOKIE
+}
+
+/// Walk STUN attributes looking for the SOFTWARE attribute (0x8022).
+fn parse_software_attribute(payload: &[u8], message_length: usize) -> Option {
+ let attrs_end = STUN_HEADER_SIZE + message_length;
+ let mut offset = STUN_HEADER_SIZE;
+
+ while offset + 4 <= attrs_end {
+ let attr_type = u16::from_be_bytes([payload[offset], payload[offset + 1]]);
+ let attr_length = u16::from_be_bytes([payload[offset + 2], payload[offset + 3]]) as usize;
+
+ offset += 4;
+
+ if offset + attr_length > attrs_end {
+ return None;
+ }
+
+ if attr_type == ATTR_SOFTWARE {
+ let effective_len = attr_length.min(MAX_SOFTWARE_LEN);
+ return std::str::from_utf8(&payload[offset..offset + effective_len])
+ .ok()
+ .map(|s| s.trim_end_matches('\0').to_string());
+ }
+
+ // Advance to next attribute, padding to 4-byte boundary
+ let padded_length = (attr_length + 3) & !3;
+ offset += padded_length;
+ }
+
+ None
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ /// Build a STUN packet with the given class, method, and body (attributes).
+ fn build_stun_packet(class: u8, method: u16, msg_body: &[u8]) -> Vec {
+ let mut packet = Vec::new();
+
+ // Encode message type from class + method (RFC 5389 section 6)
+ let c0 = class & 0x1;
+ let c1 = (class >> 1) & 0x1;
+ let m0_3 = method & 0x000F;
+ let m4_6 = (method & 0x0070) << 1;
+ let m7_11 = (method & 0x0F80) << 2;
+ let msg_type = m7_11 | ((c1 as u16) << 8) | m4_6 | ((c0 as u16) << 4) | m0_3;
+
+ packet.push((msg_type >> 8) as u8);
+ packet.push((msg_type & 0xFF) as u8);
+
+ // Message length
+ let body_len = msg_body.len() as u16;
+ packet.push((body_len >> 8) as u8);
+ packet.push((body_len & 0xFF) as u8);
+
+ // Magic cookie
+ packet.extend_from_slice(&STUN_MAGIC_COOKIE.to_be_bytes());
+
+ // Transaction ID (12 bytes)
+ packet.extend_from_slice(&[
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C,
+ ]);
+
+ packet.extend_from_slice(msg_body);
+ packet
+ }
+
+ /// Build a SOFTWARE attribute (0x8022) with proper TLV + padding.
+ fn build_software_attr(software: &str) -> Vec {
+ let mut attr = Vec::new();
+ attr.push(0x80);
+ attr.push(0x22);
+ let len = software.len() as u16;
+ attr.push((len >> 8) as u8);
+ attr.push((len & 0xFF) as u8);
+ attr.extend_from_slice(software.as_bytes());
+ while attr.len() % 4 != 0 {
+ attr.push(0x00);
+ }
+ attr
+ }
+
+ #[test]
+ fn test_empty_payload_safe() {
+ assert!(analyze_stun(&[]).is_none());
+ }
+
+ #[test]
+ fn test_short_payload_safe() {
+ assert!(analyze_stun(&[0x00, 0x01]).is_none());
+ }
+
+ #[test]
+ fn test_binding_request() {
+ let packet = build_stun_packet(0, 0x0001, &[]);
+ let info = analyze_stun(&packet).expect("should parse");
+ assert_eq!(info.message_class, StunMessageClass::Request);
+ assert_eq!(info.method, StunMethod::Binding);
+ assert_eq!(info.transaction_id, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
+ assert!(info.software.is_none());
+ }
+
+ #[test]
+ fn test_binding_success_response() {
+ let packet = build_stun_packet(2, 0x0001, &[]);
+ let info = analyze_stun(&packet).expect("should parse");
+ assert_eq!(info.message_class, StunMessageClass::SuccessResponse);
+ assert_eq!(info.method, StunMethod::Binding);
+ }
+
+ #[test]
+ fn test_binding_error_response() {
+ let packet = build_stun_packet(3, 0x0001, &[]);
+ let info = analyze_stun(&packet).expect("should parse");
+ assert_eq!(info.message_class, StunMessageClass::ErrorResponse);
+ assert_eq!(info.method, StunMethod::Binding);
+ }
+
+ #[test]
+ fn test_binding_indication() {
+ let packet = build_stun_packet(1, 0x0001, &[]);
+ let info = analyze_stun(&packet).expect("should parse");
+ assert_eq!(info.message_class, StunMessageClass::Indication);
+ }
+
+ #[test]
+ fn test_with_software_attribute() {
+ let software_attr = build_software_attr("TestAgent/1.0");
+ let packet = build_stun_packet(2, 0x0001, &software_attr);
+ let info = analyze_stun(&packet).expect("should parse");
+ assert_eq!(info.software, Some("TestAgent/1.0".to_string()));
+ }
+
+ #[test]
+ fn test_too_short() {
+ let packet = [0x00; 10];
+ assert!(analyze_stun(&packet).is_none());
+ }
+
+ #[test]
+ fn test_wrong_magic_cookie() {
+ let mut packet = build_stun_packet(0, 0x0001, &[]);
+ packet[4] = 0xFF;
+ assert!(analyze_stun(&packet).is_none());
+ }
+
+ #[test]
+ fn test_first_two_bits_nonzero() {
+ let mut packet = build_stun_packet(0, 0x0001, &[]);
+ packet[0] |= 0x80;
+ assert!(analyze_stun(&packet).is_none());
+ }
+
+ #[test]
+ fn test_message_length_not_multiple_of_4() {
+ let mut packet = build_stun_packet(0, 0x0001, &[]);
+ packet[2] = 0x00;
+ packet[3] = 0x03;
+ assert!(analyze_stun(&packet).is_none());
+ }
+
+ #[test]
+ fn test_message_length_exceeds_payload() {
+ let mut packet = build_stun_packet(0, 0x0001, &[]);
+ packet[2] = 0x00;
+ packet[3] = 0x64;
+ assert!(analyze_stun(&packet).is_none());
+ }
+
+ #[test]
+ fn test_unknown_method() {
+ let packet = build_stun_packet(0, 0x0003, &[]);
+ let info = analyze_stun(&packet).expect("should parse");
+ assert_eq!(info.method, StunMethod::Unknown(0x0003));
+ }
+
+ #[test]
+ fn test_class_bits_exhaustive_for_unknown_method() {
+ // Lock the invariant the refactor relies on: class_bits is bounded
+ // to 0..=3 by construction (each of c0/c1 is masked to one bit).
+ // Pair every class with a non-Binding method to confirm the class
+ // decode is independent of method recognition — otherwise a future
+ // change to method handling could mask a class-bit regression.
+ for (class, expected) in [
+ (0u8, StunMessageClass::Request),
+ (1u8, StunMessageClass::Indication),
+ (2u8, StunMessageClass::SuccessResponse),
+ (3u8, StunMessageClass::ErrorResponse),
+ ] {
+ let packet = build_stun_packet(class, 0x0003, &[]); // unknown method
+ let info = analyze_stun(&packet).expect("should parse");
+ assert_eq!(info.message_class, expected);
+ assert_eq!(info.method, StunMethod::Unknown(0x0003));
+ }
+ }
+
+ #[test]
+ fn test_is_likely_stun() {
+ let packet = build_stun_packet(0, 0x0001, &[]);
+ assert!(is_likely_stun(&packet));
+
+ let not_stun = [0x80u8; 20];
+ assert!(!is_likely_stun(¬_stun));
+
+ let too_short = [0x00u8; 10];
+ assert!(!is_likely_stun(&too_short));
+ }
+}
diff --git a/crates/rustnet-core/src/network/geoip.rs b/crates/rustnet-core/src/network/geoip.rs
new file mode 100644
index 0000000..5965020
--- /dev/null
+++ b/crates/rustnet-core/src/network/geoip.rs
@@ -0,0 +1,469 @@
+//! GeoIP resolver with caching for Country and ASN lookups.
+//!
+//! Provides GeoIP lookups using MaxMind databases with an LRU cache
+//! to avoid repeated lookups for the same IP address.
+
+use dashmap::DashMap;
+use log::{debug, info, warn};
+use maxminddb::{Reader, geoip2};
+use std::net::IpAddr;
+use std::path::PathBuf;
+use std::sync::Arc;
+use std::time::{Duration, Instant};
+
+/// GeoIP information for an IP address
+#[derive(Debug, Clone, Default)]
+pub struct GeoIpInfo {
+ /// ISO 3166-1 alpha-2 country code (e.g., "US", "DE", "JP")
+ pub country_code: Option,
+ /// Country name in English (e.g., "United States", "Germany")
+ pub country_name: Option,
+ /// Autonomous System Number (e.g., 15169 for Google)
+ pub asn: Option,
+ /// AS Organization name (e.g., "GOOGLE")
+ pub as_org: Option,
+ /// postal code (e.g., "94043")
+ pub postal_code: Option,
+ /// city name (e.g., "Mountain View")
+ pub city: Option,
+}
+
+impl GeoIpInfo {
+ /// Check if any GeoIP data is available
+ pub fn has_data(&self) -> bool {
+ self.country_code.is_some() || self.asn.is_some() || self.city.is_some()
+ }
+
+ /// Get just the country code or "-" if unavailable
+ pub fn country_display(&self) -> &str {
+ self.country_code.as_deref().unwrap_or("-")
+ }
+}
+
+/// Cached GeoIP entry
+#[derive(Debug, Clone)]
+struct CachedGeoIp {
+ /// The resolved GeoIP info
+ info: GeoIpInfo,
+ /// When this entry was cached
+ cached_at: Instant,
+}
+
+/// Configuration for GeoIP resolver
+#[derive(Debug, Clone)]
+pub struct GeoIpConfig {
+ /// Path to GeoLite2-Country.mmdb database
+ pub country_db_path: Option,
+ /// Path to GeoLite2-ASN.mmdb database
+ pub asn_db_path: Option,
+ /// Path to GeoLite2-City.mmdb database
+ pub city_db_path: Option,
+ /// Cache TTL (default: 1 hour - GeoIP data rarely changes)
+ pub cache_ttl: Duration,
+ /// Maximum cache size (default: 50000 entries)
+ pub max_cache_size: usize,
+}
+
+impl Default for GeoIpConfig {
+ fn default() -> Self {
+ Self {
+ country_db_path: None,
+ asn_db_path: None,
+ city_db_path: None,
+ cache_ttl: Duration::from_secs(3600), // 1 hour
+ max_cache_size: 50000,
+ }
+ }
+}
+
+/// GeoIP resolver with caching
+pub struct GeoIpResolver {
+ /// Country database reader
+ country_reader: Option>>,
+ /// ASN database reader
+ asn_reader: Option>>,
+ /// City database reader
+ city_reader: Option>>,
+ /// Cache: IP -> CachedGeoIp
+ cache: Arc>,
+ /// Configuration
+ config: GeoIpConfig,
+}
+
+impl GeoIpResolver {
+ /// Create a new GeoIP resolver with the given configuration
+ pub fn new(config: GeoIpConfig) -> Self {
+ let country_reader =
+ config
+ .country_db_path
+ .as_ref()
+ .and_then(|path| match Reader::open_readfile(path) {
+ Ok(reader) => {
+ info!("Loaded GeoIP Country database from: {:?}", path);
+ Some(reader)
+ }
+ Err(e) => {
+ warn!(
+ "Failed to load GeoIP Country database from {:?}: {}",
+ path, e
+ );
+ None
+ }
+ });
+
+ let asn_reader =
+ config
+ .asn_db_path
+ .as_ref()
+ .and_then(|path| match Reader::open_readfile(path) {
+ Ok(reader) => {
+ info!("Loaded GeoIP ASN database from: {:?}", path);
+ Some(reader)
+ }
+ Err(e) => {
+ warn!("Failed to load GeoIP ASN database from {:?}: {}", path, e);
+ None
+ }
+ });
+
+ let city_reader =
+ config
+ .city_db_path
+ .as_ref()
+ .and_then(|path| match Reader::open_readfile(path) {
+ Ok(reader) => {
+ info!("Loaded GeoIP City database from: {:?}", path);
+ Some(reader)
+ }
+ Err(e) => {
+ warn!("Failed to load GeoIP City database from {:?}: {}", path, e);
+ None
+ }
+ });
+
+ Self {
+ country_reader,
+ asn_reader,
+ city_reader,
+ cache: Arc::new(DashMap::new()),
+ config,
+ }
+ }
+
+ /// Try to auto-discover and load databases from common paths
+ pub fn with_auto_discovery() -> Self {
+ let mut config = GeoIpConfig::default();
+
+ // Common paths to search for databases
+ let search_paths = Self::get_search_paths();
+
+ for base_path in search_paths {
+ // Try Country database
+ if config.country_db_path.is_none() {
+ let country_path = base_path.join("GeoLite2-Country.mmdb");
+ if country_path.exists() {
+ config.country_db_path = Some(country_path);
+ }
+ }
+
+ // Try ASN database
+ if config.asn_db_path.is_none() {
+ let asn_path = base_path.join("GeoLite2-ASN.mmdb");
+ if asn_path.exists() {
+ config.asn_db_path = Some(asn_path);
+ }
+ }
+
+ // Try City database
+ if config.city_db_path.is_none() {
+ let city_path = base_path.join("GeoLite2-City.mmdb");
+ if city_path.exists() {
+ config.city_db_path = Some(city_path);
+ }
+ }
+
+ // Stop if all three found
+ if config.country_db_path.is_some()
+ && config.asn_db_path.is_some()
+ && config.city_db_path.is_some()
+ {
+ break;
+ }
+ }
+
+ Self::new(config)
+ }
+
+ /// Get common search paths for GeoIP databases
+ ///
+ /// This is public so that the Landlock sandbox can whitelist these paths
+ /// for read access.
+ pub fn get_search_paths() -> Vec {
+ let mut paths = Vec::new();
+
+ // Current directory / resources
+ paths.push(PathBuf::from("resources/geoip2"));
+ paths.push(PathBuf::from("."));
+
+ // XDG data directory
+ if let Ok(xdg_data) = std::env::var("XDG_DATA_HOME") {
+ paths.push(PathBuf::from(&xdg_data).join("rustnet/geoip"));
+ paths.push(PathBuf::from(xdg_data).join("GeoIP"));
+ }
+
+ // Home directory
+ if let Ok(home) = std::env::var("HOME") {
+ let home_path = PathBuf::from(&home);
+ paths.push(home_path.join(".local/share/rustnet/geoip"));
+ paths.push(home_path.join(".local/share/GeoIP"));
+ }
+
+ // System paths
+ paths.push(PathBuf::from("/usr/share/GeoIP"));
+ paths.push(PathBuf::from("/usr/local/share/GeoIP"));
+ paths.push(PathBuf::from("/opt/homebrew/share/GeoIP"));
+ paths.push(PathBuf::from("/var/lib/GeoIP"));
+
+ // Windows paths
+ #[cfg(target_os = "windows")]
+ {
+ if let Ok(program_data) = std::env::var("ProgramData") {
+ paths.push(PathBuf::from(program_data).join("GeoIP"));
+ }
+ }
+
+ paths
+ }
+
+ /// Check if the resolver has any databases loaded
+ pub fn is_available(&self) -> bool {
+ self.country_reader.is_some() || self.asn_reader.is_some() || self.city_reader.is_some()
+ }
+
+ /// Check which databases are available.
+ /// Returns (has_location, has_asn, has_city) where has_location is true when
+ /// either the country or city database is loaded (city DB is a superset of country).
+ pub fn get_status(&self) -> (bool, bool, bool) {
+ let has_location = self.country_reader.is_some() || self.city_reader.is_some();
+ (
+ has_location,
+ self.asn_reader.is_some(),
+ self.city_reader.is_some(),
+ )
+ }
+
+ /// Lookup GeoIP information for an IP address
+ pub fn lookup(&self, ip: IpAddr) -> GeoIpInfo {
+ // Skip private/local addresses
+ if is_private_or_local(&ip) {
+ return GeoIpInfo::default();
+ }
+
+ // Check cache first
+ if let Some(cached) = self.cache.get(&ip)
+ && cached.cached_at.elapsed() < self.config.cache_ttl
+ {
+ return cached.info.clone();
+ }
+
+ // Perform lookup
+ let info = self.do_lookup(ip);
+
+ // Cache the result
+ self.cache.insert(
+ ip,
+ CachedGeoIp {
+ info: info.clone(),
+ cached_at: Instant::now(),
+ },
+ );
+
+ // Evict old entries if cache is too large
+ if self.cache.len() > self.config.max_cache_size {
+ self.evict_oldest_entries();
+ }
+
+ info
+ }
+
+ /// Perform the actual database lookup
+ fn do_lookup(&self, ip: IpAddr) -> GeoIpInfo {
+ let mut info = GeoIpInfo::default();
+
+ // Country lookup
+ if let Some(ref reader) = self.country_reader
+ && let Ok(Some(country)) = reader
+ .lookup(ip)
+ .and_then(|r| r.decode::())
+ {
+ let c = &country.country;
+ info.country_code = c.iso_code.map(|s| s.to_string());
+ info.country_name = c.names.english.map(|s| s.to_string());
+ }
+
+ // ASN lookup
+ if let Some(ref reader) = self.asn_reader
+ && let Ok(Some(asn)) = reader.lookup(ip).and_then(|r| r.decode::())
+ {
+ info.asn = asn.autonomous_system_number;
+ info.as_org = asn.autonomous_system_organization.map(|s| s.to_string());
+ }
+
+ // City lookup (City DB is a superset of Country — also fills country fields as fallback)
+ if let Some(ref reader) = self.city_reader
+ && let Ok(Some(city)) = reader.lookup(ip).and_then(|r| r.decode::())
+ {
+ info.postal_code = city.postal.code.map(|s| s.to_string());
+ // NOTE: City names can be in multiple languages, we take English if available
+ info.city = city.city.names.english.map(|s| s.to_string());
+ // Fall back to country info from City DB if Country DB was not loaded
+ if info.country_code.is_none() {
+ info.country_code = city.country.iso_code.map(|s| s.to_string());
+ info.country_name = city.country.names.english.map(|s| s.to_string());
+ }
+ }
+
+ info
+ }
+
+ /// Evict oldest entries from cache
+ fn evict_oldest_entries(&self) {
+ let target_size = self.config.max_cache_size * 3 / 4; // Evict to 75%
+
+ let mut entries: Vec<_> = self.cache.iter().map(|e| (*e.key(), e.cached_at)).collect();
+
+ let to_remove = self.cache.len().saturating_sub(target_size);
+ if to_remove == 0 {
+ return;
+ }
+ entries.select_nth_unstable_by_key(to_remove - 1, |(_, time)| *time);
+ for (ip, _) in entries.into_iter().take(to_remove) {
+ self.cache.remove(&ip);
+ }
+
+ debug!(
+ "GeoIP cache evicted {} entries, now {} entries",
+ to_remove,
+ self.cache.len()
+ );
+ }
+}
+
+/// Check if IP is private, local, or reserved
+fn is_private_or_local(ip: &IpAddr) -> bool {
+ match ip {
+ IpAddr::V4(v4) => {
+ v4.is_private()
+ || v4.is_loopback()
+ || v4.is_link_local()
+ || v4.is_broadcast()
+ || v4.is_documentation()
+ || v4.is_unspecified()
+ // 100.64.0.0/10 (CGNAT)
+ || (v4.octets()[0] == 100 && (v4.octets()[1] & 0xc0) == 64)
+ }
+ IpAddr::V6(v6) => {
+ v6.is_loopback()
+ || v6.is_unspecified()
+ // Link-local (fe80::/10)
+ || ((v6.segments()[0] & 0xffc0) == 0xfe80)
+ // Unique local (fc00::/7)
+ || ((v6.segments()[0] & 0xfe00) == 0xfc00)
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_geoip_info_has_data() {
+ let info = GeoIpInfo {
+ country_code: Some("US".to_string()),
+ country_name: Some("United States".to_string()),
+ asn: Some(15169),
+ as_org: Some("GOOGLE".to_string()),
+ postal_code: Some("94043".to_string()),
+ city: Some("Mountain View".to_string()),
+ };
+
+ assert!(info.has_data());
+ assert_eq!(info.country_display(), "US");
+ }
+
+ #[test]
+ fn test_geoip_info_country_only() {
+ let info = GeoIpInfo {
+ country_code: Some("DE".to_string()),
+ country_name: Some("Germany".to_string()),
+ asn: None,
+ as_org: None,
+ postal_code: None,
+ city: None,
+ };
+
+ assert!(info.has_data());
+ assert_eq!(info.country_display(), "DE");
+ }
+
+ #[test]
+ fn test_geoip_info_asn_only() {
+ let info = GeoIpInfo {
+ country_code: None,
+ country_name: None,
+ asn: Some(13335),
+ as_org: Some("CLOUDFLARENET".to_string()),
+ postal_code: None,
+ city: None,
+ };
+
+ assert!(info.has_data());
+ assert_eq!(info.country_display(), "-");
+ }
+
+ #[test]
+ fn test_geoip_info_empty() {
+ let info = GeoIpInfo::default();
+ assert_eq!(info.country_display(), "-");
+ assert!(!info.has_data());
+ }
+
+ #[test]
+ fn test_private_ip_detection() {
+ // IPv4 private
+ assert!(is_private_or_local(&"192.168.1.1".parse().unwrap()));
+ assert!(is_private_or_local(&"10.0.0.1".parse().unwrap()));
+ assert!(is_private_or_local(&"172.16.0.1".parse().unwrap()));
+ assert!(is_private_or_local(&"127.0.0.1".parse().unwrap()));
+ assert!(is_private_or_local(&"169.254.1.1".parse().unwrap())); // Link-local
+
+ // CGNAT range
+ assert!(is_private_or_local(&"100.64.0.1".parse().unwrap()));
+ assert!(is_private_or_local(&"100.127.255.255".parse().unwrap()));
+
+ // Public IPs
+ assert!(!is_private_or_local(&"8.8.8.8".parse().unwrap()));
+ assert!(!is_private_or_local(&"1.1.1.1".parse().unwrap()));
+
+ // IPv6
+ assert!(is_private_or_local(&"::1".parse().unwrap())); // Loopback
+ assert!(is_private_or_local(&"fe80::1".parse().unwrap())); // Link-local
+ assert!(is_private_or_local(&"fc00::1".parse().unwrap())); // Unique local
+ assert!(!is_private_or_local(
+ &"2001:4860:4860::8888".parse().unwrap()
+ )); // Google DNS
+ }
+
+ #[test]
+ fn test_country_display() {
+ let info = GeoIpInfo {
+ country_code: Some("JP".to_string()),
+ ..Default::default()
+ };
+ assert_eq!(info.country_display(), "JP");
+
+ let empty = GeoIpInfo::default();
+ assert_eq!(empty.country_display(), "-");
+ }
+}
diff --git a/crates/rustnet-core/src/network/interface_stats.rs b/crates/rustnet-core/src/network/interface_stats.rs
new file mode 100644
index 0000000..bbafd89
--- /dev/null
+++ b/crates/rustnet-core/src/network/interface_stats.rs
@@ -0,0 +1,162 @@
+use std::io;
+use std::time::SystemTime;
+
+/// Statistics for a network interface
+#[derive(Debug, Clone)]
+pub struct InterfaceStats {
+ pub interface_name: String,
+ pub rx_bytes: u64,
+ pub tx_bytes: u64,
+ pub rx_packets: u64,
+ pub tx_packets: u64,
+ pub rx_errors: u64,
+ pub tx_errors: u64,
+ pub rx_dropped: u64,
+ pub tx_dropped: u64,
+ pub collisions: u64,
+ pub timestamp: SystemTime,
+}
+
+impl InterfaceStats {
+ /// Calculate rates from two snapshots
+ pub fn calculate_rates(&self, previous: &InterfaceStats) -> InterfaceRates {
+ let duration = self
+ .timestamp
+ .duration_since(previous.timestamp)
+ .unwrap_or_default()
+ .as_secs_f64();
+
+ if duration == 0.0 {
+ return InterfaceRates::default();
+ }
+
+ InterfaceRates {
+ rx_bytes_per_sec: ((self.rx_bytes.saturating_sub(previous.rx_bytes)) as f64 / duration)
+ as u64,
+ tx_bytes_per_sec: ((self.tx_bytes.saturating_sub(previous.tx_bytes)) as f64 / duration)
+ as u64,
+ }
+ }
+}
+
+/// Rate calculations for interface statistics
+#[derive(Debug, Clone, Default)]
+pub struct InterfaceRates {
+ pub rx_bytes_per_sec: u64,
+ pub tx_bytes_per_sec: u64,
+}
+
+/// Trait for platform-specific interface statistics providers
+pub trait InterfaceStatsProvider: Send + Sync {
+ /// Get statistics for all available interfaces
+ fn get_all_stats(&self) -> Result, io::Error>;
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::time::Duration;
+
+ #[test]
+ fn test_rate_calculation() {
+ let t1 = SystemTime::now();
+ let t2 = t1 + Duration::from_secs(1);
+
+ let stats1 = InterfaceStats {
+ interface_name: "test".to_string(),
+ rx_bytes: 1000,
+ tx_bytes: 500,
+ rx_packets: 10,
+ tx_packets: 5,
+ rx_errors: 0,
+ tx_errors: 0,
+ rx_dropped: 0,
+ tx_dropped: 0,
+ collisions: 0,
+ timestamp: t1,
+ };
+
+ let stats2 = InterfaceStats {
+ interface_name: "test".to_string(),
+ rx_bytes: 2000,
+ tx_bytes: 1000,
+ rx_packets: 20,
+ tx_packets: 10,
+ rx_errors: 0,
+ tx_errors: 0,
+ rx_dropped: 0,
+ tx_dropped: 0,
+ collisions: 0,
+ timestamp: t2,
+ };
+
+ let rates = stats2.calculate_rates(&stats1);
+ assert_eq!(rates.rx_bytes_per_sec, 1000);
+ assert_eq!(rates.tx_bytes_per_sec, 500);
+ }
+
+ #[test]
+ fn test_rate_calculation_zero_duration() {
+ let t = SystemTime::now();
+
+ let stats1 = InterfaceStats {
+ interface_name: "test".to_string(),
+ rx_bytes: 1000,
+ tx_bytes: 500,
+ rx_packets: 10,
+ tx_packets: 5,
+ rx_errors: 0,
+ tx_errors: 0,
+ rx_dropped: 0,
+ tx_dropped: 0,
+ collisions: 0,
+ timestamp: t,
+ };
+
+ let stats2 = stats1.clone();
+
+ let rates = stats2.calculate_rates(&stats1);
+ assert_eq!(rates.rx_bytes_per_sec, 0);
+ assert_eq!(rates.tx_bytes_per_sec, 0);
+ }
+
+ #[test]
+ fn test_rate_calculation_with_counter_wrapping() {
+ let t1 = SystemTime::now();
+ let t2 = t1 + Duration::from_secs(1);
+
+ let stats1 = InterfaceStats {
+ interface_name: "test".to_string(),
+ rx_bytes: 1000,
+ tx_bytes: 500,
+ rx_packets: 10,
+ tx_packets: 5,
+ rx_errors: 0,
+ tx_errors: 0,
+ rx_dropped: 0,
+ tx_dropped: 0,
+ collisions: 0,
+ timestamp: t1,
+ };
+
+ // Simulate counter reset (should use saturating_sub to avoid panic)
+ let stats2 = InterfaceStats {
+ interface_name: "test".to_string(),
+ rx_bytes: 500, // Less than previous
+ tx_bytes: 250,
+ rx_packets: 5,
+ tx_packets: 2,
+ rx_errors: 0,
+ tx_errors: 0,
+ rx_dropped: 0,
+ tx_dropped: 0,
+ collisions: 0,
+ timestamp: t2,
+ };
+
+ let rates = stats2.calculate_rates(&stats1);
+ // Should result in 0 due to saturating_sub
+ assert_eq!(rates.rx_bytes_per_sec, 0);
+ assert_eq!(rates.tx_bytes_per_sec, 0);
+ }
+}
diff --git a/crates/rustnet-core/src/network/link_layer/ethernet.rs b/crates/rustnet-core/src/network/link_layer/ethernet.rs
new file mode 100644
index 0000000..270a68e
--- /dev/null
+++ b/crates/rustnet-core/src/network/link_layer/ethernet.rs
@@ -0,0 +1,136 @@
+//! Ethernet (IEEE 802.3) frame parsing
+//!
+//! Handles DLT_EN10MB (Ethernet) frames with 14-byte headers and
+//! 802.1Q VLAN-tagged frames with 18-byte headers.
+
+use crate::network::parser::{PacketParser, ParsedPacket};
+
+/// Extract the effective EtherType and payload offset from an Ethernet frame.
+///
+/// Returns `(ethertype, offset)` where `offset` is the number of bytes before
+/// the IP/ARP payload:
+/// - Standard frame: offset = 14
+/// - 802.1Q VLAN-tagged frame: offset = 18 (extra 4-byte VLAN tag)
+fn extract_ethertype(data: &[u8]) -> Option<(u16, usize)> {
+ let ethertype = u16::from_be_bytes([data[12], data[13]]);
+ if ethertype == 0x8100 {
+ if data.len() < 18 {
+ log::debug!("VLAN frame too small: {} bytes", data.len());
+ return None;
+ }
+ let vlan_id = u16::from_be_bytes([data[14], data[15]]) & 0x0FFF;
+ log::trace!("Ethernet: 802.1Q VLAN tag detected (VID={})", vlan_id);
+ Some((u16::from_be_bytes([data[16], data[17]]), 18))
+ } else {
+ Some((ethertype, 14))
+ }
+}
+
+/// Parse an Ethernet frame and extract the network layer packet
+///
+/// Standard Ethernet frame format (14 bytes):
+/// - Destination MAC (6 bytes)
+/// - Source MAC (6 bytes)
+/// - EtherType (2 bytes)
+///
+/// 802.1Q VLAN-tagged frame format (18 bytes):
+/// - Destination MAC (6 bytes)
+/// - Source MAC (6 bytes)
+/// - TPID: 0x8100 (2 bytes)
+/// - TCI: PCP (3 bits) | DEI (1 bit) | VID (12 bits) (2 bytes)
+/// - Inner EtherType (2 bytes)
+///
+/// Returns the parsed packet if successful
+pub fn parse(
+ data: &[u8],
+ parser: &PacketParser,
+ process_name: Option,
+ process_id: Option,
+) -> Option {
+ if data.len() < 14 {
+ log::debug!("Ethernet frame too small: {} bytes", data.len());
+ return None;
+ }
+
+ let (ethertype, offset) = extract_ethertype(data)?;
+
+ match ethertype {
+ 0x0800 => {
+ // IPv4
+ log::trace!("Ethernet: IPv4 packet detected");
+ parser.parse_ipv4_packet_inner(data, offset, process_name, process_id)
+ }
+ 0x86dd => {
+ // IPv6
+ log::trace!("Ethernet: IPv6 packet detected");
+ parser.parse_ipv6_packet_inner(data, offset, process_name, process_id)
+ }
+ 0x0806 => {
+ // ARP
+ log::trace!("Ethernet: ARP packet detected");
+ parser.parse_arp_packet_inner(data, offset, process_name, process_id)
+ }
+ _ => {
+ log::debug!("Ethernet: Unknown EtherType: 0x{:04x}", ethertype);
+ None
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_ethernet_frame_too_small() {
+ // Ethernet frames must be at least 14 bytes
+ let small_frame = vec![0x00, 0x11, 0x22];
+ let parser = PacketParser::new();
+ assert!(parse(&small_frame, &parser, None, None).is_none());
+ }
+
+ #[test]
+ fn test_extract_ethertype_vlan() {
+ // Build an 18-byte 802.1Q VLAN-tagged frame:
+ // bytes 0-11: MACs (zeroed)
+ // bytes 12-13: TPID 0x8100
+ // bytes 14-15: TCI (VID = 42)
+ // bytes 16-17: inner EtherType 0x0800 (IPv4)
+ let mut frame = vec![0u8; 18];
+ frame[12] = 0x81;
+ frame[13] = 0x00;
+ frame[14] = 0x00;
+ frame[15] = 0x2a; // VID = 42
+ frame[16] = 0x08;
+ frame[17] = 0x00;
+
+ let (ethertype, offset) = extract_ethertype(&frame).unwrap();
+ assert_eq!(ethertype, 0x0800);
+ assert_eq!(offset, 18);
+ }
+
+ #[test]
+ fn test_extract_ethertype_vlan_ipv6() {
+ let mut frame = vec![0u8; 18];
+ frame[12] = 0x81;
+ frame[13] = 0x00;
+ frame[14] = 0x00;
+ frame[15] = 0x2a; // VID = 42
+ frame[16] = 0x86;
+ frame[17] = 0xdd; // inner EtherType = IPv6
+
+ let (ethertype, offset) = extract_ethertype(&frame).unwrap();
+ assert_eq!(ethertype, 0x86dd);
+ assert_eq!(offset, 18);
+ }
+
+ #[test]
+ fn test_extract_ethertype_vlan_too_small() {
+ // VLAN frame must be at least 18 bytes; 17 should return None
+ let mut frame = vec![0u8; 17];
+ frame[12] = 0x81;
+ frame[13] = 0x00;
+
+ assert!(extract_ethertype(&frame).is_none());
+ }
+}
diff --git a/crates/rustnet-core/src/network/link_layer/linux_sll.rs b/crates/rustnet-core/src/network/link_layer/linux_sll.rs
new file mode 100644
index 0000000..895ba55
--- /dev/null
+++ b/crates/rustnet-core/src/network/link_layer/linux_sll.rs
@@ -0,0 +1,194 @@
+//! Linux "cooked" capture parsing
+//!
+//! Handles DLT_LINUX_SLL (113) and DLT_LINUX_SLL2 (276)
+//! Used by the Linux "any" pseudo-interface
+
+use crate::network::parser::{PacketParser, ParsedPacket};
+
+/// Parse Linux Cooked Capture v1 packet (DLT_LINUX_SLL)
+///
+/// Header format (16 bytes):
+/// - Packet type (2 bytes)
+/// - ARPHRD type (2 bytes)
+/// - Link-layer address length (2 bytes)
+/// - Link-layer address (8 bytes)
+/// - Protocol type (2 bytes) - EtherType
+///
+/// IP payload starts at byte 16
+pub fn parse_sll(
+ data: &[u8],
+ parser: &PacketParser,
+ process_name: Option,
+ process_id: Option,
+) -> Option {
+ if data.len() < 16 {
+ log::debug!("Linux SLL packet too small: {} bytes", data.len());
+ return None;
+ }
+
+ // Protocol type is at bytes 14-15 (EtherType)
+ let protocol = u16::from_be_bytes([data[14], data[15]]);
+
+ match protocol {
+ 0x0800 => {
+ // IPv4 - payload starts at byte 16
+ log::trace!("Linux SLL: IPv4 packet detected");
+ let ip_data = &data[16..];
+ parser.parse_raw_ipv4_packet(ip_data, process_name, process_id)
+ }
+ 0x86dd => {
+ // IPv6 - payload starts at byte 16
+ log::trace!("Linux SLL: IPv6 packet detected");
+ let ip_data = &data[16..];
+ parser.parse_raw_ipv6_packet(ip_data, process_name, process_id)
+ }
+ 0x0806 => {
+ // ARP - payload starts at byte 16
+ log::trace!("Linux SLL: ARP packet detected");
+ parser.parse_arp_packet_with_offset(data, 16, process_name, process_id)
+ }
+ 0x8100 => {
+ // 802.1Q VLAN-tagged (reconstructed by libpcap from kernel metadata)
+ // Layout after reconstruction: SLL header (14 bytes) + TPID (2) + TCI (2) + inner EtherType (2)
+ if data.len() < 20 {
+ log::debug!("Linux SLL: VLAN frame too small: {} bytes", data.len());
+ return None;
+ }
+ let inner_proto = u16::from_be_bytes([data[18], data[19]]);
+ match inner_proto {
+ 0x0800 => {
+ log::trace!("Linux SLL: VLAN-tagged IPv4 packet detected");
+ parser.parse_raw_ipv4_packet(&data[20..], process_name, process_id)
+ }
+ 0x86dd => {
+ log::trace!("Linux SLL: VLAN-tagged IPv6 packet detected");
+ parser.parse_raw_ipv6_packet(&data[20..], process_name, process_id)
+ }
+ 0x0806 => {
+ log::trace!("Linux SLL: VLAN-tagged ARP packet detected");
+ parser.parse_arp_packet_with_offset(data, 20, process_name, process_id)
+ }
+ _ => None,
+ }
+ }
+ _ => {
+ log::debug!("Linux SLL: Unknown protocol: 0x{:04x}", protocol);
+ None
+ }
+ }
+}
+
+/// Parse Linux Cooked Capture v2 packet (DLT_LINUX_SLL2)
+///
+/// Header format (20 bytes):
+/// - Protocol type (2 bytes) - EtherType
+/// - Reserved (2 bytes)
+/// - Interface index (4 bytes)
+/// - ARPHRD type (2 bytes)
+/// - Packet type (1 byte)
+/// - Link-layer address length (1 byte)
+/// - Link-layer address (8 bytes)
+///
+/// IP payload starts at byte 20
+pub fn parse_sll2(
+ data: &[u8],
+ parser: &PacketParser,
+ process_name: Option,
+ process_id: Option,
+) -> Option {
+ if data.len() < 20 {
+ log::debug!("Linux SLL2 packet too small: {} bytes", data.len());
+ return None;
+ }
+
+ // Protocol type is at bytes 0-1 (EtherType)
+ let protocol = u16::from_be_bytes([data[0], data[1]]);
+
+ match protocol {
+ 0x0800 => {
+ // IPv4 - payload starts at byte 20
+ log::trace!("Linux SLL2: IPv4 packet detected");
+ let ip_data = &data[20..];
+ parser.parse_raw_ipv4_packet(ip_data, process_name, process_id)
+ }
+ 0x86dd => {
+ // IPv6 - payload starts at byte 20
+ log::trace!("Linux SLL2: IPv6 packet detected");
+ let ip_data = &data[20..];
+ parser.parse_raw_ipv6_packet(ip_data, process_name, process_id)
+ }
+ 0x0806 => {
+ // ARP - payload starts at byte 20
+ log::trace!("Linux SLL2: ARP packet detected");
+ parser.parse_arp_packet_with_offset(data, 20, process_name, process_id)
+ }
+ 0x8100 => {
+ // 802.1Q VLAN-tagged (visible when rx-vlan-offload is disabled;
+ // libpcap does not reconstruct VLAN tags for SLL2, see libpcap#1105)
+ // Layout: SLL2 header (20 bytes) + TCI (2) + inner EtherType (2)
+ if data.len() < 24 {
+ log::debug!("Linux SLL2: VLAN frame too small: {} bytes", data.len());
+ return None;
+ }
+ let inner_proto = u16::from_be_bytes([data[22], data[23]]);
+ match inner_proto {
+ 0x0800 => {
+ log::trace!("Linux SLL2: VLAN-tagged IPv4 packet detected");
+ parser.parse_raw_ipv4_packet(&data[24..], process_name, process_id)
+ }
+ 0x86dd => {
+ log::trace!("Linux SLL2: VLAN-tagged IPv6 packet detected");
+ parser.parse_raw_ipv6_packet(&data[24..], process_name, process_id)
+ }
+ 0x0806 => {
+ log::trace!("Linux SLL2: VLAN-tagged ARP packet detected");
+ parser.parse_arp_packet_with_offset(data, 24, process_name, process_id)
+ }
+ _ => None,
+ }
+ }
+ _ => {
+ log::debug!("Linux SLL2: Unknown protocol: 0x{:04x}", protocol);
+ None
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_sll_packet_too_small() {
+ let small_packet = vec![0x00; 10];
+ let parser = PacketParser::new();
+ assert!(parse_sll(&small_packet, &parser, None, None).is_none());
+ }
+
+ #[test]
+ fn test_sll2_packet_too_small() {
+ let small_packet = vec![0x00; 15];
+ let parser = PacketParser::new();
+ assert!(parse_sll2(&small_packet, &parser, None, None).is_none());
+ }
+
+ #[test]
+ fn test_sll_vlan_too_small() {
+ // SLL VLAN frame needs at least 20 bytes (16 header + 4 VLAN tag)
+ let mut packet = vec![0x00; 19];
+ packet[14] = 0x81; // Protocol = 0x8100 (VLAN)
+ packet[15] = 0x00;
+ let parser = PacketParser::new();
+ assert!(parse_sll(&packet, &parser, None, None).is_none());
+ }
+
+ #[test]
+ fn test_sll2_vlan_too_small() {
+ // SLL2 VLAN frame needs at least 24 bytes (20 header + 4 VLAN tag)
+ let mut packet = vec![0x00; 23];
+ packet[0] = 0x81; // Protocol = 0x8100 (VLAN)
+ packet[1] = 0x00;
+ let parser = PacketParser::new();
+ assert!(parse_sll2(&packet, &parser, None, None).is_none());
+ }
+}
diff --git a/crates/rustnet-core/src/network/link_layer/mod.rs b/crates/rustnet-core/src/network/link_layer/mod.rs
new file mode 100644
index 0000000..db65d7f
--- /dev/null
+++ b/crates/rustnet-core/src/network/link_layer/mod.rs
@@ -0,0 +1,173 @@
+//! Link layer (Layer 2) packet parsing
+//!
+//! This module handles data-link layer protocols and extracts network-layer packets:
+//! - Ethernet (DLT_EN10MB)
+//! - Linux Cooked Capture v1 and v2 (DLT_LINUX_SLL, DLT_LINUX_SLL2)
+//! - Raw IP packets (DLT_RAW, LINKTYPE_IPV4, LINKTYPE_IPV6)
+//! - TUN/TAP interfaces
+//! - PKTAP (macOS process metadata)
+
+pub mod ethernet;
+pub mod linux_sll;
+#[cfg(target_os = "macos")]
+pub mod pktap;
+pub mod raw_ip;
+pub mod tun_tap;
+
+/// Data Link Type (DLT) constants
+/// These match the values from libpcap
+pub mod dlt {
+ pub const EN10MB: i32 = 1; // Ethernet
+ pub const RAW: i32 = 12; // Raw IP (no link layer)
+ pub const NULL: i32 = 0; // BSD loopback (sometimes used by TUN)
+ pub const LINUX_SLL: i32 = 113; // Linux "cooked" capture v1
+ pub const PKTAP: i32 = 149; // Apple PKTAP (DLT_USER2)
+ pub const PKTAP_STANDARD: i32 = 258; // Standard PKTAP
+ pub const LINUX_SLL2: i32 = 276; // Linux "cooked" capture v2
+
+ // Link type values for raw IP packets
+ pub const LINKTYPE_RAW: i32 = 101; // Raw IPv4/IPv6
+ pub const LINKTYPE_IPV4: i32 = 228; // Raw IPv4 only
+ pub const LINKTYPE_IPV6: i32 = 229; // Raw IPv6 only
+}
+
+/// Link layer type enum for identifying interface types
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum LinkLayerType {
+ Ethernet,
+ RawIP,
+ LinuxSLL,
+ LinuxSLL2,
+ Pktap,
+ Tun,
+ Tap,
+ Unknown,
+}
+
+impl LinkLayerType {
+ /// Determine link layer type from DLT value
+ pub fn from_dlt(dlt: i32) -> Self {
+ match dlt {
+ dlt::EN10MB => LinkLayerType::Ethernet,
+ dlt::RAW | dlt::NULL | dlt::LINKTYPE_RAW | dlt::LINKTYPE_IPV4 | dlt::LINKTYPE_IPV6 => {
+ LinkLayerType::RawIP
+ }
+ dlt::LINUX_SLL => LinkLayerType::LinuxSLL,
+ dlt::LINUX_SLL2 => LinkLayerType::LinuxSLL2,
+ dlt::PKTAP | dlt::PKTAP_STANDARD => LinkLayerType::Pktap,
+ _ => LinkLayerType::Unknown,
+ }
+ }
+
+ /// Determine link layer type from both DLT value and interface name
+ ///
+ /// This is more accurate than `from_dlt()` alone because it can distinguish
+ /// TUN/TAP interfaces from regular interfaces that use the same DLT codes.
+ ///
+ /// # Example
+ ///
+ /// ```rust,ignore
+ /// let link_type = LinkLayerType::from_dlt_and_name(12, "tun0");
+ /// assert!(matches!(link_type, LinkLayerType::Tun));
+ ///
+ /// let link_type = LinkLayerType::from_dlt_and_name(1, "tap0");
+ /// assert!(matches!(link_type, LinkLayerType::Tap));
+ /// ```
+ pub fn from_dlt_and_name(dlt: i32, interface_name: &str) -> Self {
+ // Check if this is a TUN/TAP interface by name
+ if tun_tap::is_tun_interface(interface_name) {
+ return LinkLayerType::Tun;
+ }
+ if tun_tap::is_tap_interface(interface_name) {
+ return LinkLayerType::Tap;
+ }
+
+ // Otherwise, use DLT-based detection
+ Self::from_dlt(dlt)
+ }
+
+ /// Check if this link type represents a TUN/TAP interface
+ pub fn is_tunnel(&self) -> bool {
+ matches!(self, LinkLayerType::Tun | LinkLayerType::Tap)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_linktype_from_dlt() {
+ assert_eq!(
+ LinkLayerType::from_dlt(dlt::EN10MB),
+ LinkLayerType::Ethernet
+ );
+ assert_eq!(LinkLayerType::from_dlt(dlt::RAW), LinkLayerType::RawIP);
+ assert_eq!(LinkLayerType::from_dlt(dlt::NULL), LinkLayerType::RawIP);
+ assert_eq!(
+ LinkLayerType::from_dlt(dlt::LINUX_SLL),
+ LinkLayerType::LinuxSLL
+ );
+ assert_eq!(
+ LinkLayerType::from_dlt(dlt::LINUX_SLL2),
+ LinkLayerType::LinuxSLL2
+ );
+ assert_eq!(LinkLayerType::from_dlt(dlt::PKTAP), LinkLayerType::Pktap);
+ assert_eq!(
+ LinkLayerType::from_dlt(dlt::PKTAP_STANDARD),
+ LinkLayerType::Pktap
+ );
+ assert_eq!(
+ LinkLayerType::from_dlt(dlt::LINKTYPE_RAW),
+ LinkLayerType::RawIP
+ );
+ assert_eq!(
+ LinkLayerType::from_dlt(dlt::LINKTYPE_IPV4),
+ LinkLayerType::RawIP
+ );
+ assert_eq!(
+ LinkLayerType::from_dlt(dlt::LINKTYPE_IPV6),
+ LinkLayerType::RawIP
+ );
+ assert_eq!(LinkLayerType::from_dlt(999), LinkLayerType::Unknown);
+ }
+
+ #[test]
+ fn test_is_tunnel() {
+ assert!(LinkLayerType::Tun.is_tunnel());
+ assert!(LinkLayerType::Tap.is_tunnel());
+ assert!(!LinkLayerType::Ethernet.is_tunnel());
+ assert!(!LinkLayerType::RawIP.is_tunnel());
+ assert!(!LinkLayerType::LinuxSLL.is_tunnel());
+ assert!(!LinkLayerType::Pktap.is_tunnel());
+ }
+
+ #[test]
+ fn test_from_dlt_and_name() {
+ // Test TUN interface detection
+ assert_eq!(
+ LinkLayerType::from_dlt_and_name(dlt::RAW, "tun0"),
+ LinkLayerType::Tun
+ );
+ assert_eq!(
+ LinkLayerType::from_dlt_and_name(dlt::RAW, "utun0"),
+ LinkLayerType::Tun
+ );
+
+ // Test TAP interface detection
+ assert_eq!(
+ LinkLayerType::from_dlt_and_name(dlt::EN10MB, "tap0"),
+ LinkLayerType::Tap
+ );
+
+ // Test regular interface (not TUN/TAP)
+ assert_eq!(
+ LinkLayerType::from_dlt_and_name(dlt::EN10MB, "eth0"),
+ LinkLayerType::Ethernet
+ );
+ assert_eq!(
+ LinkLayerType::from_dlt_and_name(dlt::RAW, "wlan0"),
+ LinkLayerType::RawIP
+ );
+ }
+}
diff --git a/crates/rustnet-core/src/network/link_layer/pktap.rs b/crates/rustnet-core/src/network/link_layer/pktap.rs
new file mode 100644
index 0000000..a14bd4c
--- /dev/null
+++ b/crates/rustnet-core/src/network/link_layer/pktap.rs
@@ -0,0 +1,288 @@
+// PKTAP (Packet Tap) support for macOS
+// Provides process identification for network packets
+use log::{debug, warn};
+use std::mem;
+
+/// Largest valid Darwin PID. macOS wraps PIDs at 100000, so a live PID is
+/// always in `1..=99999`.
+const DARWIN_PID_MAX: u32 = 99_999;
+
+/// PKTAP header structure as defined by Apple
+/// Based on the LINKTYPE_PKTAP specification and Apple's pktap.h
+#[repr(C)]
+#[derive(Debug, Clone)]
+pub struct PktapHeader {
+ pub pth_length: u32, // Total header length (minimum 108 bytes)
+ pub pth_type_next: u32, // Type of next header
+ pub pth_dlt: u32, // DLT type of actual packet (e.g., DLT_EN10MB)
+ pub pth_ifname: [u8; 24], // Interface name (null-terminated)
+ pub pth_flags: u32, // Flags
+ pub pth_protocol_family: u16, // Protocol family (e.g., PF_INET)
+ pub pth_frame_pre_length: u16, // Frame prefix length
+ pub pth_frame_post_length: u16, // Frame postfix length
+ pub pth_iftype: u16, // Interface type
+ pub pth_unit: u16, // Interface unit
+ pub pth_epid: u32, // Effective process ID
+ pub pth_comm: [u8; 20], // Command name (process name)
+ pub pth_svc_class: u32, // Service class
+ pub pth_flowid: u32, // Flow ID
+ pub pth_ipproto: u32, // IP protocol (e.g., IPPROTO_TCP)
+ pub pth_pid: u32, // Process ID
+ pub pth_e_comm: [u8; 20], // Effective command name
+ // Note: There may be additional fields after this
+}
+
+impl PktapHeader {
+ /// Parse PKTAP header from raw packet data
+ pub fn from_bytes(data: &[u8]) -> Option {
+ // Check minimum size
+ if data.len() < mem::size_of::() {
+ debug!("Packet too small for PKTAP header: {} bytes", data.len());
+ return None;
+ }
+
+ // Parse the header as little-endian. PKTAP is macOS-only (Apple's DLT_PKTAP),
+ // and all macOS platforms (x86_64 and ARM64) are little-endian.
+ let length = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
+
+ // Sanity check the length field
+ if length < 108 || length as usize > data.len() {
+ debug!(
+ "Invalid PKTAP header length: {} (packet size: {})",
+ length,
+ data.len()
+ );
+ return None;
+ }
+
+ // SAFETY: We verified data.len() >= size_of::() above.
+ // Using read_unaligned because data (&[u8]) may not satisfy PktapHeader's
+ // alignment requirement from its u32 fields. PKTAP is macOS-only and the
+ // wire format is little-endian, so reading the struct natively on a
+ // little-endian host already yields the correct field values — no
+ // explicit byte-swap is required.
+ let header = unsafe { std::ptr::read_unaligned(data.as_ptr() as *const PktapHeader) };
+
+ Some(header)
+ }
+
+ /// Extract process information from the header using the correct offsets
+ /// Based on our successful test: process name at offset 56, PID at offset 52
+ pub fn get_process_info(&self) -> (Option, Option) {
+ // Extract process name from pth_comm (offset 56, length 20)
+ let process_name = extract_process_name_from_bytes(&self.pth_comm);
+
+ // Extract PID - use pth_epid which is at the right offset (52)
+ // Based on our test, the PID was consistently at offset 52.
+ // PKTAP is macOS-only and Darwin PIDs are at most five digits (the
+ // kernel wraps PIDs at 100000), so a valid PID is 1..=99999. The old
+ // `< 65535` ceiling was a u16 assumption that dropped legitimate high
+ // PIDs on busy systems, leaving those packets with no process
+ // attribution.
+ let pid = if self.pth_epid != 0 && self.pth_epid <= DARWIN_PID_MAX {
+ Some(self.pth_epid)
+ } else if self.pth_pid != 0 && self.pth_pid <= DARWIN_PID_MAX {
+ Some(self.pth_pid)
+ } else {
+ None
+ };
+
+ // Also try effective command name if regular one is empty
+ let final_process_name = if process_name.is_none() {
+ extract_process_name_from_bytes(&self.pth_e_comm)
+ } else {
+ process_name
+ };
+
+ debug!(
+ "PKTAP process info: name={:?}, pid={:?}",
+ final_process_name, pid
+ );
+ (final_process_name, pid)
+ }
+
+ /// Get the interface name
+ pub fn get_interface(&self) -> String {
+ std::str::from_utf8(&self.pth_ifname)
+ .unwrap_or("")
+ .trim_end_matches('\0')
+ .trim()
+ .to_string()
+ }
+
+ /// Get the offset where the actual packet data starts
+ pub fn payload_offset(&self) -> usize {
+ self.pth_length as usize
+ }
+
+ /// Get the DLT type of the inner packet
+ pub fn inner_dlt(&self) -> u32 {
+ self.pth_dlt
+ }
+
+ /// Check if this PKTAP header looks valid
+ pub fn is_valid(&self) -> bool {
+ // Basic sanity checks
+ self.pth_length >= 108 &&
+ self.pth_length <= 4096 && // Reasonable upper bound
+ self.pth_dlt > 0 &&
+ self.pth_dlt < 1000 // Reasonable DLT range
+ }
+}
+
+/// Extract and normalize process name from raw PKTAP bytes
+/// Handles all types of padding: null bytes, spaces, tabs, and other whitespace
+fn extract_process_name_from_bytes(bytes: &[u8; 20]) -> Option {
+ // First, find the actual string content
+ let mut end_pos = bytes.len();
+ for (i, &byte) in bytes.iter().enumerate() {
+ if byte == 0 {
+ end_pos = i;
+ break;
+ }
+ }
+
+ // Convert bytes to string, handling invalid UTF-8
+ let raw_str = std::str::from_utf8(&bytes[..end_pos]).ok()?;
+
+ // Apply aggressive normalization
+ let normalized = raw_str
+ .chars()
+ .map(|c| {
+ if c.is_whitespace() || c.is_control() {
+ ' ' // Convert whitespace and control characters to space
+ } else {
+ c
+ }
+ })
+ .collect::()
+ .split_whitespace() // Split on any whitespace
+ .collect::>()
+ .join(" "); // Join with single spaces
+
+ if normalized.is_empty() || !normalized.chars().all(|c| c.is_ascii_graphic() || c == ' ') {
+ debug!(
+ "🚫 Rejected PKTAP process name: raw='{:?}', normalized='{}'",
+ raw_str, normalized
+ );
+ None
+ } else {
+ debug!(
+ "✅ Extracted PKTAP process name: raw='{:?}' -> normalized='{}'",
+ raw_str, normalized
+ );
+ Some(normalized)
+ }
+}
+
+/// Check if the given linktype represents PKTAP data
+pub fn is_pktap_linktype(linktype: i32) -> bool {
+ match linktype {
+ 149 => true, // DLT_USER2 (Apple's PKTAP on Darwin)
+ 258 => true, // DLT_PKTAP (standard)
+ _ => false,
+ }
+}
+
+/// Try to extract PKTAP metadata and payload from a packet
+pub fn parse_pktap_packet(data: &[u8]) -> Option<(PktapHeader, &[u8])> {
+ let header = PktapHeader::from_bytes(data)?;
+
+ if !header.is_valid() {
+ warn!("Invalid PKTAP header detected");
+ return None;
+ }
+
+ let payload_offset = header.payload_offset();
+ if data.len() <= payload_offset {
+ warn!(
+ "PKTAP header claims payload at offset {} but packet is only {} bytes",
+ payload_offset,
+ data.len()
+ );
+ return None;
+ }
+
+ let payload = &data[payload_offset..];
+ debug!(
+ "PKTAP: header_len={}, inner_dlt={}, payload_len={}",
+ header.pth_length,
+ header.pth_dlt,
+ payload.len()
+ );
+
+ Some((header, payload))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_pktap_linktype_detection() {
+ assert!(is_pktap_linktype(149)); // DLT_USER2
+ assert!(is_pktap_linktype(258)); // DLT_PKTAP
+ assert!(!is_pktap_linktype(1)); // DLT_EN10MB
+ assert!(!is_pktap_linktype(12)); // DLT_RAW
+ }
+
+ #[test]
+ fn test_pktap_header_size() {
+ // Ensure our struct size matches expectations
+ assert!(mem::size_of::() >= 108);
+ }
+
+ #[test]
+ fn test_invalid_pktap_data() {
+ // Too small
+ let small_data = [0u8; 50];
+ assert!(PktapHeader::from_bytes(&small_data).is_none());
+
+ // Invalid length field
+ let mut bad_data = [0u8; 200];
+ bad_data[0] = 50; // Length too small
+ assert!(PktapHeader::from_bytes(&bad_data).is_none());
+ }
+
+ fn header_with_pids(epid: u32, pid: u32) -> PktapHeader {
+ PktapHeader {
+ pth_length: 108,
+ pth_type_next: 0,
+ pth_dlt: 0,
+ pth_ifname: [0u8; 24],
+ pth_flags: 0,
+ pth_protocol_family: 0,
+ pth_frame_pre_length: 0,
+ pth_frame_post_length: 0,
+ pth_iftype: 0,
+ pth_unit: 0,
+ pth_epid: epid,
+ pth_comm: [0u8; 20],
+ pth_svc_class: 0,
+ pth_flowid: 0,
+ pth_ipproto: 0,
+ pth_pid: pid,
+ pth_e_comm: [0u8; 20],
+ }
+ }
+
+ #[test]
+ fn test_get_process_info_accepts_high_pid() {
+ // PIDs above the old 65535 (u16) ceiling are valid on macOS (Darwin
+ // wraps PIDs at 100000); they must still be attributed, not dropped.
+ assert_eq!(
+ header_with_pids(70_000, 0).get_process_info().1,
+ Some(70_000)
+ );
+
+ // The pth_pid fallback path also honours a high PID when pth_epid is unset.
+ assert_eq!(
+ header_with_pids(0, 99_999).get_process_info().1,
+ Some(99_999)
+ );
+
+ // The zero sentinel and out-of-range garbage still yield no PID.
+ assert_eq!(header_with_pids(0, 0).get_process_info().1, None);
+ assert_eq!(header_with_pids(100_000, 0).get_process_info().1, None);
+ }
+}
diff --git a/crates/rustnet-core/src/network/link_layer/raw_ip.rs b/crates/rustnet-core/src/network/link_layer/raw_ip.rs
new file mode 100644
index 0000000..65a182b
--- /dev/null
+++ b/crates/rustnet-core/src/network/link_layer/raw_ip.rs
@@ -0,0 +1,117 @@
+//! Raw IP packet parsing (no link-layer header)
+//!
+//! Handles DLT_RAW (12), DLT_NULL (0), LINKTYPE_RAW (101),
+//! LINKTYPE_IPV4 (228), and LINKTYPE_IPV6 (229)
+//!
+//! Used by TUN devices which operate at Layer 3 (network layer)
+
+use crate::network::parser::{PacketParser, ParsedPacket};
+
+/// Parse a raw IP packet (auto-detect IPv4 or IPv6)
+///
+/// TUN devices typically send raw IP packets without any link-layer header.
+/// The first nibble of the packet indicates the IP version.
+pub fn parse(
+ data: &[u8],
+ parser: &PacketParser,
+ process_name: Option,
+ process_id: Option,
+) -> Option {
+ if data.is_empty() {
+ log::debug!("Raw IP: Empty packet");
+ return None;
+ }
+
+ // Check IP version from first nibble
+ let version = data[0] >> 4;
+ match version {
+ 4 => {
+ log::trace!("Raw IP: IPv4 packet detected");
+ parser.parse_raw_ipv4_packet(data, process_name, process_id)
+ }
+ 6 => {
+ log::trace!("Raw IP: IPv6 packet detected");
+ parser.parse_raw_ipv6_packet(data, process_name, process_id)
+ }
+ _ => {
+ log::debug!("Raw IP: Unknown IP version: {}", version);
+ None
+ }
+ }
+}
+
+/// Parse a raw IPv4 packet only
+///
+/// Used when the link type explicitly indicates IPv4 (LINKTYPE_IPV4 = 228)
+pub fn parse_ipv4(
+ data: &[u8],
+ parser: &PacketParser,
+ process_name: Option,
+ process_id: Option,
+) -> Option {
+ if data.is_empty() || data.len() < 20 {
+ log::debug!("Raw IPv4: Packet too small");
+ return None;
+ }
+
+ let version = data[0] >> 4;
+ if version != 4 {
+ log::warn!("Raw IPv4: Expected IPv4 but got version {}", version);
+ return None;
+ }
+
+ log::trace!("Raw IPv4: Parsing IPv4 packet");
+ parser.parse_raw_ipv4_packet(data, process_name, process_id)
+}
+
+/// Parse a raw IPv6 packet only
+///
+/// Used when the link type explicitly indicates IPv6 (LINKTYPE_IPV6 = 229)
+pub fn parse_ipv6(
+ data: &[u8],
+ parser: &PacketParser,
+ process_name: Option,
+ process_id: Option,
+) -> Option {
+ if data.is_empty() || data.len() < 40 {
+ log::debug!("Raw IPv6: Packet too small");
+ return None;
+ }
+
+ let version = data[0] >> 4;
+ if version != 6 {
+ log::warn!("Raw IPv6: Expected IPv6 but got version {}", version);
+ return None;
+ }
+
+ log::trace!("Raw IPv6: Parsing IPv6 packet");
+ parser.parse_raw_ipv6_packet(data, process_name, process_id)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_raw_ip_empty_packet() {
+ let empty = vec![];
+ let parser = PacketParser::new();
+ assert!(parse(&empty, &parser, None, None).is_none());
+ }
+
+ #[test]
+ fn test_raw_ipv4_version_check() {
+ // Create a minimal IPv6 packet but try to parse as IPv4
+ let ipv6_packet = vec![0x60, 0x00, 0x00, 0x00]; // Version 6
+ let parser = PacketParser::new();
+ assert!(parse_ipv4(&ipv6_packet, &parser, None, None).is_none());
+ }
+
+ #[test]
+ fn test_raw_ipv6_version_check() {
+ // Create a minimal IPv4 packet but try to parse as IPv6
+ let ipv4_packet = vec![0x45, 0x00, 0x00, 0x00]; // Version 4
+ let parser = PacketParser::new();
+ assert!(parse_ipv6(&ipv4_packet, &parser, None, None).is_none());
+ }
+}
diff --git a/crates/rustnet-core/src/network/link_layer/tun_tap.rs b/crates/rustnet-core/src/network/link_layer/tun_tap.rs
new file mode 100644
index 0000000..881a53e
--- /dev/null
+++ b/crates/rustnet-core/src/network/link_layer/tun_tap.rs
@@ -0,0 +1,221 @@
+//! TUN/TAP interface support
+//!
+//! TUN (Layer 3) and TAP (Layer 2) virtual network interfaces
+//!
+//! - TUN: Operates at IP layer, carries raw IP packets (DLT_RAW or DLT_NULL)
+//! - TAP: Operates at Ethernet layer, carries Ethernet frames (DLT_EN10MB)
+
+use crate::network::link_layer::{ethernet, raw_ip};
+use crate::network::parser::{PacketParser, ParsedPacket};
+
+/// Detect if an interface name is a TUN interface
+///
+/// TUN interface naming conventions:
+/// - Linux: `tun0`, `tun1`, etc.
+/// - macOS: `utun0`, `utun1`, etc.
+/// - BSD: `tun0`, `tun1`, etc.
+pub fn is_tun_interface(name: &str) -> bool {
+ name.starts_with("tun") || name.starts_with("utun")
+}
+
+/// Detect if an interface name is a TAP interface
+///
+/// TAP interface naming conventions:
+/// - Linux: `tap0`, `tap1`, etc.
+/// - macOS: `tap0`, `tap1`, etc. (requires third-party drivers)
+/// - BSD: `tap0`, `tap1`, etc.
+pub fn is_tap_interface(name: &str) -> bool {
+ name.starts_with("tap")
+}
+
+/// Detect if an interface name is any tunnel interface (TUN or TAP)
+pub fn is_tunnel_interface(name: &str) -> bool {
+ is_tun_interface(name) || is_tap_interface(name)
+}
+
+/// Parse a TUN packet (raw IP, no link-layer header)
+///
+/// TUN devices operate at the network layer (Layer 3) and carry raw IP packets.
+/// This is a convenience wrapper around raw_ip::parse()
+pub fn parse_tun(
+ data: &[u8],
+ parser: &PacketParser,
+ process_name: Option,
+ process_id: Option,
+) -> Option {
+ log::trace!("TUN: Parsing raw IP packet ({} bytes)", data.len());
+ raw_ip::parse(data, parser, process_name, process_id)
+}
+
+/// Parse a TAP packet (Ethernet frame with full header)
+///
+/// TAP devices operate at the data-link layer (Layer 2) and carry Ethernet frames.
+/// This is a convenience wrapper around ethernet::parse()
+pub fn parse_tap(
+ data: &[u8],
+ parser: &PacketParser,
+ process_name: Option,
+ process_id: Option,
+) -> Option {
+ log::trace!("TAP: Parsing Ethernet frame ({} bytes)", data.len());
+ ethernet::parse(data, parser, process_name, process_id)
+}
+
+/// Determine the appropriate parser based on DLT type
+///
+/// This is the main entry point for parsing TUN/TAP packets when you know the DLT type.
+pub fn parse_by_dlt(
+ data: &[u8],
+ dlt: i32,
+ parser: &PacketParser,
+ process_name: Option,
+ process_id: Option,
+) -> Option {
+ match dlt {
+ // DLT_NULL - BSD/macOS loopback: a 4-byte address-family header (host
+ // byte order) precedes the raw IP packet, so strip it before parsing.
+ // The inner IP version nibble selects v4/v6, so the family value itself
+ // need not be decoded (its endianness varies by platform). Without this
+ // skip, raw IP parsing reads the family header's first byte as the IP
+ // version and silently drops every loopback packet.
+ 0 => {
+ log::trace!("TUN/TAP: DLT_NULL (0)");
+ if data.len() < 4 {
+ return None;
+ }
+ parse_tun(&data[4..], parser, process_name, process_id)
+ }
+ // TAP devices use Ethernet (Layer 2)
+ 1 => {
+ log::trace!("TUN/TAP: DLT_EN10MB (1) - TAP");
+ parse_tap(data, parser, process_name, process_id)
+ }
+ // DLT_RAW - Raw IP packets (no link layer)
+ 12 | 101 => {
+ log::trace!("TUN/TAP: DLT_RAW ({}) - TUN", dlt);
+ parse_tun(data, parser, process_name, process_id)
+ }
+ // IPv4-only packets
+ 228 => {
+ log::trace!("TUN/TAP: LINKTYPE_IPV4 (228) - TUN");
+ raw_ip::parse_ipv4(data, parser, process_name, process_id)
+ }
+ // IPv6-only packets
+ 229 => {
+ log::trace!("TUN/TAP: LINKTYPE_IPV6 (229) - TUN");
+ raw_ip::parse_ipv6(data, parser, process_name, process_id)
+ }
+ _ => {
+ log::warn!("TUN/TAP: Unsupported DLT type: {}", dlt);
+ None
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_tun_interface_detection() {
+ assert!(is_tun_interface("tun0"));
+ assert!(is_tun_interface("tun1"));
+ assert!(is_tun_interface("tun10"));
+ assert!(is_tun_interface("utun0")); // macOS
+ assert!(is_tun_interface("utun1"));
+ assert!(!is_tun_interface("eth0"));
+ assert!(!is_tun_interface("tap0"));
+ assert!(!is_tun_interface("wlan0"));
+ }
+
+ #[test]
+ fn test_tap_interface_detection() {
+ assert!(is_tap_interface("tap0"));
+ assert!(is_tap_interface("tap1"));
+ assert!(is_tap_interface("tap10"));
+ assert!(!is_tap_interface("tun0"));
+ assert!(!is_tap_interface("eth0"));
+ assert!(!is_tap_interface("wlan0"));
+ }
+
+ #[test]
+ fn test_tunnel_interface_detection() {
+ assert!(is_tunnel_interface("tun0"));
+ assert!(is_tunnel_interface("tap0"));
+ assert!(is_tunnel_interface("utun0"));
+ assert!(!is_tunnel_interface("eth0"));
+ assert!(!is_tunnel_interface("wlan0"));
+ assert!(!is_tunnel_interface("lo"));
+ }
+
+ #[test]
+ fn test_parse_by_dlt() {
+ use crate::network::parser::PacketParser;
+
+ let parser = PacketParser::new();
+
+ // Test with empty packet - should return None
+ let empty = vec![];
+ assert!(parse_by_dlt(&empty, 12, &parser, None, None).is_none());
+ assert!(parse_by_dlt(&empty, 1, &parser, None, None).is_none());
+ }
+
+ #[test]
+ fn test_parse_tun_tap() {
+ use crate::network::parser::PacketParser;
+
+ let parser = PacketParser::new();
+ let empty = vec![];
+
+ // TUN parsing should fail on empty packet
+ assert!(parse_tun(&empty, &parser, None, None).is_none());
+
+ // TAP parsing should fail on empty packet
+ assert!(parse_tap(&empty, &parser, None, None).is_none());
+ }
+
+ #[test]
+ fn test_parse_by_dlt_null_strips_loopback_family_header() {
+ use crate::network::parser::PacketParser;
+ use crate::network::types::Protocol;
+
+ let parser = PacketParser::new();
+
+ // Inner IPv4/UDP packet: 192.168.1.100:1234 -> 8.8.8.8:53 (DNS).
+ let inner_ip: &[u8] = &[
+ // IPv4 header (protocol 0x11 = UDP)
+ 0x45, 0x00, 0x00, 0x20, 0x00, 0x01, 0x00, 0x00, 0x40, 0x11, 0x00, 0x00, 192, 168, 1,
+ 100, 8, 8, 8, 8, // UDP header: src 1234, dst 53
+ 0x04, 0xd2, 0x00, 0x35, 0x00, 0x0c, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04,
+ ];
+
+ // A DLT_NULL frame is a 4-byte address-family header (AF_INET = 2, host
+ // byte order / little-endian here) followed by the raw IP packet.
+ let mut null_frame = vec![0x02, 0x00, 0x00, 0x00];
+ null_frame.extend_from_slice(inner_ip);
+
+ // The family header's first byte (0x02) has IP version nibble 0, so
+ // parsing the frame as raw IP without stripping the header drops it.
+ // This is exactly what the old DLT_NULL arm did.
+ assert!(
+ raw_ip::parse(&null_frame, &parser, None, None).is_none(),
+ "raw IP parse must fail when the 4-byte family header is not stripped",
+ );
+
+ // parse_by_dlt(DLT_NULL) must strip the header and parse the inner packet.
+ let parsed = parse_by_dlt(&null_frame, 0, &parser, None, None)
+ .expect("DLT_NULL loopback frame should parse after stripping the family header");
+ assert_eq!(parsed.protocol, Protocol::Udp);
+ // Orientation (which side is local) depends on configured local IPs, so
+ // assert on the port set rather than a fixed local/remote split.
+ let ports = [parsed.local_addr.port(), parsed.remote_addr.port()];
+ assert!(
+ ports.contains(&1234) && ports.contains(&53),
+ "inner UDP ports 1234/53 must survive the header strip, got {ports:?}",
+ );
+
+ // A truncated DLT_NULL frame (header only / shorter) must not panic.
+ assert!(parse_by_dlt(&[0x02, 0x00, 0x00, 0x00], 0, &parser, None, None).is_none());
+ assert!(parse_by_dlt(&[0x02, 0x00], 0, &parser, None, None).is_none());
+ }
+}
diff --git a/crates/rustnet-core/src/network/merge.rs b/crates/rustnet-core/src/network/merge.rs
new file mode 100644
index 0000000..137852d
--- /dev/null
+++ b/crates/rustnet-core/src/network/merge.rs
@@ -0,0 +1,1100 @@
+// src/network/merge.rs - Connection merging and update utilities
+
+use log::{debug, info, warn};
+use std::time::{Instant, SystemTime};
+
+use crate::network::dpi::{DpiResult, is_partial_sni, try_extract_tls_from_reassembler};
+use crate::network::parser::{ParsedPacket, TcpFlags};
+use crate::network::types::{
+ ApplicationProtocol, Connection, DnsInfo, DpiInfo, FtpInfo, HttpInfo, HttpsInfo, MqttInfo,
+ ProtocolState, QuicConnectionState, QuicInfo, SshInfo, TcpState,
+};
+
+/// Get the priority of a QUIC connection state for proper state progression
+/// Higher priority = more advanced state. States should only progress forward.
+/// Upper bound on DNS response IPs accumulated per connection across packets.
+/// The per-packet parser already caps extraction (see `MAX_RESPONSE_IPS_PER_PACKET`
+/// in dpi/dns.rs); this bounds the cross-packet merge accumulator so a sustained
+/// flow cannot grow it without limit.
+const MAX_MERGED_RESPONSE_IPS: usize = 64;
+
+/// Update TCP connection state based on observed flags and current state
+/// This implements the TCP state machine according to RFC 793
+fn update_tcp_state(current_state: TcpState, flags: &TcpFlags, is_outgoing: bool) -> TcpState {
+ debug!(
+ "Updating TCP state: current_state={:?}, flags={:?}, is_outgoing={}",
+ current_state, flags, is_outgoing
+ );
+
+ match (current_state, flags.syn, flags.ack, flags.fin, flags.rst) {
+ // Connection establishment - three-way handshake
+ (TcpState::Unknown, true, false, false, false) if !is_outgoing => TcpState::SynReceived,
+ (TcpState::Unknown, true, false, false, false) if is_outgoing => TcpState::SynSent,
+ (TcpState::SynSent, true, true, false, false) if !is_outgoing => TcpState::Established,
+ (TcpState::SynReceived, false, true, false, false) if is_outgoing => TcpState::Established,
+
+ // This might happen if we start parsing connections after the SYN-ACK
+ (TcpState::Unknown, false, true, false, false) => TcpState::Established,
+ (TcpState::Unknown, false, true, true, false) => TcpState::Established,
+
+ // Connection termination - normal close
+ (TcpState::Established, false, _, true, false) if is_outgoing => TcpState::FinWait1,
+ (TcpState::Established, false, _, true, false) if !is_outgoing => TcpState::CloseWait,
+ (TcpState::FinWait1, false, true, false, false) if !is_outgoing => TcpState::FinWait2,
+ (TcpState::FinWait1, false, _, true, false) if !is_outgoing => TcpState::Closing,
+ (TcpState::FinWait2, false, _, true, false) if !is_outgoing => TcpState::TimeWait,
+ (TcpState::CloseWait, false, _, true, false) if is_outgoing => TcpState::LastAck,
+ (TcpState::LastAck, false, true, false, false) if !is_outgoing => TcpState::Closed,
+ (TcpState::Closing, false, true, false, false) if !is_outgoing => TcpState::TimeWait,
+
+ // Connection reset
+ (_, _, _, _, true) => TcpState::Closed,
+
+ // Keep current state if no state transition
+ _ => current_state,
+ }
+}
+
+/// Analyze TCP segment and update analytics for retransmissions and packet quality
+/// Returns (new_retransmits, new_out_of_order, new_fast_retransmits)
+fn analyze_tcp_segment(
+ analytics: &mut crate::network::types::TcpAnalytics,
+ seq: u32,
+ ack: u32,
+ window: u16,
+ payload_len: u32,
+ is_outgoing: bool,
+ has_ack_flag: bool,
+) -> (u64, u64, u64) {
+ let mut new_retransmits = 0;
+ let mut new_out_of_order = 0;
+ let mut new_fast_retransmits = 0;
+
+ // Track window size
+ analytics.last_window_size = window;
+
+ if is_outgoing {
+ // Outbound packet - check for retransmissions
+ if payload_len > 0 {
+ // Only consider packets with payload for retransmit detection
+ if analytics.last_seq_outbound != 0 {
+ // Check if this sequence number is less than expected (retransmission)
+ let expected_seq = analytics.last_seq_outbound;
+
+ if seq < expected_seq {
+ // This is a retransmission
+ analytics.retransmit_count += 1;
+ new_retransmits += 1;
+ debug!(
+ "TCP retransmission detected: seq={}, expected={}",
+ seq, expected_seq
+ );
+ } else if seq > expected_seq {
+ // Gap in sequence numbers - might be out of order or packet loss
+ debug!(
+ "TCP sequence gap detected: seq={}, expected={}",
+ seq, expected_seq
+ );
+ } else {
+ // seq == expected_seq, normal in-order packet
+ analytics.last_seq_outbound = seq.wrapping_add(payload_len);
+ }
+ } else {
+ // First packet with data
+ analytics.last_seq_outbound = seq.wrapping_add(payload_len);
+ }
+ }
+ } else {
+ // Inbound packet - check for out-of-order and duplicate ACKs
+ if payload_len > 0 {
+ if analytics.last_seq_inbound != 0 {
+ let expected_seq = analytics.last_seq_inbound;
+
+ if seq < expected_seq {
+ // Out-of-order packet (arrived late)
+ analytics.out_of_order_count += 1;
+ new_out_of_order += 1;
+ debug!(
+ "TCP out-of-order packet: seq={}, expected={}",
+ seq, expected_seq
+ );
+ } else if seq > expected_seq {
+ // Gap - possible packet loss
+ analytics.last_seq_inbound = seq.wrapping_add(payload_len);
+ } else {
+ // Normal in-order packet
+ analytics.last_seq_inbound = seq.wrapping_add(payload_len);
+ }
+ } else {
+ // First inbound packet with data
+ analytics.last_seq_inbound = seq.wrapping_add(payload_len);
+ }
+ }
+
+ // Check for duplicate ACKs (fast retransmit indicator)
+ if has_ack_flag {
+ if analytics.last_ack_received != 0 {
+ if ack == analytics.last_ack_received {
+ // Duplicate ACK
+ analytics.duplicate_ack_count += 1;
+
+ // RFC 2581: 3 duplicate ACKs trigger fast retransmit
+ if analytics.duplicate_ack_count == 3 {
+ analytics.fast_retransmit_count += 1;
+ new_fast_retransmits += 1;
+ debug!("TCP fast retransmit triggered (3 duplicate ACKs)");
+ }
+ } else {
+ // New ACK - reset duplicate counter
+ analytics.last_ack_received = ack;
+ analytics.duplicate_ack_count = 0;
+ }
+ } else {
+ // First ACK seen
+ analytics.last_ack_received = ack;
+ }
+ }
+ }
+
+ (new_retransmits, new_out_of_order, new_fast_retransmits)
+}
+
+/// Merge a parsed packet into an existing connection, mutating it in place.
+/// Returns (new_retransmits, new_out_of_order, new_fast_retransmits).
+pub fn merge_packet_into_connection(
+ conn: &mut Connection,
+ parsed: &ParsedPacket,
+ now: SystemTime,
+) -> (u64, u64, u64) {
+ let mut tcp_events = (0, 0, 0); // (retransmits, out_of_order, fast_retransmits)
+ // Update timing
+ conn.last_activity = now;
+
+ // Update packet counts and bytes
+ if parsed.is_outgoing {
+ conn.packets_sent += 1;
+ conn.bytes_sent += parsed.packet_len as u64;
+ } else {
+ conn.packets_received += 1;
+ conn.bytes_received += parsed.packet_len as u64;
+ }
+
+ // Update protocol state (from packet flags/state)
+ if let Some(tcp_header) = parsed.tcp_header {
+ let current_tcp_state = match conn.protocol_state {
+ ProtocolState::Tcp(state) => state,
+ _ => {
+ warn!("Merging TCP packet into non-TCP connection, resetting to Unknown state");
+ TcpState::Unknown
+ }
+ };
+
+ let new_tcp_state =
+ update_tcp_state(current_tcp_state, &tcp_header.flags, parsed.is_outgoing);
+
+ if current_tcp_state != new_tcp_state {
+ debug!(
+ "TCP state transition: {:?} -> {:?}",
+ current_tcp_state, new_tcp_state
+ );
+ }
+
+ conn.protocol_state = ProtocolState::Tcp(new_tcp_state);
+
+ // Update TCP analytics for retransmission and quality metrics
+ if let Some(analytics) = conn.tcp_analytics.as_mut() {
+ // Use actual TCP payload length from header
+ // Note: SYN and FIN flags also consume 1 sequence number each, even with no payload
+ let payload_len = tcp_header.payload_len;
+ let seq_consumed = payload_len
+ + if tcp_header.flags.syn { 1 } else { 0 }
+ + if tcp_header.flags.fin { 1 } else { 0 };
+
+ tcp_events = analyze_tcp_segment(
+ analytics,
+ tcp_header.seq,
+ tcp_header.ack,
+ tcp_header.window,
+ seq_consumed,
+ parsed.is_outgoing,
+ tcp_header.flags.ack,
+ );
+ }
+ } else {
+ // If no TCP flags, keep existing state or use the one from packet
+ match (&conn.protocol_state, &parsed.protocol_state) {
+ (ProtocolState::Tcp(_), _) => {
+ // Keep existing TCP state if we have it
+ }
+ _ => {
+ // Use the state from the packet for non-TCP protocols
+ conn.protocol_state = parsed.protocol_state.clone();
+ }
+ }
+ }
+
+ // Update DPI info if available
+ if let Some(dpi_result) = &parsed.dpi_result {
+ merge_dpi_info(conn, dpi_result);
+ }
+
+ // Update PKTAP process metadata if available
+ // Once set, process info should be immutable to prevent conflicts between sources
+ if let Some(new_process_name) = &parsed.process_name {
+ match &conn.process_name {
+ None => {
+ // First time setting process name - this becomes immutable
+ conn.process_name = Some(new_process_name.clone());
+ info!(
+ "🔒 Set IMMUTABLE process name for connection {} from PKTAP: '{}' (len:{})",
+ conn.key(),
+ new_process_name,
+ new_process_name.len()
+ );
+ }
+ Some(existing_name) => {
+ // Process name is already set - it's now IMMUTABLE
+ // Log the attempt but NEVER change it
+ if existing_name != new_process_name {
+ warn!(
+ "🚫 IMMUTABILITY VIOLATION: Attempt to change process name for {} from '{}' to '{}' - REJECTED",
+ conn.key(),
+ existing_name,
+ new_process_name
+ );
+ debug!(
+ "🔒 Existing: '{}' (len:{}, bytes:{:?})",
+ existing_name,
+ existing_name.len(),
+ existing_name.as_bytes()
+ );
+ debug!(
+ "🚫 Rejected: '{}' (len:{}, bytes:{:?})",
+ new_process_name,
+ new_process_name.len(),
+ new_process_name.as_bytes()
+ );
+ } else {
+ debug!(
+ "✅ Process name confirmed unchanged for {}: '{}'",
+ conn.key(),
+ existing_name
+ );
+ }
+ // NEVER update - process name is immutable once set
+ }
+ }
+ }
+
+ if let Some(new_pid) = parsed.process_id {
+ match conn.pid {
+ None => {
+ // First time setting PID - this becomes immutable
+ conn.pid = Some(new_pid);
+ info!(
+ "🔒 Set IMMUTABLE process ID for connection {} from PKTAP: {}",
+ conn.key(),
+ new_pid
+ );
+ }
+ Some(existing_pid) if existing_pid != new_pid => {
+ warn!(
+ "🚫 IMMUTABILITY VIOLATION: Attempt to change PID for {} from {} to {} - REJECTED",
+ conn.key(),
+ existing_pid,
+ new_pid
+ );
+ // NEVER update - PID is immutable once set
+ }
+ Some(existing_pid) => {
+ debug!(
+ "✅ Process ID confirmed unchanged for {}: {}",
+ conn.key(),
+ existing_pid
+ );
+ }
+ }
+ }
+
+ // Update rate calculations
+ update_connection_rates(conn);
+
+ tcp_events
+}
+
+/// Create a new connection from a parsed packet
+pub fn create_connection_from_packet(parsed: &ParsedPacket, now: SystemTime) -> Connection {
+ let mut conn = Connection::new(
+ parsed.protocol,
+ parsed.local_addr,
+ parsed.remote_addr,
+ parsed.protocol_state.clone(),
+ );
+
+ // Set initial TCP state based on flags if TCP
+ if let Some(tcp_header) = parsed.tcp_header {
+ let tcp_state = update_tcp_state(TcpState::Unknown, &tcp_header.flags, parsed.is_outgoing);
+ conn.protocol_state = ProtocolState::Tcp(tcp_state);
+
+ // Set connection direction only if we observed the TCP handshake
+ // SynSent = we initiated (outgoing), SynReceived = they initiated (incoming)
+ // Also detect from SYN+ACK: receiving SYN+ACK means we initiated (outgoing)
+ conn.connection_direction = match tcp_state {
+ TcpState::SynSent => Some(true), // outgoing - we sent SYN
+ TcpState::SynReceived => Some(false), // incoming - we received SYN
+ _ => {
+ // Check if first packet is SYN+ACK - can also determine direction
+ if tcp_header.flags.syn && tcp_header.flags.ack {
+ // SYN+ACK received = we initiated (outgoing)
+ // SYN+ACK sent = they initiated (incoming)
+ Some(!parsed.is_outgoing)
+ } else {
+ None // mid-stream capture, direction unknown
+ }
+ }
+ };
+
+ debug!(
+ "Created new {} connection: {:?} -> {:?}, state: {:?}, direction: {:?}",
+ parsed.protocol,
+ parsed.local_addr,
+ parsed.remote_addr,
+ conn.protocol_state,
+ conn.connection_direction
+ );
+ } else {
+ // For non-TCP protocols, use the provided state directly
+ // Connection direction is not determinable for stateless protocols
+ conn.protocol_state = parsed.protocol_state.clone();
+ }
+
+ // Set initial stats based on packet direction
+ if parsed.is_outgoing {
+ conn.packets_sent = 1;
+ conn.bytes_sent = parsed.packet_len as u64;
+ conn.packets_received = 0;
+ conn.bytes_received = 0;
+ } else {
+ conn.packets_sent = 0;
+ conn.bytes_sent = 0;
+ conn.packets_received = 1;
+ conn.bytes_received = parsed.packet_len as u64;
+ }
+
+ // Apply DPI results if any
+ if let Some(dpi_result) = &parsed.dpi_result {
+ conn.dpi_info = Some(DpiInfo {
+ application: dpi_result.application.clone(),
+ last_update_time: Instant::now(),
+ });
+
+ debug!(
+ "New connection with DPI: {} - {}",
+ conn.key(),
+ dpi_result.application
+ );
+ }
+
+ // Apply PKTAP process metadata if available
+ if let Some(process_name) = &parsed.process_name {
+ conn.process_name = Some(process_name.clone());
+ debug!(
+ "✓ New connection {} with process name: {}",
+ conn.key(),
+ process_name
+ );
+ }
+ if let Some(process_id) = parsed.process_id {
+ conn.pid = Some(process_id);
+ debug!(
+ "✓ New connection {} with process ID: {}",
+ conn.key(),
+ process_id
+ );
+ }
+
+ conn.created_at = now;
+ conn.last_activity = now;
+
+ // Initialize the rate tracker with the initial byte counts
+ // This prevents incorrect delta calculation on the first update
+ conn.rate_tracker
+ .initialize_with_counts(conn.bytes_sent, conn.bytes_received);
+
+ conn
+}
+
+/// Merge DPI information into an existing connection
+fn merge_dpi_info(conn: &mut Connection, dpi_result: &DpiResult) {
+ match &mut conn.dpi_info {
+ None => {
+ // No existing DPI info, use the new one
+ conn.dpi_info = Some(DpiInfo {
+ application: dpi_result.application.clone(),
+ last_update_time: Instant::now(),
+ });
+
+ debug!(
+ "Added DPI info to connection: {} - {}",
+ conn.key(),
+ dpi_result.application
+ );
+ }
+ Some(dpi_info) => {
+ // Update the last update time
+ dpi_info.last_update_time = Instant::now();
+
+ // Match on both the existing and new application protocols
+ match (&mut dpi_info.application, &dpi_result.application) {
+ // HTTP merging
+ (ApplicationProtocol::Http(old_info), ApplicationProtocol::Http(new_info)) => {
+ merge_http_info(old_info, new_info);
+ }
+
+ // HTTPS/TLS merging
+ (ApplicationProtocol::Https(old_info), ApplicationProtocol::Https(new_info)) => {
+ merge_https_info(old_info, new_info);
+ }
+
+ // QUIC merging - this is where the reassembly happens
+ (ApplicationProtocol::Quic(old_info), ApplicationProtocol::Quic(new_info)) => {
+ merge_quic_info(old_info.as_mut(), new_info.as_ref());
+ }
+
+ // DNS merging
+ (ApplicationProtocol::Dns(old_info), ApplicationProtocol::Dns(new_info)) => {
+ merge_dns_info(old_info, new_info);
+ }
+
+ // SSH - merge SSH info
+ (ApplicationProtocol::Ssh(old_info), ApplicationProtocol::Ssh(new_info)) => {
+ merge_ssh_info(old_info, new_info);
+ }
+
+ // BitTorrent - merge peer info
+ (
+ ApplicationProtocol::BitTorrent(old_info),
+ ApplicationProtocol::BitTorrent(new_info),
+ ) => {
+ if old_info.client.is_none() {
+ old_info.client.clone_from(&new_info.client);
+ }
+ if old_info.info_hash.is_none() {
+ old_info.info_hash.clone_from(&new_info.info_hash);
+ }
+ }
+
+ // MQTT - merge client_id and topic from subsequent packets
+ (ApplicationProtocol::Mqtt(old_info), ApplicationProtocol::Mqtt(new_info)) => {
+ merge_mqtt_info(old_info, new_info);
+ }
+
+ // FTP - dialog state evolves across requests/responses
+ (ApplicationProtocol::Ftp(old_info), ApplicationProtocol::Ftp(new_info)) => {
+ merge_ftp_info(old_info, new_info);
+ }
+
+ _ => {
+ // Keep existing protocol
+ }
+ }
+ }
+ }
+}
+
+/// Merge HTTP information
+fn merge_http_info(old_info: &mut HttpInfo, new_info: &HttpInfo) {
+ // Update method if not set
+ if old_info.method.is_none() && new_info.method.is_some() {
+ old_info.method = new_info.method.clone();
+ }
+
+ // Update path if not set
+ if old_info.path.is_none() && new_info.path.is_some() {
+ old_info.path = new_info.path.clone();
+ }
+
+ // Update host if not set
+ if old_info.host.is_none() && new_info.host.is_some() {
+ old_info.host = new_info.host.clone();
+ }
+
+ // Update user agent if not set
+ if old_info.user_agent.is_none() && new_info.user_agent.is_some() {
+ old_info.user_agent = new_info.user_agent.clone();
+ }
+
+ // Update status code if not set
+ if old_info.status_code.is_none() && new_info.status_code.is_some() {
+ old_info.status_code = new_info.status_code;
+ }
+}
+
+/// Merge HTTPS/TLS information
+fn merge_https_info(old_info: &mut HttpsInfo, new_info: &HttpsInfo) {
+ // Update version if not set or if new is more specific
+ if old_info.tls_info.is_none() && new_info.tls_info.is_some() {
+ old_info.tls_info = new_info.tls_info.clone();
+ } else if let (Some(old_tls), Some(new_tls)) = (&mut old_info.tls_info, &new_info.tls_info) {
+ // Merge TLS info - prefer more complete info
+ if old_tls.version.is_none() && new_tls.version.is_some() {
+ old_tls.version = new_tls.version;
+ }
+ if old_tls.sni.is_none() && new_tls.sni.is_some() {
+ old_tls.sni = new_tls.sni.clone();
+ }
+ if old_tls.alpn.is_empty() && !new_tls.alpn.is_empty() {
+ old_tls.alpn = new_tls.alpn.clone();
+ }
+ if old_tls.cipher_suite.is_none() && new_tls.cipher_suite.is_some() {
+ old_tls.cipher_suite = new_tls.cipher_suite;
+ }
+ }
+}
+
+/// Merge QUIC information with reassembly support
+fn merge_quic_info(old_info: &mut QuicInfo, new_info: &QuicInfo) {
+ // Update connection state only if it progresses forward
+ // State progression: Unknown -> Initial -> Handshaking -> Connected -> Draining -> Closed
+ let old_priority = old_info.connection_state.priority();
+ let new_priority = new_info.connection_state.priority();
+
+ if new_priority > old_priority {
+ debug!(
+ "QUIC connection state progressed: {:?} -> {:?}",
+ old_info.connection_state, new_info.connection_state
+ );
+ old_info.connection_state = new_info.connection_state;
+ }
+
+ // Update packet type
+ old_info.packet_type = new_info.packet_type;
+
+ // Update connection ID if we didn't have it
+ if old_info.connection_id.is_empty() && !new_info.connection_id.is_empty() {
+ old_info.connection_id = new_info.connection_id.clone();
+ old_info.connection_id_hex = new_info.connection_id_hex.clone();
+ }
+
+ // Update version string if we didn't have it
+ if old_info.version_string.is_none() && new_info.version_string.is_some() {
+ old_info.version_string = new_info.version_string.clone();
+ }
+
+ // Merge CRYPTO frame reassembler state - this is crucial for proper SNI extraction
+ // The reassembler must persist across multiple packets to handle fragmented TLS handshakes
+ if let Some(new_reassembler) = &new_info.crypto_reassembler {
+ if old_info.crypto_reassembler.is_none() {
+ // First time seeing crypto frames, initialize the connection-level reassembler
+ old_info.crypto_reassembler = Some(new_reassembler.clone());
+ debug!(
+ "QUIC: Initialized crypto reassembler for connection with Connection ID: {:?}",
+ old_info.connection_id_hex
+ );
+ } else if let Some(old_reassembler) = &mut old_info.crypto_reassembler {
+ // Merge fragments from new reassembler into connection-level reassembler
+ // This handles out-of-order CRYPTO frames across packets
+ for (&offset, data) in new_reassembler.get_fragments() {
+ match old_reassembler.add_fragment(offset, data.clone()) {
+ Ok(_) => {
+ debug!(
+ "QUIC: Merged CRYPTO fragment at offset {} for connection {}",
+ offset,
+ old_info.connection_id_hex.as_deref().unwrap_or("unknown")
+ );
+ }
+ Err(e) => {
+ warn!("QUIC: Failed to merge CRYPTO fragment: {}", e);
+ }
+ }
+ }
+
+ // If current SNI is partial or missing, try re-extracting from merged reassembler
+ let should_retry = match &old_info.tls_info {
+ None => true,
+ Some(tls) => {
+ tls.sni.is_none() || tls.sni.as_ref().is_some_and(|s| is_partial_sni(s))
+ }
+ };
+
+ if should_retry {
+ debug!(
+ "QUIC: SNI is partial or missing, attempting re-extraction from merged fragments"
+ );
+ // First try without partial extraction to get complete SNI
+ if let Some(new_tls) = try_extract_tls_from_reassembler(old_reassembler, false) {
+ debug!(
+ "QUIC: Re-extraction succeeded with complete SNI: {:?}",
+ new_tls.sni
+ );
+ old_info.tls_info = Some(new_tls);
+ } else {
+ // If complete extraction failed, allow partial as fallback
+ if let Some(new_tls) = try_extract_tls_from_reassembler(old_reassembler, true) {
+ debug!(
+ "QUIC: Re-extraction returned partial SNI as fallback: {:?}",
+ new_tls.sni
+ );
+ old_info.tls_info = Some(new_tls);
+ }
+ }
+ }
+
+ // Update cached TLS info if new reassembler has it and it's better
+ if let Some(tls_info) = new_reassembler.get_cached_tls_info() {
+ let new_is_complete = tls_info.sni.as_ref().is_some_and(|s| !is_partial_sni(s));
+ let should_update = match &old_info.tls_info {
+ None => true,
+ Some(old_tls) => {
+ let old_is_partial =
+ old_tls.sni.as_ref().is_some_and(|s| is_partial_sni(s));
+ old_tls.sni.is_none() || (old_is_partial && new_is_complete)
+ }
+ };
+ if should_update {
+ old_info.tls_info = Some(tls_info.clone());
+ debug!(
+ "QUIC: Updated TLS info from reassembler - SNI: {:?}, ALPN: {:?}",
+ tls_info.sni, tls_info.alpn
+ );
+ }
+ }
+ }
+ }
+
+ // Update TLS info if new packet has better info
+ match (&old_info.tls_info, &new_info.tls_info) {
+ (None, Some(new_tls)) => {
+ old_info.tls_info = Some(new_tls.clone());
+ debug!("QUIC: Added TLS info - SNI: {:?}", new_tls.sni);
+ }
+ (Some(old_tls), Some(new_tls)) => {
+ // Merge TLS info - prefer more complete info
+ let mut updated = false;
+ let mut merged_tls = old_tls.clone();
+
+ // Prefer complete SNI over partial, or any SNI over none
+ let old_sni_is_partial = old_tls.sni.as_ref().is_some_and(|s| is_partial_sni(s));
+ let new_sni_is_partial = new_tls.sni.as_ref().is_some_and(|s| is_partial_sni(s));
+
+ if old_tls.sni.is_none() && new_tls.sni.is_some() {
+ merged_tls.sni = new_tls.sni.clone();
+ updated = true;
+ } else if old_sni_is_partial && new_tls.sni.is_some() && !new_sni_is_partial {
+ // Replace partial with complete
+ debug!(
+ "QUIC: Replacing partial SNI {:?} with complete SNI {:?}",
+ old_tls.sni, new_tls.sni
+ );
+ merged_tls.sni = new_tls.sni.clone();
+ updated = true;
+ }
+
+ if old_tls.alpn.is_empty() && !new_tls.alpn.is_empty() {
+ merged_tls.alpn = new_tls.alpn.clone();
+ updated = true;
+ }
+
+ if old_tls.version.is_none() && new_tls.version.is_some() {
+ merged_tls.version = new_tls.version;
+ updated = true;
+ }
+
+ if old_tls.cipher_suite.is_none() && new_tls.cipher_suite.is_some() {
+ merged_tls.cipher_suite = new_tls.cipher_suite;
+ updated = true;
+ }
+
+ if updated {
+ old_info.tls_info = Some(merged_tls);
+ debug!("QUIC: Merged TLS info");
+ }
+ }
+ _ => {}
+ }
+
+ // Update has_crypto_frame flag
+ if new_info.has_crypto_frame {
+ old_info.has_crypto_frame = true;
+ }
+
+ // Handle CONNECTION_CLOSE frame detection
+ if let Some(new_close) = &new_info.connection_close {
+ // CONNECTION_CLOSE is final - always update
+ old_info.connection_close = Some(new_close.clone());
+
+ // Update connection state based on close frame
+ old_info.connection_state = match new_close.frame_type {
+ 0x1c if new_close.error_code == 0 => {
+ // NO_ERROR transport close - enter draining state
+ debug!("QUIC: Connection entering draining state (NO_ERROR transport close)");
+ QuicConnectionState::Draining
+ }
+ 0x1c => {
+ // Transport error - connection is closed
+ debug!(
+ "QUIC: Connection closed due to transport error: {}",
+ new_close.error_code
+ );
+ QuicConnectionState::Closed
+ }
+ 0x1d => {
+ // Application close - connection is closed
+ debug!(
+ "QUIC: Connection closed by application: {}",
+ new_close.error_code
+ );
+ QuicConnectionState::Closed
+ }
+ _ => {
+ // Unknown close type - assume closed
+ debug!(
+ "QUIC: Connection closed (unknown frame type: 0x{:02x})",
+ new_close.frame_type
+ );
+ QuicConnectionState::Closed
+ }
+ };
+
+ debug!(
+ "QUIC: Updated connection state to {:?} due to CONNECTION_CLOSE frame",
+ old_info.connection_state
+ );
+ }
+
+ // Update idle timeout if provided
+ if new_info.idle_timeout.is_some() {
+ old_info.idle_timeout = new_info.idle_timeout;
+ }
+}
+
+/// Merge DNS information
+fn merge_dns_info(old_info: &mut DnsInfo, new_info: &DnsInfo) {
+ // Update query name if not set
+ if old_info.query_name.is_none() && new_info.query_name.is_some() {
+ old_info.query_name = new_info.query_name.clone();
+ }
+
+ // Update query type if not set
+ if old_info.query_type.is_none() && new_info.query_type.is_some() {
+ old_info.query_type = new_info.query_type;
+ }
+
+ // Merge response IPs (keep unique). Cap the accumulator: a long-lived
+ // DNS-shaped UDP flow (the idle timeout is refreshed on every packet) would
+ // otherwise grow this Vec without bound, and the `contains` dedup is a linear
+ // scan, so an attacker feeding a steady stream of distinct A/AAAA answers
+ // drives O(n^2) CPU and unbounded memory on the processing pipeline. The UI
+ // only renders a short list and the cap is far above any real resolver answer.
+ for ip in &new_info.response_ips {
+ if old_info.response_ips.len() >= MAX_MERGED_RESPONSE_IPS {
+ break;
+ }
+ if !old_info.response_ips.contains(ip) {
+ old_info.response_ips.push(*ip);
+ }
+ }
+
+ // Update response flag
+ if new_info.is_response {
+ old_info.is_response = true;
+ }
+}
+
+/// Merge SSH information
+fn merge_ssh_info(old_info: &mut SshInfo, new_info: &SshInfo) {
+ // Update version if not set
+ if old_info.version.is_none() && new_info.version.is_some() {
+ old_info.version = new_info.version.clone();
+ }
+
+ // Update client software if not set
+ if old_info.client_software.is_none() && new_info.client_software.is_some() {
+ old_info.client_software = new_info.client_software.clone();
+ }
+
+ // Update server software if not set
+ if old_info.server_software.is_none() && new_info.server_software.is_some() {
+ old_info.server_software = new_info.server_software.clone();
+ }
+
+ // Update connection state to the more advanced state
+ use crate::network::types::SshConnectionState;
+ match (&old_info.connection_state, &new_info.connection_state) {
+ (SshConnectionState::Banner, _) => {
+ old_info.connection_state = new_info.connection_state.clone()
+ }
+ (SshConnectionState::KeyExchange, SshConnectionState::Authentication) => {
+ old_info.connection_state = new_info.connection_state.clone()
+ }
+ (SshConnectionState::KeyExchange, SshConnectionState::Established) => {
+ old_info.connection_state = new_info.connection_state.clone()
+ }
+ (SshConnectionState::Authentication, SshConnectionState::Established) => {
+ old_info.connection_state = new_info.connection_state.clone()
+ }
+ _ => {} // Keep existing state if it's more advanced
+ }
+
+ // Merge algorithms - prioritize final negotiated algorithms over initial offers
+ match (&old_info.connection_state, &new_info.connection_state) {
+ // If we're moving to Established state and new info has algorithms, use those (final negotiated)
+ (_, SshConnectionState::Established) if !new_info.algorithms.is_empty() => {
+ old_info.algorithms = new_info.algorithms.clone();
+ }
+ // If both are in Established state, merge unique algorithms
+ (SshConnectionState::Established, SshConnectionState::Established) => {
+ for algo in &new_info.algorithms {
+ if !old_info.algorithms.contains(algo) {
+ old_info.algorithms.push(algo.clone());
+ }
+ }
+ }
+ // For earlier states, accumulate all seen algorithms
+ _ => {
+ for algo in &new_info.algorithms {
+ if !old_info.algorithms.contains(algo) {
+ old_info.algorithms.push(algo.clone());
+ }
+ }
+ }
+ }
+
+ // Update auth method if not set
+ if old_info.auth_method.is_none() && new_info.auth_method.is_some() {
+ old_info.auth_method = new_info.auth_method.clone();
+ }
+}
+
+/// Merge FTP information across packets in the same control connection.
+///
+/// Identity-like fields (`username`, `server_software`, `system_type`) are
+/// first-wins so the first observed value is preserved across long-lived
+/// sessions. Dialog state (`message_type`, `command`, `args`, `response_code`,
+/// `response_message`) is latest-wins so the connection-table column reflects
+/// the most recent exchange.
+fn merge_ftp_info(old_info: &mut FtpInfo, new_info: &FtpInfo) {
+ if old_info.username.is_none() && new_info.username.is_some() {
+ old_info.username.clone_from(&new_info.username);
+ }
+ if old_info.server_software.is_none() && new_info.server_software.is_some() {
+ old_info
+ .server_software
+ .clone_from(&new_info.server_software);
+ }
+ if old_info.system_type.is_none() && new_info.system_type.is_some() {
+ old_info.system_type.clone_from(&new_info.system_type);
+ }
+ old_info.message_type = new_info.message_type;
+ if new_info.command.is_some() {
+ old_info.command.clone_from(&new_info.command);
+ }
+ if new_info.args.is_some() {
+ old_info.args.clone_from(&new_info.args);
+ }
+ if new_info.response_code.is_some() {
+ old_info.response_code = new_info.response_code;
+ }
+ if new_info.response_message.is_some() {
+ old_info
+ .response_message
+ .clone_from(&new_info.response_message);
+ }
+}
+
+/// Merge MQTT information
+fn merge_mqtt_info(old_info: &mut MqttInfo, new_info: &MqttInfo) {
+ if old_info.version.is_none() && new_info.version.is_some() {
+ old_info.version = new_info.version;
+ }
+ if old_info.client_id.is_none() && new_info.client_id.is_some() {
+ old_info.client_id.clone_from(&new_info.client_id);
+ }
+ if old_info.topic.is_none() && new_info.topic.is_some() {
+ old_info.topic.clone_from(&new_info.topic);
+ }
+ if old_info.qos.is_none() && new_info.qos.is_some() {
+ old_info.qos = new_info.qos;
+ }
+ // Always update packet_type to show the latest activity
+ old_info.packet_type = new_info.packet_type;
+}
+
+/// Update connection rate calculations using sliding window
+fn update_connection_rates(conn: &mut Connection) {
+ // Use the new rate tracker with sliding window calculation
+ conn.update_rates();
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::network::types::{Protocol, ProtocolState, TcpState};
+ use std::net::{IpAddr, Ipv4Addr, SocketAddr};
+
+ fn create_test_connection() -> Connection {
+ Connection::new(
+ Protocol::Tcp,
+ SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 100)), 12345),
+ SocketAddr::new(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)), 80),
+ ProtocolState::Tcp(TcpState::Established),
+ )
+ }
+
+ fn create_test_packet(is_outgoing: bool, fin: bool) -> ParsedPacket {
+ use crate::network::protocol::tcp::{TcpFlags, TcpHeaderInfo};
+
+ ParsedPacket {
+ protocol: Protocol::Tcp,
+ local_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 100)), 12345),
+ remote_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)), 80),
+ protocol_state: ProtocolState::Tcp(TcpState::Unknown),
+ tcp_header: Some(TcpHeaderInfo {
+ seq: 1000,
+ ack: 2000,
+ window: 65535,
+ flags: TcpFlags {
+ syn: false,
+ ack: false,
+ fin,
+ rst: false,
+ psh: false,
+ urg: false,
+ },
+ payload_len: 60, // Simulated payload length
+ }),
+ is_outgoing,
+ packet_len: 100,
+ dpi_result: None,
+ process_name: None,
+ process_id: None,
+ }
+ }
+
+ #[test]
+ fn test_merge_dns_response_ips_is_capped() {
+ // A sustained DNS-shaped flow must not grow response_ips without bound.
+ // Feed far more distinct answer IPs than the cap and confirm it stops.
+ let mut old = DnsInfo {
+ query_name: Some("example.com".to_string()),
+ query_type: None,
+ response_ips: Vec::new(),
+ is_response: true,
+ };
+
+ for i in 0..(MAX_MERGED_RESPONSE_IPS as u32 * 4) {
+ let octets = i.to_be_bytes();
+ let new = DnsInfo {
+ query_name: None,
+ query_type: None,
+ response_ips: vec![IpAddr::V4(Ipv4Addr::new(
+ 10, octets[1], octets[2], octets[3],
+ ))],
+ is_response: true,
+ };
+ merge_dns_info(&mut old, &new);
+ }
+
+ assert_eq!(old.response_ips.len(), MAX_MERGED_RESPONSE_IPS);
+ }
+
+ #[test]
+ fn test_merge_packet_into_connection() {
+ let mut conn = create_test_connection();
+ let packet = create_test_packet(true, false);
+
+ let _tcp_events = merge_packet_into_connection(&mut conn, &packet, SystemTime::now());
+
+ assert_eq!(conn.packets_sent, 1);
+ assert_eq!(conn.bytes_sent, 100);
+ assert_eq!(conn.packets_received, 0);
+ }
+
+ #[test]
+ fn test_create_connection_from_packet() {
+ let packet = create_test_packet(false, false);
+ let conn = create_connection_from_packet(&packet, SystemTime::now());
+
+ assert_eq!(conn.packets_received, 1);
+ assert_eq!(conn.bytes_received, 100);
+ assert_eq!(conn.packets_sent, 0);
+ }
+
+ #[test]
+ fn test_new_connection_rate_tracker_initialization() {
+ // Test that the rate tracker is properly initialized for new connections
+ let packet = create_test_packet(true, false);
+ let mut conn = create_connection_from_packet(&packet, SystemTime::now());
+
+ // The connection should have initial bytes
+ assert_eq!(conn.bytes_sent, 100);
+ assert_eq!(conn.bytes_received, 0);
+
+ // Now simulate merging another packet
+ let packet2 = create_test_packet(true, false);
+ let _tcp_events = merge_packet_into_connection(&mut conn, &packet2, SystemTime::now());
+
+ // Bytes should have increased
+ assert_eq!(conn.bytes_sent, 200);
+ assert_eq!(conn.bytes_received, 0);
+
+ // Update rates - this should not cause a huge spike
+ conn.update_rates();
+
+ // The rate should be reasonable (not include the initial 100 bytes as a spike)
+ // Since we just added 100 bytes, the rate should be based on that delta
+ // not on the full 200 bytes
+ assert!(conn.current_outgoing_rate_bps >= 0.0);
+ }
+
+ #[test]
+ fn test_tcp_state_transitions() {
+ // Test SYN -> SYN_SENT
+ let flags = TcpFlags {
+ syn: true,
+ ack: false,
+ fin: false,
+ rst: false,
+ psh: false,
+ urg: false,
+ };
+ let new_state = update_tcp_state(TcpState::Unknown, &flags, true);
+ assert_eq!(new_state, TcpState::SynSent);
+
+ // Test SYN-ACK -> ESTABLISHED
+ let flags = TcpFlags {
+ syn: true,
+ ack: true,
+ fin: false,
+ rst: false,
+ psh: false,
+ urg: false,
+ };
+ let new_state = update_tcp_state(TcpState::SynSent, &flags, false);
+ assert_eq!(new_state, TcpState::Established);
+
+ // Test FIN -> FIN_WAIT_1
+ let flags = TcpFlags {
+ syn: false,
+ ack: false,
+ fin: true,
+ rst: false,
+ psh: false,
+ urg: false,
+ };
+ let new_state = update_tcp_state(TcpState::Established, &flags, true);
+ assert_eq!(new_state, TcpState::FinWait1);
+
+ // Test RST -> CLOSED
+ let flags = TcpFlags {
+ syn: false,
+ ack: false,
+ fin: false,
+ rst: true,
+ psh: false,
+ urg: false,
+ };
+ let new_state = update_tcp_state(TcpState::Established, &flags, true);
+ assert_eq!(new_state, TcpState::Closed);
+ }
+}
diff --git a/crates/rustnet-core/src/network/mod.rs b/crates/rustnet-core/src/network/mod.rs
new file mode 100644
index 0000000..572ee53
--- /dev/null
+++ b/crates/rustnet-core/src/network/mod.rs
@@ -0,0 +1,22 @@
+//! Networking core: link-layer/IP/transport parsers, deep packet inspection
+//! (HTTP, TLS SNI, DNS, SSH, QUIC, NTP, mDNS, LLMNR, DHCP, SNMP, SSDP,
+//! NetBIOS), connection merging, GeoIP lookups, OUI vendor resolution,
+//! interface-statistics traits, and the shared connection/protocol types.
+//!
+//! This is the platform-independent, capture-independent analysis layer.
+//! Raw packet capture lives in the `rustnet-capture` crate and platform-specific
+//! process attribution in the `rustnet-host` crate.
+
+pub mod bogon;
+pub mod dns;
+pub mod dpi;
+pub mod geoip;
+pub mod interface_stats;
+pub mod link_layer;
+pub mod merge;
+pub mod oui;
+pub mod parser;
+pub mod protocol;
+pub mod services;
+pub mod tracker;
+pub mod types;
diff --git a/crates/rustnet-core/src/network/oui.rs b/crates/rustnet-core/src/network/oui.rs
new file mode 100644
index 0000000..7e0597e
--- /dev/null
+++ b/crates/rustnet-core/src/network/oui.rs
@@ -0,0 +1,168 @@
+use anyhow::Result;
+use flate2::read::GzDecoder;
+use log::debug;
+use std::collections::HashMap;
+use std::io::Read;
+
+const OUI_DATA_GZ: &[u8] = include_bytes!("../../assets/oui.gz");
+
+/// OUI (Organizationally Unique Identifier) vendor lookup table
+#[derive(Debug, Clone)]
+pub struct OuiLookup {
+ vendors: HashMap<[u8; 3], String>,
+}
+
+impl OuiLookup {
+ /// Load OUI data from the embedded gzip-compressed IEEE database
+ pub fn from_embedded() -> Result {
+ let mut decoder = GzDecoder::new(OUI_DATA_GZ);
+ let mut oui_text = String::new();
+ decoder.read_to_string(&mut oui_text)?;
+
+ let mut vendors = HashMap::new();
+
+ for line in oui_text.lines() {
+ let line = line.trim();
+ if line.is_empty() {
+ continue;
+ }
+
+ // Format: AABBCC\tVendor Name
+ let Some((prefix_str, vendor)) = line.split_once('\t') else {
+ continue;
+ };
+
+ if prefix_str.len() != 6 {
+ continue;
+ }
+
+ let Ok(bytes) = (0..3)
+ .map(|i| u8::from_str_radix(&prefix_str[i * 2..i * 2 + 2], 16))
+ .collect::, _>>()
+ else {
+ continue;
+ };
+
+ let prefix = [bytes[0], bytes[1], bytes[2]];
+ vendors.entry(prefix).or_insert_with(|| vendor.to_string());
+ }
+
+ if vendors.is_empty() {
+ return Err(anyhow::anyhow!("No OUI entries found in embedded data"));
+ }
+ debug!(
+ "Loaded {} OUI vendor entries from embedded data",
+ vendors.len()
+ );
+
+ Ok(Self { vendors })
+ }
+
+ /// Look up a vendor name by MAC address string (e.g., "aa:bb:cc:dd:ee:ff")
+ pub fn lookup(&self, mac: &str) -> Option<&str> {
+ let prefix = parse_mac_prefix(mac)?;
+ self.vendors.get(&prefix).map(|s| s.as_str())
+ }
+}
+
+/// Parse the first 3 octets of a MAC address string into a byte array.
+/// Supports formats: "aa:bb:cc:...", "aa-bb-cc-...", "aabbcc..."
+fn parse_mac_prefix(mac: &str) -> Option<[u8; 3]> {
+ let mac = mac.trim();
+
+ // Try colon or hyphen-separated format
+ let parts: Vec<&str> = if mac.contains(':') {
+ mac.split(':').collect()
+ } else if mac.contains('-') {
+ mac.split('-').collect()
+ } else if mac.len() >= 6 {
+ // Unseparated hex format
+ let a = u8::from_str_radix(&mac[0..2], 16).ok()?;
+ let b = u8::from_str_radix(&mac[2..4], 16).ok()?;
+ let c = u8::from_str_radix(&mac[4..6], 16).ok()?;
+ return Some([a, b, c]);
+ } else {
+ return None;
+ };
+
+ if parts.len() < 3 {
+ return None;
+ }
+
+ let a = u8::from_str_radix(parts[0], 16).ok()?;
+ let b = u8::from_str_radix(parts[1], 16).ok()?;
+ let c = u8::from_str_radix(parts[2], 16).ok()?;
+ Some([a, b, c])
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_parse_mac_prefix_colon() {
+ assert_eq!(
+ parse_mac_prefix("aa:bb:cc:dd:ee:ff"),
+ Some([0xaa, 0xbb, 0xcc])
+ );
+ }
+
+ #[test]
+ fn test_parse_mac_prefix_hyphen() {
+ assert_eq!(
+ parse_mac_prefix("AA-BB-CC-DD-EE-FF"),
+ Some([0xaa, 0xbb, 0xcc])
+ );
+ }
+
+ #[test]
+ fn test_parse_mac_prefix_unseparated() {
+ assert_eq!(parse_mac_prefix("aabbccddeeff"), Some([0xaa, 0xbb, 0xcc]));
+ }
+
+ #[test]
+ fn test_parse_mac_prefix_too_short() {
+ assert_eq!(parse_mac_prefix("aa:bb"), None);
+ assert_eq!(parse_mac_prefix("aabb"), None);
+ }
+
+ #[test]
+ fn test_parse_mac_prefix_invalid_hex() {
+ assert_eq!(parse_mac_prefix("zz:yy:xx:00:00:00"), None);
+ }
+
+ #[test]
+ fn test_from_embedded() {
+ let lookup = OuiLookup::from_embedded().expect("should load embedded OUI data");
+ assert!(lookup.vendors.len() > 1000, "should have many OUI entries");
+ }
+
+ #[test]
+ fn test_lookup_miss() {
+ let lookup = OuiLookup::from_embedded().unwrap();
+ // All-zeros OUI is unlikely to match a real vendor
+ assert!(
+ lookup.lookup("00:00:00:00:00:00").is_none()
+ || lookup.lookup("00:00:00:00:00:00").is_some()
+ );
+ // Truly random prefix
+ assert!(
+ lookup.lookup("ff:ff:ff:dd:ee:00").is_none()
+ || lookup.lookup("ff:ff:ff:dd:ee:00").is_some()
+ );
+ }
+
+ #[test]
+ fn test_lookup_known_vendor() {
+ let lookup = OuiLookup::from_embedded().unwrap();
+ // Apple has many OUIs - test a well-known one
+ // 00:1B:63 is Apple
+ // If the database changes this test may need updating
+ // Just verify the lookup function works with a real MAC
+ let result = lookup.lookup("00:1b:63:00:00:00");
+ // We just verify it returns Some (exact vendor name may vary)
+ if let Some(vendor) = result {
+ assert!(!vendor.is_empty());
+ }
+ }
+}
diff --git a/crates/rustnet-core/src/network/parser.rs b/crates/rustnet-core/src/network/parser.rs
new file mode 100644
index 0000000..4b47cf1
--- /dev/null
+++ b/crates/rustnet-core/src/network/parser.rs
@@ -0,0 +1,1044 @@
+// network/parser.rs - Updated with DPI integration, PKTAP, and link_layer support
+use crate::network::dpi::DpiResult;
+use crate::network::link_layer;
+#[cfg(target_os = "macos")]
+use crate::network::link_layer::ethernet;
+#[cfg(target_os = "macos")]
+use crate::network::link_layer::pktap;
+use crate::network::oui::OuiLookup;
+use crate::network::protocol;
+use crate::network::protocol::TransportParams;
+use crate::network::types::*;
+use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
+
+// Re-export TCP types
+pub use crate::network::protocol::tcp::{TcpFlags, TcpHeaderInfo};
+
+/// Result of parsing a packet
+#[derive(Debug)]
+pub struct ParsedPacket {
+ pub protocol: Protocol,
+ pub local_addr: SocketAddr,
+ pub remote_addr: SocketAddr,
+ pub tcp_header: Option, // TCP header info (seq, ack, window, flags)
+ pub protocol_state: ProtocolState,
+ pub is_outgoing: bool,
+ pub packet_len: usize,
+ pub dpi_result: Option, // DPI results if available
+ pub process_name: Option, // Process name from PKTAP metadata
+ pub process_id: Option, // Process ID from PKTAP metadata
+}
+
+impl ParsedPacket {
+ /// The flow identity this packet belongs to, derived from protocol and
+ /// addresses. `ConnectionKey` is `Copy`, so this costs nothing on the
+ /// per-packet path (no allocation, unlike the former `String` key field).
+ #[inline]
+ pub fn connection_key(&self) -> ConnectionKey {
+ ConnectionKey::new(self.protocol, self.local_addr, self.remote_addr)
+ }
+}
+
+/// Configuration for packet parsing
+///
+/// # Example
+///
+/// ```rust,ignore
+/// // Create parser with custom DPI limit
+/// let config = ParserConfig {
+/// enable_dpi: true,
+/// dpi_packet_limit: 5, // Only inspect first 5 packets per connection
+/// };
+/// let parser = PacketParser::with_config(config);
+///
+/// // In connection tracking code:
+/// if config.should_perform_dpi(connection.packet_count) {
+/// // Perform DPI on this packet
+/// }
+/// ```
+#[derive(Clone)]
+pub struct ParserConfig {
+ pub enable_dpi: bool,
+ /// Maximum number of packets per connection to inspect with DPI
+ /// Use `should_perform_dpi()` method to check if DPI should be applied
+ pub dpi_packet_limit: usize,
+}
+
+impl Default for ParserConfig {
+ fn default() -> Self {
+ let config = Self {
+ enable_dpi: true,
+ dpi_packet_limit: 10, // Only inspect first 10 packets
+ };
+
+ // Log DPI configuration for debugging
+ log::trace!(
+ "ParserConfig: DPI {} (limit: {} packets per connection)",
+ if config.enable_dpi {
+ "enabled"
+ } else {
+ "disabled"
+ },
+ config.dpi_packet_limit
+ );
+
+ // Demonstrate usage: check if we should perform DPI on hypothetical packet counts
+ if config.enable_dpi {
+ log::trace!(" - Packet 0: DPI = {}", config.should_perform_dpi(0));
+ log::trace!(
+ " - Packet {}: DPI = {}",
+ config.dpi_packet_limit - 1,
+ config.should_perform_dpi(config.dpi_packet_limit - 1)
+ );
+ log::trace!(
+ " - Packet {}: DPI = {}",
+ config.dpi_packet_limit,
+ config.should_perform_dpi(config.dpi_packet_limit)
+ );
+ }
+
+ config
+ }
+}
+
+impl ParserConfig {
+ /// Check if DPI should be performed based on packet count
+ /// Returns true if packet_count is less than dpi_packet_limit
+ pub fn should_perform_dpi(&self, packet_count: usize) -> bool {
+ let should_dpi = self.enable_dpi && packet_count < self.dpi_packet_limit;
+ if !should_dpi && self.enable_dpi {
+ log::trace!(
+ "DPI skipped: packet {} exceeds limit {}",
+ packet_count,
+ self.dpi_packet_limit
+ );
+ }
+ should_dpi
+ }
+}
+
+/// Packet parser - stateless, thread-safe
+pub struct PacketParser {
+ local_ips: std::collections::HashSet,
+ config: ParserConfig,
+ linktype: Option, // DLT linktype - 149 means PKTAP on macOS
+ oui_lookup: Option,
+}
+
+impl Default for PacketParser {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl PacketParser {
+ /// Create a new packet parser with default configuration
+ /// Automatically detects local IP addresses from network interfaces
+ pub fn new() -> Self {
+ Self {
+ local_ips: collect_local_ips(),
+ config: ParserConfig::default(),
+ linktype: None,
+ oui_lookup: None,
+ }
+ }
+
+ pub fn with_config(config: ParserConfig) -> Self {
+ Self {
+ local_ips: collect_local_ips(),
+ config,
+ linktype: None,
+ oui_lookup: None,
+ }
+ }
+
+ /// Set the OUI lookup for MAC vendor resolution
+ pub fn with_oui_lookup(mut self, oui_lookup: OuiLookup) -> Self {
+ self.oui_lookup = Some(oui_lookup);
+ self
+ }
+
+ /// Set the linktype for this parser (needed for PKTAP detection)
+ pub fn with_linktype(mut self, linktype: i32) -> Self {
+ self.linktype = Some(linktype);
+
+ // Log linktype info for debugging, including TUN/TAP support
+ let link_type = link_layer::LinkLayerType::from_dlt(linktype);
+ if link_type.is_tunnel() {
+ log::debug!(
+ "Parser configured for tunnel interface: linktype {} ({:?})",
+ linktype,
+ link_type
+ );
+
+ // Log TUN/TAP parsing capabilities for documentation
+ log::trace!("TUN/TAP parsing available via link_layer::tun_tap module");
+ log::trace!(" - TUN interfaces (Layer 3): tun*, utun*");
+ log::trace!(" - TAP interfaces (Layer 2): tap*");
+ } else {
+ log::trace!(
+ "Parser configured with linktype {} ({:?})",
+ linktype,
+ link_type
+ );
+ }
+
+ self
+ }
+
+ /// Parse a raw packet using the appropriate link-layer parser
+ pub fn parse_packet(&self, data: &[u8]) -> Option {
+ if let Some(linktype) = self.linktype {
+ // Determine the link layer type
+ let link_type = link_layer::LinkLayerType::from_dlt(linktype);
+ log::trace!(
+ "Parsing packet with linktype {} ({:?})",
+ linktype,
+ link_type
+ );
+
+ match linktype {
+ // PKTAP (macOS process metadata)
+ #[cfg(target_os = "macos")]
+ 149 | 258 if pktap::is_pktap_linktype(linktype) => {
+ log::debug!("Parsing as PKTAP (linktype {})", linktype);
+ return self.parse_pktap_packet(data);
+ }
+ // Linux SLL (Linux "any" interface)
+ 113 => {
+ log::debug!("Parsing as Linux SLL (linktype 113)");
+ return link_layer::linux_sll::parse_sll(data, self, None, None);
+ }
+ // Linux SLL2
+ 276 => {
+ log::debug!("Parsing as Linux SLL2 (linktype 276)");
+ return link_layer::linux_sll::parse_sll2(data, self, None, None);
+ }
+ // TUN/TAP interfaces - use unified parser
+ 0
+ | 1
+ | 12
+ | 101
+ | link_layer::dlt::LINKTYPE_IPV4
+ | link_layer::dlt::LINKTYPE_IPV6 => {
+ log::debug!("Parsing TUN/TAP packet (linktype {})", linktype);
+ return link_layer::tun_tap::parse_by_dlt(data, linktype, self, None, None);
+ }
+ _ => {
+ log::debug!("Unknown linktype {}, trying Ethernet", linktype);
+ }
+ }
+ }
+
+ // Fallback: try Ethernet parsing if no linktype or unknown linktype
+ log::debug!("Using fallback Ethernet parsing");
+ link_layer::ethernet::parse(data, self, None, None)
+ }
+
+ #[cfg(target_os = "macos")]
+ fn parse_pktap_packet(&self, data: &[u8]) -> Option {
+ let (pktap_header, payload) = pktap::parse_pktap_packet(data)?;
+ let (process_name, process_id) = pktap_header.get_process_info();
+
+ log::debug!(
+ "PKTAP packet: interface={}, process={:?}, pid={:?}, payload_len={}",
+ pktap_header.get_interface(),
+ process_name,
+ process_id,
+ payload.len()
+ );
+
+ // Now parse the inner packet based on the DLT type
+ match pktap_header.inner_dlt() {
+ 1 => {
+ // DLT_EN10MB - Ethernet frame
+ // Note: macOS/XNU strips 802.1Q VLAN tags in ether_demux() before
+ // packets reach PKTAP, and libpcap on macOS does not reconstruct them.
+ // VLAN-tagged frames will never have EtherType 0x8100 here, but we
+ // delegate to ethernet::parse() to avoid duplicating the parsing logic.
+ ethernet::parse(payload, self, process_name, process_id)
+ }
+ 12 => {
+ // DLT_RAW - Raw IP packet
+ if payload.is_empty() {
+ return None;
+ }
+ let version = payload[0] >> 4;
+ match version {
+ 4 => self.parse_raw_ipv4_packet(payload, process_name, process_id),
+ 6 => self.parse_raw_ipv6_packet(payload, process_name, process_id),
+ _ => None,
+ }
+ }
+ _ => {
+ log::debug!("Unsupported PKTAP inner DLT: {}", pktap_header.inner_dlt());
+ None
+ }
+ }
+ }
+
+ /// Parse an IPv4 packet from Ethernet frame data
+ /// (data includes the 14-byte Ethernet header)
+ pub fn parse_ipv4_packet_inner(
+ &self,
+ data: &[u8],
+ offset: usize,
+ process_name: Option