Uma história sobre a falta de pacotes DNS do suporte técnico do Google Cloud

Do Editor de blogs do Google: Você já se perguntou como os engenheiros de soluções técnicas do Google Cloud (TSE) lidam com suas solicitações de suporte? Os engenheiros de suporte técnico da TSE são responsáveis ​​por identificar e corrigir fontes de problemas relatadas pelos usuários. Alguns desses problemas são bastante simples, mas às vezes você se depara com um ticket que requer a atenção de vários engenheiros ao mesmo tempo. Neste artigo, um dos funcionários do TSE nos contará sobre um problema muito complicado de sua prática recente - caso de pacotes DNS perdidos. Nesta história, veremos como os engenheiros conseguiram resolver a situação e que coisas novas aprenderam ao corrigir o erro. Esperamos que esta história não apenas eduque você sobre um bug profundo, mas também forneça informações sobre os processos necessários para preencher um tíquete de suporte no Google Cloud.

Uma história sobre a falta de pacotes DNS do suporte técnico do Google Cloud

A solução de problemas é uma ciência e uma arte. Tudo começa com a construção de uma hipótese sobre o motivo do comportamento atípico do sistema, após a qual sua resistência é testada. No entanto, antes de formularmos uma hipótese, devemos definir claramente e formular com precisão o problema. Se a pergunta parecer muito vaga, você terá que analisar tudo com cuidado; Esta é a “arte” da solução de problemas.

No Google Cloud, esses processos se tornam exponencialmente mais complexos, à medida que o Google Cloud faz o possível para garantir a privacidade de seus usuários. Por causa disso, os engenheiros do TSE não têm acesso para editar seus sistemas, nem a capacidade de visualizar as configurações de forma tão ampla quanto os usuários. Portanto, para testar qualquer uma de nossas hipóteses, nós (engenheiros) não podemos modificar rapidamente o sistema.

Alguns usuários acreditam que vamos consertar tudo como um mecânico em um serviço de carro, e simplesmente nos enviar o id de uma máquina virtual, quando na realidade o processo ocorre em formato conversacional: coleta de informações, formação e confirmação (ou refutação) de hipóteses, e, no final, a decisão dos problemas é baseada na comunicação com o cliente.

Problema em questão

Hoje temos uma história com um bom final. Uma das razões para o sucesso da resolução do caso proposto é uma descrição muito detalhada e precisa do problema. Abaixo você confere uma cópia do primeiro ticket (editado para ocultar informações confidenciais):
Uma história sobre a falta de pacotes DNS do suporte técnico do Google Cloud
Esta mensagem contém muitas informações úteis para nós:

  • VM específica especificada
  • O problema em si é indicado - DNS não funciona
  • É indicado onde o problema se manifesta – VM e container
  • As etapas que o usuário seguiu para identificar o problema são indicadas.

A solicitação foi registrada como “P1: Impacto Crítico - Serviço Inutilizável em produção”, o que significa monitoramento constante da situação 24 horas por dia, 7 dias por semana de acordo com o esquema “Follow the Sun” (você pode ler mais sobre prioridades de solicitações do usuário), com sua transferência de uma equipe de suporte técnico para outra a cada mudança de fuso horário. Na verdade, quando o problema chegou à nossa equipa em Zurique, já tinha circulado pelo mundo. A essa altura, o usuário já havia tomado medidas de mitigação, mas temia que a situação se repetisse na produção, pois a causa raiz ainda não havia sido descoberta.

Quando a passagem chegou a Zurique, já tínhamos em mãos as seguintes informações:

  • Conteúdo /etc/hosts
  • Conteúdo /etc/resolv.conf
  • Jogar aviator online grátis: hack aviator funciona iptables-save
  • Montado pela equipe ngrep arquivo pcap

Com esses dados, estávamos prontos para iniciar a fase de “investigação” e solução de problemas.

Nossos primeiros passos

