Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

Attribute™ SensorでgRPCとGoogle PubSubメッセージを解析する:ネットワークスタックを辿る旅

By Hili ParyentiOct 9, 20245 min read

このページはEnglishDeutschEspañolFrançaisItalianoPortuguêsでもご覧いただけます。

ネットワークメッセージを監視・解析する高度なセンサーを構築するのは、決して簡単な仕事ではありません。特に、現代的で複雑なクラウドサービスを対象にする場合はなおさらです。

Attribute™では、自社のSensorに強い誇りを持っています。トラフィックの中身を見通し、クラウドリソースの利用状況やアプリケーションの流れに関する知見を得られる仕組みであり、だからこそSensorの機能強化に継続的に投資し、開発を進めています。

本記事では、Google Pub/Sub対応の新しい解析機能を追加する過程で得られた学びを共有します。

課題:Google Pub/Subプロトコルの解析

課題は明確でした。gRPCプロトコル上で動作するGoogle Cloud Pub/Subのメッセージ解析にどう対応しつつ、他のGCPサービスやPythonベースのgRPC通信もカバーするか、という点です。

この課題の中心にあったのが、Googleのメッセージキューサービスである PubSub です。PubSubは、HTTP/2をベースとしProtobufでメッセージをシリアライズするgRPCを用いて通信します。ネットワーク上を流れる暗号化・圧縮されたデータを相手にしながら、これらのメッセージを細かい粒度でキャプチャしデコードする方法を突き止める必要がありました。

第一歩:PubSub環境のセットアップ

まずはGoogle Cloud上に、PubSubメッセージを生成するための制御された環境を用意しました。PubSubトピックを作成し、サブスクリプションチャネルを設定したうえで、実際のパブリッシュ・サブスクライブ動作を再現するPythonスクリプトを組みました。

次に、チャネルへメッセージを送信するシンプルなパブリッシャースクリプトと、作成したサブスクリプションチャネルからメッセージを待ち受けるサブスクライバーサーバーとして動作するスクリプトを追加で作成しました。

以下は、パブリッシャースクリプトのロジックの一部です:

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()}")`

すべての準備が整ったところで、目指したのはネットワーク上を流れるPubSubメッセージをSensorで捕捉することでした。ところが、Sensorはネットワークトラフィックも、SSLライブラリの動作も一切検出しませんでした。この最初のつまずきをきっかけに、ネットワークスタックをさらに深く掘り下げて調査することになりました。

さらに深掘り:PubSubのネットワークスタックを読み解く

調査を進めた結果、Google PubSubはgRPC経由で通信しており、gRPCはトランスポート層にHTTP/2を、メッセージのシリアライズにProtobufを用いていることが分かりました。PythonにおけるgRPCの土台となる `grpcio` ライブラリは、暗号化とトランスポートセキュリティのためにOpenSSLの一部を独自のCythonモジュールに組み込んでおり、構造をいっそう複雑にしています。

`grpcio` ライブラリの内部動作を追跡した結果、暗号化されたgRPCメッセージの処理を担うC拡張モジュール(`cygrpc.cpython-x86_64-linux-gnu.so`)が使われていることを突き止めました。これは大きな手がかりでした。PubSubメッセージを捕捉するために、低レベルのgRPC関数をフックするターゲットが見えたのです。

ブレイクスルー:gRPCメッセージのインターセプト

ブレイクスルーは、`grpcio` 内のTransport Security Interface(TSI)に着目した瞬間に訪れました。このモジュールはgRPCメッセージの暗号化と復号を担っており、特定のTSI関数にプローブを仕込めば、処理中の暗号化データをそのままキャプチャできると気付いたのです。

`protect` や `unprotect` といった主要なTSI関数にeBPF(extended Berkeley Packet Filter)プローブを設置することで、生のHTTP/2フレーム、つまりProtobufに包まれたgRPCメッセージそのものをインターセプトできるようになりました。

以下は、ライブラリ内部で平文・暗号文の暗号化/復号に用いられる「tsi_frame_protector_vtable」構造体です:

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>};`

PubSubメッセージのデコード

生のHTTP/2フレームをキャプチャできたら、次はそれをデコードする段階です。ここでは、長さフィールドと圧縮フラグが先頭に付き、その後にProtobufでエンコードされたメッセージが続くgRPCヘッダーを識別する必要があります。この処理に対応するため、HTTP/2デコーダーを拡張しました。

プロセスを整理すると、次のようになります:

1. gRPCトラフィックのインターセプト:eBPFプローブがgRPCのTSI関数から平文データを取得します。

2. HTTP/2フレームのデコード:gRPCメッセージのヘッダーと、それに紐づくProtobufメッセージを抽出します。

3. Protobufデータのパース:PubSubプロトコルに従い、`PublishRequest` や `AcknowledgeRequest` といったメッセージをデコードします。

これにより、Google PubSubのメッセージをリアルタイムで解析できるようになり、Sensorは GCP環境のネットワークトラフィックを監視するための強力なツールへと進化しました。

他のgRPCバージョンへの対応拡張

最後の関門のひとつが、Pythonの `grpcio` ライブラリの複数バージョンとの互換性を確保することでした。そこで、`grpcio` の各リリースで使われているシンボルを動的に特定・解析するロジックを実装しました。実現にはバイナリ解析への踏み込みが不可欠で、DWARFからのデバッグシンボルのパース、バイナリ解析(Zanalysisフレームワークや、Ghidraのpre-script・post-scriptなどの活用)、スクリプトによる自動リバースエンジニアリング、シンボル抽出といった手法を組み合わせ、各バージョンで対象となる関数を洗い出すプロセスを自動化しました。

今後の展望:Pythonの枠を超えて

今回の主眼はPythonでしたが、gRPCフレームワーク自体はC++、Java、Goなど多くの言語で使われています。PythonベースのgRPCメッセージ解析に成功したことで、比較的少ない労力で他の言語への展開に踏み出す土台ができました。新しい言語ごとにgRPC実装への追加調査は必要になるものの、根本の考え方は変わりません。

まとめ:新たな可視性のステージへ

綿密な調査、試行錯誤、そしてGoogle PubSubとgRPCスタックへの深い理解を積み重ねた結果、PubSubメッセージをインターセプトして解析する堅牢な仕組みを構築できました。このソリューションはGCPサービスの可視性を高めるだけでなく、さまざまな言語や環境にわたるgRPCサポートを広げていくための足場にもなります。

これらの機能が揃ったことで、gRPCとPubSubを利用するクラウドサービスの監視、トラブルシューティング、パフォーマンス最適化により的確に取り組めるようになりました。対応サービスを広げていくにつれ、このSensorの可能性はさらに広がっていくはずです。

本記事が、gRPCベースのプロトコルを解析される皆さまの一助になれば幸いです。さらに詳しい情報が必要な場合は、どうぞお気軽にご連絡ください!

あわせて、以下の参考資料もおすすめです:

GRPC Core Concepts GRPC Stacks

Interceptors Guide