diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/payloads/library/general/remote_debug_bridge/README.md b/payloads/library/general/remote_debug_bridge/README.md new file mode 100644 index 0000000..875a6fc --- /dev/null +++ b/payloads/library/general/remote_debug_bridge/README.md @@ -0,0 +1,154 @@ +# Shark Jack Display - Remote Debug Bridge + +Turns the Shark Jack into a temporary remote-troubleshooting appliance. Plug it +into the stranded network, walk to a desk, and reach the gear from a laptop +instead of from a cold aisle. It pulls an address, opens a **reverse SSH tunnel** +out to a jump host you control, exposes its own sshd back through that tunnel, and +**auto-reconnects** if the link blips, all while showing live status on the OLED +and logging every attempt on-device. + +> Born from a three-day production outage debugging isolated Brocade VCS fabric +> nodes with five people plugged directly into switches in a datacenter. The +> point of this payload is to never have to do that again: drop it on the +> segment, and triage from the conference room. + +> **Authorized use only.** This opens a remote-access path *into* a network. Run +> it only on networks you own or are contracted to support, with written +> authorization, and tear it down when the work is done. Auth is key-only to a +> jump host you control, with strict host-key checking; nothing is exfiltrated and +> local logs stay on-device under `/root/loot/debug_bridge/`. See the Legal and +> Disclaimer sections of the repository README. + +## How it works + +| Phase | What happens | +|-------|--------------| +| **Setup** | preflight: `ssh` present, key present, `JUMP_HOST` configured, jump host key pinned in `KNOWN_HOSTS`. Any miss fails fast with a screen hint | +| **Network up** | `NETMODE` per `NET_MODE` (DHCP / static / auto), waits for carrier and an IPv4 address, best-effort NTP sync | +| **Reachability** | pings the jump host (informational; it still tries SSH if ICMP is filtered) and writes the connection summary | +| **Bridge loop** | a short auth'd command proves egress + key auth and drops an "I'm alive" breadcrumb on the jump host, then opens `ssh -N -R` and holds it. On exit it reconnects (see below) | + +## What you connect to + +The reverse forward maps a port on the jump host's loopback back to the Shark +Jack's own sshd: + +``` +jump host 127.0.0.1:REMOTE_PORT -> Shark Jack 127.0.0.1:22 +``` + +So from the jump host you run: + +``` +ssh -p 2222 root@127.0.0.1 +``` + +and you land on the **Shark Jack**, which is L2-present on the stranded segment. +From that shell you reach the switches / fabric (`ssh admin@`, +set up further local forwards, etc). The Shark Jack is your beachhead; it is not +forwarding the switches directly. + +## One-time setup + +**1. Jump host** (a small always-on VM with a public IP). Create an unprivileged +user for the bridge: + +``` +sudo adduser --disabled-password sharkdebug +``` + +**2. Keypair on the Shark Jack:** + +``` +ssh-keygen -t ed25519 -f /root/.ssh/sharkjack_debug -N "" +``` + +**3. Authorize the key on the jump host** (paste `sharkjack_debug.pub` into the +jump user's `authorized_keys`). Optionally lock the key down to just this tunnel: + +``` +restrict,permitlisten="127.0.0.1:2222" ssh-ed25519 AAAA... sharkjack_debug +``` + +**4. Pin the jump host's key** on the Shark Jack (required, because the payload +enforces `StrictHostKeyChecking=yes`): + +``` +ssh-keyscan -p 22 >> /root/.ssh/known_hosts +``` + +**5. Edit the CONFIG block** of [`payload.txt`](payload.txt): set at least +`JUMP_HOST` (and `JUMP_USER` if you changed it). Deploy. + +## Output (loot) + +`/root/loot/debug_bridge/_/`: + +``` +summary.txt <- connection info + how to connect + final status (read this first) +bridge.log <- chronological attempt timeline (dials, uptimes, exit codes) +``` + +## Auto-reconnect behavior + +The loop distinguishes a healthy session from a hard failure: + +- A tunnel that stays up at least `STABLE_SECS` is treated as **healthy**. When it + later drops, the consecutive-failure counter resets, so a long triage session + survives any number of transient blips. +- Only **consecutive fast failures** (bad key, jump host down, `REMOTE_PORT` + already bound) count toward `MAX_RETRIES`, with a backoff that grows on each + repeat (capped at 60s). After `MAX_RETRIES` in a row, it gives up and ends red. + +## Configuration (top of [`payload.txt`](payload.txt)) + +| Knob | Default | Notes | +|------|---------|-------| +| `JUMP_USER` | `sharkdebug` | unprivileged user on the jump host | +| `JUMP_HOST` | `X.X.X.X` | **must set**: jump host public IP/DNS. Placeholder fails fast | +| `JUMP_PORT` | `22` | jump host sshd port | +| `REMOTE_BIND` | `127.0.0.1` | bind the listener to the jump host loopback (not public) | +| `REMOTE_PORT` | `2222` | jump-host port that maps back to the Shark Jack | +| `SSH_KEY` | `/root/.ssh/sharkjack_debug` | private key for the bridge | +| `KNOWN_HOSTS` | `/root/.ssh/known_hosts` | pinned jump host key | +| `NET_MODE` | `DHCP_CLIENT` | `STATIC` for a datacenter mgmt drop (fill `STATIC_*`), or `AUTO` | +| `STATIC_IP` / `STATIC_SUBNET` / `STATIC_GATEWAY` | `192.168.1.*` | used only when `NET_MODE=STATIC` | +| `DHCP_WAIT_SECS` | `30` | wait for an address before failing | +| `CONNECT_TIMEOUT` | `10` | per-dial TCP/auth timeout | +| `MAX_RETRIES` | `15` | give up after this many *consecutive fast* failures | +| `RETRY_SLEEP` | `10` | base wait between dials (grows on repeats) | +| `STABLE_SECS` | `60` | a tunnel up this long resets the failure counter | +| `LOOT_BASE` | `/root/loot/debug_bridge` | where loot lands | + +## Requirements + +- **`openssh-client` on the Shark Jack.** The options used (`ServerAliveInterval`, + `ExitOnForwardFailure`, `UserKnownHostsFile`, etc.) are OpenSSH syntax. If your + firmware ships only Dropbear, install it: `opkg update && opkg install + openssh-client`. +- **The Shark Jack's own sshd** listening on `127.0.0.1:22` (this is how you + manage the device, so it is normally already running). +- **A reachable jump host** running sshd with TCP forwarding allowed (the default). + Binding to `127.0.0.1` needs no `GatewayPorts`. + +## LED / screen cues + +`SETUP` -> `SPECIAL` (network up) -> `STAGE1` (jump reachability) -> `STAGE2` +(dialing) -> `ATTACK` (tunnel up, solid). It cycles `STAGE2`/`ATTACK` across +reconnects. Red `FAIL` means a preflight failed (no `ssh`, missing key, +unconfigured `JUMP_HOST`, empty `KNOWN_HOSTS`), no network, or the reconnect +budget was exhausted. The OLED shows the current dial count, the reverse-forward +target, and reconnect state. + +## Notes / limitations + +- **The drop must have egress to the jump host.** The Shark Jack has one port: the + network you land on must be able to reach `JUMP_HOST` on `JUMP_PORT` outbound. A + fully air-gapped segment cannot form the tunnel. +- **Pick a free `REMOTE_PORT`.** If it is already bound on the jump host, + `ExitOnForwardFailure` makes the tunnel exit immediately (counts as a fast + failure). One Shark Jack per `REMOTE_PORT`. +- **Tear it down.** Pull the device when finished, and remove the key from the + jump host's `authorized_keys` at the end of the engagement. +- **Keep the listener on loopback.** `REMOTE_BIND=127.0.0.1` means the bridge is + reachable only by someone already on the jump host, not the public internet. diff --git a/payloads/library/general/remote_debug_bridge/payload.txt b/payloads/library/general/remote_debug_bridge/payload.txt new file mode 100644 index 0000000..b6af02e --- /dev/null +++ b/payloads/library/general/remote_debug_bridge/payload.txt @@ -0,0 +1,217 @@ +#!/bin/bash +# ============================================================================ +# Title: Remote Debug Bridge (Shark Jack Display) +# Author: Brent Mills +# Target: An authorized wired drop you need to reach back into remotely. +# Category: general / remote-access / troubleshooting +# Version: 1.1 +# Firmware: Tested on Shark Jack Display 1.0.1 +# ---------------------------------------------------------------------------- +# WHAT IT DOES +# Turns the Shark Jack into a temporary remote-troubleshooting appliance, so +# you can debug stranded gear from a desk instead of from a cold aisle. It: +# - Brings up eth0 (DHCP, or a static datacenter mgmt drop). +# - Opens a reverse SSH tunnel to a jump host you control, exposing the +# Shark Jack's own sshd back through it. +# - Auto-reconnects: a tunnel that was healthy re-dials on a drop; only +# repeated fast failures count toward giving up. +# - Shows live status on the OLED and logs every attempt on-device. +# From the jump host you then: ssh -p root@127.0.0.1 +# which lands you on the Shark Jack. It is L2-present on the stranded +# segment, so you reach the switches/fabric from there. +# +# AUTHORIZATION +# This opens a remote-access path INTO a network. Deploy ONLY on networks you +# own or are contracted to support, with written authorization, and tear it +# down when the work is finished. Auth is key-only to a jump host you control, +# with strict host-key checking. Local logs stay on-device under /root/loot. +# +# PREREQUISITES (one-time; see README) +# - A keypair at SSH_KEY, its public key in the jump user's authorized_keys. +# - The jump host's key pinned in KNOWN_HOSTS (ssh-keyscan), because this +# enforces StrictHostKeyChecking=yes. +# - openssh-client on the Shark Jack (the options below are OpenSSH syntax). +# ============================================================================ + +# ------------------------------- CONFIG ------------------------------------- +# Jump host: a small always-on VM you control. The reverse tunnel lands here. +JUMP_USER="sharkdebug" +JUMP_HOST="X.X.X.X" # cloud VM public IP or DNS. MUST be set. +JUMP_PORT="22" + +# Reverse forward: on the jump host this port maps back to the Shark Jack sshd. +REMOTE_BIND="127.0.0.1" # keep the listener local to the jump host +REMOTE_PORT="2222" # then, on the jump host: ssh -p 2222 root@127.0.0.1 + +# Key-only auth and a pinned host key (no passwords, no blind TOFU). +SSH_KEY="/root/.ssh/sharkjack_debug" +KNOWN_HOSTS="/root/.ssh/known_hosts" + +# Network bring-up. DHCP_CLIENT suits a normal drop; a stranded datacenter mgmt +# port is often static, so fill STATIC_* and set NET_MODE="STATIC". +NET_MODE="DHCP_CLIENT" # DHCP_CLIENT | AUTO | STATIC +STATIC_IP="192.168.1.250" # used only when NET_MODE=STATIC +STATIC_SUBNET="255.255.255.0" # used only when NET_MODE=STATIC +STATIC_GATEWAY="192.168.1.1" # used only when NET_MODE=STATIC +DHCP_WAIT_SECS=30 + +# Tunnel behavior. +CONNECT_TIMEOUT=10 # per-dial TCP/auth timeout +MAX_RETRIES=15 # give up after this many CONSECUTIVE fast fails +RETRY_SLEEP=10 # base wait between dials (grows on repeat fails) +STABLE_SECS=60 # a tunnel up at least this long counts as healthy + # and resets the consecutive-failure counter + +LOOT_BASE="/root/loot/debug_bridge" +# ---------------------------------------------------------------------------- + +have() { command -v "$1" >/dev/null 2>&1; } + +# Shared SSH options: key-only, pinned host key, dead-peer detection. +SSH_OPTS="-i $SSH_KEY -p $JUMP_PORT \ + -o BatchMode=yes \ + -o IdentitiesOnly=yes \ + -o PasswordAuthentication=no \ + -o ConnectTimeout=$CONNECT_TIMEOUT \ + -o ServerAliveInterval=15 \ + -o ServerAliveCountMax=2 \ + -o ExitOnForwardFailure=yes \ + -o StrictHostKeyChecking=yes \ + -o UserKnownHostsFile=$KNOWN_HOSTS" + +# ------------------------------ SETUP --------------------------------------- +LED SETUP +CLEAR_SCREEN 2>/dev/null +SCREEN_WRITE "Remote Debug Bridge" + +RUN="$LOOT_BASE/$(date -u +%Y%m%dT%H%M%SZ 2>/dev/null || echo run)_$$" +mkdir -p "$RUN" +REPORT="$RUN/summary.txt" +LOG="$RUN/bridge.log" + +fail() { # fail + LED FAIL + SCREEN_WRITE "$1" + echo "FAILED: $2" > "$REPORT" + ALERT "$2" 8 2>/dev/null + exit 1 +} + +have ssh || fail "ssh missing" "ssh client missing (opkg install openssh-client)" +[ -f "$SSH_KEY" ] || fail "missing SSH key" "missing SSH key at $SSH_KEY" +[ "$JUMP_HOST" = "X.X.X.X" ] || [ -z "$JUMP_HOST" ] && fail "set JUMP_HOST" "JUMP_HOST is not configured (still the placeholder)" +[ -s "$KNOWN_HOSTS" ] || fail "pin host key" "KNOWN_HOSTS empty; pin the jump host: ssh-keyscan -p $JUMP_PORT $JUMP_HOST >> $KNOWN_HOSTS" + +chmod 600 "$SSH_KEY" 2>/dev/null + +# --------------------------- PHASE: NETWORK UP ------------------------------ +LED SPECIAL +SCREEN_WRITE "Bringing up eth0 ($NET_MODE)" +case "$NET_MODE" in + STATIC) NETMODE STATIC IP_${STATIC_IP} SUBNET_${STATIC_SUBNET} GATEWAY_${STATIC_GATEWAY} 2>/dev/null ;; + AUTO) NETMODE AUTO 2>/dev/null ;; + *) NETMODE DHCP_CLIENT 2>/dev/null ;; +esac +WAIT_FOR_LINK 2>/dev/null # block until carrier is up (the race fix) + +IP=""; PREFIX="" +for _ in $(seq 1 "$DHCP_WAIT_SECS"); do + line=$(ip -o -4 addr show eth0 2>/dev/null | awk '{print $4}' | head -1) + if [ -n "$line" ]; then IP=${line%/*}; PREFIX=${line#*/}; break; fi + sleep 1 +done +[ -z "$IP" ] && fail "no network" "no IPv4 on eth0 after ${DHCP_WAIT_SECS}s (NET_MODE=$NET_MODE; NAC/802.1X, no DHCP, or dead drop)" + +GW=$(ip route 2>/dev/null | awk '/default/{print $3; exit}') +ENABLE_NTP 2>/dev/null # best-effort clock sync for honest timestamps +SCREEN_WRITE "IP $IP/$PREFIX" + +# --------------------------- PHASE: REACHABILITY ---------------------------- +LED STAGE1 +SCREEN_WRITE "Testing jump reachability" +if ! ping -c 1 -W 3 "$JUMP_HOST" >/dev/null 2>&1; then + echo "$(date -u 2>/dev/null) ping to $JUMP_HOST failed (ICMP may be filtered); trying SSH anyway" >> "$LOG" +fi + +{ + echo "==========================================================" + echo " SHARK JACK DISPLAY - REMOTE DEBUG BRIDGE" + echo "==========================================================" + echo " Run time : $(date -u '+%Y-%m-%d %H:%M:%SZ' 2>/dev/null)" + echo " Local IP : $IP/$PREFIX (NET_MODE=$NET_MODE)" + echo " Gateway : ${GW:-unknown}" + echo " Jump host : $JUMP_USER@$JUMP_HOST:$JUMP_PORT" + echo " Reverse fwd : $REMOTE_BIND:$REMOTE_PORT -> Shark Jack 127.0.0.1:22" + echo "" + echo " From the jump host, connect to the Shark Jack with:" + echo " ssh -p $REMOTE_PORT root@127.0.0.1" + echo "" + echo " Live attempt timeline is in bridge.log." + echo "==========================================================" +} > "$REPORT" + +# --------------------------- PHASE: BRIDGE LOOP ----------------------------- +# Reverse tunnel with resilient reconnect. A tunnel that stays up past +# STABLE_SECS is "healthy", so a later drop resets the failure counter; only +# repeated FAST failures (bad key, port in use, jump host down) count toward +# MAX_RETRIES, with a growing backoff between them. +attempt=0 +fails=0 +while [ "$fails" -lt "$MAX_RETRIES" ]; do + attempt=$((attempt + 1)) + LED STAGE2 + SCREEN_WRITE "Dial $((fails + 1))/$MAX_RETRIES" + + STAMP=$(date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null) + echo "$STAMP attempt $attempt (consecutive fails $fails)" >> "$LOG" + + # Pre-check + breadcrumb: a quick auth'd command proves egress and key auth + # before we commit to the blocking tunnel, and leaves an "I'm alive" marker. + if ! ssh $SSH_OPTS "$JUMP_USER@$JUMP_HOST" \ + "printf '%s\n' \"shark bridge up from $IP/$PREFIX at $STAMP\" >> ~/sharkjack-bridge.log" \ + >> "$LOG" 2>&1; then + fails=$((fails + 1)) + SCREEN_WRITE "Jump unreachable" + echo "$STAMP jump host unreachable or auth failed" >> "$LOG" + back=$((RETRY_SLEEP * fails)); [ "$back" -gt 60 ] && back=60 + sleep "$back" + continue + fi + + LED ATTACK + SCREEN_WRITE "Tunnel up" + SCREEN_WRITE "$REMOTE_BIND:$REMOTE_PORT" + + start=$(date +%s 2>/dev/null) + ssh -N $SSH_OPTS -R "$REMOTE_BIND:$REMOTE_PORT:127.0.0.1:22" "$JUMP_USER@$JUMP_HOST" >> "$LOG" 2>&1 + rc=$? + end=$(date +%s 2>/dev/null) + up=$((end - start)) + echo "$(date -u '+%Y-%m-%d %H:%M:%SZ' 2>/dev/null) tunnel exited rc=$rc after ${up}s" >> "$LOG" + + if [ "$up" -ge "$STABLE_SECS" ]; then + fails=0 # healthy session; a drop is transient + SCREEN_WRITE "Reconnecting" + sleep "$RETRY_SLEEP" + else + fails=$((fails + 1)) # fast failure; count it, back off + SCREEN_WRITE "Drop, retrying" + back=$((RETRY_SLEEP * fails)); [ "$back" -gt 60 ] && back=60 + sleep "$back" + fi +done + +# ------------------------------ PHASE: ENDED -------------------------------- +LED FAIL +SCREEN_WRITE "Bridge ended" +SCREEN_WRITE "$MAX_RETRIES fails" +{ + echo "" + echo "---- FINAL STATUS ----------------------------------------" + echo " Ended at : $(date -u '+%Y-%m-%d %H:%M:%SZ' 2>/dev/null)" + echo " Dials made : $attempt" + echo " Reason : $MAX_RETRIES consecutive fast failures (see bridge.log)" + echo "==========================================================" +} >> "$REPORT" +ALERT "Debug bridge ended after $MAX_RETRIES consecutive failures" 10 2>/dev/null +exit 0 diff --git a/payloads/library/recon/dhcp_dns_ad/README.md b/payloads/library/recon/dhcp_dns_ad/README.md new file mode 100644 index 0000000..1548687 --- /dev/null +++ b/payloads/library/recon/dhcp_dns_ad/README.md @@ -0,0 +1,62 @@ +# Shark Jack Display - DHCP + DNS / AD Profiler + +Answers "what kind of network is this?" in one pass: it reads the whole DHCP +offer, finds the Active Directory domain and its domain controllers, attempts a +zone transfer, checks DNS hygiene, and samples host naming. + +> **Authorized use only.** Run only on networks you own or are contracted to +> assess, within the agreed scope and time window. The AXFR attempt is a +> standard assessment check; keep it within authorized scope. Nothing is +> exfiltrated: all output stays on-device under `/root/loot/dhcp_dns_ad/` until +> you retrieve it. + +## What it collects + +| Section | How | +|---------|-----| +| **DHCP profile** | the whole offer: DNS, domain, NTP, lease time, PXE/TFTP (66/67), WPAD/proxy (252), server-id | +| **Active Directory** | SRV lookups (`_ldap`/`_kerberos`/`_gc`) to find domain controllers and confirm an AD domain | +| **Zone transfer** | attempts AXFR against the internal DNS (a classic, reportable misconfiguration) | +| **DNS hygiene** | open-recursion check on the internal resolver | +| **Naming sample** | low-noise reverse-PTR sweep (`nmap -sL`, no host probes) to learn host naming | + +## Output (loot) + +`/root/loot/dhcp_dns_ad/_/`: + +``` +summary.txt <- read this first +dhcp.txt <- full DHCP discover output +ad_srv.txt <- AD SRV enumeration +axfr.txt <- zone-transfer attempt +dns_recursion.txt ptr_names.txt vlan_profiles.txt +``` + +## Configuration (top of [`payload.txt`](payload.txt)) + +| Knob | Default | Notes | +|------|---------|-------| +| `DHCP_WAIT_SECS` | `30` | how long to wait for a lease before failing | +| `DOMAIN_OVERRIDE` | `""` | force the AD domain if DHCP does not advertise one | +| `TRY_ZONE_TRANSFER` | `1` | attempt AXFR against the internal DNS server | +| `PTR_SWEEP` | `1` | reverse-resolve the local subnet for naming | +| `SCAN_TAGGED_VLANS` | `1` | **active VLAN-hopping** on a trunk; set `0` for client sites unless explicitly authorized | +| `VLAN_PROBE_IDS` | `""` | fallback VIDs if none are sniffed, e.g. `"2 3 10"` | +| `PASSIVE_VLAN_SECS` | `12` | passive 802.1Q listen window (needs `tcpdump`) | +| `LOOT_BASE` | `/root/loot/dhcp_dns_ad` | where loot lands | + +## Requirements + +- **`nmap`** (ships on the device). **`tcpdump`** optional, only for passive + 802.1Q tag sniffing on a trunk. + +## LED / screen cues + +`SETUP` -> `SPECIAL` (lease) -> `STAGE1` DHCP -> `STAGE2` DNS/AD -> `STAGE3` +per-VLAN -> `CLEANUP` -> `FINISH`. Red `FAIL` means no DHCP lease (NAC/802.1X, +no DHCP, or a dead drop). + +## Notes + +- `SCAN_TAGGED_VLANS=1` is active VLAN-hopping: noisy and intrusive. It no-ops on + a plain access port. Leave it `0` unless the engagement authorizes it. diff --git a/payloads/library/recon/dhcp_dns_ad/payload.txt b/payloads/library/recon/dhcp_dns_ad/payload.txt new file mode 100644 index 0000000..2c494ae --- /dev/null +++ b/payloads/library/recon/dhcp_dns_ad/payload.txt @@ -0,0 +1,272 @@ +#!/bin/bash +# ============================================================================ +# Title: DHCP + DNS / AD Profiler (Shark Jack Display) +# Author: Brent Mills +# Target: An authorized wired drop, scoping a new environment. +# Category: recon +# Version: 1.0 +# Firmware: Tested on Shark Jack Display 1.0.1 +# ---------------------------------------------------------------------------- +# WHAT IT DOES +# Answers "what kind of network is this?" in one pass: +# - DHCP profile ...... the WHOLE offer: DNS, domain, NTP, lease time, +# PXE/TFTP (66/67), WPAD/proxy (252), server-id. +# - Active Directory .. SRV lookups (_ldap/_kerberos/_gc) to find domain +# controllers and confirm an AD domain. +# - Zone transfer ..... attempts AXFR against the internal DNS (a classic, +# common, reportable misconfiguration). +# - DNS hygiene ....... open-recursion check on the internal resolver. +# - Naming sample ..... low-noise reverse-PTR sweep to learn host naming. +# +# AUTHORIZATION +# Active but benign DNS/DHCP queries against the in-scope network only. The +# AXFR attempt is a standard assessment check; keep it within authorized +# scope. Output stays on-device in /root/loot until you pull it. +# ============================================================================ + +# ------------------------------- CONFIG ------------------------------------- +DHCP_WAIT_SECS=30 +DOMAIN_OVERRIDE="" # force the AD domain if DHCP doesn't advertise one +TRY_ZONE_TRANSFER=1 # 1 = attempt AXFR against the internal DNS server +PTR_SWEEP=1 # 1 = reverse-resolve the local /24 for naming +LOOT_BASE="/root/loot/dhcp_dns_ad" +# VLAN-aware scanning: also tag onto and scan tagged VLANs seen on a trunk. +SCAN_TAGGED_VLANS=0 # 1 = discover+scan tagged VLANs too. ACTIVE VLAN- + # hopping; set 0 for client sites unless it is + # authorized. No-op on a plain access port. +VLAN_PROBE_IDS="" # fallback VIDs if none are sniffed, e.g. "2 3 10" +PASSIVE_VLAN_SECS=12 # passive 802.1Q listen window (needs tcpdump) +# ---------------------------------------------------------------------------- + +# ============================ helpers ======================================= +have() { command -v "$1" >/dev/null 2>&1; } +field() { grep -iE "$2" "$1" 2>/dev/null | head -1 | sed 's/^[^:]*: *//' | sed 's/^[ \t|]*//; s/[ \t]*$//'; } +# ============================================================================ + + +# ---- VLAN-aware scanning (shared block; keep identical across payloads) ----- +ip_to_int() { local IFS=.; set -- $1; echo $(( ($1<<24)|($2<<16)|($3<<8)|$4 )); } +int_to_ip() { local i=$1; echo "$(((i>>24)&255)).$(((i>>16)&255)).$(((i>>8)&255)).$((i&255))"; } +mask_for() { local p=$1; echo $(( (0xFFFFFFFF << (32-p)) & 0xFFFFFFFF )); } +network_cidr() { local ip=$1 p=$2 m; m=$(mask_for "$p"); echo "$(int_to_ip $(( $(ip_to_int "$ip") & m )))/$p"; } +VLAN_SUBNETS="" # "vid:net/prefix ..." for labeling +vlan_for_ip() { + local ip=$1 ipi entry vid cidr net p m; ipi=$(ip_to_int "$ip") + for entry in $VLAN_SUBNETS; do + vid=${entry%%:*}; cidr=${entry#*:}; net=${cidr%/*}; p=${cidr#*/}; m=$(mask_for "$p") + [ $(( ipi & m )) -eq $(( $(ip_to_int "$net") & m )) ] && { echo "vlan$vid"; return; } + done + echo "access" +} +vlan_candidates() { # VIDs to scan: sniffed 802.1Q tags, else fallback + local tags="" + have tcpdump && tags=$(timeout "${PASSIVE_VLAN_SECS}s" tcpdump -e -nn -i eth0 -c 300 vlan 2>/dev/null \ + | grep -oE 'vlan [0-9]+' | awk '{print $2}' | sort -un | tr '\n' ' ') + [ -z "$tags" ] && tags="$VLAN_PROBE_IDS" + echo "$tags" +} +# Run over the native VLAN, then (if enabled) each tagged VLAN: bring up +# eth0., DHCP, derive its CIDR, call "