Em primeiro lugar, verificamos os logs e o status do servidor de metadados e nos certificamos de que estava funcionando corretamente. O servidor de metadados responde ao endereço IP 169.254.169.254 e, entre outras coisas, é responsável por controlar os nomes de domínio. Também verificamos se o firewall funciona corretamente com a VM e não bloqueia pacotes.

Foi algum tipo de problema estranho: a verificação do nmap refutou nossa hipótese principal sobre a perda de pacotes UDP, então criamos mentalmente mais algumas opções e maneiras de verificá-los:

  • Os pacotes são descartados seletivamente? => Verifique as regras do iptables
  • Não é muito pequeno? MTU? => Verifique a saída ip a show
  • O problema afeta apenas pacotes UDP ou TCP também? => Vá embora dig +tcp
  • Os pacotes gerados pelo dig são retornados? => Vá embora tcpdump
  • O libdns está funcionando corretamente? => Vá embora strace para verificar a transmissão de pacotes em ambas as direções

Aqui decidimos ligar para o usuário para solucionar problemas ao vivo.

Durante a ligação podemos verificar várias coisas:

  • Após várias verificações, excluímos as regras do iptables da lista de motivos
  • Verificamos interfaces de rede e tabelas de roteamento e verificamos novamente se o MTU está correto
  • Nós descobrimos que dig +tcp google.com (TCP) funciona como deveria, mas dig google.com (UDP) não funciona
  • Tendo ido embora tcpdump enquanto trabalho dig, descobrimos que os pacotes UDP estão sendo retornados
  • Nós vamos embora strace dig google.com e vemos como dig chama corretamente sendmsg() и recvms(), porém o segundo é interrompido por um timeout

Infelizmente, chega o fim do turno e somos obrigados a escalar o problema para o próximo fuso horário. A solicitação, porém, despertou interesse em nossa equipe, e um colega sugeriu a criação do pacote DNS inicial usando o módulo scrapy Python.

from scapy.all import *

answer = sr1(IP(dst="169.254.169.254")/UDP(dport=53)/DNS(rd=1,qd=DNSQR(qname="google.com")),verbose=0)
print ("169.254.169.254", answer[DNS].summary())

Este fragmento cria um pacote DNS e envia a solicitação ao servidor de metadados.

O usuário executa o código, a resposta do DNS é retornada e a aplicação a recebe, confirmando que não há problema no nível da rede.

Depois de mais uma “viagem ao redor do mundo”, a solicitação retorna para nossa equipe, e eu a transfiro completamente para mim, pensando que será mais conveniente para o usuário se a solicitação parar de circular de um lugar para outro.

Enquanto isso, o usuário concorda em fornecer um instantâneo da imagem do sistema. Esta é uma notícia muito boa: a capacidade de testar o sistema sozinho torna a solução de problemas muito mais rápida, pois não preciso mais pedir ao usuário para executar comandos, enviar-me os resultados e analisá-los, posso fazer tudo sozinho!

Meus colegas estão começando a me invejar um pouco. Durante o almoço discutimos a conversão, mas ninguém tem ideia do que está acontecendo. Felizmente, o próprio usuário já tomou medidas para mitigar as consequências e não tem pressa, então temos tempo para dissecar o problema. E como temos uma imagem, podemos fazer os testes que nos interessam. Ótimo!

Dando um passo para trás

Uma das perguntas de entrevista mais populares para cargos de engenheiro de sistemas é: “O que acontece quando você faz ping www.google.com? A questão é ótima, pois o candidato precisa descrever tudo, desde o shell até o espaço do usuário, passando pelo kernel do sistema e depois pela rede. Eu sorrio: às vezes as perguntas da entrevista acabam sendo úteis na vida real...

