Construir um sensor avançado para monitorar e dissecar mensagens de rede não é tarefa fácil, ainda mais quando o assunto são serviços de nuvem modernos e complexos.
No Attribute™, temos um orgulho especial do nosso Sensor. Ele nos permite enxergar o tráfego e extrair insights sobre o uso de recursos de nuvem e sobre os fluxos das aplicações — por isso investimos e evoluímos as capacidades do sensor do Attribute o tempo todo.
Neste post, vamos compartilhar o que aprendemos ao adicionar novos recursos de dissecação para dar suporte ao Google Pub/Sub.
O desafio: dissecar o protocolo do Google Pub/Sub
Nosso desafio era claro: como adicionar suporte para dissecar mensagens do serviço Google Cloud Pub/Sub, que opera sobre o protocolo gRPC, cobrindo também outros serviços do GCP e comunicações gRPC em Python?
No centro desse desafio estava o PubSub, o serviço de fila de mensagens do Google, que se comunica via gRPC — um protocolo baseado em HTTP/2 que serializa mensagens em Protobuf. Precisávamos entender como capturar e decodificar essas mensagens em nível granular, lidando ao mesmo tempo com dados criptografados e comprimidos trafegando pela rede.
O primeiro passo: preparando o ambiente do PubSub
Começamos criando um ambiente controlado no Google Cloud para gerar mensagens do PubSub. Para isso, configuramos um tópico no PubSub, criamos um canal de subscription e escrevemos scripts em Python para simular eventos reais de publicação e assinatura.
Em seguida, escrevemos um script publisher simples para enviar mensagens ao canal e outro script que atua como servidor subscriber, aguardando mensagens do canal de subscription criado.
Veja a seguir um trecho da lógica do script publisher:
from``google.cloud``import``pubsub_v1from``google.oauth2``import``service_accountdef``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 messagemessage_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()}")` |
Com tudo pronto, a ideia era que nosso sensor capturasse as mensagens do PubSub à medida que trafegassem pela rede. Só que o sensor não detectou nenhum tráfego de rede nem atividade da biblioteca SSL. Essa falha inicial nos empurrou a investigar a pilha de rede em mais detalhes.
Cavando mais fundo: entendendo a pilha de rede do PubSub
Depois de alguma pesquisa, descobrimos que o Google PubSub se comunica via gRPC, que usa HTTP/2 como camada de transporte e Protobuf para serialização das mensagens. A biblioteca `grpcio`, que dá base ao gRPC em Python, adiciona ainda mais complexidade ao embutir partes do OpenSSL dentro dos seus próprios módulos Cython customizados para criptografia e segurança de transporte.
Rastreamos o funcionamento interno da biblioteca `grpcio` e, no fim, identificamos o uso de um módulo C-Extension (`cygrpc.cpython-x86_64-linux-gnu.so`) responsável por tratar as mensagens gRPC criptografadas. Essa descoberta foi decisiva: agora tínhamos um alvo para o nosso sensor — interceptar essas funções gRPC de baixo nível para capturar as mensagens do PubSub.
O grande avanço: interceptando mensagens gRPC
O grande avanço veio quando focamos na Transport Security Interface (TSI) dentro do `grpcio`. Esse módulo cuida da criptografia e da descriptografia das mensagens gRPC, e percebemos que, ao posicionar probes em funções específicas da TSI, dava para capturar os dados criptografados no exato momento em que eram processados.
Instalando probes eBPF (extended Berkeley Packet Filter) em funções críticas da TSI, como `protect` e `unprotect`, conseguimos interceptar quadros HTTP/2 brutos — na prática, as mensagens gRPC encapsuladas em Protobuf.
A seguir está a struct "tsi_frame_protector_vtable", usada internamente na biblioteca para criptografia/descriptografia de texto claro/texto cifrado:
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>};` |
Decodificando as mensagens do PubSub
Depois de capturar os quadros HTTP/2 brutos, o próximo passo era decodificá-los. Isso passava por identificar os cabeçalhos gRPC, que vêm precedidos por um campo de tamanho e uma flag de compressão, seguidos pela mensagem codificada em Protobuf. Estendemos nosso decodificador HTTP/2 para dar conta disso.
Veja um resumo simplificado do processo:
1. Interceptar o tráfego gRPC: nosso probe eBPF captura os dados em texto claro a partir das funções TSI do gRPC.
2. Decodificar os quadros HTTP/2: extraímos os cabeçalhos das mensagens gRPC e as mensagens Protobuf que vêm junto.
3. Fazer o parsing dos dados Protobuf: usando o protocolo do PubSub, decodificamos mensagens como `PublishRequest` e `AcknowledgeRequest`.
Isso nos permitiu dissecar mensagens do Google PubSub em tempo real, transformando nosso sensor em uma ferramenta poderosa para monitorar o tráfego de rede em ambientes GCP.
Ampliando o suporte para outras versões do gRPC
Um dos últimos obstáculos foi garantir compatibilidade com várias versões da biblioteca `grpcio` em Python. Implementamos uma lógica para localizar e analisar dinamicamente os símbolos usados nas diferentes releases do `grpcio`. Isso exigiu mergulhar em análise binária, e usamos métodos como parsing de símbolos de debug do DWARF, análise binária (contando, entre outros, com o framework Zanalysis e com pre-script/post-script do Ghidra etc.), engenharia reversa automatizada via scripting e extração de símbolos — tudo para automatizar o processo de identificar as funções relevantes em cada versão da biblioteca.
O futuro: escalando para além do Python
Nosso foco principal foi o Python, mas o framework gRPC é usado em várias linguagens de programação, incluindo C++, Java e Go. Ao conseguir dissecar mensagens gRPC baseadas em Python, criamos a base para estender esse suporte a outras linguagens com esforço relativamente pequeno. Cada nova linguagem vai exigir mais pesquisa sobre sua implementação de gRPC, mas os princípios centrais são os mesmos.
Conclusão: um novo nível de visibilidade
Com muita pesquisa, tentativa e erro e um entendimento profundo da pilha do Google PubSub e do gRPC, conseguimos construir um sistema robusto para interceptar e dissecar mensagens do PubSub. Essa solução não só amplia a visibilidade sobre os serviços do GCP como também prepara o terreno para um suporte mais amplo ao gRPC em diferentes linguagens e ambientes.
Com esses recursos em mãos, estamos mais bem preparados para monitorar, resolver problemas e otimizar o desempenho de serviços de nuvem que usam gRPC e PubSub. E, conforme seguimos ampliando o suporte para novos serviços, as possibilidades desse sensor só crescem.
Esperamos que este post ajude você na sua jornada de dissecar protocolos baseados em gRPC. Se quiser saber mais, é só chamar a gente!
E aqui vão algumas leituras complementares recomendadas: