Wie bitmapper mitschneidet
Diese Seite beschreibt die tatsächliche Pipeline des Befehls capture: was er öffnet, was er
filtert, was er sich merkt, und was jede Stellschraube Sie kostet.
Alles Folgende geschieht auf dem Host. Die Pakete selbst verlassen ihn nie — was ihn verlässt, ist ein Flow-Datensatz, der jede verfolgte Verbindung zusammenfasst. Siehe was den Host verlässt.
interfaces → BPF filter (kernel) → worker pool → connection tracker → flow record → platform
│ │
│ └→ ein letzter
│ Zuordnungsversuch
├→ Prozesszuordnung, auf späteren Paketen
│ mit Backoff wiederholt
└→ statistics (structured JSON log)
Schnittstellen auswählen
bitmapper interfaces listet auf, worauf dieser Host mitschneiden kann. Benennen Sie sie dann
entweder, oder benennen Sie keine und lassen Sie den Agenten wählen:
capture:
interfaces: ["eth0", "eth1"] # empty = every interface that is up and not loopback
Ist capture.interfaces leer, schneidet der Agent auf jeder Schnittstelle mit, die aktiv und
kein Loopback ist. Auf einem Host mit mehreren NICs, einer Bridge und virtuellen
Container-Schnittstellen ist das meist mehr, als Sie meinten — dasselbe Paket kann mehr als
einmal gesehen werden, wenn es eine Bridge passiert. Benennen Sie die Schnittstellen, auf die es
Ihnen tatsächlich ankommt.
capture.exclude_interfaces entfernt Namen aus der Liste, bei der Sie am Ende landen:
capture:
interfaces: [] # auto-detect: every interface up and non-loopback
exclude_interfaces: ["docker0", "veth0"]
Es gilt sowohl für eine explizite capture.interfaces-Liste als auch für die automatisch
erkannte Menge, und der Abgleich erfolgt ohne Beachtung der Groß-/Kleinschreibung und ignoriert
umgebende Leerzeichen. Das macht es zum richtigen Werkzeug für den häufigen Fall: den Agenten
die Schnittstellen finden lassen und dann die Bridges und virtuellen Schnittstellen abziehen,
die Sie nicht doppelt haben wollen.
Filterung: vier Schlüssel, ein Kernel-Filter
Vier Schlüssel entscheiden, welche Pakete bitmapper überhaupt zu sehen bekommt, und alle vier werden zu einem einzigen BPF-Ausdruck kompiliert, der libpcap übergeben wird, bevor der Mitschnitt startet. Alles, was sie ausschließen, verwirft der Kernel — es wird nicht nachträglich aus der Ausgabe gefiltert, es wird überhaupt nicht in den Agenten kopiert.
| Schlüssel | Voreinstellung | Was er beiträgt |
|---|---|---|
capture.filter | "" — keiner | Ein roher BPF-Ausdruck, wörtlich übernommen, in Klammern gesetzt |
capture.protocols | ["tcp", "udp"] | Eine Positivliste: (tcp or udp). Akzeptiert nur tcp, udp, icmp, all |
capture.ports | [] — keiner | Eine Positivliste: (port 80 or port 443) |
capture.exclude_ports | [] — keiner | Eine Ausschlussliste: not (port 22) |
Die Klauseln werden in dieser Reihenfolge mit and verbunden. Die mitgelieferte Konfiguration
(configs/bitmapper.yaml) setzt protocols: [tcp, udp] und exclude_ports: [22], was zu
Folgendem kompiliert:
(tcp or udp) and not (port 22)
Setzen Sie alle vier, erhalten Sie einen Ausdruck — zum Beispiel kompiliert
filter: "host 10.0.0.1", protocols: [tcp], ports: [443], exclude_ports: [22] zu
(host 10.0.0.1) and (tcp) and (port 443) and not (port 22).
Der Agent protokolliert den kompilierten Ausdruck beim Start, sobald er von dem abweicht, was
Sie in capture.filter geschrieben haben. Sie können also genau nachlesen, was dem Kernel
übergeben wurde, statt es zu erschließen. Ein Port außerhalb von 1–65535 in einer der beiden
Listen wird beim Start abgelehnt, statt später innerhalb von libpcap zu scheitern.
Eine frühere Fassung dieser Seite trug eine Warnung mit der Überschrift „Drei Filterschlüssel, die
von niemandem gelesen werden“ und stellte fest, capture.protocols, capture.ports und
capture.exclude_ports würden validiert und dann ignoriert. Das war falsch. Alle drei werden
in den oben beschriebenen BPF-Filter kompiliert und vom Kernel durchgesetzt.
Der Fehler ging in die Richtung, die Sie etwas kostet, und capture.exclude_ports ist der Grund,
Ihre Konfiguration jetzt noch einmal zu lesen. Die mitgelieferte Konfiguration enthält
exclude_ports: [22] — „SSH nicht mitschneiden“ — und dieser Ausschluss ist echt: SSH-Pakete
werden verworfen, bevor sie den Agenten erreichen. Wenn Sie die alte Warnung gelesen und den
Schlüssel als totes Gewicht entfernt haben, haben Sie eine funktionierende
Datenschutz-Maßnahme abgeschaltet und begonnen, Verkehr zu sammeln, den Sie bewusst
ausgeschlossen hatten. Nichts hätte Sie gewarnt, denn aus Sicht des Agenten hatten Sie einfach
mehr Verkehr angefordert.
Auch das Gegenbeispiel der alten Warnung war exakt verkehrt herum. capture.ports: [443]
schneidet nicht alles mit. Mit den voreingestellten Protokollen kompiliert es zu
(tcp or udp) and (port 443) und schneidet ausschließlich Port 443 mit:
# This captures TCP/UDP port 443 and nothing else — the ports key is applied, in the kernel
capture:
ports: [443]
Die Voreinstellung ist tcp + udp, ICMP wird also nicht mitgeschnitten
capture.protocols steht auch dann auf ["tcp", "udp"], wenn Ihre Konfigurationsdatei den
Schlüssel nie erwähnt. Ein Host mit einer Standardkonfiguration sieht daher nie ICMP, ICMPv6,
SCTP, GRE, ESP oder AH: Der Kernel verwirft sie, der Agent bekommt sie nie, und kein Zähler
protokolliert, was weggefiltert wurde. In der Ausgabe sehen ein Fehlen und ein Schweigen gleich
aus.
Das ist ein echter blinder Fleck, wenn Sie bitmapper einsetzen, um Ping-Sweeps, getunnelten Verkehr oder IPsec zu sehen. Um ihn zu beseitigen, fordern Sie gar keine Protokolleinschränkung an:
capture:
protocols: ["all"]
Zwei Details, die man leicht übersieht:
protocols: ["icmp"]deckt beide Familien ab — es kompiliert zu(icmp or icmp6), sodass die Anforderung von ICMP Ihnen nicht stillschweigend nur IPv4 gibt.- Es gibt keinen Wert für SCTP, GRE, ESP oder AH.
protocolsakzeptiert nurtcp,udp,icmpundall; alles andere wird beim Start abgelehnt mitinvalid protocol: … (valid: tcp, udp, icmp, all). Wenn Sie eines davon konkret brauchen, setzen Sieprotocols: ["all"]und formulieren Sie das Protokoll incapture.filter.
capture.exclude_ports allein erzeugt diesen blinden Fleck nicht. Es wird zu
not (port 22), und ein BPF-Porttest ist auch für Pakete wahr, die gar keine Ports haben — SSH
auszuschließen verwirft ICMP also nicht nebenbei mit. Das tut nur die Protokoll-Positivliste.
Bevorzugen Sie den engsten Filter, gleich welcher Schlüssel ihn ausdrückt
BPF zu lernen lohnt sich ohnehin. tcp and not port 22, port 443 or port 8443,
not net 10.0.0.0/8 — jeder davon wird angewendet, bevor das Paket Sie irgendetwas kostet, und
capture.filter bleibt der einzige Schlüssel, der Host-, Netz- oder Richtungsbedingungen
ausdrücken kann. Die drei Listenschlüssel sind die lesbare Kurzform für die Protokoll- und
Portfälle; sie landen an derselben Stelle.
Wirklich wirkungslos sind hier die Logging-Optionen
--log-level und --log-format tun nichtsDiese Seite hat früher die Filterschlüssel als das benannt, was zwar geparst wird, aber keine Wirkung hat. Was sich tatsächlich so verhält, sind die Logging-Optionen auf der Kommandozeile.
--log-level und --log-format sind am Wurzelbefehl deklariert und erscheinen in --help mit
den Voreinstellungen info und json, aber kein Code liest eine von beiden. Der Logger ist
ein fester zap-Produktionslogger, der beim Start gebaut wird — JSON, Level info — und keine der
beiden Optionen kann ihn ändern. --log-level debug bringt Ihnen kein zusätzliches Detail;
--log-format console liefert Ihnen weiterhin JSON.
Die übrigen capture-Optionen verhalten sich genauso. --interfaces, --filter, --snaplen,
--promiscuous, --buffer-size, --workers, --queue-size und --stats-interval werden in
Viper unter ihrem Optionsnamen gebunden, während die Konfiguration aus verschachtelten Schlüsseln
gelesen wird (capture.filter, capture.snaplen, performance.workers, …) — sie werden also
geparst und ignoriert. --enable-grpc und --enable-http sind schlimmer als ignoriert: Sie
stehen in --help auf true und beschreiben Server, die dieser Agent
nie konstruiert — es gibt keinen gRPC- oder HTTP-Listener zu
aktivieren.
--config ist die einzige Option, die etwas am Verhalten des Agenten ändert. Alles andere
gehört in die YAML-Datei.
Pakete lesen
| Schlüssel | Voreinstellung | Was er tut |
|---|---|---|
capture.snaplen | 65535 | Je Paket mitgeschnittene Bytes (validiert 0–65535) |
capture.promiscuous | true | Promiscuous-Modus |
capture.buffer_size | 104857600 | Mitschnitt-Puffer im Kernel, in Bytes |
capture.timeout | 100ms | Lese-Timeout für Pakete |
Snaplen ist der Hebel, den herunterzudrehen sich am meisten lohnt. bitmapper baut eine Verbindungskarte — wer spricht mit wem, worüber, wie viel — und die steckt vollständig in den Paket-Headern. Die volle Nutzlast von 65535 Byte mitzuschneiden kopiert Anwendungsdaten in den Speicher des Agenten, die hier nichts liest. Ein Snaplen im Bereich weniger hundert Bytes behält jeden Header, den der Tracker braucht, und verhindert, dass Nutzlast überhaupt kopiert wird — das ist zugleich billiger und eine weit kleinere Exposition, falls der Host kompromittiert wird.
Der Promiscuous-Modus bringt die NIC dazu, auch Frames anzunehmen, die nicht an sie adressiert sind. In einem geswitchten Netz liefert das überwiegend Broadcast und Multicast; an einem Mirror-/SPAN-Port ist er das, was den Mitschnitt überhaupt erst nützlich macht. Er erfordert außerdem erhöhte Berechtigungen und ist es wert, abgeschaltet zu werden, wenn Sie nur die eigenen Gespräche dieses Hosts wollen.
Prozesszuordnung
capture:
enable_process_mapping: true
process_mapping_interval: 5s
Alles Folgende — Zuordnung pro Flow, das Nachtragen, die Ableitung aus lauschenden Sockets und
das Feld attribution — ist unveröffentlicht. Es steckt nicht in 0.1.0-ga (Commit
64ebc64), das nach wie vor das einzige veröffentlichte Binary ist. Führen Sie
bitmapper version aus und vergleichen Sie, bevor Sie etwas auf dieser Seite einplanen.
Was das Binary tut, das Sie heute herunterladen können. Aus dem Code zu Commit 64ebc64
gelesen:
- Die Zuordnung wird genau einmal versucht, beim Anlegen der Verbindung, aus einer
Momentaufnahme der Socket-Tabelle, die bis zu
process_mapping_intervalalt sein kann. Es gibt keinerlei Wiederholung. - Es gibt keinerlei Ableitung aus lauschenden Sockets. Der Code, der sie ausführt, existiert
zu diesem Commit nicht.
0.1.0-gakannlistening_socketnie ausgeben und nie einen Prozess nennen, den es nicht auf dem eigenen Socket des Flows gefunden hat. - Die Antwort wird immer am Quell-Ende festgehalten.
destination_processwird in dieser Version nirgends zugewiesen und kann daher nie in einem exportierten Datensatz auftauchen. - Das exportierte Prozessobjekt trägt
pid,name,executableunduser— und kein Feldattribution.
Der Socket einer ausgehenden Verbindung kann nicht in einer Momentaufnahme stehen, die vor dieser
Verbindung gemacht wurde: Flows aus 0.1.0-ga tragen daher häufig gar keinen Prozess. Auf dem
Entwicklungshost gemessen: 0 von 12 Flows zugeordnet. Rechnen Sie bei dieser Version damit,
statt es als Fehlkonfiguration zu lesen.
Was die nächste Version tut. Der Versuch wird auf den späteren Paketen des Flows und noch
einmal auf dem Export-Pfad wiederholt; beide Enden werden zugeordnet; und jedes Prozessobjekt
trägt ein Feld attribution, das angibt, ob die Identität vom eigenen Socket des Flows oder von
einem lauschenden Socket stammt. Diese Ableitung ist dreifach abgesichert: Das Ende muss eine
Adresse dieses Hosts sein, es muss bewiesen sein, dass genau ein Prozess den lauschenden
Socket hält, und der Flow muss TCP sein.
Das ist es, was einen Mitschnitt von einem Paket-Dump unterscheidet: Eine verfolgte Verbindung wird auf die PID, den Prozessnamen, den Pfad der ausführbaren Datei und den Benutzer aufgelöst, dem der Socket gehört, indem die Socket-Tabellen des Hosts gelesen werden.
Die Zuordnung erfolgt pro Flow, nicht pro Paket, und sie wird an dem Ende des Flows
festgehalten, dem der Socket gehört — ein Flow-Datensatz kann also source_process,
destination_process oder keines von beiden tragen. Auf einem Host, der ein Ende der
Konversation ist, ist das andere Ende ein entfernter Peer, dessen Prozess nicht auf dieser
Maschine liegt und es nie sein wird; es ist zu erwarten, dass nur ein Ende gefüllt ist.
Sie wird nachgetragen, nicht einmalig aufgelöst
Der erste Zuordnungsversuch eines Flows geschieht beim Anlegen der Verbindung, und er scheitert sehr oft: Der Socket einer ausgehenden Verbindung kann nicht in einer Momentaufnahme der Socket-Tabelle auftauchen, die vor der Existenz der Verbindung gemacht wurde. Deshalb wird der Versuch wiederholt:
- Auf den späteren Paketen des Flows, mit einem Backoff, der bei 100 ms beginnt und sich bis zu einer Obergrenze von 30 s verdoppelt. Ein Flow, der tatsächlich keinen lokalen Socket hat — Verkehr, den der Host lediglich weiterleitet — pendelt sich damit auf ein paar Abfragen pro Minute ein statt auf eine pro Paket.
- Auf dem Export-Pfad, bevor ein Datensatz für diesen Flow gebaut wird. Für einen bereits beendeten Flow ist das seine einzige verbleibende Chance: Ein lokaler Request/Response kann deutlich unter einer Millisekunde leben, sein Socket ist also Mikrosekunden nach seinem einzigen Versuch auf dem Paketpfad bereits verschwunden.
process_mapping_interval ist nicht der Regler für nicht zugeordnete FlowsDiese Seite beschrieb früher einen Kompromiss, in dem ein kurzes Intervall kurzlebige
Verbindungen häufiger zuordnet und ein langes sie verliert. So funktioniert die Zuordnung
nicht mehr, und es widerspricht jetzt der Konfiguration, die der Agent ausliefert —
configs/bitmapper.yaml sagt in einem Kommentar zu genau diesem Schlüssel das Gegenteil.
process_mapping_interval legt die geplante Aktualisierung der Socket-Tabelle fest. Es ist
nicht mehr das Einzige, wovon die Zuordnung abhängt: Eine Abfrage, die die Momentaufnahme
verfehlt, löst ein bedarfsgesteuertes erneutes Lesen der Kernel-Socket-Tabellen für genau diesen
Flow aus, und der Versuch wird wie oben beschrieben wiederholt. Das Intervall zu senken ist nicht
die Lösung für Flows, die ohne Zuordnung ankommen, und es zu erhöhen kostet Sie nicht mehr die
kurzen Flows, die es früher kostete.
Verbindungen, die sich nicht zuordnen lassen, werden trotzdem verfolgt und exportiert; sie tragen einfach keine Prozessidentität. Das ist eine korrekte Antwort, keine Lücke — siehe unten, warum sie der Alternative vorzuziehen ist.
Wie die Zuordnung zustande kam: socket vs. listening_socket
Jedes Prozessobjekt in einem exportierten Flow-Datensatz trägt ein Feld attribution, das
angibt, wie diese Identität bestimmt wurde. Es ist ein Vertrauenssignal, und die beiden Werte
sind nicht austauschbar:
"source_process": {
"pid": 41207,
"name": "curl",
"executable": "/usr/bin/curl",
"user": "deploy",
"attribution": "socket"
}
| Wert | Wie er bestimmt wurde | Wie weit ihm zu trauen ist |
|---|---|---|
socket | Traf auf den eigenen Socket dieses Flows in den Socket-Tabellen des Hosts, über das vollständige Vierertupel | Eine Tatsache. Dieser Prozess hielt diesen Socket. |
listening_socket | Für diesen Flow wurde kein Socket gefunden. Die Identität stammt von dem Prozess, der auf dieser Adresse und diesem Port lauscht — und nur dann, wenn diese Adresse auf diesem Host liegt, bewiesen ist, dass genau ein Prozess den Socket hält, und der Flow TCP ist | Eine Schlussfolgerung, und als solche gekennzeichnet. Abgesichert, aber weiterhin eine schwächere Aussage als socket: siehe die Regel des alleinigen Eigentümers und nur TCP. |
Der schwächere Wert existiert, weil die Alternative gar nichts wäre: Ein Flow, der ein paar
hundert Mikrosekunden lebt, ist längst geschlossen, bevor ein Lesen von /proc seinen Socket
sehen könnte, und der lauschende Socket ist das Einzige, was ihn überdauert. Er ist es wert,
berichtet zu werden — er darf aber nicht als dieselbe Aussage wie socket gelesen werden.
Ein Verbraucher, der beide gleich behandelt, zieht einen Schluss, den der Agent nicht gezogen hat. Wenn Sie Alarme, Abhängigkeitskarten oder Richtlinien auf Flow-Datensätzen aufbauen, verzweigen Sie über dieses Feld.
Eine frühere Fassung dieser Seite trug einen Danger-Block mit der Überschrift
„listening_socket ist eine Schlussfolgerung, und sie wird heute zu weit angewendet". Er stellte
zwei konkrete Behauptungen auf — die Ableitung werde „nicht gegen den lokalen Host geprüft" und
sie sage „nicht, welcher lauschende Prozess" — und riet Ihnen, listening_socket für jedes Ende
rundheraus zu verwerfen, das keine Adresse des berichtenden Hosts ist.
Beide Behauptungen sind für jede Fassung von bitmapper falsch, die Sie beziehen können. Sie beschrieben einen internen Stand, der korrigiert wurde, bevor irgendetwas veröffentlicht war:
- Das veröffentlichte
0.1.0-gahat nichts, was zu weit angewendet werden könnte. Es enthält überhaupt keine Ableitung aus lauschenden Sockets und kannlistening_socketnicht ausgeben. - Die nächste Version prüft beides, bevor sie ableitet. Das Ende muss eine Adresse dieses Hosts sein — das ist es, was verhindert, dass ein ausgehender Flow zu einem entfernten Peer einem lokalen Wildcard-Listener zugeschrieben wird — und es muss bewiesen sein, dass genau ein Prozess diesen lauschenden Socket hält, was verhindert, dass eines der Geschwister eines vorab forkenden Servers genannt wird. Nach der Korrektur gemessen, auf demselben Host und mit demselben Verkehr, der die Erfindungen hervorgebracht hatte: keine einzige erfundene Zuordnung, und jede Zuordnung, die der Agent tatsächlich vornahm, war richtig.
Der Rat, der aus diesen Behauptungen folgte, war eine stichhaltige Überlegung zu Code, den kein
Kunde hat. listening_socket ist weiterhin der schwächere der beiden Werte und weiterhin eine
Verzweigung wert — es ist eine Schlussfolgerung, und sie ist als solche gekennzeichnet —, aber
sie ist eine abgesicherte Schlussfolgerung und keine ungeprüfte, und „für ein nicht-lokales
Ende verwerfen" beschreibt inzwischen eine Regel, die der Agent selbst anwendet, bevor der
Datensatz gebaut wird.
Die Ableitung gilt nur für TCP, ein UDP-Flow trägt sie also nie
listening_socket kann ausschließlich auf einem TCP-Flow erscheinen. UDP hat keinen
LISTEN-Zustand: Ein gebundener UDP-Socket ist von dem eines Servers nicht zu unterscheiden, es
gibt also nichts, woraus abgeleitet werden könnte. Die Ableitung liefert für jedes andere
Protokoll als TCP nichts zurück, noch bevor sie irgendetwas anderes prüft.
Das ist keine Grenze, die darauf wartet, aufgehoben zu werden — sie verhindert eine ganz bestimmte falsche Antwort. Ein UDP-Flow kann durchaus auf einem Port landen, auf dem ein TCP-Server lauscht; 53 und 853 sind der Normalfall. Ohne die Protokollprüfung würde der TCP- Listener eines DNS-Servers als Eigentümer unbeteiligten UDP-Verkehrs auf demselben Port genannt.
Ein UDP-Flow, dessen eigener Socket nicht gefunden werden konnte, wird deshalb ohne Prozess
exportiert, und das ist bei UDP der Normalfall und keine Seltenheit: Eine einzelne DNS-Abfrage
ist in deutlich unter einer Millisekunde vorbei, ihr Socket ist verschwunden, bevor ein Lesen von
/proc ihn erreichen könnte, und es gibt keine schwächere Antwort, auf die zurückzufallen wäre.
Lesen Sie ein leeres Prozessfeld auf einem UDP-Flow als die einzige ehrliche Antwort, die es
für dieses Protokoll gibt, nicht als Defekt.
Die direkte Zuordnung ist von der Protokollprüfung nicht betroffen. UDP-Sockets werden in
dieselben Socket-Tabellen eingelesen, ein UDP-Flow, dessen eigener Socket dort mit passendem
Vierertupel steht, wird also genauso zugeordnet wie ein TCP-Flow, mit "attribution": "socket".
Der Haken ist, dass ein UDP-Socket, für den nie connect() aufgerufen wurde, keine
Gegenstellen-Adresse zu melden hat — es gibt also kein Vierertupel zum Abgleichen. Das ist der
zweite Grund, warum die Zuordnung bei UDP dünner ausfällt als bei TCP.
Eine Schlussfolgerung braucht einen Socket mit genau einem Eigentümer
bitmapper übernimmt eine Identität von einem lauschenden Socket nur dann, wenn es beweisen kann, dass genau ein Prozess diesen Socket hält. Wo es das nicht beweisen kann, wird der Flow ganz ohne Prozess exportiert statt mit einem der Kandidaten.
Ein lauschender Socket mit mehreren Eigentümern ist der gewöhnliche Fall, kein exotischer. Auf
dem Entwicklungshost gemessen: ein lauschender nginx-Socket auf 0.0.0.0:443 — ein einziger
Socket-Inode — wurde von 17 Prozessen gehalten, und so sieht jeder Server aus, der seine
Worker-Prozesse vorab forkt. Ein Socket, siebzehn Namen, die in den Datensatz geschrieben werden
könnten. Eingehende Flows zu einem solchen Socket bleiben absichtlich ohne Zuordnung. Einen der
siebzehn zu nennen hieße, eine Vermutung in dasselbe Feld und in dieselbe Form zu setzen wie eine
Messung.
Nur der vollständige periodische /proc-Durchlauf kann diesen Beweis liefern. Er geht jeden
Prozess durch und kann deshalb feststellen, dass ein Socket-Inode in den Deskriptoren eines
Prozesses und in denen keines anderen auftaucht. Der billigere Durchlauf auf Anforderung — den
ein fehlgeschlagener Lookup für einen einzelnen Flow auslöst — hält beim ersten Prozess an, der
den gesuchten Inode hält: Er kann beweisen, dass ein Prozess einen Socket hält, aber nie, dass er
der einzige ist.
Ein Prozess, der gerade erst zu lauschen begonnen hat, dient deshalb erst dann als Grundlage einer Schlussfolgerung, wenn der nächste vollständige Durchlauf ihn erfasst hat. Kurze Zeit nach dem Start eines Dienstes kann kurzlebiger Verkehr zu ihm hin ganz ohne Prozesszuordnung erscheinen. Zwei Eigenschaften begrenzen das, und beide zählen, wenn Sie entscheiden, ob Sie es mit einem Defekt zu tun haben:
- Es kostet Zuordnung, niemals Korrektheit. In diesem Fenster wird kein falscher Prozess erzeugt; das Feld ist leer, nicht irreführend.
- Ein lauschender Socket überdauert das Aktualisierungsintervall mit großem Abstand. Das ist also ein Effekt des Startfensters und kein Dauerzustand: Hat ein vollständiger Durchlauf einen lauschenden Prozess einmal erfasst, bleibt er erfasst, solange er lauscht.
Wie lang das Fenster ist, hängt von process_mapping_interval und vom Host ab; eine einzelne
Zahl lässt sich dafür nicht nennen. Die Aktualisierungsschleife ist auf einen Arbeitszyklus
begrenzt — das Durchlaufen von /proc darf nicht selbst zur Last einer ausgelasteten Maschine
werden —, sodass ein Host mit großer Prozesstabelle länger für einen Durchlauf braucht und das
Fenster dort entsprechend länger ist.
Nichts davon hindert einen Flow daran, mitgeschnitten, verfolgt, gezählt und exportiert zu
werden. Wenn Sie Datensätze mit leerem source_process und destination_process vor sich haben,
lesen Sie erst die periodischen Statistiken, die der Agent protokolliert, bevor Sie den Agenten
für kaputt halten: Paket- und Verbindungszähler, die weiterlaufen, sagen Ihnen, dass die
Erfassung arbeitet und dass Sie eine zurückgehaltene Zuordnung sehen.
Eine falsche Zuordnung ist schlimmer als gar keine: Ein nicht zugeordneter Flow ist sichtbar unvollständig, während ein Flow, der den falschen Prozess nennt, eine selbstsichere Falschaussage ist, die ein Verbraucher nicht erkennen kann. Deshalb bleibt ein nicht zuordenbarer Flow ohne Zuordnung, statt geraten zu werden, und deshalb wird die Schlussfolgerung gekennzeichnet, statt stillschweigend unter die Tatsachen gemischt zu werden.
Die Worker-Pipeline
| Schlüssel | Voreinstellung | Bedingung |
|---|---|---|
performance.workers | 4 | ≥ 1 |
performance.queue_size | 100000 | ≥ 100 |
Pakete werden über eine begrenzte Warteschlange an einen Pool von Workern übergeben. Ist die Warteschlange voll, wird das Paket verworfen und der Drop-Zähler erhöht — der Agent blockiert den Mitschnittpfad nicht, um mitzuhalten, denn eine Blockade dort würde stattdessen den Puffer des Kernels selbst überlaufen lassen.
Der Drop-Zähler ist die Zahl, die man beobachten muss. Er erscheint in den periodischen
Statistiken, und ein dauerhaft steigender Drop-Zähler bedeutet, dass der Host mehr Verkehr sieht,
als diese Konfiguration verarbeiten kann. Nach Wirkung geordnet lauten die Abhilfen: den
Kernel-Filter verschärfen — wahlweise über capture.filter, capture.protocols,
capture.ports oder capture.exclude_ports, denn alle vier landen im selben Ausdruck —, dann
capture.snaplen senken, dann performance.workers erhöhen.
Verbindungsverfolgung
| Schlüssel | Voreinstellung | Bedingung |
|---|---|---|
performance.connection_table_size | 1000000 | ≥ 1000 — maximal verfolgte Verbindungen vor der Verdrängung |
performance.connection_timeout | 5m | ≥ 1s — Leerlaufzeit, bevor eine Verbindung eingesammelt wird |
performance.gc_interval | 1m | ≥ 1s — wie oft das Einsammeln läuft |
Der Tracker fasst beide Richtungen eines Gesprächs zu einer Verbindung zusammen, führt einen
TCP-Zustandsautomaten aus (SYN_SENT → ESTABLISHED → … → CLOSED) und zählt Pakete und
Bytes je Verbindung.
Zwei Schranken halten sie endlich, und es sind unterschiedliche Mechanismen:
connection_table_sizeist eine harte Obergrenze. Darüber hinaus werden Einträge verdrängt — ein Host unter einer Verbindungsflut verliert alte Einträge, statt zu wachsen, bis der Prozess getötet wird.connection_timeout+gc_intervalsammeln Verbindungen ein, die schlicht untätig geworden sind, unabhängig davon, ob die Tabelle nahe an ihrer Obergrenze ist.
Ein Host, der viele kurzlebige ausgehende Verbindungen aufbaut — ein vielbeschäftigter Web-Crawler, ein CI-Runner — wird diese Tabelle stark durchwälzen. Das ist das beabsichtigte Verhalten: Eine begrenzte Tabelle, die vergisst, ist besser als eine unbegrenzte, die den Prozess beendet.
Statistiken
performance:
stats_interval: 10s # 0 disables the reporter entirely
Paket-, Byte-, Drop- und Verbindungszähler werden in diesem Intervall als strukturiertes JSON über den Logger des Agenten geschrieben. Sie sind der lokale Beleg dafür, dass der Mitschnitt selbst funktioniert — unabhängig davon, ob der Export die Plattform erreicht:
bitmapper capture --config /etc/bitmapper/config.yaml
Die Ausgabe ist JSON auf Level info, und keine Option ändert daran etwas — siehe
die Logging-Optionen. Schicken Sie sie durch jq, wenn Sie sie lesbar wollen.
Beobachten Sie den Drop-Zähler über mehrere Intervalle hinweg. Null Drops und eine steigende Verbindungszahl bedeuten, dass die Pipeline mithält. Bleibt der Paketzähler auf einem Host, von dem Sie wissen, dass er beschäftigt ist, bei null, prüfen Sie erst den kompilierten Filter, den der Agent beim Start protokolliert hat, bevor Sie den Mitschnittpfad verdächtigen — die Protokoll-Voreinstellung schließt mehr aus, als die meisten erwarten.
Was den Host verlässt, und was nicht
Der Verkehr selbst bleibt hier. Was ihn verlässt, ist ein Flow-Datensatz — die Zusammenfassung einer Verbindung, nicht ihr Inhalt. Um genau zu sein:
| Daten | Wohin sie gehen |
|---|---|
Paket-Header und Nutzlast bis snaplen | Ausschließlich der Speicher des Agenten, für die Dauer der Verarbeitung des Pakets |
| Nutzlast-Bytes der Pakete | Nirgendwohin. Werden nie exportiert, nie auf die Festplatte geschrieben |
| Verbindungsdatensätze, mit Prozesszuordnung | Die Verbindungstabelle im Speicher — und, als Flow-Datensätze, über https an die Plattform |
Wie jede Zuordnung zustande kam (socket / listening_socket) | Steht ab der nächsten Version im Flow-Datensatz neben dem Prozess — siehe Vertrauen in die Zuordnung |
| Die Kommandozeile des Prozesses | Nirgendwohin. Bewusst ausgeschlossen: Kommandozeilen tragen routinemäßig Zugangsdaten als Argumente |
| Paket-/Byte-/Drop-/Verbindungszähler | Die Log-Ausgabe des Agenten selbst |
| Registrierung und Heartbeat des Agenten | Die Control Plane, über https |
Ein Flow-Datensatz trägt die Endpunkte, Ports, das Protokoll, den Verbindungszustand, die Byte-
und Paketzähler sowie — für dasjenige Ende des Flows, dem der Socket gehört — PID, Name, Pfad der
ausführbaren Datei und Benutzer des zugeordneten Prozesses und — ab der nächsten Version — die
Markierung attribution, die angibt, wie diese Identität bestimmt wurde. Das ist alles. Wenn Sie ein Subnetz davon fernhalten müssen, verwirft
output.filter.exclude_cidrs den Flow beim Eingang — bevor ein Datensatz gebaut wird — und
greift an beiden Endpunkten. Siehe den Überblick.
Es gibt keine storage.*-Implementierung und keinen Dateiexport, also wird vom Agenten
nichts persistiert. Wenn der Prozess stoppt, geht die Verbindungstabelle mit ihm.
Nächste Schritte
- bitmapper — Überblick — Release, Plattformen, Berechtigungen und die vollständige Liste der Schlüssel, die validiert werden, aber nichts tun.
- bitscanner — nach außen gerichtete Erkennung.
- Was ist bits? — wie die Agenten zusammenpassen.
War diese Seite hilfreich?