Zum Hauptinhalt springen
Version: 1.0.0

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üsselVoreinstellungWas er beiträgt
capture.filter"" — keinerEin 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[] — keinerEine Positivliste: (port 80 or port 443)
capture.exclude_ports[] — keinerEine 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.

Korrektur: Diese drei Schlüssel sind aktiv, und diese Seite behauptete das Gegenteil

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. protocols akzeptiert nur tcp, udp, icmp und all; alles andere wird beim Start abgelehnt mit invalid protocol: … (valid: tcp, udp, icmp, all). Wenn Sie eines davon konkret brauchen, setzen Sie protocols: ["all"] und formulieren Sie das Protokoll in capture.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 nichts

Diese 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üsselVoreinstellungWas er tut
capture.snaplen65535Je Paket mitgeschnittene Bytes (validiert 0–65535)
capture.promiscuoustruePromiscuous-Modus
capture.buffer_size104857600Mitschnitt-Puffer im Kernel, in Bytes
capture.timeout100msLese-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
Dieser Abschnitt beschreibt die NÄCHSTE Version, nicht das herunterladbare Binary

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_interval alt 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-ga kann listening_socket nie 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_process wird in dieser Version nirgends zugewiesen und kann daher nie in einem exportierten Datensatz auftauchen.
  • Das exportierte Prozessobjekt trägt pid, name, executable und user — und kein Feld attribution.

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 Flows

Diese 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"
}
WertWie er bestimmt wurdeWie weit ihm zu trauen ist
socketTraf auf den eigenen Socket dieses Flows in den Socket-Tabellen des Hosts, über das vollständige VierertupelEine Tatsache. Dieser Prozess hielt diesen Socket.
listening_socketFü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 istEine 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.

Korrektur: Diese Seite beschrieb eine zu weit angewendete Ableitung, die in keiner Version steckt

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-ga hat nichts, was zu weit angewendet werden könnte. Es enthält überhaupt keine Ableitung aus lauschenden Sockets und kann listening_socket nicht 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üsselVoreinstellungBedingung
performance.workers4≥ 1
performance.queue_size100000≥ 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üsselVoreinstellungBedingung
performance.connection_table_size1000000≥ 1000 — maximal verfolgte Verbindungen vor der Verdrängung
performance.connection_timeout5m≥ 1s — Leerlaufzeit, bevor eine Verbindung eingesammelt wird
performance.gc_interval1m≥ 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_size ist 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_interval sammeln 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:

DatenWohin sie gehen
Paket-Header und Nutzlast bis snaplenAusschließlich der Speicher des Agenten, für die Dauer der Verarbeitung des Pakets
Nutzlast-Bytes der PaketeNirgendwohin. Werden nie exportiert, nie auf die Festplatte geschrieben
Verbindungsdatensätze, mit ProzesszuordnungDie 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 ProzessesNirgendwohin. Bewusst ausgeschlossen: Kommandozeilen tragen routinemäßig Zugangsdaten als Argumente
Paket-/Byte-/Drop-/VerbindungszählerDie Log-Ausgabe des Agenten selbst
Registrierung und Heartbeat des AgentenDie 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?