Decido aplicar esta questão de RH a um problema atual. Grosso modo, quando você tenta determinar um nome DNS, acontece o seguinte:

  1. O aplicativo chama uma biblioteca de sistema como libdns
  2. libdns verifica a configuração do sistema com qual servidor DNS ele deve entrar em contato (no diagrama é 169.254.169.254, servidor de metadados)
  3. libdns usa chamadas de sistema para criar um soquete UDP (SOKET_DGRAM) e enviar pacotes UDP com uma consulta DNS em ambas as direções
  4. Através da interface sysctl você pode configurar a pilha UDP no nível do kernel
  5. O kernel interage com o hardware para transmitir pacotes pela rede através da interface de rede
  6. O hipervisor captura e transmite o pacote para o servidor de metadados ao entrar em contato com ele
  7. O servidor de metadados, por sua magia, determina o nome DNS e retorna uma resposta usando o mesmo método

Uma história sobre a falta de pacotes DNS do suporte técnico do Google Cloud
Deixe-me lembrá-lo de quais hipóteses já consideramos:

Hipótese: bibliotecas quebradas

  • Teste 1: execute strace no sistema, verifique se dig chama as chamadas de sistema corretas
  • Resultado: chamadas de sistema corretas são chamadas
  • Teste 2: usando srapy para verificar se podemos determinar nomes ignorando as bibliotecas do sistema
  • Resultado: podemos
  • Teste 3: execute rpm –V no pacote libdns e nos arquivos da biblioteca md5sum
  • Resultado: o código da biblioteca é completamente idêntico ao código do sistema operacional em funcionamento
  • Teste 4: monte a imagem do sistema raiz do usuário em uma VM sem esse comportamento, execute chroot, veja se o DNS funciona
  • Resultado: DNS funciona corretamente

Conclusão baseada em testes: o problema não está nas bibliotecas

Hipótese: Há um erro nas configurações de DNS

  • Teste 1: verifique o tcpdump e veja se os pacotes DNS são enviados e retornados corretamente após executar o dig
  • Resultado: os pacotes são transmitidos corretamente
  • Teste 2: verificação dupla no servidor /etc/nsswitch.conf и /etc/resolv.conf
  • Resultado: está tudo correto

Conclusão baseada em testes: o problema não está na configuração do DNS

Hipótese: núcleo danificado

  • Teste: instale o novo kernel, verifique a assinatura, reinicie
  • Resultado: comportamento semelhante

Conclusão baseada em testes: o kernel não está danificado

Hipótese: comportamento incorreto da rede do usuário (ou interface de rede do hipervisor)

  • Teste 1: verifique as configurações do firewall
  • Resultado: o firewall passa pacotes DNS no host e no GCP
  • Teste 2: interceptar o tráfego e monitorar a exatidão da transmissão e retorno das solicitações DNS
  • Resultado: tcpdump confirma que o host recebeu pacotes de retorno

Conclusão baseada em testes: o problema não está na rede

Hipótese: o servidor de metadados não está funcionando

  • Teste 1: verifique se há anomalias nos logs do servidor de metadados
  • Resultado: não há anomalias nos logs
  • Teste 2: Ignore o servidor de metadados via dig @8.8.8.8
  • Resultado: a resolução é quebrada mesmo sem usar um servidor de metadados

Conclusão baseada em testes: o problema não está no servidor de metadados

A linha inferior: testamos todos os subsistemas, exceto configurações de tempo de execução!

Mergulhando nas configurações de tempo de execução do kernel

Para configurar o ambiente de execução do kernel, você pode usar opções de linha de comando (grub) ou a interface sysctl. Eu olhei para dentro /etc/sysctl.conf e pense só, descobri várias configurações personalizadas. Sentindo-me como se tivesse agarrado algo, descartei todas as configurações que não fossem de rede ou não TCP, permanecendo com as configurações de montanha net.core. Depois fui até onde estavam as permissões do host na VM e comecei a aplicar as configurações uma por uma, uma após a outra, com a VM quebrada, até encontrar o culpado:

net.core.rmem_default = 2147483647

Aqui está, uma configuração de quebra de DNS! Encontrei a arma do crime. Mas por que isso está acontecendo? Eu ainda precisava de um motivo.

O tamanho básico do buffer do pacote DNS é configurado via net.core.rmem_default. Um valor típico é algo em torno de 200 KiB, mas se o seu servidor receber muitos pacotes DNS, você pode querer aumentar o tamanho do buffer. Se o buffer estiver cheio quando um novo pacote chegar, por exemplo, porque o aplicativo não o está processando rápido o suficiente, você começará a perder pacotes. Nosso cliente aumentou corretamente o tamanho do buffer porque tinha medo de perda de dados, pois estava utilizando um aplicativo para coleta de métricas por meio de pacotes DNS. O valor que ele definiu foi o máximo possível: 231-1 (se definido como 231, o kernel retornará “ARGUMENTO INVÁLIDO”).

De repente, percebi porque o nmap e o scapy funcionavam corretamente: eles estavam usando soquetes brutos! Os soquetes brutos são diferentes dos soquetes normais: eles ignoram o iptables e não são armazenados em buffer!

Mas por que “buffer muito grande” causa problemas? Claramente não funciona como pretendido.

Neste ponto eu poderia reproduzir o problema em vários kernels e múltiplas distribuições. O problema já apareceu no kernel 3.x e agora apareceu também no kernel 5.x.

Na verdade, na inicialização

sysctl -w net.core.rmem_default=$((2**31-1))

O DNS parou de funcionar.

Comecei a procurar valores funcionais por meio de um algoritmo de busca binária simples e descobri que o sistema funcionava com 2147481343, mas esse número era um conjunto de números sem sentido para mim. Sugeri ao cliente que tentasse esse número e ele respondeu que o sistema funcionava com google.com, mas ainda apresentava erro com outros domínios, então continuei minha investigação.

eu instalei dropwatch, uma ferramenta que deveria ter sido usada anteriormente: ela mostra exatamente onde um pacote termina no kernel. O culpado foi a função udp_queue_rcv_skb. Eu baixei os fontes do kernel e adicionei alguns funções printk para rastrear exatamente onde o pacote termina. Eu rapidamente encontrei a condição certa if, e simplesmente fiquei olhando para ele por algum tempo, porque foi então que tudo finalmente se juntou em uma imagem completa: 231-1, um número sem sentido, um domínio que não funciona... Era um pedaço de código em __udp_enqueue_schedule_skb:

if (rmem > (size + sk->sk_rcvbuf))
		goto uncharge_drop;

Por favor, note:

  • rmem é do tipo int
  • size é do tipo u16 (int não assinado de dezesseis bits) e armazena o tamanho do pacote
  • sk->sk_rcybuf é do tipo int e armazena o tamanho do buffer que, por definição, é igual ao valor em net.core.rmem_default

Quando sk_rcvbuf se aproxima de 231, somar o tamanho do pacote pode resultar em estouro de número inteiro. E como é um int, seu valor se torna negativo, então a condição se torna verdadeira quando deveria ser falsa (você pode ler mais sobre isso em link).

O erro pode ser corrigido de uma forma trivial: lançando unsigned int. Apliquei a correção e reiniciei o sistema e o DNS funcionou novamente.

Sabor da vitória

Encaminhei minhas descobertas ao cliente e enviei LKML patch do kernel. Estou satisfeito: cada peça do quebra-cabeça se encaixa, posso explicar exatamente por que observamos o que observamos e, o mais importante, conseguimos encontrar uma solução para o problema graças ao nosso trabalho em equipe!

É importante reconhecer que o caso acabou sendo raro e, felizmente, raramente recebemos solicitações tão complexas dos usuários.

Uma história sobre a falta de pacotes DNS do suporte técnico do Google Cloud


Fonte: habr.com

Adicionar um comentário