Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

gRPC- und Google-PubSub-Nachrichten im Attribute™ Sensor zerlegen: eine Reise durch den Network Stack

By Hili ParyentiOct 9, 20245 min read

Diese Seite ist auch in English, Español, Français, Italiano, 日本語 und Português verfügbar.

Einen leistungsfähigen Sensor zu bauen, der Netzwerknachrichten überwacht und zerlegt, ist alles andere als trivial – erst recht bei modernen, komplexen Cloud-Diensten.

Bei Attribute™ sind wir besonders stolz auf unseren Sensor. Er zeigt uns, was im Traffic passiert, und liefert Erkenntnisse zur Nutzung von Cloud-Ressourcen und zu Anwendungsflüssen. Genau deshalb investieren wir laufend in den Ausbau der Sensor-Funktionen von Attribute.

In diesem Blogpost teilen wir, was wir beim Ausbau unserer Dissecting-Funktionen für Google Pub/Sub gelernt haben.

Die Herausforderung: das Google-Pub/Sub-Protokoll zerlegen

Die Aufgabe war klar: Wie ergänzen wir die Unterstützung, um Nachrichten des Google Cloud Pub/Sub-Dienstes zu zerlegen, die über gRPC laufen – und decken dabei auch weitere GCP-Dienste sowie Python-basierte gRPC-Kommunikation ab?

Im Zentrum stand PubSub, Googles Messaging-Queue-Dienst, der über gRPC kommuniziert – ein HTTP/2-basiertes Protokoll, das Nachrichten in Protobuf serialisiert. Wir mussten herausfinden, wie sich diese Nachrichten granular erfassen und dekodieren lassen, während verschlüsselte und komprimierte Daten durchs Netzwerk laufen.

Der erste Schritt: die PubSub-Umgebung aufsetzen

Zunächst haben wir in Google Cloud eine kontrollierte Umgebung geschaffen, um PubSub-Nachrichten zu erzeugen. Dazu gehörten das Anlegen eines PubSub-Topics, das Einrichten eines Subscription-Kanals sowie Python-Skripte, die reale Publish- und Subscribe-Ereignisse simulieren.

Danach entstand ein einfaches Publisher-Skript, das Nachrichten in den Kanal sendet, sowie ein zweites Skript, das als Subscriber-Server auf Nachrichten aus dem angelegten Subscription-Kanal wartet.

Hier ein Ausschnitt aus der Logik des Publisher-Skripts:

from``google.cloud``import``pubsub_v1
from``google.oauth2``import``service_account
def``test_pubsub_v1_publisher_client():
# Create a Pub/Sub publisher client with the credentials`<br>credentials=service_account.Credentials.from_service_account_file(<br>```TEST_CONFIG[``"service_account_json_file_path"``]
)`<br>publisher=pubsub_v1.PublisherClient(credentials=credentials)<br>```# Publish the message
message_data``=``"Hello, Pub/Sub!"`<br>future=publisher.publish(<br>```topic``=``TEST_CONFIG[``"topic_path"``], data``=``message_data.encode(``"utf-8"``)
)`<br>logger.info(f"Published message: {future.result()}")`

Nach dem Setup sollte unser Sensor die PubSub-Nachrichten auf ihrem Weg durchs Netzwerk erfassen. Doch er erkannte weder Netzwerkverkehr noch Aktivitäten der SSL-Bibliothek. Dieser erste Fehlschlag hat uns dazu gebracht, den Network Stack genauer unter die Lupe zu nehmen.

Tiefer bohren: den PubSub Network Stack verstehen

Bei der Recherche stellte sich heraus, dass Google PubSub über gRPC kommuniziert, welches wiederum HTTP/2 als Transportschicht und Protobuf zur Serialisierung nutzt. Die Bibliothek `grpcio`, die gRPC in Python bereitstellt, bringt zusätzliche Komplexität mit, weil sie Teile von OpenSSL in eigene Cython-Module für Verschlüsselung und Transportsicherheit einbettet.

Wir haben das Innenleben der `grpcio`-Bibliothek nachvollzogen und schließlich das C-Extension-Modul (`cygrpc.cpython-x86_64-linux-gnu.so`) identifiziert, das für die Verarbeitung verschlüsselter gRPC-Nachrichten zuständig ist. Diese Entdeckung war entscheidend: Damit hatten wir einen Ansatzpunkt für unseren Sensor, um genau diese Low-Level-gRPC-Funktionen abzugreifen und PubSub-Nachrichten zu erfassen.

Der Durchbruch: gRPC-Nachrichten abfangen

Der Durchbruch kam, als wir uns auf das Transport Security Interface (TSI) innerhalb von `grpcio` konzentrierten. Dieses Modul übernimmt die Ver- und Entschlüsselung von gRPC-Nachrichten – und uns wurde klar: Setzen wir Probes an bestimmten TSI-Funktionen, lassen sich die verschlüsselten Daten während der Verarbeitung abfangen.

