bitmapper
bitmapper risponde alla domanda "con cosa sta davvero parlando questa macchina, e quale processo lo sta facendo?"
bitcollector legge lo stato dell'host. bitscanner guarda verso l'esterno, verso il
segmento. bitmapper osserva il traffico stesso — pacchetti dal vivo sulle interfacce
dell'host, raggruppati in flussi e attribuiti al processo che possiede il socket.
bitmapper consegna record di flusso, non traffico. Da 0.1.0-ga il motore di cattura è
collegato all'exporter, e le connessioni tracciate raggiungono la piattaforma come record di
flusso: estremi, porte, protocollo, contatori di byte e pacchetti, stato della connessione e —
quando può essere determinato, cosa oggi rara — il processo attribuito.
Nessun byte di payload, nessuna cattura di pacchetti, e non la riga di
comando del processo, che porta abitualmente credenziali come argomenti. Se ti aspettavi che
arrivasse un PCAP da qualche parte, non è ciò che questo agente invia.
L'attribuzione dei processi funziona a malapena nel binario pubblicato, e la correzione non è
ancora rilasciata. In 0.1.0-ga l'attribuzione è tentata una volta, alla creazione della
connessione, contro un'istantanea della tabella dei socket che non può contenere il socket di una
nuova connessione uscente, e non esiste alcuna inferenza dai socket in ascolto — quindi i
flussi arrivano spesso senza alcun processo (misurato sull'host di sviluppo: 0 attribuiti su
12), e destination_process non viene mai valorizzato. Il sostituto — completamento per
flusso, su entrambi gli estremi, e un campo attribution che distingue una corrispondenza
diretta di socket (socket) da un'inferenza (listening_socket) — non è rilasciato.
Quell'inferenza è vincolata: viene tratta solo per un indirizzo di questo host, solo quando è
dimostrato che esattamente un processo detiene il socket in ascolto, e solo per TCP, quindi un
flusso UDP non la porta mai. Leggi
attribuzione dei processi prima di programmare qualsiasi cosa su
di essa.
È un binario, su una sola piattaforma. 0.1.0-ga è pubblicato solo per linux/amd64.
Non esiste alcun download per macOS, Windows o arm64, e le ragioni sono
esposte più sotto anziché lasciate come un vuoto in una pagina di download.
Non cattura tutti i protocolli in modo predefinito. capture.protocols vale
["tcp", "udp"] che il tuo file di configurazione la nomini o no, quindi un'installazione di
serie non vede ICMP, ICMPv6, SCTP, GRE, ESP o AH — il kernel li scarta e nulla nell'output lo
dice. Allargarlo è una riga.
Parti di questo agente non sono ancora costruite. Non c'è aggregazione della topologia dei
servizi, né metriche Prometheus, né rilevamento delle policy di rete; diverse chiavi di
configurazione vengono interpretate e non fanno nulla, e lo stesso vale per tutte le opzioni
della riga di comando tranne --config. Cosa non è collegato è l'elenco, e
vale la pena leggerlo prima di pianificare qualcosa attorno a una chiave.
| Release | 0.1.0-ga, commit 64ebc64, compilato con go1.25.12 |
| Piattaforme | Solo linux/amd64 — macOS, Windows e arm64 non sono pubblicati, ed ecco perché |
| Supply chain | Build riproducibile, SBOM CycloneDX, firma cosign, attestazione dell'SBOM, SHA256SUMS firmato — la stessa chiave di famiglia degli altri bits |
| Stato | Cattura, attribuisce e traccia le connessioni; esporta record di flusso verso la piattaforma |
| Superficie di rete in ingresso | Nessun servizio in ascolto. I server HTTP/WebSocket e gRPC esistono nell'albero dei sorgenti ma non vengono mai costruiti, ed entrambi sono distribuiti disattivati |
| Traffico in uscita | Solo https — registrazione, heartbeat e record di flusso verso l'endpoint che configuri |
| Privilegi | Elevati: root, oppure CAP_NET_RAW + CAP_NET_ADMIN |
| Requisito di build | CGO e libpcap, collegata staticamente nel binario pubblicato |
Dove si colloca tra i bits
I quattro agenti rispondono a quattro domande diverse, e il confine è ciò che ciascuno di essi guarda:
| Guarda | Risponde a | |
|---|---|---|
bitcollector | Lo stato dell'host stesso | "Cos'è questa macchina, e in che stato si trova?" |
bitscanner | Il segmento attorno all'host | "Cos'altro c'è su questo cavo di cui non sono a conoscenza?" |
bitmapper | I pacchetti che attraversano le interfacce dell'host — TCP e UDP in modo predefinito, di più se lo chiedi | "Con cosa sta parlando questa macchina, e quale processo lo sta facendo?" |
bitenforcer | La configurazione dell'host | "Questa macchina corrisponde alla policy che ho impostato?" |
Una tabella dei vicini ti dice che un dispositivo esiste. Una cattura ti dice che ci si sta parlando, da parte di cosa e con quale frequenza — che è la differenza tra un inventario e una mappa delle dipendenze.
Cosa funziona oggi
Presente e collegato al percorso di codice del comando capture:
- Cattura di pacchetti dal vivo — libpcap tramite
gopacket, collegata staticamente nel binario Linux pubblicato, con filtro BPF, snaplen, modalità promiscua e dimensione del buffer di cattura. - Filtraggio della cattura lato kernel —
capture.filter,capture.protocols,capture.portsecapture.exclude_portsvengono compilate in un'unica espressione BPF applicata dal kernel, così il traffico escluso non viene mai copiato nell'agente. L'espressione compilata viene registrata a log all'avvio. Attenzione al predefinito:capture.protocolsvale["tcp", "udp"]anche se il tuo file la omette, il che significa che ICMP, SCTP, GRE ed ESP non vengono catturati se non lo chiedi. Vedi il filtraggio. - Cattura su più interfacce — un elenco di interfacce indicate per nome, oppure ogni interfaccia attiva e non di loopback quando non ne indichi nessuna.
- Attribuzione dei processi — presente, ma a malapena funzionante nel binario pubblicato.
PID, nome del processo, percorso dell'eseguibile e utente, risolti dalle tabelle dei socket
dell'host. In
0.1.0-gala ricerca è tentata una sola volta, alla creazione della connessione, contro un'istantanea che non può contenere il socket di una nuova connessione uscente, quindi i flussi spesso non portano alcun processo edestination_processnon viene mai valorizzato. Il completamento per flusso, l'attribuzione di entrambi gli estremi e il campo di affidabilitàattribution(socketvslistening_socket) non sono nel binario pubblicato, che non ha alcuna inferenza dai socket in ascolto — vedi attribuzione dei processi per ciò che viene consegnato oggi e ciò che lo sostituisce. Quel sostituto inoltre rifiuta anziché indovinare: nessuna identità viene presa da un socket in ascolto se l'indirizzo non è su questo host o se non è dimostrato che un solo processo lo detiene — un socket nginx sull'host di sviluppo è detenuto da 17 processi — e non inferisce mai per UDP, che non ha stato LISTEN. Un campo processo vuoto è un rifiuto, non un guasto. - Tracciamento con stato delle connessioni — corrispondenza bidirezionale dei flussi, una
macchina a stati TCP (
SYN_SENT→ESTABLISHED→ … →CLOSED), contatori di pacchetti e byte per connessione, una tabella con dimensione limitata e sfratto delle voci, e garbage collection periodica delle connessioni inattive. - Flusso di eventi interno al processo — eventi di pacchetto, di nuova connessione e di statistica distribuiti ai sottoscrittori all'interno del processo.
- Statistiche periodiche — contatori di pacchetti, byte, scarti e connessioni scritti come
JSON strutturato secondo
performance.stats_interval. - Integrazione con il control plane — registrazione e heartbeat dell'agente quando
control_plane.*è configurato. - Export dei flussi — ogni connessione tracciata passa dal percorso di cattura all'exporter
e viene inviata in POST alla piattaforma come record di flusso. Ogni flusso produce almeno un
record, e l'ultimo è marcato come finale con contatori completi; una connessione di lunga
durata viene aggiornata una volta per
batch_export.batch_intervalse i suoi contatori si sono mossi, così la vedi prima che termini. - Un filtraggio dell'export che filtra davvero —
output.filter(exclude_cidrs,exclude_loopback,protocols,ports) è applicato in ingresso, prima che un record venga costruito, così un flusso escluso non viene mai tenuto in memoria e non può mai essere inviato. Le esclusioni valgono su entrambi gli estremi: escludere10.0.0.0/8significa «non parlare a Cert-IX di quella rete», e riportare che10.1.2.3ha aperto una connessione verso l'esterno gliene parla altrettanto compiutamente. Una voce che non può essere compilata rifiuta l'esecuzione anziché degradare a «esporta tutto». - Convalida fail-fast della configurazione — l'agente si rifiuta di avviarsi con una configurazione non valida o insicura per omissione, anziché avviarsi in uno stato indebolito.
Cosa non è collegato
Dichiarato esplicitamente, così nessuno debba scoprirlo leggendo il codice sorgente:
- I server HTTP/WebSocket e gRPC non vengono mai costruiti. Poiché nulla li costruisce, il
middleware di sicurezza che sta dietro di essi — autenticazione, allowlist di IP, rate
limiting, audit logging — non viene eseguito. Non considerare oggi la configurazione
security.*come una protezione. Il lato positivo è quello onesto: senza alcun server costruito, l'agente non espone alcuna porta in ingresso. - Nessuna aggregazione della topologia dei servizi. I tipi
ServiceTopologyeServiceEdgeesistono ma non vengono mai costruiti, quindi non esiste ancora alcun grafo della topologia. - Nessuna metrica Prometheus e nessun endpoint
/metrics. - Nessun rilevamento delle policy di rete.
Chiavi di configurazione convalidate che non fanno nulla
Vengono interpretate, e l'agente le accetta, ma nessun codice le consuma:
output.backend, output.kafka, output.nats, output.redis, output.webhook,
output.file, kubernetes.*, storage.*, logging.*, performance.batch_size,
performance.connection_pooling, performance.memory_limit, performance.cpu_limit.
Le opzioni della riga di comando rientrano nella stessa categoria, e sono più numerose delle
chiavi. --log-level e --log-format sono dichiarate, documentate in --help, e non le legge
nulla: il logger è un logger zap di produzione fisso (JSON, livello info). Le opzioni di
capture (--interfaces, --filter, --snaplen, --promiscuous, --buffer-size,
--workers, --queue-size, --stats-interval) sono legate sotto il loro nome di opzione mentre
la configurazione è letta da chiavi annidate, quindi vengono analizzate e ignorate;
--enable-grpc ed --enable-http valgono true in modo predefinito in --help e si
riferiscono a server che non vengono mai costruiti. --config è l'unica opzione che cambia il
comportamento. Non prendere bitmapper --help come una dichiarazione di ciò che questa
release fa.
Tre cose non sono in quell'elenco, e le distinzioni contano:
output.filterviene letta e applicata sul percorso di export da0.1.0-ga. Prima era in questo elenco, e finché l'agente non esportava proprio nulla era solo peso morto. Nel momento in cui i record di flusso hanno cominciato a lasciare l'host sarebbe diventata una falsa dichiarazione di privacy: un operatore che escludeva una sottorete sensibile non avrebbe ottenuto protezione, né errore, né modo di accorgersene. Ora è applicata in ingresso. Quattro chiavi che non potevano agire su nulla (export_packets,export_stats,min_packet_size,require_payload) sono state rimosse dallo schema anziché lasciate a farsi interpretare, e l'agente le nomina all'avvio se il tuo file ne imposta ancora una.capture.protocols,capture.portsecapture.exclude_portsvengono compilate nel filtro BPF del kernel e applicate lì. Una versione precedente di questa pagina elencava tutte e tre come convalidate-ma-inerti, ed era falso — vedi la correzione qui sotto.capture.exclude_interfacesviene letta e applicata sul percorso di cattura dal vivo. Vedi scegliere le interfacce.
capture.exclude_ports è un controllo di riservatezza che funzionaFino a 0.1.0-ga questa pagina annoverava capture.protocols, capture.ports e
capture.exclude_ports fra le chiavi che «vengono interpretate e non fanno nulla», e la
pagina sulla cattura lo ripeteva. Era l'opposto della verità: tutte e tre
vengono compilate nel filtro BPF e applicate dal kernel prima che un pacchetto raggiunga
l'agente.
capture.exclude_ports è quella su cui agire. La configurazione distribuita con bitmapper
imposta exclude_ports: [22] — «non catturare SSH» — e funziona. Se hai letto il vecchio testo
e hai cancellato la chiave ritenendola peso morto, hai rimosso un controllo che ti stava
proteggendo e hai iniziato a raccogliere traffico che avevi deliberatamente escluso, senza nulla
che segnalasse il cambiamento. Controlla che cosa dice la tua configurazione in produzione prima
di dare per scontato di non essere stato coinvolto.
capture.protocols richiede l'attenzione opposta: vale ["tcp", "udp"] che tu la imposti o no,
quindi un'installazione di serie non cattura silenziosamente né ICMP, né ICMPv6, né SCTP, né GRE,
né ESP, né AH. Il valore predefinito dei protocolli spiega come
allargarlo.
Fanno parte dello schema di configurazione e vengono convalidate nella forma, così un file scritto usandole viene caricato. È esattamente per questo che sono elencate qui: una chiave che viene interpretata senza errori e non fa nulla è il genere di cosa che si legge come una funzionalità funzionante. Dai per scontato che una chiave non abbia alcun effetto a meno che non compaia in cosa funziona oggi.
Il pacchetto di arricchimento Kubernetes è stato rimosso anziché lasciato dormiente. Non
aveva alcun importatore e costruiva un proprio client HTTP, che seguiva i redirect e poteva far
trapelare un bearer token di service account verso una destinazione in chiaro quando un
kubeconfig puntava a un server che effettuava redirect. Il blocco kubernetes.* resta nello
schema e ora non viene consumato da nulla.
Come eseguirlo
bitmapper è un singolo binario con tre comandi:
# What you have
bitmapper version
# Bitmapper 0.1.0-ga
# Git Commit: 64ebc64
# Build Date: 2026-08-10T05:56:16Z
# Which interfaces this host can capture on
bitmapper interfaces
# Capture, with the configuration you supply
bitmapper capture --config /etc/bitmapper/config.yaml
--config, --log-level e --log-format sono flag persistenti sul comando radice, ma solo
--config fa qualcosa. Non c'è modo di alzare il dettaglio del logging mentre lo stai
configurando: il logger è fisso su JSON, livello info. Ciò che l'agente ti dice davvero all'avvio
è il filtro di cattura compilato e le interfacce che ha selezionato, e a
ogni performance.stats_interval registra a log i contatori di pacchetti, byte, scarti e
connessioni — sono queste le prove da leggere mentre metti in servizio un host.
Richiede privilegi elevati
La cattura dei pacchetti è un'operazione privilegiata. Sulla piattaforma pubblicata:
| Piattaforma | Cosa richiede |
|---|---|
| Linux | CAP_NET_RAW e CAP_NET_ADMIN (oppure root) |
Richiede CGO — e il binario pubblicato ce l'ha già
La cattura passa da gopacket/pcap, che è un binding a una libreria C. Una build con
CGO_ENABLED=0 si compila comunque, ma la cattura e l'elenco delle interfacce si rifiutano di
funzionare a runtime:
packet capture requires CGO (build with CGO_ENABLED=1 and libpcap installed)
È una scelta deliberata — un binario che in silenzio non catturasse nulla sarebbe peggio di uno che dice perché non può farlo. È anche l'unico fatto che decide quali piattaforme vengono pubblicate, ed è il motivo per cui la pipeline di rilascio rifiuta qualsiasi binario non compilato con CGO, non collegato staticamente, o privo di libpcap al suo interno. Vedi piattaforme.
Il download che ricevi non richiede alcuna libpcap installata sull'host: libpcap è collegata staticamente nel binario pubblicato.
La credenziale di enrolment
Se configuri control_plane.*, la credenziale di enrolment viene letta da
control_plane.enrollment_token_file. L'agente esige che sia un file regolare, con modo 0600
o più restrittivo, di proprietà di root o dell'utente dell'agente stesso, e dentro una
directory non scrivibile dal gruppo né da tutti. Una directory in cui chiunque può scrivere
permette a qualcuno di sostituire il file, quindi controllare soltanto il modo del file stesso
significherebbe controllare la cosa sbagliata.
Il controllo si basa su POSIX. Questo copre ogni binario pubblicato, dato che 0.1.0-ga è
distribuito solo su Linux — ma vale la pena sapere che è una proprietà della piattaforma e non
dell'agente, se un giorno dovesse comparire una build per Windows.
control_plane:
gateway_url: "https://<your-cert-ix-agent-endpoint>"
ingest_url: "https://<your-cert-ix-ingest-endpoint>"
tenant_id: "<your-tenant-id>"
enrollment_token_file: "/etc/bitmapper/enrollment.token"
Entrambi gli URL devono essere https://, e ingest_url è obbligatorio non appena il
blocco del control plane viene usato. tenant_id viene inviato come header X-Tenant-ID — è
un identificatore, non una credenziale, quindi non è ciò che autentica l'agente.
La credenziale può anche arrivare dall'ambiente come
BITMAPPER_CONTROL_PLANE_ENROLLMENT_TOKEN, oppure essere sostituita del tutto da un
certificato client mutual-TLS (control_plane.client_cert_file / client_key_file, con il
file della chiave sottoposto allo stesso controllo dei permessi). Impostare
control_plane.enrollment_token direttamente nello YAML funziona ed è sconsigliato — un
segreto in un file di configurazione è un segreto su disco.
Quanto costa sull'host
La cattura dei pacchetti è la cosa più costosa che faccia uno qualsiasi dei bits, e il costo cresce con il traffico anziché con la dimensione dell'host. Due controlli contano più di tutti:
- Un filtro BPF è la riduzione più economica possibile — scarta i pacchetti nel kernel prima ancora che vengano copiati verso l'agente.
- La tabella delle connessioni ha una dimensione massima, con sfratto delle voci e GC periodica di quelle inattive, così un host che vede molte connessioni di breve durata non fa crescere la tabella senza limiti.
performance.memory_limit e performance.cpu_limit non sono implementati — sono
nell'elenco qui sopra. Limita il processo con i controlli del tuo sistema di init
(MemoryMax=, CPUQuota= in un'unità systemd) se ti serve un tetto reale.
I flussi in attesa di export sono limitati separatamente da
batch_export.max_buffered_connections, e non da performance.connection_table_size, il
cui valore predefinito metterebbe una seconda tabella a sei cifre su un host di cattura. Ogni
scarto è contato e registrato; nessuno è silenzioso.
Piattaforme
0.1.0-ga pubblica un binario firmato. È l'elenco di piattaforme più stretto della famiglia,
e ogni omissione qui sotto è una decisione misurata e non una dimenticanza:
| Piattaforma | Pubblicata | Perché no |
|---|---|---|
Linux amd64 | Sì | |
Linux arm64 | No | Non può essere assemblata da un gcc amd64, e non esiste una macchina di build arm64 |
macOS amd64/arm64 | No | Richiede clang e l'SDK di macOS |
Windows amd64 | No | Compila, ma non è mai stata eseguita su Windows — vedi sotto |
La causa unica dietro tutto questo è che bitmapper è l'unico bit che non può essere compilato
in modo incrociato. La cattura è gopacket → libpcap → CGO, quindi una build richiede una vera
toolchain C e una vera libpcap per il target. Tutti gli altri bits sono Go puro, e bastano
GOOS/GOARCH.
La trappola che questo crea è precisa e merita di essere nominata. internal/capture porta uno
stub //go:build !cgo che compila su ogni piattaforma e non cattura nulla. Una build con
CGO_ENABLED=0 quindi riesce — produce un binario che parte, si registra, invia heartbeat,
risulta sano, e non vede mai un pacchetto. Per questo la pipeline di rilascio controlla
l'artefatto anziché il comando di build: rifiuta qualsiasi binario non compilato con CGO, non
collegato staticamente, o privo di libpcap al suo interno.
Windows è il caso interessante, e quello onesto. Compila davvero — il supporto Windows di
gopacket è Go puro e carica wpcap.dll a runtime — quindi potremmo pubblicarlo oggi. Non è
mai stato eseguito su Windows. Questo lo rende una lacuna di test e non di toolchain, e
pubblicarlo significherebbe distribuire un binario firmato il cui percorso di cattura nessuno ha
mai visto funzionare. Un binario firmato che non può catturare è peggio di nessun binario, perché
la firma viene letta come un'affermazione sull'idoneità. Resta trattenuto finché qualcuno non lo
avrà eseguito.
Se oggi ti servono questi dati da un host macOS, Windows o arm64, bitmapper non è la risposta
per quell'host. bitcollector è distribuito su tutte e cinque le
piattaforme e riporta i peer di rete stabiliti di quell'host — una risposta più grossolana
dell'attribuzione per flusso, ma reale sulla macchina che hai davvero.
Passi successivi
- Come funziona la cattura — le interfacce, i filtri BPF, l'attribuzione dei processi e la macchina a stati delle connessioni, e cosa ciascuno di essi mette in memoria.
- bitscanner — l'agente che trova i dispositivi che il tuo inventario non contiene.
- bitcollector — l'inventario dell'host che guarda verso l'interno.
- Verificare i tuoi download — l'ancora di fiducia, e perché conta più della firma.
- Cos'è bits? — come si incastrano gli agenti.
Questa pagina ti è stata utile?