[{"data":1,"prerenderedAt":2940},["ShallowReactive",2],{"post:\u002F2026\u002F01\u002F07\u002Fpython-discovery-fingerprint-every-device-on-a-subnet\u002F":3},{"post":4,"newer":2893,"older":2902,"related":2911,"series":2938},{"id":5,"title":6,"body":7,"canonical":2875,"categories":2876,"date":2879,"description":2880,"extension":2881,"featured":2882,"hero":2883,"image":2883,"meta":2884,"navigation":223,"path":2885,"readingTime":220,"seo":2886,"series":2883,"seriesOrder":2883,"sites":2887,"source":2883,"stem":2888,"tags":2889,"updated":2883,"url":2891,"__hash__":2892},"blog\u002Fblog\u002F2026\u002F01\u002F07\u002Fpython-discovery-fingerprint-every-device-on-a-subnet.md","Python: Discovery – Fingerprint Every Device on a Subnet",{"type":8,"value":9,"toc":2868},"minimark",[10,14,32,37,84,88,91,128,131,172,180,195,199,2730,2734,2808,2812,2864],[11,12,13],"p",{},"Commercial discovery tools are great once they're deployed, but there's a recurring need for something faster and cheaper: a quick, scriptable sweep of a subnet during a site walk, a merger due-diligence pass, or a \"what's actually plugged into this switch\" moment. I keep a small Python script around for exactly this. It ARP-sweeps the subnet to find live hosts, maps each MAC address to its registered vendor for a rough device-type guess, probes a handful of common ports, pulls a service banner where one is offered, and writes everything to a CSV I can drop straight into a spreadsheet or a ticket.",[11,15,16,17,22,23,27,28,31],{},"It's the \"what is on this VLAN\" collector in the ",[18,19,21],"a",{"href":20},"\u002F2026\u002F07\u002F15\u002Finfrastructure-a-discovery-first-approach-to-documenting-an-undocumented-network\u002F","discovery-first approach to documenting an undocumented network",". When nmap is available and allowed, ",[24,25,26],"code",{},"nmap -sn"," does the same ARP discovery; this script is for the boxes where you'd rather ",[24,29,30],{},"pip install"," than get a scanner approved.",[33,34,36],"h2",{"id":35},"requirements","Requirements",[38,39,40,44,55,62,70,81],"ul",{},[41,42,43],"li",{},"Python 3.10 or later.",[41,45,46,47,50,51,54],{},"The ",[24,48,49],{},"scapy"," package (",[24,52,53],{},"pip install scapy",") for the ARP sweep.",[41,56,57,58,61],{},"Root on Linux\u002FmacOS (",[24,59,60],{},"sudo","), because Scapy sends raw frames. On Windows, Scapy needs Npcap installed and an elevated prompt.",[41,63,64,65,69],{},"Layer 2 adjacency to the subnet. ARP doesn't cross routers, so this only sees the broadcast domain the machine running it is plugged into. For routed subnets, use a TCP-based sweep like the ",[18,66,68],{"href":67},"\u002F2026\u002F02\u002F11\u002Fnode-js-discovery-scanning-a-subnet-for-open-rdp-and-ssh\u002F","Node.js RDP\u002FSSH scanner",".",[41,71,72,73,76,77,80],{},"Optional: a copy of the IEEE MA-L (OUI) registry in its text format, ",[24,74,75],{},"oui.txt",", for full vendor lookups. Many Linux distributions already ship it (for example ",[24,78,79],{},"\u002Fusr\u002Fshare\u002Fhwdata\u002Foui.txt"," from the hwdata package), or download it from the IEEE Registration Authority's public listing.",[41,82,83],{},"Written authorization for the network you're sweeping. Even an ARP sweep plus a few TCP connects shows up in IDS and switch logs.",[33,85,87],{"id":86},"usage","Usage",[11,89,90],{},"Sweep a \u002F24 and write the report:",[92,93,98],"pre",{"className":94,"code":95,"language":96,"meta":97,"style":97},"language-bash shiki shiki-themes github-dark","sudo python3 fingerprint_subnet.py --subnet 192.168.10.0\u002F24 --output subnet-fingerprint.csv\n","bash","",[24,99,100],{"__ignoreMap":97},[101,102,105,108,112,115,119,122,125],"span",{"class":103,"line":104},"line",1,[101,106,60],{"class":107},"svObZ",[101,109,111],{"class":110},"sU2Wk"," python3",[101,113,114],{"class":110}," fingerprint_subnet.py",[101,116,118],{"class":117},"sDLfK"," --subnet",[101,120,121],{"class":110}," 192.168.10.0\u002F24",[101,123,124],{"class":117}," --output",[101,126,127],{"class":110}," subnet-fingerprint.csv\n",[11,129,130],{},"Use the full IEEE vendor list, a custom port list, and a longer timeout for a slow or lossy segment:",[92,132,134],{"className":94,"code":133,"language":96,"meta":97,"style":97},"sudo python3 fingerprint_subnet.py --subnet 10.20.30.0\u002F24 --oui-file \u002Fusr\u002Fshare\u002Fhwdata\u002Foui.txt --ports 22,80,443,3389,8080,9100 --timeout 1.5 --output site-b.csv\n",[24,135,136],{"__ignoreMap":97},[101,137,138,140,142,144,146,149,152,155,158,161,164,167,169],{"class":103,"line":104},[101,139,60],{"class":107},[101,141,111],{"class":110},[101,143,114],{"class":110},[101,145,118],{"class":117},[101,147,148],{"class":110}," 10.20.30.0\u002F24",[101,150,151],{"class":117}," --oui-file",[101,153,154],{"class":110}," \u002Fusr\u002Fshare\u002Fhwdata\u002Foui.txt",[101,156,157],{"class":117}," --ports",[101,159,160],{"class":110}," 22,80,443,3389,8080,9100",[101,162,163],{"class":117}," --timeout",[101,165,166],{"class":117}," 1.5",[101,168,124],{"class":117},[101,170,171],{"class":110}," site-b.csv\n",[92,173,178],{"className":174,"code":176,"language":177,"meta":97},[175],"language-text","ARP-sweeping 10.20.30.0\u002F24...\n6 host(s) responded.\n10.20.30.1      70:10:6f:xx:xx:xx  Hewlett Packard Enterprise   ports: 22;443\n10.20.30.10     00:50:56:xx:xx:xx  VMware, Inc.                 ports: 22;80;443\n10.20.30.21     00:15:5d:xx:xx:xx  Microsoft Corporation        ports: 3389\n10.20.30.40     3c:d9:2b:xx:xx:xx  Hewlett Packard              ports: 80;443;9100\n10.20.30.55     24:a4:3c:xx:xx:xx  Ubiquiti Inc                 ports: 22;443\n10.20.30.77     b8:ca:3a:xx:xx:xx  Dell Inc.                    ports: none\nWrote 6 row(s) to site-b.csv\n","text",[24,179,176],{"__ignoreMap":97},[11,181,182,183,186,187,190,191,194],{},"The script prints full MAC addresses; the last three octets are masked above for publication. The CSV carries the detail the console line leaves out, for example ",[24,184,185],{},"22:SSH-2.0-OpenSSH_9.6"," in the ",[24,188,189],{},"banners"," column, a ",[24,192,193],{},"Server:"," header from port 80, and the reverse DNS name when one exists.",[33,196,198],{"id":197},"script","Script",[92,200,204],{"className":201,"code":202,"language":203,"meta":97,"style":97},"language-python shiki shiki-themes github-dark","#!\u002Fusr\u002Fbin\u002Fenv python3\n\"\"\"fingerprint_subnet.py\n\nARP-sweeps a local subnet to find live hosts, resolves each MAC address's OUI to a\nvendor name (from the IEEE oui.txt registry if supplied, otherwise a small built-in\ntable), probes a set of common TCP ports, and grabs a short banner from services that\noffer one (SSH identification strings, HTTP Server headers, SMTP\u002FFTP greetings).\nWrites one row per discovered host to a CSV report.\n\nReads: command-line arguments; optionally the IEEE oui.txt file named by --oui-file.\nWrites: the CSV report named by --output.\nRequires: Python 3.10+, scapy, root (or Npcap + admin on Windows).\n\"\"\"\n\nimport argparse\nimport csv\nimport ipaddress\nimport re\nimport socket\nfrom concurrent.futures import ThreadPoolExecutor\nfrom datetime import datetime, timezone\n\nfrom scapy.all import ARP, Ether, srp\n\n# Fallback vendor table, checked against the IEEE MA-L registry. Use --oui-file for real work.\nOUI_VENDORS = {\n    \"00:50:56\": \"VMware, Inc.\",\n    \"00:0C:29\": \"VMware, Inc.\",\n    \"00:05:69\": \"VMware, Inc.\",\n    \"00:15:5D\": \"Microsoft Corporation\",\n    \"00:1A:A0\": \"Dell Inc.\",\n    \"B8:CA:3A\": \"Dell Inc.\",\n    \"00:23:04\": \"Cisco Systems, Inc\",\n    \"00:1D:A1\": \"Cisco Systems, Inc\",\n    \"3C:D9:2B\": \"Hewlett Packard\",\n    \"70:10:6F\": \"Hewlett Packard Enterprise\",\n    \"24:A4:3C\": \"Ubiquiti Inc\",\n    \"3C:22:FB\": \"Apple, Inc.\",\n}\n\nCOMMON_PORTS = [22, 23, 25, 80, 443, 445, 3389, 8080, 8443, 9100]\nHTTP_PORTS = {80, 8080}\nBASE16_LINE = re.compile(r\"^([0-9A-F]{6})\\s+\\(base 16\\)\\s+(.+)$\")\n\n\ndef load_oui_file(path):\n    \"\"\"Parse the IEEE oui.txt format: '286FB9     (base 16)\\t\\tVendor Name'.\"\"\"\n    vendors = {}\n    with open(path, encoding=\"utf-8\", errors=\"replace\") as handle:\n        for line in handle:\n            match = BASE16_LINE.match(line.strip())\n            if match:\n                hex_prefix, name = match.groups()\n                key = \":\".join(hex_prefix[i:i + 2] for i in range(0, 6, 2))\n                vendors[key] = name.strip()\n    return vendors\n\n\ndef guess_vendor(mac_address, vendors):\n    prefix = mac_address.upper()[0:8]\n    if int(prefix[0:2], 16) & 0x02:\n        # Locally administered bit set: randomized\u002Fprivate MAC, no registered vendor.\n        return \"Locally administered (random MAC)\"\n    return vendors.get(prefix, \"Unknown\")\n\n\ndef arp_sweep(subnet, timeout):\n    \"\"\"Broadcast an ARP who-has for every address in subnet; return (ip, mac) replies.\"\"\"\n    packet = Ether(dst=\"ff:ff:ff:ff:ff:ff\") \u002F ARP(pdst=subnet)\n    answered, _ = srp(packet, timeout=timeout, verbose=False)\n    return sorted({(received.psrc, received.hwsrc) for _, received in answered},\n                  key=lambda pair: ipaddress.ip_address(pair[0]))\n\n\ndef probe_port(ip_address, port, timeout):\n    \"\"\"Return (is_open, banner). Sends a HEAD request on HTTP ports, otherwise just listens.\"\"\"\n    try:\n        with socket.create_connection((ip_address, port), timeout=timeout) as sock:\n            sock.settimeout(timeout)\n            if port in HTTP_PORTS:\n                sock.sendall(f\"HEAD \u002F HTTP\u002F1.0\\r\\nHost: {ip_address}\\r\\n\\r\\n\".encode(\"ascii\"))\n            raw = b\"\"\n            try:\n                # TCP may split the greeting; read until the first line ends (or 512 bytes).\n                while b\"\\n\" not in raw and len(raw) \u003C 512:\n                    chunk = sock.recv(512 - len(raw))\n                    if not chunk:\n                        break\n                    raw += chunk\n            except (socket.timeout, OSError):\n                if not raw:\n                    return True, \"\"\n            data = raw.decode(\"utf-8\", errors=\"replace\")\n            lines = data.splitlines()\n            if port in HTTP_PORTS:\n                server = [line for line in lines if line.lower().startswith(\"server:\")]\n                if server:\n                    return True, server[0].strip()\n            return True, lines[0].strip() if lines else \"\"\n    except OSError:\n        return False, \"\"\n\n\ndef reverse_dns(ip_address):\n    try:\n        return socket.gethostbyaddr(ip_address)[0]\n    except OSError:\n        return \"\"\n\n\ndef fingerprint(host, ports, timeout, vendors):\n    ip_address, mac_address = host\n    open_ports = []\n    for port in ports:\n        is_open, banner = probe_port(ip_address, port, timeout)\n        if is_open:\n            open_ports.append((port, banner))\n    return {\n        \"ip_address\": ip_address,\n        \"mac_address\": mac_address.lower(),\n        \"vendor\": guess_vendor(mac_address, vendors),\n        \"reverse_dns\": reverse_dns(ip_address),\n        \"open_ports\": \";\".join(str(port) for port, _ in open_ports),\n        \"banners\": \" | \".join(f\"{port}:{banner}\" for port, banner in open_ports if banner),\n        \"scanned_at\": datetime.now(timezone.utc).isoformat(timespec=\"seconds\"),\n    }\n\n\ndef main():\n    parser = argparse.ArgumentParser(description=\"Fingerprint every live device on a local subnet.\")\n    parser.add_argument(\"--subnet\", required=True, help=\"Subnet in CIDR notation, e.g. 192.168.10.0\u002F24\")\n    parser.add_argument(\"--ports\", default=\",\".join(str(p) for p in COMMON_PORTS),\n                        help=\"Comma-separated TCP ports to probe\")\n    parser.add_argument(\"--timeout\", type=float, default=1.0, help=\"ARP and per-port timeout in seconds\")\n    parser.add_argument(\"--workers\", type=int, default=32, help=\"Hosts probed in parallel\")\n    parser.add_argument(\"--oui-file\", help=\"Path to the IEEE oui.txt registry for vendor lookups\")\n    parser.add_argument(\"--output\", required=True, help=\"Path to write the CSV report\")\n    args = parser.parse_args()\n\n    network = ipaddress.ip_network(args.subnet, strict=False)\n    if network.version != 4 or network.prefixlen \u003C 16:\n        parser.error(\"ARP sweeps are IPv4 only; use a \u002F16 or smaller.\")\n\n    try:\n        ports = [int(p) for p in args.ports.split(\",\") if p.strip()]\n    except ValueError:\n        parser.error(\"--ports must be a comma-separated list of integers.\")\n    if not ports or any(not 1 \u003C= port \u003C= 65535 for port in ports):\n        parser.error(\"--ports values must be between 1 and 65535.\")\n    vendors = load_oui_file(args.oui_file) if args.oui_file else OUI_VENDORS\n\n    print(f\"ARP-sweeping {network}...\")\n    hosts = arp_sweep(str(network), args.timeout)\n    print(f\"{len(hosts)} host(s) responded.\")\n\n    with ThreadPoolExecutor(max_workers=args.workers) as pool:\n        rows = list(pool.map(lambda h: fingerprint(h, ports, args.timeout, vendors), hosts))\n\n    for row in rows:\n        print(f\"{row['ip_address']:15s} {row['mac_address']}  {row['vendor'][:28]:28s} \"\n              f\"ports: {row['open_ports'] or 'none'}\")\n\n    fieldnames = [\"ip_address\", \"mac_address\", \"vendor\", \"reverse_dns\", \"open_ports\", \"banners\", \"scanned_at\"]\n    with open(args.output, \"w\", newline=\"\", encoding=\"utf-8\") as csv_file:\n        writer = csv.DictWriter(csv_file, fieldnames=fieldnames)\n        writer.writeheader()\n        writer.writerows(rows)\n    print(f\"Wrote {len(rows)} row(s) to {args.output}\")\n\n\nif __name__ == \"__main__\":\n    main()\n","python",[24,205,206,212,218,225,231,237,243,249,255,260,266,272,278,284,289,300,308,316,324,332,346,359,364,380,385,391,403,418,430,442,455,468,480,493,505,518,531,544,557,563,568,631,650,708,713,718,730,742,754,794,808,822,831,842,894,905,914,919,924,935,956,994,1000,1009,1022,1027,1032,1043,1049,1084,1113,1134,1151,1156,1161,1172,1178,1186,1207,1213,1228,1265,1279,1287,1293,1334,1356,1367,1373,1385,1400,1411,1424,1447,1458,1471,1503,1511,1526,1552,1563,1575,1580,1585,1596,1603,1615,1624,1631,1636,1641,1652,1663,1674,1687,1698,1707,1713,1720,1729,1738,1747,1756,1786,1838,1858,1864,1869,1874,1885,1906,1937,1974,1987,2025,2062,2081,2108,2119,2124,2144,2171,2182,2187,2194,2226,2236,2246,2290,2300,2320,2325,2350,2366,2390,2395,2416,2436,2441,2454,2517,2546,2551,2596,2634,2653,2659,2665,2698,2703,2708,2724],{"__ignoreMap":97},[101,207,208],{"class":103,"line":104},[101,209,211],{"class":210},"sAwPA","#!\u002Fusr\u002Fbin\u002Fenv python3\n",[101,213,215],{"class":103,"line":214},2,[101,216,217],{"class":110},"\"\"\"fingerprint_subnet.py\n",[101,219,221],{"class":103,"line":220},3,[101,222,224],{"emptyLinePlaceholder":223},true,"\n",[101,226,228],{"class":103,"line":227},4,[101,229,230],{"class":110},"ARP-sweeps a local subnet to find live hosts, resolves each MAC address's OUI to a\n",[101,232,234],{"class":103,"line":233},5,[101,235,236],{"class":110},"vendor name (from the IEEE oui.txt registry if supplied, otherwise a small built-in\n",[101,238,240],{"class":103,"line":239},6,[101,241,242],{"class":110},"table), probes a set of common TCP ports, and grabs a short banner from services that\n",[101,244,246],{"class":103,"line":245},7,[101,247,248],{"class":110},"offer one (SSH identification strings, HTTP Server headers, SMTP\u002FFTP greetings).\n",[101,250,252],{"class":103,"line":251},8,[101,253,254],{"class":110},"Writes one row per discovered host to a CSV report.\n",[101,256,258],{"class":103,"line":257},9,[101,259,224],{"emptyLinePlaceholder":223},[101,261,263],{"class":103,"line":262},10,[101,264,265],{"class":110},"Reads: command-line arguments; optionally the IEEE oui.txt file named by --oui-file.\n",[101,267,269],{"class":103,"line":268},11,[101,270,271],{"class":110},"Writes: the CSV report named by --output.\n",[101,273,275],{"class":103,"line":274},12,[101,276,277],{"class":110},"Requires: Python 3.10+, scapy, root (or Npcap + admin on Windows).\n",[101,279,281],{"class":103,"line":280},13,[101,282,283],{"class":110},"\"\"\"\n",[101,285,287],{"class":103,"line":286},14,[101,288,224],{"emptyLinePlaceholder":223},[101,290,292,296],{"class":103,"line":291},15,[101,293,295],{"class":294},"snl16","import",[101,297,299],{"class":298},"s95oV"," argparse\n",[101,301,303,305],{"class":103,"line":302},16,[101,304,295],{"class":294},[101,306,307],{"class":298}," csv\n",[101,309,311,313],{"class":103,"line":310},17,[101,312,295],{"class":294},[101,314,315],{"class":298}," ipaddress\n",[101,317,319,321],{"class":103,"line":318},18,[101,320,295],{"class":294},[101,322,323],{"class":298}," re\n",[101,325,327,329],{"class":103,"line":326},19,[101,328,295],{"class":294},[101,330,331],{"class":298}," socket\n",[101,333,335,338,341,343],{"class":103,"line":334},20,[101,336,337],{"class":294},"from",[101,339,340],{"class":298}," concurrent.futures ",[101,342,295],{"class":294},[101,344,345],{"class":298}," ThreadPoolExecutor\n",[101,347,349,351,354,356],{"class":103,"line":348},21,[101,350,337],{"class":294},[101,352,353],{"class":298}," datetime ",[101,355,295],{"class":294},[101,357,358],{"class":298}," datetime, timezone\n",[101,360,362],{"class":103,"line":361},22,[101,363,224],{"emptyLinePlaceholder":223},[101,365,367,369,372,374,377],{"class":103,"line":366},23,[101,368,337],{"class":294},[101,370,371],{"class":298}," scapy.all ",[101,373,295],{"class":294},[101,375,376],{"class":117}," ARP",[101,378,379],{"class":298},", Ether, srp\n",[101,381,383],{"class":103,"line":382},24,[101,384,224],{"emptyLinePlaceholder":223},[101,386,388],{"class":103,"line":387},25,[101,389,390],{"class":210},"# Fallback vendor table, checked against the IEEE MA-L registry. Use --oui-file for real work.\n",[101,392,394,397,400],{"class":103,"line":393},26,[101,395,396],{"class":117},"OUI_VENDORS",[101,398,399],{"class":294}," =",[101,401,402],{"class":298}," {\n",[101,404,406,409,412,415],{"class":103,"line":405},27,[101,407,408],{"class":110},"    \"00:50:56\"",[101,410,411],{"class":298},": ",[101,413,414],{"class":110},"\"VMware, Inc.\"",[101,416,417],{"class":298},",\n",[101,419,421,424,426,428],{"class":103,"line":420},28,[101,422,423],{"class":110},"    \"00:0C:29\"",[101,425,411],{"class":298},[101,427,414],{"class":110},[101,429,417],{"class":298},[101,431,433,436,438,440],{"class":103,"line":432},29,[101,434,435],{"class":110},"    \"00:05:69\"",[101,437,411],{"class":298},[101,439,414],{"class":110},[101,441,417],{"class":298},[101,443,445,448,450,453],{"class":103,"line":444},30,[101,446,447],{"class":110},"    \"00:15:5D\"",[101,449,411],{"class":298},[101,451,452],{"class":110},"\"Microsoft Corporation\"",[101,454,417],{"class":298},[101,456,458,461,463,466],{"class":103,"line":457},31,[101,459,460],{"class":110},"    \"00:1A:A0\"",[101,462,411],{"class":298},[101,464,465],{"class":110},"\"Dell Inc.\"",[101,467,417],{"class":298},[101,469,471,474,476,478],{"class":103,"line":470},32,[101,472,473],{"class":110},"    \"B8:CA:3A\"",[101,475,411],{"class":298},[101,477,465],{"class":110},[101,479,417],{"class":298},[101,481,483,486,488,491],{"class":103,"line":482},33,[101,484,485],{"class":110},"    \"00:23:04\"",[101,487,411],{"class":298},[101,489,490],{"class":110},"\"Cisco Systems, Inc\"",[101,492,417],{"class":298},[101,494,496,499,501,503],{"class":103,"line":495},34,[101,497,498],{"class":110},"    \"00:1D:A1\"",[101,500,411],{"class":298},[101,502,490],{"class":110},[101,504,417],{"class":298},[101,506,508,511,513,516],{"class":103,"line":507},35,[101,509,510],{"class":110},"    \"3C:D9:2B\"",[101,512,411],{"class":298},[101,514,515],{"class":110},"\"Hewlett Packard\"",[101,517,417],{"class":298},[101,519,521,524,526,529],{"class":103,"line":520},36,[101,522,523],{"class":110},"    \"70:10:6F\"",[101,525,411],{"class":298},[101,527,528],{"class":110},"\"Hewlett Packard Enterprise\"",[101,530,417],{"class":298},[101,532,534,537,539,542],{"class":103,"line":533},37,[101,535,536],{"class":110},"    \"24:A4:3C\"",[101,538,411],{"class":298},[101,540,541],{"class":110},"\"Ubiquiti Inc\"",[101,543,417],{"class":298},[101,545,547,550,552,555],{"class":103,"line":546},38,[101,548,549],{"class":110},"    \"3C:22:FB\"",[101,551,411],{"class":298},[101,553,554],{"class":110},"\"Apple, Inc.\"",[101,556,417],{"class":298},[101,558,560],{"class":103,"line":559},39,[101,561,562],{"class":298},"}\n",[101,564,566],{"class":103,"line":565},40,[101,567,224],{"emptyLinePlaceholder":223},[101,569,571,574,576,579,582,585,588,590,593,595,598,600,603,605,608,610,613,615,618,620,623,625,628],{"class":103,"line":570},41,[101,572,573],{"class":117},"COMMON_PORTS",[101,575,399],{"class":294},[101,577,578],{"class":298}," [",[101,580,581],{"class":117},"22",[101,583,584],{"class":298},", ",[101,586,587],{"class":117},"23",[101,589,584],{"class":298},[101,591,592],{"class":117},"25",[101,594,584],{"class":298},[101,596,597],{"class":117},"80",[101,599,584],{"class":298},[101,601,602],{"class":117},"443",[101,604,584],{"class":298},[101,606,607],{"class":117},"445",[101,609,584],{"class":298},[101,611,612],{"class":117},"3389",[101,614,584],{"class":298},[101,616,617],{"class":117},"8080",[101,619,584],{"class":298},[101,621,622],{"class":117},"8443",[101,624,584],{"class":298},[101,626,627],{"class":117},"9100",[101,629,630],{"class":298},"]\n",[101,632,634,637,639,642,644,646,648],{"class":103,"line":633},42,[101,635,636],{"class":117},"HTTP_PORTS",[101,638,399],{"class":294},[101,640,641],{"class":298}," {",[101,643,597],{"class":117},[101,645,584],{"class":298},[101,647,617],{"class":117},[101,649,562],{"class":298},[101,651,653,656,658,661,664,667,670,673,676,679,683,687,690,693,695,698,700,703,705],{"class":103,"line":652},43,[101,654,655],{"class":117},"BASE16_LINE",[101,657,399],{"class":294},[101,659,660],{"class":298}," re.compile(",[101,662,663],{"class":294},"r",[101,665,666],{"class":110},"\"",[101,668,669],{"class":117},"^([0-9A-F]",[101,671,672],{"class":294},"{6}",[101,674,675],{"class":117},")\\s",[101,677,678],{"class":294},"+",[101,680,682],{"class":681},"sRjNt","\\(",[101,684,686],{"class":685},"sns5M","base 16",[101,688,689],{"class":681},"\\)",[101,691,692],{"class":117},"\\s",[101,694,678],{"class":294},[101,696,697],{"class":117},"(.",[101,699,678],{"class":294},[101,701,702],{"class":117},")$",[101,704,666],{"class":110},[101,706,707],{"class":298},")\n",[101,709,711],{"class":103,"line":710},44,[101,712,224],{"emptyLinePlaceholder":223},[101,714,716],{"class":103,"line":715},45,[101,717,224],{"emptyLinePlaceholder":223},[101,719,721,724,727],{"class":103,"line":720},46,[101,722,723],{"class":294},"def",[101,725,726],{"class":107}," load_oui_file",[101,728,729],{"class":298},"(path):\n",[101,731,733,736,739],{"class":103,"line":732},47,[101,734,735],{"class":110},"    \"\"\"Parse the IEEE oui.txt format: '286FB9     (base 16)",[101,737,738],{"class":117},"\\t\\t",[101,740,741],{"class":110},"Vendor Name'.\"\"\"\n",[101,743,745,748,751],{"class":103,"line":744},48,[101,746,747],{"class":298},"    vendors ",[101,749,750],{"class":294},"=",[101,752,753],{"class":298}," {}\n",[101,755,757,760,763,766,770,772,775,777,780,782,785,788,791],{"class":103,"line":756},49,[101,758,759],{"class":294},"    with",[101,761,762],{"class":117}," open",[101,764,765],{"class":298},"(path, ",[101,767,769],{"class":768},"s9osk","encoding",[101,771,750],{"class":294},[101,773,774],{"class":110},"\"utf-8\"",[101,776,584],{"class":298},[101,778,779],{"class":768},"errors",[101,781,750],{"class":294},[101,783,784],{"class":110},"\"replace\"",[101,786,787],{"class":298},") ",[101,789,790],{"class":294},"as",[101,792,793],{"class":298}," handle:\n",[101,795,797,800,803,806],{"class":103,"line":796},50,[101,798,799],{"class":294},"        for",[101,801,802],{"class":298}," line ",[101,804,805],{"class":294},"in",[101,807,793],{"class":298},[101,809,811,814,816,819],{"class":103,"line":810},51,[101,812,813],{"class":298},"            match ",[101,815,750],{"class":294},[101,817,818],{"class":117}," BASE16_LINE",[101,820,821],{"class":298},".match(line.strip())\n",[101,823,825,828],{"class":103,"line":824},52,[101,826,827],{"class":294},"            if",[101,829,830],{"class":298}," match:\n",[101,832,834,837,839],{"class":103,"line":833},53,[101,835,836],{"class":298},"                hex_prefix, name ",[101,838,750],{"class":294},[101,840,841],{"class":298}," match.groups()\n",[101,843,845,848,850,853,856,858,861,864,867,870,872,875,878,881,883,886,888,891],{"class":103,"line":844},54,[101,846,847],{"class":298},"                key ",[101,849,750],{"class":294},[101,851,852],{"class":110}," \":\"",[101,854,855],{"class":298},".join(hex_prefix[i:i ",[101,857,678],{"class":294},[101,859,860],{"class":117}," 2",[101,862,863],{"class":298},"] ",[101,865,866],{"class":294},"for",[101,868,869],{"class":298}," i ",[101,871,805],{"class":294},[101,873,874],{"class":117}," range",[101,876,877],{"class":298},"(",[101,879,880],{"class":117},"0",[101,882,584],{"class":298},[101,884,885],{"class":117},"6",[101,887,584],{"class":298},[101,889,890],{"class":117},"2",[101,892,893],{"class":298},"))\n",[101,895,897,900,902],{"class":103,"line":896},55,[101,898,899],{"class":298},"                vendors[key] ",[101,901,750],{"class":294},[101,903,904],{"class":298}," name.strip()\n",[101,906,908,911],{"class":103,"line":907},56,[101,909,910],{"class":294},"    return",[101,912,913],{"class":298}," vendors\n",[101,915,917],{"class":103,"line":916},57,[101,918,224],{"emptyLinePlaceholder":223},[101,920,922],{"class":103,"line":921},58,[101,923,224],{"emptyLinePlaceholder":223},[101,925,927,929,932],{"class":103,"line":926},59,[101,928,723],{"class":294},[101,930,931],{"class":107}," guess_vendor",[101,933,934],{"class":298},"(mac_address, vendors):\n",[101,936,938,941,943,946,948,951,954],{"class":103,"line":937},60,[101,939,940],{"class":298},"    prefix ",[101,942,750],{"class":294},[101,944,945],{"class":298}," mac_address.upper()[",[101,947,880],{"class":117},[101,949,950],{"class":298},":",[101,952,953],{"class":117},"8",[101,955,630],{"class":298},[101,957,959,962,965,968,970,972,974,977,980,982,985,988,991],{"class":103,"line":958},61,[101,960,961],{"class":294},"    if",[101,963,964],{"class":117}," int",[101,966,967],{"class":298},"(prefix[",[101,969,880],{"class":117},[101,971,950],{"class":298},[101,973,890],{"class":117},[101,975,976],{"class":298},"], ",[101,978,979],{"class":117},"16",[101,981,787],{"class":298},[101,983,984],{"class":294},"&",[101,986,987],{"class":294}," 0x",[101,989,990],{"class":117},"02",[101,992,993],{"class":298},":\n",[101,995,997],{"class":103,"line":996},62,[101,998,999],{"class":210},"        # Locally administered bit set: randomized\u002Fprivate MAC, no registered vendor.\n",[101,1001,1003,1006],{"class":103,"line":1002},63,[101,1004,1005],{"class":294},"        return",[101,1007,1008],{"class":110}," \"Locally administered (random MAC)\"\n",[101,1010,1012,1014,1017,1020],{"class":103,"line":1011},64,[101,1013,910],{"class":294},[101,1015,1016],{"class":298}," vendors.get(prefix, ",[101,1018,1019],{"class":110},"\"Unknown\"",[101,1021,707],{"class":298},[101,1023,1025],{"class":103,"line":1024},65,[101,1026,224],{"emptyLinePlaceholder":223},[101,1028,1030],{"class":103,"line":1029},66,[101,1031,224],{"emptyLinePlaceholder":223},[101,1033,1035,1037,1040],{"class":103,"line":1034},67,[101,1036,723],{"class":294},[101,1038,1039],{"class":107}," arp_sweep",[101,1041,1042],{"class":298},"(subnet, timeout):\n",[101,1044,1046],{"class":103,"line":1045},68,[101,1047,1048],{"class":110},"    \"\"\"Broadcast an ARP who-has for every address in subnet; return (ip, mac) replies.\"\"\"\n",[101,1050,1052,1055,1057,1060,1063,1065,1068,1070,1073,1076,1079,1081],{"class":103,"line":1051},69,[101,1053,1054],{"class":298},"    packet ",[101,1056,750],{"class":294},[101,1058,1059],{"class":298}," Ether(",[101,1061,1062],{"class":768},"dst",[101,1064,750],{"class":294},[101,1066,1067],{"class":110},"\"ff:ff:ff:ff:ff:ff\"",[101,1069,787],{"class":298},[101,1071,1072],{"class":294},"\u002F",[101,1074,1075],{"class":298}," ARP(",[101,1077,1078],{"class":768},"pdst",[101,1080,750],{"class":294},[101,1082,1083],{"class":298},"subnet)\n",[101,1085,1087,1090,1092,1095,1098,1100,1103,1106,1108,1111],{"class":103,"line":1086},70,[101,1088,1089],{"class":298},"    answered, _ ",[101,1091,750],{"class":294},[101,1093,1094],{"class":298}," srp(packet, ",[101,1096,1097],{"class":768},"timeout",[101,1099,750],{"class":294},[101,1101,1102],{"class":298},"timeout, ",[101,1104,1105],{"class":768},"verbose",[101,1107,750],{"class":294},[101,1109,1110],{"class":117},"False",[101,1112,707],{"class":298},[101,1114,1116,1118,1121,1124,1126,1129,1131],{"class":103,"line":1115},71,[101,1117,910],{"class":294},[101,1119,1120],{"class":117}," sorted",[101,1122,1123],{"class":298},"({(received.psrc, received.hwsrc) ",[101,1125,866],{"class":294},[101,1127,1128],{"class":298}," _, received ",[101,1130,805],{"class":294},[101,1132,1133],{"class":298}," answered},\n",[101,1135,1137,1140,1143,1146,1148],{"class":103,"line":1136},72,[101,1138,1139],{"class":768},"                  key",[101,1141,1142],{"class":294},"=lambda",[101,1144,1145],{"class":298}," pair: ipaddress.ip_address(pair[",[101,1147,880],{"class":117},[101,1149,1150],{"class":298},"]))\n",[101,1152,1154],{"class":103,"line":1153},73,[101,1155,224],{"emptyLinePlaceholder":223},[101,1157,1159],{"class":103,"line":1158},74,[101,1160,224],{"emptyLinePlaceholder":223},[101,1162,1164,1166,1169],{"class":103,"line":1163},75,[101,1165,723],{"class":294},[101,1167,1168],{"class":107}," probe_port",[101,1170,1171],{"class":298},"(ip_address, port, timeout):\n",[101,1173,1175],{"class":103,"line":1174},76,[101,1176,1177],{"class":110},"    \"\"\"Return (is_open, banner). Sends a HEAD request on HTTP ports, otherwise just listens.\"\"\"\n",[101,1179,1181,1184],{"class":103,"line":1180},77,[101,1182,1183],{"class":294},"    try",[101,1185,993],{"class":298},[101,1187,1189,1192,1195,1197,1199,1202,1204],{"class":103,"line":1188},78,[101,1190,1191],{"class":294},"        with",[101,1193,1194],{"class":298}," socket.create_connection((ip_address, port), ",[101,1196,1097],{"class":768},[101,1198,750],{"class":294},[101,1200,1201],{"class":298},"timeout) ",[101,1203,790],{"class":294},[101,1205,1206],{"class":298}," sock:\n",[101,1208,1210],{"class":103,"line":1209},79,[101,1211,1212],{"class":298},"            sock.settimeout(timeout)\n",[101,1214,1216,1218,1221,1223,1226],{"class":103,"line":1215},80,[101,1217,827],{"class":294},[101,1219,1220],{"class":298}," port ",[101,1222,805],{"class":294},[101,1224,1225],{"class":117}," HTTP_PORTS",[101,1227,993],{"class":298},[101,1229,1231,1234,1237,1240,1243,1246,1249,1252,1255,1257,1260,1263],{"class":103,"line":1230},81,[101,1232,1233],{"class":298},"                sock.sendall(",[101,1235,1236],{"class":294},"f",[101,1238,1239],{"class":110},"\"HEAD \u002F HTTP\u002F1.0",[101,1241,1242],{"class":117},"\\r\\n",[101,1244,1245],{"class":110},"Host: ",[101,1247,1248],{"class":117},"{",[101,1250,1251],{"class":298},"ip_address",[101,1253,1254],{"class":117},"}\\r\\n\\r\\n",[101,1256,666],{"class":110},[101,1258,1259],{"class":298},".encode(",[101,1261,1262],{"class":110},"\"ascii\"",[101,1264,893],{"class":298},[101,1266,1268,1271,1273,1276],{"class":103,"line":1267},82,[101,1269,1270],{"class":298},"            raw ",[101,1272,750],{"class":294},[101,1274,1275],{"class":294}," b",[101,1277,1278],{"class":110},"\"\"\n",[101,1280,1282,1285],{"class":103,"line":1281},83,[101,1283,1284],{"class":294},"            try",[101,1286,993],{"class":298},[101,1288,1290],{"class":103,"line":1289},84,[101,1291,1292],{"class":210},"                # TCP may split the greeting; read until the first line ends (or 512 bytes).\n",[101,1294,1296,1299,1301,1303,1306,1308,1311,1314,1317,1320,1323,1326,1329,1332],{"class":103,"line":1295},85,[101,1297,1298],{"class":294},"                while",[101,1300,1275],{"class":294},[101,1302,666],{"class":110},[101,1304,1305],{"class":117},"\\n",[101,1307,666],{"class":110},[101,1309,1310],{"class":294}," not",[101,1312,1313],{"class":294}," in",[101,1315,1316],{"class":298}," raw ",[101,1318,1319],{"class":294},"and",[101,1321,1322],{"class":117}," len",[101,1324,1325],{"class":298},"(raw) ",[101,1327,1328],{"class":294},"\u003C",[101,1330,1331],{"class":117}," 512",[101,1333,993],{"class":298},[101,1335,1337,1340,1342,1345,1348,1351,1353],{"class":103,"line":1336},86,[101,1338,1339],{"class":298},"                    chunk ",[101,1341,750],{"class":294},[101,1343,1344],{"class":298}," sock.recv(",[101,1346,1347],{"class":117},"512",[101,1349,1350],{"class":294}," -",[101,1352,1322],{"class":117},[101,1354,1355],{"class":298},"(raw))\n",[101,1357,1359,1362,1364],{"class":103,"line":1358},87,[101,1360,1361],{"class":294},"                    if",[101,1363,1310],{"class":294},[101,1365,1366],{"class":298}," chunk:\n",[101,1368,1370],{"class":103,"line":1369},88,[101,1371,1372],{"class":294},"                        break\n",[101,1374,1376,1379,1382],{"class":103,"line":1375},89,[101,1377,1378],{"class":298},"                    raw ",[101,1380,1381],{"class":294},"+=",[101,1383,1384],{"class":298}," chunk\n",[101,1386,1388,1391,1394,1397],{"class":103,"line":1387},90,[101,1389,1390],{"class":294},"            except",[101,1392,1393],{"class":298}," (socket.timeout, ",[101,1395,1396],{"class":117},"OSError",[101,1398,1399],{"class":298},"):\n",[101,1401,1403,1406,1408],{"class":103,"line":1402},91,[101,1404,1405],{"class":294},"                if",[101,1407,1310],{"class":294},[101,1409,1410],{"class":298}," raw:\n",[101,1412,1414,1417,1420,1422],{"class":103,"line":1413},92,[101,1415,1416],{"class":294},"                    return",[101,1418,1419],{"class":117}," True",[101,1421,584],{"class":298},[101,1423,1278],{"class":110},[101,1425,1427,1430,1432,1435,1437,1439,1441,1443,1445],{"class":103,"line":1426},93,[101,1428,1429],{"class":298},"            data ",[101,1431,750],{"class":294},[101,1433,1434],{"class":298}," raw.decode(",[101,1436,774],{"class":110},[101,1438,584],{"class":298},[101,1440,779],{"class":768},[101,1442,750],{"class":294},[101,1444,784],{"class":110},[101,1446,707],{"class":298},[101,1448,1450,1453,1455],{"class":103,"line":1449},94,[101,1451,1452],{"class":298},"            lines ",[101,1454,750],{"class":294},[101,1456,1457],{"class":298}," data.splitlines()\n",[101,1459,1461,1463,1465,1467,1469],{"class":103,"line":1460},95,[101,1462,827],{"class":294},[101,1464,1220],{"class":298},[101,1466,805],{"class":294},[101,1468,1225],{"class":117},[101,1470,993],{"class":298},[101,1472,1474,1477,1479,1482,1484,1486,1488,1491,1494,1497,1500],{"class":103,"line":1473},96,[101,1475,1476],{"class":298},"                server ",[101,1478,750],{"class":294},[101,1480,1481],{"class":298}," [line ",[101,1483,866],{"class":294},[101,1485,802],{"class":298},[101,1487,805],{"class":294},[101,1489,1490],{"class":298}," lines ",[101,1492,1493],{"class":294},"if",[101,1495,1496],{"class":298}," line.lower().startswith(",[101,1498,1499],{"class":110},"\"server:\"",[101,1501,1502],{"class":298},")]\n",[101,1504,1506,1508],{"class":103,"line":1505},97,[101,1507,1405],{"class":294},[101,1509,1510],{"class":298}," server:\n",[101,1512,1514,1516,1518,1521,1523],{"class":103,"line":1513},98,[101,1515,1416],{"class":294},[101,1517,1419],{"class":117},[101,1519,1520],{"class":298},", server[",[101,1522,880],{"class":117},[101,1524,1525],{"class":298},"].strip()\n",[101,1527,1529,1532,1534,1537,1539,1542,1544,1546,1549],{"class":103,"line":1528},99,[101,1530,1531],{"class":294},"            return",[101,1533,1419],{"class":117},[101,1535,1536],{"class":298},", lines[",[101,1538,880],{"class":117},[101,1540,1541],{"class":298},"].strip() ",[101,1543,1493],{"class":294},[101,1545,1490],{"class":298},[101,1547,1548],{"class":294},"else",[101,1550,1551],{"class":110}," \"\"\n",[101,1553,1555,1558,1561],{"class":103,"line":1554},100,[101,1556,1557],{"class":294},"    except",[101,1559,1560],{"class":117}," OSError",[101,1562,993],{"class":298},[101,1564,1566,1568,1571,1573],{"class":103,"line":1565},101,[101,1567,1005],{"class":294},[101,1569,1570],{"class":117}," False",[101,1572,584],{"class":298},[101,1574,1278],{"class":110},[101,1576,1578],{"class":103,"line":1577},102,[101,1579,224],{"emptyLinePlaceholder":223},[101,1581,1583],{"class":103,"line":1582},103,[101,1584,224],{"emptyLinePlaceholder":223},[101,1586,1588,1590,1593],{"class":103,"line":1587},104,[101,1589,723],{"class":294},[101,1591,1592],{"class":107}," reverse_dns",[101,1594,1595],{"class":298},"(ip_address):\n",[101,1597,1599,1601],{"class":103,"line":1598},105,[101,1600,1183],{"class":294},[101,1602,993],{"class":298},[101,1604,1606,1608,1611,1613],{"class":103,"line":1605},106,[101,1607,1005],{"class":294},[101,1609,1610],{"class":298}," socket.gethostbyaddr(ip_address)[",[101,1612,880],{"class":117},[101,1614,630],{"class":298},[101,1616,1618,1620,1622],{"class":103,"line":1617},107,[101,1619,1557],{"class":294},[101,1621,1560],{"class":117},[101,1623,993],{"class":298},[101,1625,1627,1629],{"class":103,"line":1626},108,[101,1628,1005],{"class":294},[101,1630,1551],{"class":110},[101,1632,1634],{"class":103,"line":1633},109,[101,1635,224],{"emptyLinePlaceholder":223},[101,1637,1639],{"class":103,"line":1638},110,[101,1640,224],{"emptyLinePlaceholder":223},[101,1642,1644,1646,1649],{"class":103,"line":1643},111,[101,1645,723],{"class":294},[101,1647,1648],{"class":107}," fingerprint",[101,1650,1651],{"class":298},"(host, ports, timeout, vendors):\n",[101,1653,1655,1658,1660],{"class":103,"line":1654},112,[101,1656,1657],{"class":298},"    ip_address, mac_address ",[101,1659,750],{"class":294},[101,1661,1662],{"class":298}," host\n",[101,1664,1666,1669,1671],{"class":103,"line":1665},113,[101,1667,1668],{"class":298},"    open_ports ",[101,1670,750],{"class":294},[101,1672,1673],{"class":298}," []\n",[101,1675,1677,1680,1682,1684],{"class":103,"line":1676},114,[101,1678,1679],{"class":294},"    for",[101,1681,1220],{"class":298},[101,1683,805],{"class":294},[101,1685,1686],{"class":298}," ports:\n",[101,1688,1690,1693,1695],{"class":103,"line":1689},115,[101,1691,1692],{"class":298},"        is_open, banner ",[101,1694,750],{"class":294},[101,1696,1697],{"class":298}," probe_port(ip_address, port, timeout)\n",[101,1699,1701,1704],{"class":103,"line":1700},116,[101,1702,1703],{"class":294},"        if",[101,1705,1706],{"class":298}," is_open:\n",[101,1708,1710],{"class":103,"line":1709},117,[101,1711,1712],{"class":298},"            open_ports.append((port, banner))\n",[101,1714,1716,1718],{"class":103,"line":1715},118,[101,1717,910],{"class":294},[101,1719,402],{"class":298},[101,1721,1723,1726],{"class":103,"line":1722},119,[101,1724,1725],{"class":110},"        \"ip_address\"",[101,1727,1728],{"class":298},": ip_address,\n",[101,1730,1732,1735],{"class":103,"line":1731},120,[101,1733,1734],{"class":110},"        \"mac_address\"",[101,1736,1737],{"class":298},": mac_address.lower(),\n",[101,1739,1741,1744],{"class":103,"line":1740},121,[101,1742,1743],{"class":110},"        \"vendor\"",[101,1745,1746],{"class":298},": guess_vendor(mac_address, vendors),\n",[101,1748,1750,1753],{"class":103,"line":1749},122,[101,1751,1752],{"class":110},"        \"reverse_dns\"",[101,1754,1755],{"class":298},": reverse_dns(ip_address),\n",[101,1757,1759,1762,1764,1767,1770,1773,1776,1778,1781,1783],{"class":103,"line":1758},123,[101,1760,1761],{"class":110},"        \"open_ports\"",[101,1763,411],{"class":298},[101,1765,1766],{"class":110},"\";\"",[101,1768,1769],{"class":298},".join(",[101,1771,1772],{"class":117},"str",[101,1774,1775],{"class":298},"(port) ",[101,1777,866],{"class":294},[101,1779,1780],{"class":298}," port, _ ",[101,1782,805],{"class":294},[101,1784,1785],{"class":298}," open_ports),\n",[101,1787,1789,1792,1794,1797,1799,1801,1803,1805,1808,1811,1813,1815,1818,1820,1822,1825,1828,1830,1833,1835],{"class":103,"line":1788},124,[101,1790,1791],{"class":110},"        \"banners\"",[101,1793,411],{"class":298},[101,1795,1796],{"class":110},"\" | \"",[101,1798,1769],{"class":298},[101,1800,1236],{"class":294},[101,1802,666],{"class":110},[101,1804,1248],{"class":117},[101,1806,1807],{"class":298},"port",[101,1809,1810],{"class":117},"}",[101,1812,950],{"class":110},[101,1814,1248],{"class":117},[101,1816,1817],{"class":298},"banner",[101,1819,1810],{"class":117},[101,1821,666],{"class":110},[101,1823,1824],{"class":294}," for",[101,1826,1827],{"class":298}," port, banner ",[101,1829,805],{"class":294},[101,1831,1832],{"class":298}," open_ports ",[101,1834,1493],{"class":294},[101,1836,1837],{"class":298}," banner),\n",[101,1839,1841,1844,1847,1850,1852,1855],{"class":103,"line":1840},125,[101,1842,1843],{"class":110},"        \"scanned_at\"",[101,1845,1846],{"class":298},": datetime.now(timezone.utc).isoformat(",[101,1848,1849],{"class":768},"timespec",[101,1851,750],{"class":294},[101,1853,1854],{"class":110},"\"seconds\"",[101,1856,1857],{"class":298},"),\n",[101,1859,1861],{"class":103,"line":1860},126,[101,1862,1863],{"class":298},"    }\n",[101,1865,1867],{"class":103,"line":1866},127,[101,1868,224],{"emptyLinePlaceholder":223},[101,1870,1872],{"class":103,"line":1871},128,[101,1873,224],{"emptyLinePlaceholder":223},[101,1875,1877,1879,1882],{"class":103,"line":1876},129,[101,1878,723],{"class":294},[101,1880,1881],{"class":107}," main",[101,1883,1884],{"class":298},"():\n",[101,1886,1888,1891,1893,1896,1899,1901,1904],{"class":103,"line":1887},130,[101,1889,1890],{"class":298},"    parser ",[101,1892,750],{"class":294},[101,1894,1895],{"class":298}," argparse.ArgumentParser(",[101,1897,1898],{"class":768},"description",[101,1900,750],{"class":294},[101,1902,1903],{"class":110},"\"Fingerprint every live device on a local subnet.\"",[101,1905,707],{"class":298},[101,1907,1909,1912,1915,1917,1920,1922,1925,1927,1930,1932,1935],{"class":103,"line":1908},131,[101,1910,1911],{"class":298},"    parser.add_argument(",[101,1913,1914],{"class":110},"\"--subnet\"",[101,1916,584],{"class":298},[101,1918,1919],{"class":768},"required",[101,1921,750],{"class":294},[101,1923,1924],{"class":117},"True",[101,1926,584],{"class":298},[101,1928,1929],{"class":768},"help",[101,1931,750],{"class":294},[101,1933,1934],{"class":110},"\"Subnet in CIDR notation, e.g. 192.168.10.0\u002F24\"",[101,1936,707],{"class":298},[101,1938,1940,1942,1945,1947,1950,1952,1955,1957,1959,1962,1964,1967,1969,1972],{"class":103,"line":1939},132,[101,1941,1911],{"class":298},[101,1943,1944],{"class":110},"\"--ports\"",[101,1946,584],{"class":298},[101,1948,1949],{"class":768},"default",[101,1951,750],{"class":294},[101,1953,1954],{"class":110},"\",\"",[101,1956,1769],{"class":298},[101,1958,1772],{"class":117},[101,1960,1961],{"class":298},"(p) ",[101,1963,866],{"class":294},[101,1965,1966],{"class":298}," p ",[101,1968,805],{"class":294},[101,1970,1971],{"class":117}," COMMON_PORTS",[101,1973,1857],{"class":298},[101,1975,1977,1980,1982,1985],{"class":103,"line":1976},133,[101,1978,1979],{"class":768},"                        help",[101,1981,750],{"class":294},[101,1983,1984],{"class":110},"\"Comma-separated TCP ports to probe\"",[101,1986,707],{"class":298},[101,1988,1990,1992,1995,1997,2000,2002,2005,2007,2009,2011,2014,2016,2018,2020,2023],{"class":103,"line":1989},134,[101,1991,1911],{"class":298},[101,1993,1994],{"class":110},"\"--timeout\"",[101,1996,584],{"class":298},[101,1998,1999],{"class":768},"type",[101,2001,750],{"class":294},[101,2003,2004],{"class":117},"float",[101,2006,584],{"class":298},[101,2008,1949],{"class":768},[101,2010,750],{"class":294},[101,2012,2013],{"class":117},"1.0",[101,2015,584],{"class":298},[101,2017,1929],{"class":768},[101,2019,750],{"class":294},[101,2021,2022],{"class":110},"\"ARP and per-port timeout in seconds\"",[101,2024,707],{"class":298},[101,2026,2028,2030,2033,2035,2037,2039,2042,2044,2046,2048,2051,2053,2055,2057,2060],{"class":103,"line":2027},135,[101,2029,1911],{"class":298},[101,2031,2032],{"class":110},"\"--workers\"",[101,2034,584],{"class":298},[101,2036,1999],{"class":768},[101,2038,750],{"class":294},[101,2040,2041],{"class":117},"int",[101,2043,584],{"class":298},[101,2045,1949],{"class":768},[101,2047,750],{"class":294},[101,2049,2050],{"class":117},"32",[101,2052,584],{"class":298},[101,2054,1929],{"class":768},[101,2056,750],{"class":294},[101,2058,2059],{"class":110},"\"Hosts probed in parallel\"",[101,2061,707],{"class":298},[101,2063,2065,2067,2070,2072,2074,2076,2079],{"class":103,"line":2064},136,[101,2066,1911],{"class":298},[101,2068,2069],{"class":110},"\"--oui-file\"",[101,2071,584],{"class":298},[101,2073,1929],{"class":768},[101,2075,750],{"class":294},[101,2077,2078],{"class":110},"\"Path to the IEEE oui.txt registry for vendor lookups\"",[101,2080,707],{"class":298},[101,2082,2084,2086,2089,2091,2093,2095,2097,2099,2101,2103,2106],{"class":103,"line":2083},137,[101,2085,1911],{"class":298},[101,2087,2088],{"class":110},"\"--output\"",[101,2090,584],{"class":298},[101,2092,1919],{"class":768},[101,2094,750],{"class":294},[101,2096,1924],{"class":117},[101,2098,584],{"class":298},[101,2100,1929],{"class":768},[101,2102,750],{"class":294},[101,2104,2105],{"class":110},"\"Path to write the CSV report\"",[101,2107,707],{"class":298},[101,2109,2111,2114,2116],{"class":103,"line":2110},138,[101,2112,2113],{"class":298},"    args ",[101,2115,750],{"class":294},[101,2117,2118],{"class":298}," parser.parse_args()\n",[101,2120,2122],{"class":103,"line":2121},139,[101,2123,224],{"emptyLinePlaceholder":223},[101,2125,2127,2130,2132,2135,2138,2140,2142],{"class":103,"line":2126},140,[101,2128,2129],{"class":298},"    network ",[101,2131,750],{"class":294},[101,2133,2134],{"class":298}," ipaddress.ip_network(args.subnet, ",[101,2136,2137],{"class":768},"strict",[101,2139,750],{"class":294},[101,2141,1110],{"class":117},[101,2143,707],{"class":298},[101,2145,2147,2149,2152,2155,2158,2161,2164,2166,2169],{"class":103,"line":2146},141,[101,2148,961],{"class":294},[101,2150,2151],{"class":298}," network.version ",[101,2153,2154],{"class":294},"!=",[101,2156,2157],{"class":117}," 4",[101,2159,2160],{"class":294}," or",[101,2162,2163],{"class":298}," network.prefixlen ",[101,2165,1328],{"class":294},[101,2167,2168],{"class":117}," 16",[101,2170,993],{"class":298},[101,2172,2174,2177,2180],{"class":103,"line":2173},142,[101,2175,2176],{"class":298},"        parser.error(",[101,2178,2179],{"class":110},"\"ARP sweeps are IPv4 only; use a \u002F16 or smaller.\"",[101,2181,707],{"class":298},[101,2183,2185],{"class":103,"line":2184},143,[101,2186,224],{"emptyLinePlaceholder":223},[101,2188,2190,2192],{"class":103,"line":2189},144,[101,2191,1183],{"class":294},[101,2193,993],{"class":298},[101,2195,2197,2200,2202,2204,2206,2208,2210,2212,2214,2217,2219,2221,2223],{"class":103,"line":2196},145,[101,2198,2199],{"class":298},"        ports ",[101,2201,750],{"class":294},[101,2203,578],{"class":298},[101,2205,2041],{"class":117},[101,2207,1961],{"class":298},[101,2209,866],{"class":294},[101,2211,1966],{"class":298},[101,2213,805],{"class":294},[101,2215,2216],{"class":298}," args.ports.split(",[101,2218,1954],{"class":110},[101,2220,787],{"class":298},[101,2222,1493],{"class":294},[101,2224,2225],{"class":298}," p.strip()]\n",[101,2227,2229,2231,2234],{"class":103,"line":2228},146,[101,2230,1557],{"class":294},[101,2232,2233],{"class":117}," ValueError",[101,2235,993],{"class":298},[101,2237,2239,2241,2244],{"class":103,"line":2238},147,[101,2240,2176],{"class":298},[101,2242,2243],{"class":110},"\"--ports must be a comma-separated list of integers.\"",[101,2245,707],{"class":298},[101,2247,2249,2251,2253,2256,2259,2262,2264,2267,2270,2273,2275,2278,2281,2283,2285,2287],{"class":103,"line":2248},148,[101,2250,961],{"class":294},[101,2252,1310],{"class":294},[101,2254,2255],{"class":298}," ports ",[101,2257,2258],{"class":294},"or",[101,2260,2261],{"class":117}," any",[101,2263,877],{"class":298},[101,2265,2266],{"class":294},"not",[101,2268,2269],{"class":117}," 1",[101,2271,2272],{"class":294}," \u003C=",[101,2274,1220],{"class":298},[101,2276,2277],{"class":294},"\u003C=",[101,2279,2280],{"class":117}," 65535",[101,2282,1824],{"class":294},[101,2284,1220],{"class":298},[101,2286,805],{"class":294},[101,2288,2289],{"class":298}," ports):\n",[101,2291,2293,2295,2298],{"class":103,"line":2292},149,[101,2294,2176],{"class":298},[101,2296,2297],{"class":110},"\"--ports values must be between 1 and 65535.\"",[101,2299,707],{"class":298},[101,2301,2303,2305,2307,2310,2312,2315,2317],{"class":103,"line":2302},150,[101,2304,747],{"class":298},[101,2306,750],{"class":294},[101,2308,2309],{"class":298}," load_oui_file(args.oui_file) ",[101,2311,1493],{"class":294},[101,2313,2314],{"class":298}," args.oui_file ",[101,2316,1548],{"class":294},[101,2318,2319],{"class":117}," OUI_VENDORS\n",[101,2321,2323],{"class":103,"line":2322},151,[101,2324,224],{"emptyLinePlaceholder":223},[101,2326,2328,2331,2333,2335,2338,2340,2343,2345,2348],{"class":103,"line":2327},152,[101,2329,2330],{"class":117},"    print",[101,2332,877],{"class":298},[101,2334,1236],{"class":294},[101,2336,2337],{"class":110},"\"ARP-sweeping ",[101,2339,1248],{"class":117},[101,2341,2342],{"class":298},"network",[101,2344,1810],{"class":117},[101,2346,2347],{"class":110},"...\"",[101,2349,707],{"class":298},[101,2351,2353,2356,2358,2361,2363],{"class":103,"line":2352},153,[101,2354,2355],{"class":298},"    hosts ",[101,2357,750],{"class":294},[101,2359,2360],{"class":298}," arp_sweep(",[101,2362,1772],{"class":117},[101,2364,2365],{"class":298},"(network), args.timeout)\n",[101,2367,2369,2371,2373,2375,2377,2380,2383,2385,2388],{"class":103,"line":2368},154,[101,2370,2330],{"class":117},[101,2372,877],{"class":298},[101,2374,1236],{"class":294},[101,2376,666],{"class":110},[101,2378,2379],{"class":117},"{len",[101,2381,2382],{"class":298},"(hosts)",[101,2384,1810],{"class":117},[101,2386,2387],{"class":110}," host(s) responded.\"",[101,2389,707],{"class":298},[101,2391,2393],{"class":103,"line":2392},155,[101,2394,224],{"emptyLinePlaceholder":223},[101,2396,2398,2400,2403,2406,2408,2411,2413],{"class":103,"line":2397},156,[101,2399,759],{"class":294},[101,2401,2402],{"class":298}," ThreadPoolExecutor(",[101,2404,2405],{"class":768},"max_workers",[101,2407,750],{"class":294},[101,2409,2410],{"class":298},"args.workers) ",[101,2412,790],{"class":294},[101,2414,2415],{"class":298}," pool:\n",[101,2417,2419,2422,2424,2427,2430,2433],{"class":103,"line":2418},157,[101,2420,2421],{"class":298},"        rows ",[101,2423,750],{"class":294},[101,2425,2426],{"class":117}," list",[101,2428,2429],{"class":298},"(pool.map(",[101,2431,2432],{"class":294},"lambda",[101,2434,2435],{"class":298}," h: fingerprint(h, ports, args.timeout, vendors), hosts))\n",[101,2437,2439],{"class":103,"line":2438},158,[101,2440,224],{"emptyLinePlaceholder":223},[101,2442,2444,2446,2449,2451],{"class":103,"line":2443},159,[101,2445,1679],{"class":294},[101,2447,2448],{"class":298}," row ",[101,2450,805],{"class":294},[101,2452,2453],{"class":298}," rows:\n",[101,2455,2457,2460,2462,2464,2466,2468,2471,2474,2477,2480,2482,2484,2486,2489,2491,2493,2496,2498,2501,2504,2507,2509,2512,2514],{"class":103,"line":2456},160,[101,2458,2459],{"class":117},"        print",[101,2461,877],{"class":298},[101,2463,1236],{"class":294},[101,2465,666],{"class":110},[101,2467,1248],{"class":117},[101,2469,2470],{"class":298},"row[",[101,2472,2473],{"class":110},"'ip_address'",[101,2475,2476],{"class":298},"]",[101,2478,2479],{"class":294},":15s",[101,2481,1810],{"class":117},[101,2483,641],{"class":117},[101,2485,2470],{"class":298},[101,2487,2488],{"class":110},"'mac_address'",[101,2490,2476],{"class":298},[101,2492,1810],{"class":117},[101,2494,2495],{"class":117},"  {",[101,2497,2470],{"class":298},[101,2499,2500],{"class":110},"'vendor'",[101,2502,2503],{"class":298},"][:",[101,2505,2506],{"class":117},"28",[101,2508,2476],{"class":298},[101,2510,2511],{"class":294},":28s",[101,2513,1810],{"class":117},[101,2515,2516],{"class":110}," \"\n",[101,2518,2520,2523,2526,2528,2530,2533,2535,2537,2540,2542,2544],{"class":103,"line":2519},161,[101,2521,2522],{"class":294},"              f",[101,2524,2525],{"class":110},"\"ports: ",[101,2527,1248],{"class":117},[101,2529,2470],{"class":298},[101,2531,2532],{"class":110},"'open_ports'",[101,2534,863],{"class":298},[101,2536,2258],{"class":294},[101,2538,2539],{"class":110}," 'none'",[101,2541,1810],{"class":117},[101,2543,666],{"class":110},[101,2545,707],{"class":298},[101,2547,2549],{"class":103,"line":2548},162,[101,2550,224],{"emptyLinePlaceholder":223},[101,2552,2554,2557,2559,2561,2564,2566,2569,2571,2574,2576,2579,2581,2584,2586,2589,2591,2594],{"class":103,"line":2553},163,[101,2555,2556],{"class":298},"    fieldnames ",[101,2558,750],{"class":294},[101,2560,578],{"class":298},[101,2562,2563],{"class":110},"\"ip_address\"",[101,2565,584],{"class":298},[101,2567,2568],{"class":110},"\"mac_address\"",[101,2570,584],{"class":298},[101,2572,2573],{"class":110},"\"vendor\"",[101,2575,584],{"class":298},[101,2577,2578],{"class":110},"\"reverse_dns\"",[101,2580,584],{"class":298},[101,2582,2583],{"class":110},"\"open_ports\"",[101,2585,584],{"class":298},[101,2587,2588],{"class":110},"\"banners\"",[101,2590,584],{"class":298},[101,2592,2593],{"class":110},"\"scanned_at\"",[101,2595,630],{"class":298},[101,2597,2599,2601,2603,2606,2609,2611,2614,2616,2619,2621,2623,2625,2627,2629,2631],{"class":103,"line":2598},164,[101,2600,759],{"class":294},[101,2602,762],{"class":117},[101,2604,2605],{"class":298},"(args.output, ",[101,2607,2608],{"class":110},"\"w\"",[101,2610,584],{"class":298},[101,2612,2613],{"class":768},"newline",[101,2615,750],{"class":294},[101,2617,2618],{"class":110},"\"\"",[101,2620,584],{"class":298},[101,2622,769],{"class":768},[101,2624,750],{"class":294},[101,2626,774],{"class":110},[101,2628,787],{"class":298},[101,2630,790],{"class":294},[101,2632,2633],{"class":298}," csv_file:\n",[101,2635,2637,2640,2642,2645,2648,2650],{"class":103,"line":2636},165,[101,2638,2639],{"class":298},"        writer ",[101,2641,750],{"class":294},[101,2643,2644],{"class":298}," csv.DictWriter(csv_file, ",[101,2646,2647],{"class":768},"fieldnames",[101,2649,750],{"class":294},[101,2651,2652],{"class":298},"fieldnames)\n",[101,2654,2656],{"class":103,"line":2655},166,[101,2657,2658],{"class":298},"        writer.writeheader()\n",[101,2660,2662],{"class":103,"line":2661},167,[101,2663,2664],{"class":298},"        writer.writerows(rows)\n",[101,2666,2668,2670,2672,2674,2677,2679,2682,2684,2687,2689,2692,2694,2696],{"class":103,"line":2667},168,[101,2669,2330],{"class":117},[101,2671,877],{"class":298},[101,2673,1236],{"class":294},[101,2675,2676],{"class":110},"\"Wrote ",[101,2678,2379],{"class":117},[101,2680,2681],{"class":298},"(rows)",[101,2683,1810],{"class":117},[101,2685,2686],{"class":110}," row(s) to ",[101,2688,1248],{"class":117},[101,2690,2691],{"class":298},"args.output",[101,2693,1810],{"class":117},[101,2695,666],{"class":110},[101,2697,707],{"class":298},[101,2699,2701],{"class":103,"line":2700},169,[101,2702,224],{"emptyLinePlaceholder":223},[101,2704,2706],{"class":103,"line":2705},170,[101,2707,224],{"emptyLinePlaceholder":223},[101,2709,2711,2713,2716,2719,2722],{"class":103,"line":2710},171,[101,2712,1493],{"class":294},[101,2714,2715],{"class":117}," __name__",[101,2717,2718],{"class":294}," ==",[101,2720,2721],{"class":110}," \"__main__\"",[101,2723,993],{"class":298},[101,2725,2727],{"class":103,"line":2726},172,[101,2728,2729],{"class":298},"    main()\n",[33,2731,2733],{"id":2732},"notes","Notes",[38,2735,2736,2755,2761,2782,2792,2802],{},[41,2737,2738,2742,2743,2746,2747,2750,2751,2754],{},[2739,2740,2741],"strong",{},"Corrections from the first draft."," The original hand-written OUI table mapped ",[24,2744,2745],{},"70:10:6F"," to Cisco and ",[24,2748,2749],{},"B0:7B:25"," to Ubiquiti; the IEEE registry assigns them to Hewlett Packard Enterprise and Dell respectively. The fallback table above has been re-checked, but the lesson stands: load the registry file with ",[24,2752,2753],{},"--oui-file"," rather than trusting a hand-maintained list.",[41,2756,2757,2760],{},[2739,2758,2759],{},"Random MACs."," Phones and many laptops can use randomized, locally administered MAC addresses on Wi-Fi. The universal\u002Flocal bit (the second-least-significant bit of the first octet) marks those, so the script labels them instead of reporting \"Unknown\".",[41,2762,2763,2766,2767,2770,2771,2774,2775,2777,2778,2781],{},[2739,2764,2765],{},"What the banners are."," SSH servers must send an identification string (",[24,2768,2769],{},"SSH-2.0-...",") as soon as the connection opens, per RFC 4253, so port 22 almost always yields the server software. SMTP and FTP greet first too. HTTP says nothing until asked, so the script sends a ",[24,2772,2773],{},"HEAD"," request on ports 80 and 8080 and keeps the ",[24,2776,193],{}," header. TLS ports (443, 8443) and RDP stay silent without a protocol handshake; for those, ",[24,2779,2780],{},"nmap -sV"," is the right tool.",[41,2783,2784,2787,2788,2791],{},[2739,2785,2786],{},"Empty rows are signal."," A device that answers ARP and nothing on the probed ports still gets a row with empty ",[24,2789,2790],{},"open_ports",": a locked-down IoT device, a printer with its web UI disabled, or a workstation with a host firewall.",[41,2793,2794,2797,2798,2801],{},[2739,2795,2796],{},"ARP timing."," A single broadcast pass can miss hosts that are slow to answer or sleeping. If counts vary between runs, raise ",[24,2799,2800],{},"--timeout"," or run it twice and merge.",[41,2803,2804,2807],{},[2739,2805,2806],{},"Point in time."," Keep the CSVs and diff them, or feed the rows into whatever inventory already tracks the rest of your assets. The discovery-first essay shows how this file merges with DHCP, DNS and AD exports keyed on MAC and IP.",[33,2809,2811],{"id":2810},"references","References",[38,2813,2814,2822,2829,2836,2843,2854],{},[41,2815,2816],{},[18,2817,2821],{"href":2818,"rel":2819},"https:\u002F\u002Fscapy.readthedocs.io\u002Fen\u002Flatest\u002Fusage.html",[2820],"nofollow","Scapy usage: ARP ping, root and Npcap requirements",[41,2823,2824],{},[18,2825,2828],{"href":2826,"rel":2827},"https:\u002F\u002Fregauth.standards.ieee.org\u002F",[2820],"IEEE Registration Authority public listing (OUI \u002F MA-L)",[41,2830,2831],{},[18,2832,2835],{"href":2833,"rel":2834},"https:\u002F\u002Fstandards.ieee.org\u002Fwp-content\u002Fuploads\u002Fimport\u002Fdocuments\u002Ftutorials\u002Feui.pdf",[2820],"IEEE RA guidelines for use of EUI, OUI and CID (universal\u002Flocal bit)",[41,2837,2838],{},[18,2839,2842],{"href":2840,"rel":2841},"https:\u002F\u002Fwww.rfc-editor.org\u002Frfc\u002Frfc4253.html#section-4.2",[2820],"RFC 4253, section 4.2: SSH protocol version exchange",[41,2844,2845],{},[18,2846,2849,2850,2853],{"href":2847,"rel":2848},"https:\u002F\u002Fnmap.org\u002Fbook\u002Fman-host-discovery.html",[2820],"Nmap host discovery (",[24,2851,2852],{},"-sn",", ARP on local Ethernet)",[41,2855,2856],{},[18,2857,2860,2861],{"href":2858,"rel":2859},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Fsocket.html#socket.create_connection",[2820],"Python ",[24,2862,2863],{},"socket.create_connection",[2865,2866,2867],"style",{},"html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sRjNt, html code.shiki .sRjNt{--shiki-default:#85E89D;--shiki-default-font-weight:bold}html pre.shiki code .sns5M, html code.shiki .sns5M{--shiki-default:#DBEDFF}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":97,"searchDepth":214,"depth":214,"links":2869},[2870,2871,2872,2873,2874],{"id":35,"depth":214,"text":36},{"id":86,"depth":214,"text":87},{"id":197,"depth":214,"text":198},{"id":2732,"depth":214,"text":2733},{"id":2810,"depth":214,"text":2811},"techcolumnist",[2877,2878],"scripts","engineering","2026-01-07T14:00:00Z","A Python script that ARP-sweeps a local subnet with Scapy, maps MACs to IEEE vendors, probes common ports for banners, and writes one CSV row per device.","md",false,null,{},"\u002Fblog\u002F2026\u002F01\u002F07\u002Fpython-discovery-fingerprint-every-device-on-a-subnet",{"title":6,"description":2880},[2875],"blog\u002F2026\u002F01\u002F07\u002Fpython-discovery-fingerprint-every-device-on-a-subnet",[203,2890,2342],"discovery","\u002F2026\u002F01\u002F07\u002Fpython-discovery-fingerprint-every-device-on-a-subnet\u002F","KdBKq_FQmO34GSLEIItsfnEe0lCa8rK0-NUzfX8xH30",{"title":2894,"description":2895,"date":2896,"url":2897,"categories":2898,"tags":2899,"image":2883,"readingTime":227,"canonical":2875,"sites":2901,"series":2883,"seriesOrder":2883},"Python: gcloud – Audit IAM Bindings Across a GCP Organization","A Python script that pulls the IAM bindings across a GCP organization through Cloud Asset Inventory and flags basic roles, public access, and external identities.","2026-01-14T14:00:00Z","\u002F2026\u002F01\u002F14\u002Fpython-gcloud-audit-iam-bindings-across-a-gcp-organization\u002F",[2877,2878],[203,2900],"gcloud",[2875],{"title":2903,"description":2904,"date":2905,"url":2906,"categories":2907,"tags":2908,"image":2883,"readingTime":233,"canonical":2875,"sites":2910,"series":2883,"seriesOrder":2883},"Python: Azure – Cost Anomaly Alerts from the Cost Management API","A Python script that pulls daily Azure costs from the Cost Management Query API and posts a webhook alert when a day breaks from its rolling baseline.","2025-12-24T14:00:00Z","\u002F2025\u002F12\u002F24\u002Fpython-azure-automate-cost-anomaly-alerts-with-the-cost-management-api\u002F",[2877,2878],[203,2909],"azure",[2875],[2912,2921,2929],{"title":2913,"description":2914,"date":2915,"url":2916,"categories":2917,"tags":2918,"image":2883,"readingTime":220,"canonical":2875,"sites":2920,"series":2883,"seriesOrder":2883},"Python: Discovery – Crawling DNS Zones for Orphaned Records","A dnspython script that transfers a zone with AXFR, probes every A\u002FAAAA record for signs of life, flags dangling CNAMEs, and reports orphan cleanup candidates.","2026-01-28T14:00:00Z","\u002F2026\u002F01\u002F28\u002Fpython-discovery-crawling-dns-zones-for-orphaned-records\u002F",[2877,2878],[203,2890,2342,2919],"windows",[2875],{"title":2922,"description":2923,"date":2924,"url":20,"categories":2925,"tags":2927,"image":2883,"readingTime":251,"canonical":2875,"sites":2928,"series":2883,"seriesOrder":2883},"Infrastructure: Documenting an Undocumented Network, Discovery First","Document an undocumented network from what it already knows: DHCP leases, AD, ARP and switch MAC tables, then a scoped nmap pass. Scripts included.","2026-07-15T14:00:00Z",[2878,2926],"strategy",[2890,2342,203],[2875],{"title":2930,"description":2931,"date":2932,"url":2933,"categories":2934,"tags":2935,"image":2883,"readingTime":220,"canonical":2875,"sites":2937,"series":2883,"seriesOrder":2883},"Node.js: Discovery – Diffing Two Network Scans to Flag New Hosts","A dependency-free Node.js script that diffs two nmap XML scans and flags new, missing and moved hosts plus port changes, with an exit code for cron or CI.","2026-03-11T14:00:00Z","\u002F2026\u002F03\u002F11\u002Fnode-js-discovery-diffing-two-network-scans-to-flag-new-hosts\u002F",[2877,2878],[2936,2890,2342],"nodejs",[2875],{"doc":2883,"posts":2939},[],1790052513401]