Herramienta para armar un inventario de dispositivos de red a partir de archivos PCAP o de captura en vivo. Identifica cada equipo por su dirección MAC y le asocia el fabricante usando una base OUI en JSON (por ejemplo la de maclookup.app).
- Lee archivos
.pcapo captura en vivo desde una interfaz. - Saca las MAC de origen y destino de cada trama Ethernet (incluidas las que llevan tag VLAN 802.1Q).
- Resuelve el fabricante con coincidencia de prefijo más largo (soporta bloques MA-L /24, MA-M /28 y MA-S /36).
- Lleva conteos separados de origen y destino por dispositivo único.
- Filtra multicast/broadcast por defecto (configurable) y los marca cuando se incluyen.
- Marca las MAC administradas localmente, típicas de la aleatorización.
- Exporta a CSV o JSON reales, o imprime una tabla por stdout.
- Captura en vivo con corte limpio por Ctrl-C y filtro BPF opcional.
-
Rust y Cargo (probado con 1.75; instalá desde rustup.rs).
-
libpcap:
sudo apt install libpcap-dev
-
La base OUI en JSON: se baja de https://maclookup.app/downloads/json-database con el botón "Download JSON database".
cargo build --releaseEl binario queda en target/release/network_inventory.
bin/ni unifica las dos formas de correr todo el pipeline (pcap -> CSV -> Excel): nativo o dentro de
Docker, con el mismo comando. Por defecto elige solo: usa el binario nativo si está instalado, y si
no cae a la imagen Docker.
ni captura.pcap # auto: nativo si está, si no Docker
ni --docker captura.pcap # forzar Docker
ni --native captura.pcap # forzar nativoDeja captura.csv y captura.xlsx en la carpeta actual. Acepta las mismas variables que el pipeline
(INCLUDE_MULTICAST=1, NO_XLSX=1, OUI_DB=...), y si el primer argumento empieza con -, pasa los
flags crudos a network_inventory (modo avanzado).
Para tenerlo disponible como comando, hay dos caminos de despliegue:
- Nativo:
sudo ./install.shcompila el binario y dejanetwork_inventory,inventario_a_excel.py,pipeline.shynien el PATH, más la base OUI en/opt/network-inventory/. Requierecargo,libpcap-devypython3. - Docker:
docker build -f docker/Dockerfile -t network-inventory .(desde la raíz). Despuésnien modo auto o--dockerusa esa imagen.
Uso: network_inventory [OPCIONES]
Opciones:
-p, --pcap <PCAP> Archivo PCAP de entrada
-i, --iface <IFACE> Interfaz para captura en vivo
-j, --oui-db <OUI_DB> Base OUI JSON [por defecto: mac-vendors-export.json]
-o, --output <OUTPUT> Archivo de salida (si se omite, imprime tabla)
-f, --format <FORMAT> Formato de salida: csv | json [por defecto: csv]
--filter <FILTER> Filtro BPF (ej: "ip", "vlan")
--include-multicast Incluir MAC multicast/broadcast en el inventario
-d, --duration <N> (En vivo) Capturar N segundos y salir (útil para timers)
--snaplen <N> (En vivo) Bytes capturados por trama [por defecto: 96]
--buffer-size <N> (En vivo) Buffer del kernel en bytes [por defecto: 4194304]
-t, --threads <N> (En vivo) Hilos de captura; >1 activa PACKET_FANOUT [por defecto: 1]
--fanout <MODE> (En vivo, >1 hilo) Reparto del kernel: hash | cpu | lb [por defecto: hash]
-q, --quiet Silenciar el progreso por stderr
Un aviso importante sobre la captura: la herramienta lee las MAC de la cabecera Ethernet. Un PCAP
tomado con tcpdump -i any (LINUX_SLL) o sin capa 2 (RAW) se rechaza con un mensaje claro, porque
ahí los offsets de la MAC no son válidos. Capturá siempre sobre una interfaz Ethernet concreta como
eth0, no sobre any.
# Analizar un PCAP y mostrar la tabla
./target/release/network_inventory --pcap captura.pcap
# Exportar a CSV
./target/release/network_inventory --pcap captura.pcap --output inventario.csv
# Exportar a JSON
./target/release/network_inventory --pcap captura.pcap -o inventario.json -f json
# Captura en vivo (Ctrl-C para terminar e imprimir el inventario)
sudo ./target/release/network_inventory --iface eth0
# Sin sudo: otorgar solo la capability necesaria
sudo setcap cap_net_raw,cap_net_admin+eip ./target/release/network_inventory
./target/release/network_inventory --iface eth0
# Solo tráfico IP, con otra base OUI
./target/release/network_inventory --pcap red.pcap --filter ip --oui-db vendors.jsonTabla por stdout, ordenada por total de apariciones:
----- Inventario de Dispositivos (3 únicos) -----
MAC Vendor Src Dst Total
b8:27:eb:22:22:22 Raspberry Pi Foundation 1 4 5
00:00:0c:11:11:11 Cisco Systems, Inc 3 1 4
28:63:36:33:33:33 Siemens AG 2 0 2
CSV (los nombres con comas quedan bien comillados):
mac,vendor,src_count,dst_count,total,multicast,locally_administered
00:00:0c:11:11:11,"Cisco Systems, Inc",3,1,4,false,falseinventario_a_excel.py toma el CSV o el JSON de salida y arma un Excel con colores, filtros y hojas
de resumen (por fabricante, y totales con un par de gráficos):
python3 inventario_a_excel.py inventario.csv -o reporte.xlsx # requiere openpyxlPara Power BI conviene usar directo el CSV (Obtener datos -> Texto/CSV), que ya trae todos los valores; si tenés la captura corriendo por systemd y reescribiendo el CSV, Power BI lo puede recargar.
Si no querés instalar nada nativo, corré todo en un contenedor. La imagen empaqueta el binario, el
conversor a Excel y el pipeline; el entrypoint es el mismo bin/pipeline.sh que se usa nativo, así
que se comporta igual.
# construir (desde la raíz del repo)
docker build -f docker/Dockerfile -t network-inventory .
# usar (el lanzador arma el docker run por vos)
ni --docker captura.pcapLos detalles (base OUI, cómo cambiarla sin rebuildear) están en docker/README.md.
- Lookup diferido: durante la captura no se busca el fabricante, solo se cuentan MAC. La resolución OUI ocurre una vez por dispositivo único, al final. En una captura de millones de paquetes con cientos de dispositivos, eso son cientos de lookups en vez de millones.
- Sin asignaciones en el camino caliente: las MAC se manejan como
[u8; 6]y los OUI como enteros. No se formatean strings ni se clonan nombres de fabricante por paquete. - Memoria acotada por dispositivos, no por paquetes: el inventario crece con la cantidad de MAC únicas (el tamaño de la red), no con la cantidad de tramas.
- Hashing rápido (
FxHashMap) para el mapa de inventario.
--threads/--fanout usan PACKET_FANOUT, una API de Linux (AF_PACKET). En macOS o BSD el
binario compila igual (todo lo demás anda normal), pero pedir --threads mayor a 1 ahí da un
error claro en vez de fallar a medias; en esas plataformas usá --threads 1 (el default).
Primero el encuadre. "10 Gbit/s" a tramas de 64 bytes son ~14,88 Mpps: ningún capturador userspace
sobre libpcap sostiene eso, y para inventario OT tampoco es el objetivo. En un SPAN 10G con
Modbus/S7comm el pps medio está muy por debajo de línea; el riesgo real son las microráfagas. La meta
es absorber ráfagas con buffer, mantener barato el trabajo por paquete, y repartir en varias colas
para tener margen. AF_PACKET con PACKET_FANOUT alcanza; DPDK o AF_XDP son innecesarios acá.
# Desactivar coalescing: GRO/LRO juntan paquetes y distorsionan lo capturado (verías super-tramas).
sudo ethtool -K eth0 gro off lro off tso off gso off
# Agrandar los ring buffers de la NIC para tolerar ráfagas.
sudo ethtool -G eth0 rx 4096
# Ver descartes a nivel driver/NIC.
ethtool -S eth0 | grep -iE "drop|miss|fifo|nobuf"
cat /proc/net/devSi el SPAN llega por varias colas RSS, asigná IRQs a CPUs dedicadas y dejá esos cores libres para la captura.
# Snaplen mínimo (solo hace falta la cabecera L2) + buffer grande + varios hilos con fanout.
sudo ./target/release/network_inventory \
--iface eth0 --threads 4 --fanout hash \
--snaplen 64 --buffer-size 67108864--snaplen 64: copia solo ~64 bytes por trama en vez de la trama entera, o sea menos trabajo kernel->usuario en enlaces rápidos. Para este inventario sobra (las MAC están en los primeros 12 bytes).--buffer-size: el ring del kernel por socket. Más grande, más microráfaga absorbida.--threads Ncon--fanout: abre N sockets AF_PACKET en un grupoPACKET_FANOUT; el kernel reparte el tráfico y cada hilo procesa su parte con un inventario local (sin locks en el camino caliente), y al final se fusionan.hashmantiene cada flujo en el mismo socket,cpureparte por CPU de llegada,lbes round-robin.- Guard: con
--threads 1no se usa fanout. Un socket único dentro de un grupo fanout puede interferir con la entrega en loopback y algunos contenedores.
Tras la captura, la herramienta imprime recibidos / descartados(buffer) / descartados(iface) (vía
pcap_stats) y avisa si hubo descartes. Si descartados(buffer) > 0, subí --buffer-size o
--threads; si descartados(iface) > 0, el cuello está en la NIC o el driver (mirá ethtool -S y
ethtool -G).
scripts/capture-10g.sh autodetecta las colas RSS, aplica el tuning de NIC y lanza la captura con
tantos hilos como colas (mínimo 2):
sudo ./scripts/capture-10g.sh eth0 mac-vendors-export.json inventario.csv
# Override: THREADS=8 BUFFER_SIZE=134217728 sudo ./scripts/capture-10g.sh eth0Para correr sin root, con capabilities mínimas (CAP_NET_RAW y CAP_NET_ADMIN) y una unidad
endurecida. Los archivos están en systemd/.
# 1) Binario y usuario sin privilegios
sudo install -m 0755 target/release/network_inventory /usr/local/bin/
sudo useradd --system --no-create-home --shell /usr/sbin/nologin netcap
# 2) Estado + base OUI
sudo install -d -o netcap -g netcap /var/lib/network-inventory
# Descargá el JSON desde https://maclookup.app/downloads/json-database (el enlace lleva un token que
# rota, así que conviene bajarlo del navegador) y dejalo en su lugar:
sudo install -o netcap -g netcap mac-vendors-export.json /var/lib/network-inventory/
# 3) Configuración y unidades
sudo install -m 0644 systemd/network-inventory.env.example /etc/default/network-inventory
sudo cp systemd/network-inventory-tune@.service systemd/network-inventory@.service /etc/systemd/system/
sudo systemctl daemon-reload
# 4) Arrancar la captura en eth0 (arrastra el tuning como dependencia)
sudo systemctl start network-inventory@eth0.service
# 5) Detener -> SIGINT -> vuelca el inventario a /var/lib/network-inventory/inventario-eth0.csv
sudo systemctl stop network-inventory@eth0.serviceUn par de notas:
- El servicio escribe el inventario al detenerse. La unidad usa
KillSignal=SIGINTpara quesystemctl stopdispare el corte limpio; conSIGTERM(el default) abortaría sin volcar el archivo. - Ajustá
THREADS,BUFFER_SIZE, etc. en/etc/default/network-inventory. - Si la captura no arranca por el sandbox, ampliá
RestrictAddressFamilies(por ejemplo agregandoAF_INET) en la unidad.
- Las MAC de origen multicast se descartan por defecto (origen inválido o spoofeado). Las de
broadcast/multicast como destino también, salvo
--include-multicast, porque no representan dispositivos físicos. Cuando se incluyen, quedan marcadas. - La coincidencia de prefijo más largo distingue sub-asignaciones MA-M/MA-S dentro de un MA-L genérico. Los prefijos se indexan por sus primeros 3 bytes y se ordenan por longitud.
- El bit locally administered (
mac[0] & 0x02) se reporta para ayudar a detectar MAC aleatorizadas. - Las MAC van antes del tag VLAN 802.1Q, así que los offsets 0 a 11 valen también en tramas con tag.
- Solo Ethernet: al abrir se valida el datalink (
get_datalink). Capturas LINUX_SLL o RAW se rechazan en vez de producir un inventario mal en silencio. - Reporte de descartes: tras una captura en vivo se imprimen recibidos y descartados vía
pcap_stats. Si hubo descartes conviene subir--buffer-sizeo acotar con--filter; es lo relevante en un SPAN de OT, donde la idea es tolerar ráfagas sin perder paquetes.
cargo testCubren el parseo de prefijos MA-L/MA-M, la coincidencia de prefijo más largo, la detección de multicast y el filtrado de origen multicast.