Mit eBPF-Probes (extended Berkeley Packet Filter) an kritischen TSI-Funktionen wie `protect` und `unprotect` konnten wir rohe HTTP/2-Frames abgreifen – also im Kern die in Protobuf verpackten gRPC-Nachrichten.

Nachfolgend das Struct "tsi_frame_protector_vtable", das intern in der Bibliothek zur Ver- und Entschlüsselung von Klartext bzw. Chiffretext dient:

struct``tsi_frame_protector_vtable {
tsi_result (*protect)(tsi_frame_protector* self,`<br>constunsignedchar* unprotected_bytes,`<br>```size_t* unprotected_bytes_size,<br>```unsigned``char``* protected_output_frames,
size_t``* protected_output_frames_size);`<br>tsi_result (protect_flush)(tsi_frame_protector self,<br>```unsigned``char``* protected_output_frames,
size_t``* protected_output_frames_size,`<br>size_t* still_pending_size);`<br>```tsi_result (*unprotect)(tsi_frame_protector* self,`<br>```constunsignedchar* protected_frames_bytes,<br>```size_t``* protected_frames_bytes_size,
unsigned``char``* unprotected_bytes,`<br>size_t* unprotected_bytes_size);`<br>```void(destroy)(tsi_frame_protector self);<br>};`

Die PubSub-Nachrichten dekodieren

Sobald die rohen HTTP/2-Frames erfasst waren, ging es ans Dekodieren. Dazu mussten wir die gRPC-Header identifizieren, die mit einem Längenfeld und einem Kompressions-Flag beginnen, gefolgt von der Protobuf-codierten Nachricht. Unseren HTTP/2-Decoder haben wir entsprechend erweitert.

Hier eine vereinfachte Übersicht des Ablaufs:

1. gRPC-Traffic abfangen: Unsere eBPF-Probe erfasst die Klartextdaten aus den gRPC-TSI-Funktionen.

2. HTTP/2-Frames dekodieren: Wir extrahieren die gRPC-Nachrichten-Header und die zugehörigen Protobuf-Nachrichten.

3. Protobuf-Daten parsen: Anhand des PubSub-Protokolls dekodieren wir Nachrichten wie `PublishRequest` und `AcknowledgeRequest`.

So können wir Google-PubSub-Nachrichten in Echtzeit zerlegen – und machen unseren Sensor zu einem starken Werkzeug für das Monitoring des Netzwerkverkehrs in GCP-Umgebungen.

Support auf weitere gRPC-Versionen ausweiten

Eine der letzten Hürden war die Kompatibilität mit mehreren Versionen der Python-Bibliothek `grpcio`. Wir haben eine Logik implementiert, die die in den unterschiedlichen `grpcio`-Releases verwendeten Symbole dynamisch lokalisiert und analysiert. Dafür war Binäranalyse gefragt: Wir haben Debug-Symbole aus DWARF geparst, Binäranalyse betrieben (unter anderem mit dem Zanalysis-Framework sowie Ghidra-Pre-/Post-Scripts), automatisiertes Reverse Engineering per Scripting eingesetzt und Symbole extrahiert, um das Identifizieren der relevanten Funktionen in jeder Bibliotheksversion zu automatisieren.

Ausblick: Skalierung über Python hinaus

Unser Fokus lag zwar auf Python, doch das gRPC-Framework wird in vielen Programmiersprachen eingesetzt – darunter C++, Java und Go. Mit dem erfolgreichen Zerlegen Python-basierter gRPC-Nachrichten haben wir die Grundlage geschaffen, den Support mit vergleichsweise geringem Aufwand auf weitere Sprachen auszuweiten. Jede neue Sprache erfordert zusätzliche Recherche zu ihrer gRPC-Implementierung, doch die Grundprinzipien bleiben gleich.

Fazit: ein neues Maß an Sichtbarkeit

Durch gründliche Recherche, Trial and Error und ein tiefes Verständnis des Google-PubSub- und gRPC-Stacks haben wir ein robustes System aufgebaut, um PubSub-Nachrichten abzufangen und zu zerlegen. Diese Lösung verbessert nicht nur die Sichtbarkeit in GCP-Diensten, sondern ebnet auch den Weg für breitere gRPC-Unterstützung in weiteren Sprachen und Umgebungen.

Mit diesen Fähigkeiten sind wir deutlich besser aufgestellt, um die Performance von Cloud-Diensten auf Basis von gRPC und PubSub zu überwachen, Probleme zu beheben und zu optimieren. Und mit jedem weiteren unterstützten Dienst wachsen die Einsatzmöglichkeiten dieses Sensors.

Wir hoffen, dieser Blogpost hilft Ihnen dabei, gRPC-basierte Protokolle selbst zu zerlegen. Wenn Sie mehr erfahren möchten, melden Sie sich gerne bei uns!

Zum Weiterlesen empfehlen wir außerdem:

GRPC Core Concepts GRPC Stacks

Interceptors Guide