Loading
Cybersecurity undergraduate and web developer based in Sri Lanka, passionate about penetration testing, VAPT, and ethical hacking.
Bash/Python automation that scans the local network, identifies active hosts, pulls MAC addresses and hostnames, and logs results to a CSV for IT audit.
This writeup documents the development of a custom Python-based network inventory script built for Timex. The tool was created to automate the discovery and documentation of all devices on the Timex internal network — replacing a manually maintained spreadsheet that was frequently outdated and inaccurate. It scans the network, identifies hosts, resolves hostnames, fingerprints operating systems, and exports a structured inventory report as HTML and CSV for the IT team.
The Timex IT team outlined the following requirements before development began:
A baseline CSV of known devices was provided by the client at
project start — new devices discovered during scans are automatically
flagged as UNKNOWN in the output report.
The tool was structured as a single scheduled script with modular functions for discovery, fingerprinting, and reporting:
/timex-inventory
inventory.py -- main entry point and scheduler
discovery.py -- ARP sweep and host discovery
fingerprint.py -- OS and service fingerprinting
reporter.py -- HTML and CSV report generation
baseline.csv -- known device registry
templates/
inventory.html -- Jinja2 HTML report template
logs/ -- scan logs per run
requirements.txt
Scan logs are written per run with a timestamp filename — allowing the IT team to compare results across runs and track when new devices first appeared on the network.
ARP is used for host discovery on the local subnet — faster and more reliable than ICMP ping which is frequently blocked by Windows firewall rules:
# discovery.py
from scapy.all import ARP, Ether, srp
import socket, datetime
def arp_sweep(subnet):
print(f"[*] Scanning subnet: {subnet}")
arp = ARP(pdst=subnet)
ether = Ether(dst="ff:ff:ff:ff:ff:ff")
packet = ether / arp
result = srp(packet, timeout=3,
verbose=False)[0]
hosts = []
for sent, received in result:
try:
hostname = socket.gethostbyaddr(
received.psrc)[0]
except socket.herror:
hostname = "unknown"
hosts.append({
"ip": received.psrc,
"mac": received.hwsrc,
"hostname": hostname,
"discovered": datetime.datetime.now()
.strftime("%Y-%m-%d %H:%M")
})
print(f"[*] {len(hosts)} hosts discovered")
return hosts
MAC addresses are captured alongside IPs — allowing the tool to track devices even if they change IP via DHCP between scans.
Each discovered host is passed to Nmap for OS detection and top-port service enumeration:
# fingerprint.py
import nmap
def fingerprint_host(ip):
nm = nmap.PortScanner()
nm.scan(ip, arguments="-O --top-ports 20 -T4")
result = {
"os": "unknown",
"services": []
}
if ip in nm.all_hosts():
host = nm[ip]
# OS detection
if "osmatch" in host and host["osmatch"]:
result["os"] = \
host["osmatch"][0]["name"]
# Open services
for proto in host.all_protocols():
for port in host[proto].keys():
svc = host[proto][port]
if svc["state"] == "open":
result["services"].append(
f"{port}/{proto} "
f"({svc['name']})"
)
return result
OS detection requires root or administrator privileges. The script checks for elevated permissions on startup and exits with a clear error message if not satisfied.
Each discovered host is checked against the known device
baseline CSV. Unrecognised MAC addresses are flagged
as UNKNOWN in the report:
# inventory.py
import csv
def load_baseline(filepath="baseline.csv"):
baseline = {}
with open(filepath, newline="") as f:
reader = csv.DictReader(f)
for row in reader:
baseline[row["mac"].lower()] = {
"label": row["label"],
"owner": row["owner"],
"type": row["type"]
}
return baseline
def check_baseline(host, baseline):
mac = host["mac"].lower()
if mac in baseline:
host.update(baseline[mac])
host["status"] = "KNOWN"
else:
host["label"] = "Unrecognised Device"
host["owner"] = "—"
host["type"] = "—"
host["status"] = "UNKNOWN"
return host
Unknown devices trigger a warning entry in the scan log and are highlighted in red in the HTML report — giving the IT team immediate visibility of rogue or new devices.
Scan results are exported as a timestamped HTML report for management review and a CSV for the asset register:
# reporter.py
from jinja2 import Environment, FileSystemLoader
import csv, datetime, os
def generate_report(hosts, output_dir="reports"):
os.makedirs(output_dir, exist_ok=True)
ts = datetime.datetime.now().strftime("%Y%m%d_%H%M")
# HTML report
env = Environment(
loader=FileSystemLoader("templates"))
tmpl = env.get_template("inventory.html")
html = tmpl.render(
hosts=hosts,
generated=ts,
total=len(hosts),
unknown=sum(
1 for h in hosts
if h["status"] == "UNKNOWN")
)
with open(f"{output_dir}/inventory_{ts}.html","w") as f:
f.write(html)
# CSV export
with open(f"{output_dir}/inventory_{ts}.csv",
"w", newline="") as f:
w = csv.writer(f)
w.writerow(["IP","MAC","Hostname","OS",
"Services","Label","Owner",
"Type","Status","Discovered"])
for h in hosts:
w.writerow([
h["ip"], h["mac"], h["hostname"],
h["os"],
" | ".join(h["services"]),
h["label"], h["owner"],
h["type"], h["status"],
h["discovered"]
])
The HTML report includes a summary banner showing total hosts found, known devices, and unknown device count — colour coded green for known and red for unknown.
The script was configured to run automatically every night at 02:00 via cron on the Timex IT server:
# Add to crontab — run as root for OS detection
sudo crontab -e
# Run nightly at 02:00
0 2 * * * /usr/bin/python3 \
/opt/timex-inventory/inventory.py \
--subnet 192.168.1.0/24 \
>> /opt/timex-inventory/logs/cron.log 2>&1
# Verify the job is registered
sudo crontab -l
Each nightly run produces a new timestamped report in the
reports/ directory. The IT team configured a
shared network drive mount so reports are accessible from
any workstation on the Timex network without logging into
the scan server.
The tool installs into any Python 3 environment with Nmap present on the system:
# Clone or copy the project to the server
cd /opt/timex-inventory
# Install Python dependencies
pip3 install -r requirements.txt
# requirements.txt
python-nmap==0.7.1
scapy==2.5.0
Jinja2==3.1.2
# Ensure Nmap is installed
nmap --version
# Run a manual scan
sudo python3 inventory.py \
--subnet 192.168.1.0/24
# Run against multiple subnets
sudo python3 inventory.py \
--subnet 192.168.1.0/24 192.168.2.0/24
# Output: reports/inventory_20240115_0200.html
# reports/inventory_20240115_0200.csv
Python 3 — core language for the inventory toolScapy — ARP sweep for host and MAC address discoverypython-nmap — OS fingerprinting and service enumerationJinja2 — HTML report templatingcsv / socket — baseline comparison and hostname resolutioncron — scheduled nightly executionVS Code — development environment