Local Area Network discovery tool with a modern Terminal User Interface (TUI). Discover, explore, and understand your LAN in an intuitive way.
WhoIsOutThere performs unprivileged, concurrent scans using mDNS, SSDP, and ARP discovery. It sweeps the local subnet to trigger ARP resolution, then reads the ARP cache to identify devices on your network. All discovered devices are enhanced with OUI lookups to display manufacturers when available.
- Modern TUI — Navigate and explore discovered devices with Textual
- Fast & Concurrent — Leverages multiple discovery methods simultaneously
- No Elevated Privileges Required — Runs entirely in user-space
- Device Enrichment — MAC vendor lookup (built-in + online) and device type classification
- Daemon Mode with HTTP API — Run in the background and integrate with other tools
- Theming & Configuration — Personalize the look and behavior via YAML configuration
- Integrated Port Scanner — Async TCP port scanning with configurable concurrency
- Smart Classification — Automatically identifies device types (router, printer, phone, etc.)
- Local Host Exclusion — Automatically excludes the host machine from scan results
- Windows (primary, tested)
- Linux (supported via
/proc/net/arp) - macOS (planned)
- Python 3.10+
- PyYAML (for configuration)
- textual (for TUI)
- aiohttp (for daemon HTTP API)
- zeroconf (for mDNS scanning)
pip install -r requirements.txtOr install with optional dependencies:
# Core only (scan command)
pip install PyYAML zeroconf
# With TUI support
pip install PyYAML zeroconf textual
# With daemon HTTP API
pip install PyYAML zeroconf aiohttp
# With clipboard support (for copying IPs)
pip install PyYAML zeroconf textual pyperclip
# All features
pip install PyYAML zeroconf textual aiohttp pyperclipRun the TUI for interactive discovery (default command):
python -m whoisoutthere
# or explicitly:
python -m whoisoutthere tuiRun a single scan and print results to stdout:
python -m whoisoutthere scanAuto-Select Mode (Recommended):
The simplest way to run a scan is with --auto, which automatically selects the best available scanners based on your system's capabilities:
# Automatically use the best scanners for your system
python -m whoisoutthere scan --auto
# Auto with port scanning
python -m whoisoutthere scan --auto --ports
# Auto with passive monitoring (if admin + libpcap available)
python -m whoisoutthere scan --auto --passiveWhen running with admin privileges and scapy/libpcap installed, --auto will use:
- ActiveArpScanner — Raw ARP requests for reliable discovery
- IcmpPingScanner — Raw ICMP for fast ping sweeps (10-100x faster)
- MdnsScanner — mDNS/Bonjour service discovery
- SsdpScanner — UPnP/SSDP discovery
Without admin privileges, it falls back to:
- ArpScanner — OS ARP cache reading
- PingScanner — System ping command
- MdnsScanner & SsdpScanner — Service discovery
Include port scanning on discovered devices:
python -m whoisoutthere scan --ports
# or short form:
python -m whoisoutthere scan -pUse privileged scanners (requires admin + scapy/libpcap):
# Windows: Run as Administrator
# Linux: Run with sudo
python -m whoisoutthere scan --privilegedThe --privileged flag enables advanced scanners for more reliable device discovery:
- ActiveArpScanner — Sends raw ARP requests instead of reading the ARP cache
- IcmpPingScanner — Sends raw ICMP echo requests (10-100x faster than system ping)
Enable passive ARP monitoring to discover devices stealthily (requires admin + scapy/libpcap):
# Windows: Run as Administrator
# Linux: Run with sudo
# Monitor ARP traffic for 30 seconds (default)
python -m whoisoutthere scan --passive
# Monitor for a custom duration
python -m whoisoutthere scan --passive --passive-time 60
# Combine with active scanning for comprehensive discovery
python -m whoisoutthere scan --privileged --passive --passive-time 45The --passive flag enables PassiveArpMonitor which sniffs ARP traffic without sending any packets. This is useful for:
- Stealth discovery — Doesn't alert network intrusion detection systems
- Intermittent devices — Catches devices that communicate periodically
- Mobile devices — Detects phones/tablets as they wake up and communicate
Enable DHCP sniffing to catch devices as they join the network (requires admin + scapy/libpcap):
# Windows: Run as Administrator
# Linux: Run with sudo
# Monitor DHCP traffic for 60 seconds (default)
python -m whoisoutthere scan --dhcp
# Monitor for a custom duration
python -m whoisoutthere scan --dhcp --dhcp-time 120
# Combine with active and passive scanning for comprehensive discovery
python -m whoisoutthere scan --privileged --passive --dhcpThe --dhcp flag enables DhcpSniffer which monitors DHCP Discover/Request/ACK packets. This is especially useful for:
- New devices — Catches devices the moment they obtain an IP address
- Hostname extraction — Gets device hostname from DHCP options (Option 12)
- Device identification — Extracts vendor class identifier for better classification
- Dynamic networks — Ideal for networks with frequently joining/leaving devices
Enable TCP SYN (half-open) scanning for faster, stealthier port scans (requires admin + scapy/libpcap):
# Windows: Run as Administrator
# Linux: Run with sudo
# Use SYN scanning instead of TCP connect
python -m whoisoutthere scan --ports --syn
# Combine with privileged discovery for complete privileged scanning
python -m whoisoutthere scan --privileged --ports --synThe --syn flag enables SynScanner which sends TCP SYN packets and analyzes responses without completing the three-way handshake. This provides:
- Faster scanning — No full TCP connection overhead
- Stealthier operation — Less likely to trigger application-level logging
- Better results on firewalled hosts — Works even when hosts drop after SYN+ACK
- Host detection — Discovers hosts even when all ports are closed (via RST responses)
Enable IPv6 Neighbor Discovery Protocol (NDP) scanning to find IPv6-only and dual-stack devices (requires admin + scapy/libpcap):
# Windows: Run as Administrator
# Linux: Run with sudo
# Scan for IPv6 devices on the local network
python -m whoisoutthere scan --ipv6
# Combine with other privileged scanners
python -m whoisoutthere scan --privileged --ipv6
# Full privileged scan including IPv6
python -m whoisoutthere scan --privileged --passive --ipv6 --ports --synThe --ipv6 flag enables Ndp6Scanner which uses ICMPv6 Neighbor Discovery Protocol to find IPv6 hosts:
- Multicast ping — Sends ICMPv6 Echo Request to ff02::1 (all-nodes multicast)
- Router discovery — Sends Router Solicitation to ff02::2 to discover IPv6 routers
- Dual-stack devices — Finds devices that have IPv6 addresses even if they also have IPv4
- IPv6-only devices — Discovers devices that may not respond to IPv4 discovery methods
Enable LLDP/CDP sniffing to discover network infrastructure devices like switches and routers (requires admin + scapy/libpcap):
# Windows: Run as Administrator
# Linux: Run with sudo
# Monitor LLDP/CDP traffic for 60 seconds (default)
python -m whoisoutthere scan --lldp
# Monitor for a custom duration
python -m whoisoutthere scan --lldp --lldp-time 120
# Combine with other privileged scanners
python -m whoisoutthere scan --privileged --lldpThe --lldp flag enables LldpScanner which monitors Link Layer Discovery Protocol (LLDP) and Cisco Discovery Protocol (CDP) announcements. This is especially useful for:
- Network infrastructure — Discovers switches, routers, and managed network devices
- Port identification — Shows which switch port your device is connected to
- Device details — Extracts model, software version, and capabilities
- Management IPs — Gets management IP addresses for network devices
- VLAN info — Captures native VLAN and VTP domain (CDP)
Enable NetBIOS/LLMNR sniffing to passively capture Windows hostnames (requires admin + scapy/libpcap):
# Windows: Run as Administrator
# Linux: Run with sudo
# Monitor NetBIOS/LLMNR traffic for 60 seconds (default)
python -m whoisoutthere scan --nbns
# Monitor for a custom duration
python -m whoisoutthere scan --nbns --nbns-time 120
# Combine with other privileged scanners
python -m whoisoutthere scan --privileged --nbnsThe --nbns flag enables NbnsLlmnrSniffer which passively captures NetBIOS Name Service (UDP port 137) and Link-Local Multicast Name Resolution (LLMNR, UDP port 5355) traffic. This is especially useful for:
- Windows hostnames — Captures Windows device hostnames without active queries
- Service discovery — Extracts NetBIOS service types (Workstation, File Server, Domain Controller, etc.)
- Name conflicts — Detects NetBIOS name registration and conflicts
- Modern Windows — LLMNR captures work with Windows Vista and later when DNS fails
- Passive operation — Works without sending any packets, completely stealthy
For the most thorough network discovery, combine all privileged options:
# Windows: Run as Administrator
# Linux: Run with sudo
# Full privileged scan with all features (takes ~60s due to passive monitoring)
python -m whoisoutthere scan --privileged --passive --dhcp --ipv6 --lldp --nbns --ports --syn
# Faster privileged scan without passive monitoring (~10s)
python -m whoisoutthere scan --privileged --ports --syn
# Privileged discovery with standard TCP connect port scan
python -m whoisoutthere scan --privileged --portsNote: The --privileged flag enables faster active scanning (ActiveArpScanner, IcmpPingScanner), --syn enables faster port scanning (SynScanner), --ipv6 enables IPv6 neighbor discovery (Ndp6Scanner), --lldp discovers network infrastructure (LldpScanner), and --nbns captures Windows hostnames (NbnsLlmnrSniffer). The passive options (--passive, --dhcp, --lldp, --nbns) require additional time but catch devices and infrastructure that active scanning might miss.
Run as a daemon with HTTP API:
python -m whoisoutthere daemon
python -m whoisoutthere daemon --port 8080
python -m whoisoutthere daemon --host 0.0.0.0 --port 8765Check what scanner capabilities are available on your system:
python -m whoisoutthere capabilitiesThis shows:
- System capabilities (admin privileges, libpcap availability)
- Which scanners are available based on current privileges
- Instructions for enabling advanced scanning features
Example output:
Scanner Capabilities
============================================================
System Capabilities:
----------------------------------------
[+] ADMIN available
[+] LIBPCAP available
[+] RAW_SOCKETS available
Scanner Availability:
----------------------------------------
Basic Scanners (always available):
[+] ArpScanner OS ARP cache reading
[+] PingScanner System ping command
[+] MdnsScanner mDNS/Bonjour discovery
[+] SsdpScanner UPnP/SSDP discovery
Privileged Scanners (require admin + libpcap):
[+] ActiveArpScanner Raw ARP requests (faster, more reliable)
[+] IcmpPingScanner Raw ICMP ping (10-100x faster)
[+] SynScanner TCP SYN half-open scanning
...
Quick Start:
----------------------------------------
Privileged scanning is available!
Use: whoisoutthere scan --auto
python -m whoisoutthere version| Key | Action |
|---|---|
j |
Move cursor down |
k |
Move cursor up |
g |
Go to top of list |
G |
Go to bottom of list |
y |
Copy IP of selected device |
Enter |
Show device details |
p |
Port scan (in detail view) |
Ctrl+T |
Toggle theme selector |
Ctrl+C |
Quit application |
Escape |
Go back / dismiss modal |
q |
Go back (in detail view) |
r |
Manual refresh |
| Variable | Description |
|---|---|
WHOISOUTTHERE_CONFIG |
Path to the configuration file to override the default location |
WHOISOUTTHERE_LOG |
Set the log level (e.g., debug, info, warn, error) |
WhoIsOutThere can be configured via a YAML configuration file. By default, it looks for the configuration file in the following order:
- Path specified via
--configcommand-line argument - Path specified in the
WHOISOUTTHERE_CONFIGenvironment variable - Platform-specific default paths:
- Windows:
%APPDATA%\WhoIsOutThere\config.yaml - Linux:
$XDG_CONFIG_HOME/WhoIsOutThere/config.yamlor~/.config/WhoIsOutThere/config.yaml
- Windows:
# Discovery settings
discovery:
timeout_seconds: 10.0 # How long to wait for discovery responses
scan_interval_seconds: 30.0 # How often TUI rescans the network
auto_select: false # Automatically select best scanners based on capabilities
mdns_enabled: true
ssdp_enabled: true
arp_enabled: true
arp_sweep_rate: 1000 # UDP packets/sec for ARP sweep (0 = unlimited)
ping_enabled: true # Enable ICMP ping sweep
ping_concurrency: 64 # Concurrent ping processes
vendor_lookup_online: true # Lookup unknown MAC vendors online
privileged: false # Use privileged scanners (requires admin + libpcap)
passive_arp: false # Enable passive ARP monitoring (requires admin + libpcap)
passive_arp_time: 30.0 # Duration for passive ARP monitoring in seconds
dhcp_sniff: false # Enable DHCP traffic sniffing (requires admin + libpcap)
dhcp_sniff_time: 60.0 # Duration for DHCP sniffing in seconds
syn_scan: false # Use TCP SYN scanning instead of TCP connect (requires admin + libpcap)
ipv6_ndp: false # Enable IPv6 Neighbor Discovery Protocol scanning (requires admin + libpcap)
lldp_sniff: false # Enable LLDP/CDP sniffing for network infrastructure (requires admin + libpcap)
lldp_sniff_time: 60.0 # Duration for LLDP/CDP sniffing in seconds
nbns_llmnr_sniff: false # Enable NetBIOS/LLMNR sniffing for Windows hostname discovery (requires admin + libpcap)
nbns_llmnr_sniff_time: 60.0 # Duration for NetBIOS/LLMNR sniffing in seconds
# Uncomment to scan a specific network interface
# interface: Wi-Fi
# Port scanner settings
port_scan:
enabled: false # Disabled by default; use --ports flag or enable here
ports: [22, 80, 443, 445, 3389, 8080] # Ports to scan
timeout_seconds: 0.5 # Connection timeout per port
concurrency: 100 # Max simultaneous TCP connections
# Daemon settings
daemon:
host: 127.0.0.1
port: 8765
scan_interval_seconds: 30.0
scan_timeout_seconds: 5.0
# Logging settings
logging:
level: info
to_stdout: true
file: whoisoutthere.log
# UI settings
ui:
theme: default
show_splash: trueWhoIsOutThere includes a capability detection module that checks for advanced scanning prerequisites. This enables the tool to automatically select the best available scanners based on system privileges and installed dependencies.
from whoisoutthere.discovery.scanners import (
check_admin,
check_libpcap,
get_available_scanners,
get_capabilities,
)
# Check individual capabilities
print(f"Admin privileges: {check_admin()}")
print(f"Libpcap available: {check_libpcap()}")
# Get all available scanners based on current privileges
scanners = get_available_scanners()
print(f"Available scanners: {scanners}")
# Get detailed capability status
for cap, status in get_capabilities().items():
if status.available:
print(f"{cap.name}: Available")
else:
print(f"{cap.name}: {status.reason}")| Capability | Description | How to Enable |
|---|---|---|
| ADMIN | Administrator/root privileges | Run as Administrator (Windows) or with sudo (Linux) |
| LIBPCAP | Packet capture library | Install scapy + Npcap (Windows) or libpcap-dev (Linux) |
| RAW_SOCKETS | Raw socket access | Typically requires admin privileges |
Unprivileged (always available):
- ArpScanner (OS cache reading)
- HostnameScanner (DNS/NetBIOS)
- MdnsScanner (multicast)
- PingScanner (system ping)
- PortScanner (TCP connect)
- SsdpScanner (multicast)
Privileged (requires admin + libpcap):
- ActiveArpScanner (raw ARP requests)
- PassiveArpMonitor (ARP sniffing)
- DhcpSniffer (DHCP monitoring)
- IcmpPingScanner (raw ICMP)
- SynScanner (TCP SYN scanning)
- Ndp6Scanner (IPv6 neighbor discovery)
- LldpScanner (CDP/LLDP sniffing)
- NbnsLlmnrSniffer (NetBIOS/LLMNR)
Windows:
- Install Npcap with "WinPcap API-compatible Mode" checked
- Install scapy:
pip install scapy
Linux:
# Debian/Ubuntu
sudo apt install libpcap-dev
pip install scapy
# RHEL/CentOS/Fedora
sudo yum install libpcap-devel
pip install scapyWhoIsOutThere provides several configurable parameters to control network scan aggressiveness. These settings help balance discovery speed against network stability.
| Config Key | Default | Description |
|---|---|---|
discovery.timeout_seconds |
10.0 | How long to wait for discovery responses |
discovery.scan_interval_seconds |
30.0 | How often the TUI rescans the network |
discovery.ping_enabled |
true | Enable ICMP ping sweep for finding devices |
discovery.ping_concurrency |
64 | Maximum concurrent ping processes |
discovery.arp_enabled |
true | Enable ARP-based discovery |
discovery.arp_sweep_rate |
1000 | UDP packets per second for ARP sweep (0 = unlimited) |
discovery.privileged |
false | Use privileged scanners when available (ActiveArpScanner) |
discovery.passive_arp |
false | Enable passive ARP monitoring (requires admin + libpcap) |
discovery.passive_arp_time |
30.0 | Duration for passive ARP monitoring in seconds |
discovery.dhcp_sniff |
false | Enable DHCP traffic sniffing (requires admin + libpcap) |
discovery.dhcp_sniff_time |
60.0 | Duration for DHCP sniffing in seconds |
discovery.syn_scan |
false | Use TCP SYN scanning instead of TCP connect (requires admin + libpcap) |
discovery.ipv6_ndp |
false | Enable IPv6 Neighbor Discovery Protocol scanning (requires admin + libpcap) |
discovery.lldp_sniff |
false | Enable LLDP/CDP sniffing for network infrastructure (requires admin + libpcap) |
discovery.lldp_sniff_time |
60.0 | Duration for LLDP/CDP sniffing in seconds |
discovery.nbns_llmnr_sniff |
false | Enable NetBIOS/LLMNR sniffing for Windows hostname discovery (requires admin + libpcap) |
discovery.nbns_llmnr_sniff_time |
60.0 | Duration for NetBIOS/LLMNR sniffing in seconds |
port_scan.enabled |
false | Port scanning disabled by default; use --ports flag |
port_scan.concurrency |
100 | Maximum simultaneous TCP connections |
port_scan.timeout_seconds |
0.5 | Connection timeout per port |
Default (works for most networks):
# These are the default values - no config needed
discovery:
ping_concurrency: 64
arp_sweep_rate: 1000
port_scan:
concurrency: 100Aggressive (fast, stable Gigabit network):
discovery:
ping_concurrency: 128
arp_sweep_rate: 2000
port_scan:
concurrency: 200Conservative (slower WiFi networks):
discovery:
ping_concurrency: 32
arp_sweep_rate: 500
port_scan:
concurrency: 50Minimal Network Impact:
discovery:
scan_interval_seconds: 60.0
ping_concurrency: 16
arp_sweep_rate: 200
port_scan:
concurrency: 25
timeout_seconds: 1.0If you experience network connectivity issues during scans:
- Reduce
ping_concurrency— Lower values reduce simultaneous ICMP packets - Reduce
arp_sweep_rate— Lower values slow down UDP packet transmission - Reduce
port_scan.concurrency— Lower values reduce simultaneous TCP connections - Increase
scan_interval_seconds— Longer intervals give the network time to recover
from whoisoutthere.discovery import DiscoveryEngine
from whoisoutthere.discovery.scanners import ArpScanner, PingScanner, PortScanner
# Create scanners with custom concurrency settings
scanners = [
ArpScanner(
interface="Wi-Fi",
sweep_rate=300, # Slower UDP sweep
),
PingScanner(
interface="Wi-Fi",
concurrency=16, # Fewer concurrent pings
),
]
engine = DiscoveryEngine(scanners)
devices = engine.scan_sync(timeout=10)
# Port scan with reduced concurrency
scanner = PortScanner(
targets=[d.ip for d in devices if d.ip],
concurrency=25, # Fewer simultaneous connections
timeout=1.0, # Longer timeout per port
)The simplest way to use the discovery engine is with automatic scanner selection:
from whoisoutthere.discovery import DiscoveryEngine
# Automatically select the best scanners based on system capabilities
engine = DiscoveryEngine.create_with_best_scanners()
devices = engine.scan_sync(timeout=10)
for device in devices:
print(f"{device.ip} - {device.mac} - {device.vendor or 'unknown'}")With more options:
from whoisoutthere.discovery import DiscoveryEngine
# Create engine with options
engine = DiscoveryEngine.create_with_best_scanners(
target_network="192.168.1.0/24", # Specific network (or None for auto-detect)
prefer_privileged=True, # Use advanced scanners if available
include_passive=True, # Include passive monitoring scanners
passive_timeout=60.0, # Duration for passive scanners
)
# Check what capabilities are available
caps = DiscoveryEngine.get_scanner_capabilities_info()
print(f"Admin: {caps['admin']}")
print(f"Libpcap: {caps['libpcap']}")
print(f"Privileged scanners: {caps['privileged_scanners']}")
# Get just the scanners without creating an engine
scanners = DiscoveryEngine.get_best_scanners(prefer_privileged=True)
print(f"Selected scanners: {[s.Name() for s in scanners]}")from whoisoutthere.discovery import DiscoveryEngine
from whoisoutthere.discovery.scanners import can_use_privileged_scanners
if can_use_privileged_scanners():
# Use raw socket scanners for faster, more reliable discovery
from whoisoutthere.discovery.scanners.arp_active import ActiveArpScanner
from whoisoutthere.discovery.scanners.icmp import IcmpPingScanner
scanners = [
ActiveArpScanner(timeout=3.0), # Raw ARP requests
IcmpPingScanner(timeout=3.0), # Raw ICMP (10-100x faster)
]
else:
# Fall back to unprivileged scanners
from whoisoutthere.discovery.scanners import ArpScanner, PingScanner
scanners = [
ArpScanner(), # Uses OS ARP cache
PingScanner(), # Uses system ping command
]
engine = DiscoveryEngine(scanners)
devices = engine.scan_sync(timeout=10)State and log files are stored in platform-specific directories:
| Platform | State Directory | Log File |
|---|---|---|
| Windows | %LOCALAPPDATA%\WhoIsOutThere |
%LOCALAPPDATA%\WhoIsOutThere\whoisoutthere.log |
| Linux | $XDG_STATE_HOME/WhoIsOutThere or ~/.local/state/WhoIsOutThere |
Same directory |
Set logging.file in config to override the log file location.
When running WhoIsOutThere in daemon mode, it exposes a REST API with the following endpoints:
| Method | Endpoint | Description |
|---|---|---|
| GET | /devices |
Get list of all discovered devices |
| GET | /device/{ip} |
Get details of a specific device |
| POST | /device/{ip}/scan |
Run port scan on a specific device |
| POST | /scan/ports |
Run port scan on all discovered devices |
| GET | /health |
Health check |
# List all devices
curl http://localhost:8765/devices
# Get a specific device
curl http://localhost:8765/device/192.168.1.100
# Port scan a specific device
curl -X POST http://localhost:8765/device/192.168.1.100/scan
# Port scan with custom ports
curl -X POST http://localhost:8765/device/192.168.1.100/scan \
-H "Content-Type: application/json" \
-d '{"ports": [22, 80, 443, 8080], "timeout": 1.0}'
# Port scan all discovered devices
curl -X POST http://localhost:8765/scan/ports
# Health check
curl http://localhost:8765/health{
"ip": "192.168.1.100",
"mac": "aa:bb:cc:dd:ee:ff",
"hostname": "my-device.local",
"vendor": "Apple, Inc.",
"interface": "Wi-Fi",
"first_seen": "2025-01-24T10:30:00Z",
"last_seen": "2025-01-24T12:45:00Z",
"sources": ["arp", "mdns", "portscan"],
"services": ["_http._tcp.local."],
"ips": ["192.168.1.100"],
"ports": [22, 80, 443],
"metadata": {}
}{
"ip": "192.168.1.100",
"ports": [22, 80, 443],
"count": 3
}{
"scanned": 15,
"with_open_ports": 3,
"results": {
"192.168.1.1": [53, 80],
"192.168.1.100": [22, 80, 443],
"192.168.1.150": [22]
}
}Theme can be configured via the configuration file or selected at runtime via Ctrl+T.
| Theme | Description |
|---|---|
default |
Dark theme with purple accents |
dracula |
Popular dark color scheme |
nord |
Arctic, bluish colors |
gruvbox-dark |
Retro groove colors |
tokyonight |
Tokyo night palette |
catppuccin-mocha |
Soothing pastel theme |
rose-pine |
Soho vibes |
onedark |
Atom One Dark inspired |
monokai |
Classic Monokai |
cyberpunk |
Neon cyber colors |
matrix |
Green terminal style |
high-contrast |
High visibility |
solarized-dark |
Solarized dark |
ui:
theme: dracula
show_splash: trueimport asyncio
from whoisoutthere.discovery import DiscoveryEngine, ArpScanner, MdnsScanner, SsdpScanner, PortScanner
# Create scanners
scanners = [
ArpScanner(interface="Wi-Fi"), # Filter by interface name
MdnsScanner(),
SsdpScanner(),
]
# Run discovery (local host is automatically excluded)
engine = DiscoveryEngine(scanners)
devices = asyncio.run(engine.scan(timeout=5))
# To include local host in results, set exclude_local_host=False
# engine = DiscoveryEngine(scanners, exclude_local_host=False)
for device in devices:
print(f"{device.ip} - {device.mac} - {device.hostname or 'unknown'}")import asyncio
from whoisoutthere.discovery.scanners import PortScanner
async def scan_ports():
# Scan specific hosts for open ports
scanner = PortScanner(
targets=["192.168.1.1", "192.168.1.100"],
ports=[22, 80, 443, 445, 3389, 8080],
timeout=0.5,
concurrency=50, # Adjust based on network conditions
)
results = []
async def collect(device):
if device.ports:
results.append(device)
print(f"{device.ip}: {sorted(device.ports)}")
await scanner.scan(collect)
return results
asyncio.run(scan_ports())import asyncio
from whoisoutthere.discovery import DiscoveryEngine, ArpScanner, PortScanner
async def full_scan():
# Phase 1: Discover devices
engine = DiscoveryEngine([ArpScanner()])
devices = await engine.scan(timeout=5)
# Phase 2: Port scan discovered devices
target_ips = [d.ip for d in devices if d.ip]
scanner = PortScanner(targets=target_ips, ports=[22, 80, 443])
port_results = {}
async def collect(device):
if device.ip and device.ports:
port_results[device.ip] = sorted(device.ports)
await scanner.scan(collect)
# Print results
for device in devices:
ports = port_results.get(device.ip, [])
print(f"{device.ip} ({device.mac}): ports {ports}")
asyncio.run(full_scan())WhoIsOutThere includes a high-performance async TCP port scanner that integrates with all components.
- Async TCP Connect — Uses
asyncio.open_connectionfor non-blocking scans - Configurable Concurrency — Control simultaneous connections (default: 50)
- Timeout Control — Per-port connection timeout (default: 0.5s)
- Default Ports — Common service ports: 21, 22, 23, 53, 80, 443, 139, 445, 3389, 5900, 8080, 8443
Port scanning is disabled by default. You can enable it via CLI flag or configuration:
CLI Flag (recommended for one-time scans):
python -m whoisoutthere scan --portsConfiguration File (for persistent setting):
port_scan:
enabled: true
ports: [22, 53, 80, 443, 445, 3389, 8080]
timeout_seconds: 0.5
concurrency: 100 # Max simultaneous TCP connectionsCLI Scan with Ports:
python -m whoisoutthere scan --ports
# or with custom config:
python -m whoisoutthere --config config.yaml scanTUI: Open device details (Enter) and press p to scan ports on that device.
Daemon: Use the REST API endpoints /device/{ip}/scan or /scan/ports.
IP MAC Hostname Vendor [Sources]
--------------------------------------------------------------------------------
192.168.1.1 a0:95:7f:ab:cb:11 [arp]
192.168.1.100 60:83:e7:d2:bb:18 mydevice [arp, mdns]
--------------------------------------------------------------------------------
Found 2 device(s)
Scanning ports...
IP Open Ports
--------------------------------------------------
192.168.1.1 53
192.168.1.100 22, 80, 443
WhoIsOutThere identifies device manufacturers using MAC address OUI (Organizationally Unique Identifier) lookup.
- Built-in Database — Contains 2000+ common vendor OUI prefixes (Apple, Samsung, Intel, etc.)
- External OUI File — Optional
oui.txtfile in the data directory (IEEE format) - Online API — Falls back to macvendors.com API for unknown MACs (results cached for 30 days)
Online lookup is enabled by default. To disable it:
discovery:
vendor_lookup_online: falseVendor lookup results are cached in:
- Windows:
%LOCALAPPDATA%\WhoIsOutThere\vendor-cache.json - Linux:
~/.local/share/WhoIsOutThere/vendor-cache.json
Devices are automatically classified based on multiple signals:
| Signal | Examples |
|---|---|
| Open Ports | Port 631 → Printer, Port 3389 → Computer, Port 32400 → Plex |
| MAC Vendor | Synology → NAS, Nintendo → Game Console, Philips Hue → Smart Home |
| Services | _printer._tcp → Printer, _googlecast._tcp → Streaming Device |
| Hostname | Contains "router" → Router, "nas" → NAS, "iphone" → Smartphone |
| Type | Description |
|---|---|
| Computer | Desktop, laptop, workstation |
| Server | Web server, mail server, database |
| Router | Gateway, firewall, network switch |
| Printer | Network printers |
| Smartphone | Mobile phones, tablets |
| Smart TV | Television with network features |
| NAS | Network attached storage |
| Smart Home | Hue, Nest, Ring, etc. |
| Game Console | PlayStation, Xbox, Nintendo |
| Virtual Machine | VMware, VirtualBox, Hyper-V |
By default, WhoIsOutThere automatically excludes the machine running the scan from the results. This prevents the host from appearing as a discovered device and provides cleaner scan output.
The discovery engine detects all IP addresses assigned to the local machine using multiple methods:
- Loopback addresses (127.0.0.1, ::1)
- Primary IP via socket connection
- All IPs from hostname resolution
- Platform-specific enumeration (ipconfig on Windows, ip addr on Linux)
Any device matching a local IP address is automatically filtered out during the scan.
If you need to include the local host in scan results (e.g., for testing), you can disable this behavior:
from whoisoutthere.discovery import DiscoveryEngine, ArpScanner
# Include local host in results
engine = DiscoveryEngine([ArpScanner()], exclude_local_host=False)
devices = engine.scan_sync(timeout=5)You can also retrieve the local IP addresses programmatically:
from whoisoutthere.discovery import get_local_ip_addresses
local_ips = get_local_ip_addresses()
print(f"Local IPs: {local_ips}")
# Output: Local IPs: {'127.0.0.1', '::1', '192.168.1.50', 'fe80::1234:5678:abcd:ef01'}WhoIsOutThere works with both PowerShell 5.1 and PowerShell 7+:
# PowerShell 5.1 or 7+
python -m whoisoutthere
# Or run from source
cd C:\Dev\WhoIsOutThere
python -m whoisoutthere scanFor the best TUI experience, use Windows Terminal (available from Microsoft Store). It provides proper Unicode rendering and color support.
On Windows, the ARP scanner uses iphlpapi.dll via ctypes to read the ARP cache.
This requires no elevated privileges and works with the standard Windows networking stack.
Implementation details:
- Uses
GetIpNetTable2to retrieve the ARP cache (IPv4 and IPv6 entries) - Uses
FreeMibTableto properly release allocated memory - Uses
GetAdaptersAddressesfor interface enumeration and filtering - Parses
MIB_IPNET_ROW2structures to extract IP, MAC, and interface index - Supports filtering by interface name (e.g.,
--interface "Wi-Fi")
The ARP sweeper sends UDP packets to port 9 (discard) across the local subnet to trigger ARP resolution before reading the cache. This populates the cache with devices that haven't communicated recently.
Configuring ARP Sweep Rate:
The arp_sweep_rate parameter controls how many UDP packets per second are sent during the sweep. The default of 1000 packets/second provides a good balance between speed and network stability.
discovery:
arp_sweep_rate: 1000 # Packets/sec (0 = unlimited, reduce for slow networks)WhoIsOutThere is intended for use on networks where you have permission to perform network discovery and scanning, such as your own home network. Unauthorized scanning of networks may be illegal and unethical. Always obtain proper authorization before using this tool on any network.
- whosthere by Ramon Vermeulen — A Local Area Network discovery tool with a modern Terminal User Interface (TUI) written in Go.
See LICENSE for details.