Skip to content

Instantly share code, notes, and snippets.

@lilithebowman
Created August 8, 2025 14:58
Show Gist options
  • Save lilithebowman/8a1c4346981b4e083ff13d625d319a2a to your computer and use it in GitHub Desktop.
Save lilithebowman/8a1c4346981b4e083ff13d625d319a2a to your computer and use it in GitHub Desktop.
#!/bin/bash
# findpeers.sh - Discover peers on your subnet when connected to WiFi
# Usage: ./findpeers.sh [options]
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# Default values
FAST_SCAN=false
VERBOSE=false
SHOW_HELP=false
PING_TIMEOUT=1
NMAP_AVAILABLE=false
# Check if nmap is available
if command -v nmap >/dev/null 2>&1; then
NMAP_AVAILABLE=true
fi
show_help() {
echo "findpeers.sh - Discover peers on your subnet when connected to WiFi"
echo ""
echo "Usage: $0 [options]"
echo ""
echo "Options:"
echo " -f, --fast Fast scan (ping only, no port scanning)"
echo " -v, --verbose Verbose output"
echo " -h, --help Show this help message"
echo ""
echo "Requirements:"
echo " - Must be connected to WiFi"
echo " - nmap (optional, for enhanced scanning)"
echo ""
echo "Install nmap with: brew install nmap"
}
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-f|--fast)
FAST_SCAN=true
shift
;;
-v|--verbose)
VERBOSE=true
shift
;;
-h|--help)
show_help
exit 0
;;
*)
echo "Unknown option: $1"
show_help
exit 1
;;
esac
done
log_verbose() {
if [[ "$VERBOSE" == true ]]; then
echo -e "${CYAN}[VERBOSE]${NC} $1"
fi
}
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Check if connected to WiFi
check_wifi_connection() {
log_verbose "Checking WiFi connection..."
# Get WiFi interface name
WIFI_INTERFACE=$(networksetup -listallhardwareports | awk '/Wi-Fi|AirPort/{getline; print $2}' | head -1)
if [[ -z "$WIFI_INTERFACE" ]]; then
log_error "No WiFi interface found"
exit 1
fi
log_verbose "WiFi interface: $WIFI_INTERFACE"
# Check if WiFi interface has an IP address (more reliable than networksetup)
WIFI_IP=$(ifconfig "$WIFI_INTERFACE" | grep 'inet ' | awk '{print $2}' | head -1)
if [[ -z "$WIFI_IP" ]]; then
log_error "WiFi interface has no IP address. Please connect to a WiFi network first."
exit 1
fi
# Try to get network name, but don't fail if we can't
WIFI_STATUS=$(networksetup -getairportnetwork "$WIFI_INTERFACE" 2>/dev/null)
if [[ "$WIFI_STATUS" == *"You are not associated with an AirPort network"* ]] || [[ -z "$WIFI_STATUS" ]]; then
# Fallback: try to get SSID using airport command
NETWORK_NAME=$(/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -I 2>/dev/null | awk -F': ' '/^\s*SSID:/ {print $2}')
if [[ -z "$NETWORK_NAME" ]]; then
NETWORK_NAME="Unknown Network"
fi
else
NETWORK_NAME=$(echo "$WIFI_STATUS" | sed 's/Current Wi-Fi Network: //')
fi
log_info "Connected to WiFi network: ${GREEN}$NETWORK_NAME${NC} (IP: ${GREEN}$WIFI_IP${NC})"
}
# Get local IP and subnet
get_network_info() {
log_verbose "Getting network information..."
# Get the active WiFi interface IP
LOCAL_IP=$(ifconfig "$WIFI_INTERFACE" | grep 'inet ' | awk '{print $2}' | head -1)
if [[ -z "$LOCAL_IP" ]]; then
log_error "Could not determine local IP address"
exit 1
fi
# Get subnet mask and convert to decimal if needed
NETMASK_RAW=$(ifconfig "$WIFI_INTERFACE" | grep 'inet ' | awk '{print $4}' | head -1)
# Convert hex netmask to dotted decimal
if [[ "$NETMASK_RAW" == 0x* ]]; then
# Convert hex to decimal, then to dotted notation
NETMASK_HEX=${NETMASK_RAW#0x}
NETMASK=$(printf "%d.%d.%d.%d\n" \
$((0x${NETMASK_HEX:0:2})) \
$((0x${NETMASK_HEX:2:2})) \
$((0x${NETMASK_HEX:4:2})) \
$((0x${NETMASK_HEX:6:2})))
else
NETMASK="$NETMASK_RAW"
fi
# Convert netmask to CIDR
CIDR=$(echo "$NETMASK" | awk -F. '{
cidr = 0
for (i = 1; i <= 4; i++) {
if ($i == 255) cidr += 8
else if ($i == 254) cidr += 7
else if ($i == 252) cidr += 6
else if ($i == 248) cidr += 5
else if ($i == 240) cidr += 4
else if ($i == 224) cidr += 3
else if ($i == 192) cidr += 2
else if ($i == 128) cidr += 1
else break
}
print cidr
}')
# Calculate network address (simplified for common cases)
NETWORK=$(echo "$LOCAL_IP" | awk -F. -v cidr="$CIDR" '{
if (cidr >= 24) {
print $1 "." $2 "." $3 ".0"
} else if (cidr >= 16) {
print $1 "." $2 ".0.0"
} else {
print $1 ".0.0.0"
}
}')
SUBNET="$NETWORK/$CIDR"
log_info "Local IP: ${GREEN}$LOCAL_IP${NC}"
log_info "Subnet: ${GREEN}$SUBNET${NC}"
log_verbose "Netmask: $NETMASK"
}
# Ping scan function
ping_scan() {
local subnet=$1
local found_hosts=()
log_info "Starting ping scan on $subnet..."
# Extract network portion and CIDR
local network=$(echo "$subnet" | cut -d'/' -f1)
local cidr=$(echo "$subnet" | cut -d'/' -f2)
# Calculate IP range based on common subnet sizes
local base_ip=$(echo "$network" | cut -d'.' -f1-3)
local start=1
local end=254
if [[ "$cidr" -eq 24 ]]; then
# /24 network - scan .1 to .254
start=1
end=254
elif [[ "$cidr" -eq 16 ]]; then
# /16 network - limit scan to current /24 block for performance
local last_octet=$(echo "$LOCAL_IP" | cut -d'.' -f4)
log_warning "Large /16 network detected. Limiting scan to current /24 block for performance."
fi
log_verbose "Scanning IP range: $base_ip.$start to $base_ip.$end"
# Parallel ping scan
for i in $(seq $start $end); do
{
local ip="$base_ip.$i"
if ping -c 1 -W $PING_TIMEOUT "$ip" >/dev/null 2>&1; then
echo "$ip"
fi
} &
# Limit concurrent processes
if (( $(jobs -r | wc -l) >= 50 )); then
wait
fi
done
# Wait for all background jobs to complete
wait
}
# Enhanced scan with nmap
nmap_scan() {
local subnet=$1
log_info "Starting enhanced nmap scan on $subnet..."
log_verbose "Running: nmap -sn $subnet"
# Host discovery scan
nmap -sn "$subnet" 2>/dev/null | grep -E "Nmap scan report|MAC Address" | while read -r line; do
if [[ "$line" == *"Nmap scan report"* ]]; then
echo "$line" | awk '{print $5}'
elif [[ "$line" == *"MAC Address"* ]] && [[ "$VERBOSE" == true ]]; then
echo " └─ $line"
fi
done
}
# Get hostname and additional info
get_host_info() {
local ip=$1
local hostname=""
local mac=""
local ports=""
# Try to get hostname
hostname=$(nslookup "$ip" 2>/dev/null | grep "name =" | awk '{print $4}' | sed 's/\.$//' | head -1)
if [[ -z "$hostname" ]]; then
hostname=$(dig +short -x "$ip" 2>/dev/null | sed 's/\.$//' | head -1)
fi
# Get MAC address if possible (only works for same subnet)
mac=$(arp -n "$ip" 2>/dev/null | awk '{print $4}' | head -1)
# Basic port scan if not fast mode and nmap available
if [[ "$FAST_SCAN" == false ]] && [[ "$NMAP_AVAILABLE" == true ]]; then
log_verbose "Port scanning $ip..."
ports=$(nmap -F --open "$ip" 2>/dev/null | grep "/tcp" | head -3 | awk '{print $1}' | tr '\n' ',' | sed 's/,$//')
fi
# Format output
local output="$ip"
if [[ -n "$hostname" ]]; then
output="$output (${GREEN}$hostname${NC})"
fi
if [[ -n "$mac" ]] && [[ "$mac" != "(incomplete)" ]]; then
output="$output [MAC: $mac]"
fi
if [[ -n "$ports" ]]; then
output="$output [Ports: $ports]"
fi
echo -e " $output"
}
# Main scanning function
scan_network() {
local hosts=()
echo ""
log_info "Scanning for active hosts..."
echo ""
if [[ "$NMAP_AVAILABLE" == true ]] && [[ "$FAST_SCAN" == false ]]; then
log_verbose "Using nmap for enhanced scanning"
while IFS= read -r host; do
if [[ -n "$host" ]]; then
hosts+=("$host")
fi
done < <(nmap_scan "$SUBNET")
else
if [[ "$NMAP_AVAILABLE" == false ]]; then
log_warning "nmap not found. Using basic ping scan. Install with: brew install nmap"
fi
log_verbose "Using ping scan"
while IFS= read -r host; do
if [[ -n "$host" ]]; then
hosts+=("$host")
fi
done < <(ping_scan "$SUBNET")
fi
# Sort hosts by IP
IFS=$'\n' hosts=($(sort -V <<<"${hosts[*]}"))
echo ""
if [[ ${#hosts[@]} -eq 0 ]]; then
log_warning "No active hosts found on the network"
return
fi
log_success "Found ${#hosts[@]} active host(s):"
echo ""
# Get detailed info for each host
for host in "${hosts[@]}"; do
if [[ "$host" == "$LOCAL_IP" ]]; then
echo -e " $host (${YELLOW}this device${NC})"
else
get_host_info "$host"
fi
done
echo ""
}
# Print summary
print_summary() {
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
log_info "Scan completed!"
echo ""
if [[ "$NMAP_AVAILABLE" == false ]]; then
echo "💡 Tip: Install nmap for enhanced scanning capabilities:"
echo " ${CYAN}brew install nmap${NC}"
echo ""
fi
if [[ "$FAST_SCAN" == false ]] && [[ "$NMAP_AVAILABLE" == true ]]; then
echo "💡 Use ${CYAN}-f${NC} or ${CYAN}--fast${NC} for quicker scans (ping only)"
echo ""
fi
}
# Main execution
main() {
echo ""
echo "🔍 WiFi Peer Discovery Script"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
check_wifi_connection
get_network_info
scan_network
print_summary
}
# Run main function
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment