23 August 2026

Mapping 101 Devices Across a /16 with nmap in Four Minutes

I needed a current inventory of everything alive on our 10.140.0.0/16 network. The infrastructure had grown organically over a couple of years — Proxmox clusters, Incus containers, WiFi access points from three different vendors, IoT devices, a Windows machine, and a NAS — without a single maintained source of truth. Time to build one from scratch.

This is the method: a two-phase nmap scan, using MAC OUI lookups to classify devices before touching a single one of them.


The Setup

The scan ran from a Linux workstation directly attached to the 10.140.0.0/16 network via a wired interface (enx803f5df84e07, IP 10.140.0.192). The machine also hosts an Incus bridge (incusbr0, 10.140.20.1) giving it a second window into the container subnet.

Because both addresses sit on the same Layer 2 broadcast domain, nmap can use ARP to resolve MAC addresses for every host in the range — not just the directly connected subnets. That makes the OUI-based device classification accurate across the whole scan, not just for local neighbours.

Target range: 10.140.0.0 through 10.140.25.255 — the first 26 /24 blocks of the /16, covering all assigned infrastructure. That's 6,656 addresses.


Phase 1: Ping Sweep

The first pass is a host-discovery-only scan (-sn). No port probing, just ICMP echo and ARP:

sudo nmap -sn --open -T4 10.140.0-25.0-255 -oG /tmp/nmap_sweep.txt

-oG saves the output in greppable format. With ARP on a flat L2 network, this is fast — 6,656 addresses in just over two minutes. Result: 101 live hosts.

Extracting the IP list for phase two:

grep "^Host:" /tmp/nmap_sweep.txt | awk '{print $2}' | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n > /tmp/live_hosts.txt

Phase 2: Port and Service Scan

With 101 hosts identified, the full port scan targets only those — no wasted probes against dead addresses:

sudo nmap -iL /tmp/live_hosts.txt -sV --version-intensity 1 -F -T4 --open -R --host-timeout 60s -oN /tmp/nmap_portscan.txt -oG /tmp/nmap_portscan_greppable.txt

Key flags:
- -F — top 100 ports. Fast, catches SSH, HTTP, RDP, PostgreSQL, SMB, and most application ports.
- --version-intensity 1 — minimal service banner probing. Enough to identify OpenSSH versions and web server names without sending dozens of probes per port.
- -R — resolve hostnames for all hosts, not just ones nmap already has names for.
- --host-timeout 60s — don't let a single unresponsive host stall the scan.
- --open — only report open ports. Keeps the output clean.

101 hosts, top 100 ports each: 153 seconds. Two and a half minutes.


Phase 3: Classifying Devices by MAC OUI

The most useful output from a network scan often isn't the port list — it's the MAC address. The first three octets (the OUI) identify the manufacturer, and on a managed network that maps almost directly to device type.

Parsing the combined output with Python:

import re, ipaddress

hosts = {}

with open('/tmp/nmap_portscan.txt') as f:
    current_ip = None
    for line in f:
        line = line.rstrip()
        m = re.search(r'Nmap scan report for (?:\S+ \()?(\d+\.\d+\.\d+\.\d+)\)?', line)
        if m:
            current_ip = m.group(1)
            hosts.setdefault(current_ip, {'hostname': '', 'mac': '', 'vendor': '', 'ports': []})
            hm = re.search(r'for (\S+) \(', line)
            if hm:
                hosts[current_ip]['hostname'] = hm.group(1)
        elif 'MAC Address:' in line and current_ip:
            m = re.match(r'MAC Address:\s+(\S+)\s+\(([^)]*)\)', line.strip())
            if m:
                hosts[current_ip]['mac'] = m.group(1)
                hosts[current_ip]['vendor'] = m.group(2)

What emerged:

OUI Vendor Device type
BC:24:11 Proxmox GmbH Proxmox VMs and LXC containers
10:66:6A Ruckus Networks APs and switches
50:C7:BF TP-Link WiFi access points
68:1D:EF Shenzhen CYX Technology Proxmox bare-metal host (PVE1)
E8:DB:84 Espressif ESP8266/ESP32 IoT device
68:37:E9 Amazon Technologies Amazon Echo or Fire device
AC:3B:77, 18:1E:78, 34:8A:AE, C8:91:F9 Sagemcom Router/AP firmware devices
00:18:0A Cisco Meraki Meraki AP

