Files
mitm-webserver/tools/test_bridge.sh
malmert fefbefc5e1
All checks were successful
Build and Deploy MITM Webserver / traffic_target (push) Successful in 1s
Build and Deploy MITM Webserver / build (push) Successful in 10s
fix
2026-03-09 21:53:53 +01:00

333 lines
9.5 KiB
Bash

#!/usr/bin/env bash
#
# detect_bridge.sh -- improved / robust argument handling
#
# Usage: sudo ./detect_bridge.sh <target-ip-or-hostname> [interface] [--mac-spoof]
#
set -euo pipefail
IFS=$'\n\t'
# defaults
TARGET=""
IFACE=""
MAC_SPOOF=false
# --- parse args robustly ---
while [[ $# -gt 0 ]]; do
case "$1" in
--mac-spoof)
MAC_SPOOF=true
shift
;;
-i|--iface)
if [[ -n "${2:-}" ]]; then
IFACE="$2"
shift 2
else
echo "Error: --iface requires an argument" >&2
exit 2
fi
;;
-h|--help)
cat <<EOF
Usage: sudo $0 <target-ip-or-hostname> [interface] [--mac-spoof]
Options:
--mac-spoof Enable optional MAC-spoof learning probe (may disrupt link).
-i, --iface Specify interface to use (otherwise auto-detected).
EOF
exit 0
;;
-*)
echo "Unknown option: $1" >&2
exit 2
;;
*)
if [[ -z "$TARGET" ]]; then
TARGET="$1"
else
# allow second positional to be iface for compatibility
if [[ -z "$IFACE" ]]; then
IFACE="$1"
else
echo "Ignoring extra argument: $1" >&2
fi
fi
shift
;;
esac
done
if [[ -z "$TARGET" ]]; then
echo "Error: target IP or hostname required." >&2
echo "Usage: sudo $0 <target-ip-or-hostname> [interface] [--mac-spoof]" >&2
exit 2
fi
# helper: detect default interface to reach target
detect_iface() {
if [[ -n "$IFACE" ]]; then
printf '%s\n' "$IFACE"
return 0
fi
if route_info=$(ip route get "$TARGET" 2>/dev/null); then
dev=$(echo "$route_info" | awk '{for(i=1;i<=NF;i++){if($i=="dev"){print $(i+1);exit}}}')
if [[ -n "$dev" ]]; then
printf '%s\n' "$dev"
return 0
fi
fi
# fallback: first non-loopback up interface
for dev in $(ls /sys/class/net); do
if [[ "$dev" != "lo" ]] && [[ -f "/sys/class/net/$dev/operstate" ]] && grep -q "up" "/sys/class/net/$dev/operstate"; then
printf '%s\n' "$dev"
return 0
fi
done
printf 'eth0\n'
}
# ensure ip exists
if ! command -v ip >/dev/null 2>&1; then
echo "This script requires 'ip' (iproute2). Aborting." >&2
exit 1
fi
IFACE=$(detect_iface)
echo "Target: $TARGET"
echo "Interface: $IFACE"
echo
# resolve target IP
if [[ "$TARGET" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
TARGET_IP="$TARGET"
else
TARGET_IP=$(getent hosts "$TARGET" | awk '{print $1}' | head -n1 || true)
if [[ -z "$TARGET_IP" ]]; then
echo "Failed to resolve target '$TARGET'." >&2
exit 1
fi
fi
echo "Resolved target IP: $TARGET_IP"
echo
# check required commands
REQ_CMDS=(ip ping awk grep sed date)
for c in "${REQ_CMDS[@]}"; do
if ! command -v "$c" >/dev/null 2>&1; then
echo "Missing required command: $c" >&2
exit 1
fi
done
# optional commands
HAS_TCPDUMP=false
HAS_ETHTOOL=false
HAS_ARPING=false
HAS_LLDPC=false
HAS_TRACEROUTE=false
if command -v tcpdump >/dev/null 2>&1; then HAS_TCPDUMP=true; fi
if command -v ethtool >/dev/null 2>&1; then HAS_ETHTOOL=true; fi
if command -v arping >/dev/null 2>&1; then HAS_ARPING=true; fi
if command -v lldpctl >/dev/null 2>&1; then HAS_LLDPC=true; fi
if command -v traceroute >/dev/null 2>&1; then HAS_TRACEROUTE=true; fi
echo "Tool availability:"
echo " tcpdump: $HAS_TCPDUMP"
echo " ethtool: $HAS_ETHTOOL"
echo " arping: $HAS_ARPING"
echo " lldpctl: $HAS_LLDPC"
echo " traceroute: $HAS_TRACEROUTE"
echo
run_header() {
echo
echo "===== $1 ====="
}
# 1) interface->master check
run_header "Interface master / bridge hint (ip link)"
ip link show dev "$IFACE" | sed -n '1,4p' || true
master=$(ip link show dev "$IFACE" 2>/dev/null | tr '\n' ' ' | sed 's/.*master \([^ ]*\).*/\1/;t;d' || true)
if [[ -n "$master" ]]; then
echo "Interface reports master: $master -> This interface is enslaved to a bridge on this host (local bridge)."
else
echo "No 'master' shown; interface is not a local bridge slave (or not reported)."
fi
# 2) check /sys/class/net/<if>/bridge
run_header "Check local bridge sysfs"
if [[ -d "/sys/class/net/$IFACE/bridge" ]]; then
echo "/sys/class/net/$IFACE/bridge exists -> this host has a bridge device attached to $IFACE (local)."
else
echo "No /sys/class/net/$IFACE/bridge -> local host is not the bridge owner for this interface."
fi
# 3) LLDP via lldpctl
if $HAS_LLDPC; then
run_header "LLDP via lldpctl"
sudo lldpctl -f keyvalue "$IFACE" 2>/dev/null || echo "lldpctl produced no output or isn't running for $IFACE."
else
run_header "LLDP: lldpctl not installed"
echo "lldpctl not installed. Install lldpd and run lldpctl to try LLDP discovery."
fi
# 4) passive capture for STP/LLDP (tcpdump)
if $HAS_TCPDUMP; then
run_header "Passive capture: look for STP BPDUs and LLDP frames (tcpdump, 6s capture)"
TMPPCAP=$(mktemp /tmp/detect_bridge_pcap.XXXX.pcap)
trap 'rm -f "$TMPPCAP"' EXIT
sudo timeout 6 tcpdump -i "$IFACE" -s 128 -w "$TMPPCAP" "ether dst 01:80:c2:00:00:00 or ether proto 0x88cc or ether multicast" >/dev/null 2>&1 || true
if [[ -s "$TMPPCAP" ]]; then
echo "Captured packets; summary:"
sudo tcpdump -n -r "$TMPPCAP" -e | sed -n '1,50p' || true
if sudo tcpdump -n -r "$TMPPCAP" -e | grep -i "stp\|bpdu" >/dev/null 2>&1; then
echo "-> STP/BPDU frames observed."
fi
if sudo tcpdump -n -r "$TMPPCAP" -e | grep -i "LLDP" >/dev/null 2>&1; then
echo "-> LLDP frames observed."
fi
else
echo "No control frames captured in 6s capture."
fi
rm -f "$TMPPCAP" || true
trap - EXIT
else
run_header "Passive capture: tcpdump not available"
echo "tcpdump missing. Install tcpdump and re-run to capture control frames (STP/LLDP)."
fi
# 5) ethtool
if $HAS_ETHTOOL; then
run_header "ethtool: link/partner info"
sudo ethtool "$IFACE" || true
sudo ethtool -a "$IFACE" 2>/dev/null || true
sudo ethtool -S "$IFACE" 2>/dev/null || true
else
run_header "ethtool not available"
fi
# 6) ARP / neighbor and MAC lookup
run_header "ARP table and MAC OUI"
ip neigh show to "$TARGET_IP" | sed -n '1,50p' || true
arp_mac=$(ip neigh show to "$TARGET_IP" | awk '{print $5; exit}' || true)
if [[ -n "$arp_mac" ]]; then
echo "Observed MAC for $TARGET_IP: $arp_mac"
else
echo "No ARP entry yet."
fi
# 7) arping
if $HAS_ARPING; then
run_header "arping: 5 probes to target"
sudo timeout 6 arping -c 5 -I "$IFACE" "$TARGET_IP" || true
fi
# 8) ping micro-latency test
run_header "ping micro-latency test: 100 pings, 10ms interval"
if ping -c 1 "$TARGET_IP" >/dev/null 2>&1; then
ping -c 100 -i 0.01 "$TARGET_IP" | tail -n 5 || true
stats=$(ping -c 10 -i 0.01 -q "$TARGET_IP" 2>/dev/null | tail -n1 || true)
echo "Ping summary: $stats"
else
echo "Target is not responding to ICMP, skipping ping test."
fi
# 9) traceroute
if $HAS_TRACEROUTE; then
run_header "traceroute to target"
traceroute -n -w 1 -q 1 "$TARGET_IP" || true
fi
# 10) passive multicast sniff
if $HAS_TCPDUMP; then
run_header "Passive: sniff for LLDP/stp multicast (4s)"
TMPLOG=$(mktemp /tmp/detect_bridge_log.XXXX.txt)
sudo timeout 4 tcpdump -i "$IFACE" -s 160 -l -n 'ether multicast' 2>/dev/null | sed -n '1,200p' >"$TMPLOG" || true
if [[ -s "$TMPLOG" ]]; then
sed -n '1,50p' "$TMPLOG"
else
echo "No multicast control frames in short 4s sniff."
fi
rm -f "$TMPLOG" || true
fi
# 11) gentle ARP burst
run_header "Broadcast/ARP test (gentle)"
if $HAS_ARPING; then
sudo timeout 4 arping -c 3 -I "$IFACE" "$TARGET_IP" >/dev/null 2>&1 || true
else
echo "arping not available -> skipping."
fi
# 12) Optional MAC-spoof test (prompt/confirm)
if [ "$MAC_SPOOF" = true ]; then
echo
echo "***** MAC SPOOF TEST ENABLED *****"
read -r -p "This may disrupt link. Continue? (y/N) " yn
if [[ "$yn" =~ ^[Yy]$ ]]; then
orig_mac=$(cat "/sys/class/net/$IFACE/address")
rand_mac=$(printf '02:%02x:%02x:%02x:%02x:%02x\n' $((RANDOM%256)) $((RANDOM%256)) $((RANDOM%256)) $((RANDOM%256)) $((RANDOM%256)))
echo "Original MAC: $orig_mac"
echo "Changing $IFACE to $rand_mac"
sudo ip link set dev "$IFACE" down
sudo ip link set dev "$IFACE" address "$rand_mac"
sudo ip link set dev "$IFACE" up
ping -c 5 "$TARGET_IP" || true
echo "Restoring original MAC"
sudo ip link set dev "$IFACE" down
sudo ip link set dev "$IFACE" address "$orig_mac"
sudo ip link set dev "$IFACE" up
echo "MAC restored to $orig_mac"
else
echo "Skipping MAC spoof test."
fi
else
echo "(MAC spoof test disabled. To enable, re-run with --mac-spoof.)"
fi
# 13) Synthesis (simple)
run_header "Synthesis (brief)"
score=0
notes=()
if $HAS_TCPDUMP && sudo timeout 1 tcpdump -n -c 1 -i "$IFACE" 'ether dst 01:80:c2:00:00:00' >/dev/null 2>&1; then
score=$((score+40))
notes+=("STP/BPDU observed -> likely bridge/switch.")
fi
if $HAS_TCPDUMP && sudo timeout 1 tcpdump -n -c 1 -i "$IFACE" 'ether proto 0x88cc' >/dev/null 2>&1; then
score=$((score+40))
notes+=("LLDP observed -> neighbor advertising.")
elif $HAS_LLDPC; then
if sudo lldpctl "$IFACE" 2>/dev/null | grep -q .; then
score=$((score+40))
notes+=("lldpctl shows LLDP neighbor on $IFACE.")
fi
fi
if [[ -n "$master" ]]; then
score=$((score+30))
notes+=("Interface master indicates local bridge ($master).")
fi
if [[ -n "$arp_mac" ]]; then
score=$((score+2))
notes+=("ARP resolved target MAC ($arp_mac).")
fi
if [[ "$score" -gt 100 ]]; then score=100; fi
echo "Score: $score / 100"
for n in "${notes[@]}"; do
echo " - $n"
done
if [[ "$score" -ge 70 ]]; then
echo "Conclusion: HIGH confidence of a bridge/switch between hosts."
elif [[ "$score" -ge 35 ]]; then
echo "Conclusion: MEDIUM confidence."
else
echo "Conclusion: LOW confidence."
fi
echo
echo "Done."