The BC:24:11 prefix was the biggest reveal. Proxmox assigns MAC addresses from its own OUI pool to every VM and container it creates. Once you know that, you can identify every virtual machine on the network at a glance — no hostname needed.


What 101 Hosts Looks Like

Summarised by subnet:

Subnet Live Character
10.140.0.x 18 Mix: TP-Link APs, Proxmox VMs, one Espressif IoT device
10.140.1.x 20 Mostly Proxmox VMs, more TP-Link APs, one Amazon device
10.140.2.x 8 Network infrastructure — Sagemcom APs, Cisco Meraki, default gateway
10.140.3.x 29 Dense Proxmox cluster — VMs, NFS servers, PVE1 bare metal
10.140.4.x 1 Single PostgreSQL VM
10.140.6.x 1 Media streaming device (Luxshare MAC, ports 8080/8443/RTSP)
10.140.10.x 1 Windows PC — RDP, SMB, WinRM all open
10.140.20.x 23 Incus container subnet — known services

The 10.140.3.x subnet was the surprise. Twenty-nine live hosts, almost all Proxmox VMs (BC:24:11 MACs), with the actual PVE1 hypervisor sitting at 10.140.3.10. Several machines expose port 3128 (Squid proxy) alongside SSH and NFS — a cluster pattern I hadn't documented before.


Interesting Finds

10.140.3.200 — the undocumented NAS. Ports 21 (ProFTPD), 22, 80/443 (nginx), 111/2049 (NFS), 139/445 (Samba), 5357 (WS-Discovery). That's a full-featured NAS behind a Proxmox VM MAC. Worth investigating what's stored there.

10.140.10.104 — Windows PC with everything open. RDP on 3389, SMB on 445, MSRPC on 135, NetBIOS on 139, WinRM on 80. Intel NIC. Not in any documentation. The Tailscale topology might explain how it's routable across subnets.

10.140.2.50 — telnet still open. SSH and HTTP/HTTPS alongside port 23. On a 10.140.2.x device that looks like network infrastructure. Needs a closer look.

10.140.0.204 — Espressif on port 8081. The MAC prefix nails it as an ESP8266 or ESP32. One of the IoT devices, running its own HTTP service. Not in the known device list.

Ruckus MACs in the container subnet. The three Ruckus R720 APs documented as being at 10.140.2.16–18 didn't respond at those IPs. Ruckus-prefix MACs (10:66:6A) appear instead across 10.140.20.x — the same range as the Incus containers. Some of those IPs match known containers (faster-whisper at .6, open-webui at .61), suggesting either the MAC assignment in Incus is pulling from the Ruckus OUI range, or some APs have shifted IP leases. Needs verification.


Access Points Identified

Fourteen confirmed APs across three vendors:

TP-Link (port 9999 = TP-Link TDDP device management):
10.140.0.22, 10.140.0.63, 10.140.0.235, 10.140.1.108, 10.140.1.228, 10.140.1.238

Sagemcom (Dropbear SSH + DNS + HTTP — standard AP firmware):
10.140.2.9, 10.140.2.11, 10.140.2.12, 10.140.2.13

Cisco Meraki:
10.140.2.15

Ruckus (port 8080 = Ruckus web management UI):
10.140.20.15, 10.140.20.16, 10.140.20.30


The Output

Raw files saved at /tmp/nmap_portscan.txt and /tmp/nmap_portscan_greppable.txt. The full structured inventory is at docs/network-scan-10.140.0-25.md — 101 rows, grouped by subnet, with MAC, vendor, open ports, and annotations for known services.

Total elapsed: under four minutes from cold start to annotated inventory.


Caveats

The -F flag only covers the top 100 ports by frequency. Several known services run on non-standard ports and won't appear: the LiteLLM proxy (4000), Kokoro TTS (8880), and Ollama (11434) are all in the container subnet but showed no open ports in this scan. For services on non-standard ports, follow up with a targeted scan against the known host list:

sudo nmap -iL /tmp/live_hosts.txt -p 4000,8880,11434,8188 --open -T4

ARP-based MAC resolution only works if the scanner is on the same L2 segment. On a routed network, MAC addresses won't be visible for remote hosts — you'd need to query the ARP caches on intermediate switches or run the scan from each segment.


Scan conducted 2026-05-07. nmap 7.94SVN. Scanner: 10.140.0.192/16 (Ubuntu 24.04, kernel 6.17).

No comments:

Post a Comment