História sobre pacotes DNS perdidos do suporte do Google Cloud

Do editor do blog do Google: Você já se perguntou como os engenheiros de Soluções Técnicas do Google Cloud (TSE) tratam suas solicitações de suporte? A responsabilidade dos engenheiros de suporte técnico TSE é identificar e resolver as fontes de problemas relatadas pelos usuários. Alguns desses problemas são bastante simples, mas às vezes aparece um chamado que requer a atenção de vários engenheiros. Neste artigo, um membro da equipe TSE nos contará sobre um problema bastante complicado de sua experiência recente — um caso de pacotes DNS perdidos. Ao longo desta narrativa, veremos como os engenheiros conseguiram resolver a situação e o que aprenderam ao corrigir o erro. Esperamos que esta história não apenas explique um bug profundamente enraizado, mas também ofereça insights sobre os processos envolvidos ao enviar uma solicitação de suporte para o Google Cloud.

História sobre pacotes DNS perdidos do suporte do Google Cloud

A solução de problemas é, ao mesmo tempo, ciência e arte. Tudo começa com a formulação de uma hipótese sobre a causa do comportamento anômalo do sistema, que é então testada. No entanto, antes de formular uma hipótese, devemos definir e articular claramente o problema. Se a questão parecer vaga demais, você precisará analisá-la a fundo; esta é a essência da "arte" da solução de problemas.

No contexto do Google Cloud, esses processos se tornam significativamente mais complexos, pois o Google Cloud se esforça para garantir a privacidade de seus usuários. Como resultado, os engenheiros TSE não têm acesso à edição de seus sistemas, nem podem visualizar as configurações tão amplamente quanto os usuários. Portanto, para verificar qualquer uma de nossas hipóteses, nós (engenheiros) não podemos modificar rapidamente o sistema.

Alguns usuários acreditam que resolveremos tudo como mecânicos em uma oficina, simplesmente nos enviando o ID da máquina virtual, enquanto na realidade o processo acontece em formato de diálogo: coleta de informações, formulação e confirmação (ou refutação) de hipóteses, e, por fim, a solução do problema se baseia na comunicação com o cliente.

O problema em questão

Hoje, temos uma história com um final feliz. Uma das razões para a resolução bem-sucedida do caso proposto é a descrição muito detalhada e precisa do problema. Abaixo, você pode ver uma cópia do primeiro ticket (editado para ocultar informações confidenciais):
História sobre pacotes DNS perdidos do suporte do Google Cloud
Esta mensagem contém muitas informações úteis para nós:

  • Uma VM específica é mencionada
  • O problema em si é mencionado — DNS não funciona
  • É indicado onde o problema se manifesta — na VM e no contêiner
  • Os passos que o usuário realizou para identificar o problema são mencionados

A solicitação foi registrada como "P1: Impacto Crítico — Serviço Inoperante em produção", o que significa monitoramento contínuo da situação 24/7 no esquema "Follow the Sun" (você pode ler mais sobre prioridades dos atendimentos ao usuário), com a transferência de uma equipe de suporte técnico para outra a cada mudança de fuso horário. Na prática, quando o problema chegou à nossa equipe em Zurique, ele já havia contornado o globo. Nesse momento, o usuário tomou medidas para mitigar as consequências, mas temia a repetição da situação em produção, já que a causa principal ainda não havia sido descoberta.

Quando o ticket chegou a Zurique, já tínhamos as seguintes informações:

  • Conteúdo /etc/hosts
  • Conteúdo /etc/resolv.conf
  • Saída iptables-save
  • Coletado 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

Primeiramente, verificamos os logs e o status do servidor de metadados e confirmamos que estava funcionando corretamente. O servidor de metadados responde ao IP 169.254.169.254 e, entre outras coisas, é responsável pelo controle dos nomes de domínio. Também redistribuímos a verificação para garantir que o firewall estava funcionando corretamente com a VM e não estava bloqueando pacotes.

Era um problema estranho: a verificação com nmap refutou nossa hipótese principal sobre a perda de pacotes UDP, então mentalmente formulamos mais algumas opções e formas de verificá-las:

  • Os pacotes estão se perdendo de forma seletiva? => Verificar as regras do iptables
  • É muito pequeno? MTU? => Проверить вывод ip a show
  • O problema afeta apenas pacotes UDP ou também TCP? => Executar dig +tcp
  • Os pacotes gerados pelo dig estão retornando? => Executar tcpdump
  • O libdns está funcionando corretamente? => Executar strace para verificar a transmissão de pacotes em ambas as direções

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

Durante a chamada, conseguimos verificar várias coisas:

  • Após várias verificações, excluímos as regras do iptables da lista de causas
  • Verificamos as interfaces de rede e as tabelas de roteamento, e rechecamos a correção do MTU
  • Descobrimos que dig +tcp google.com funciona como deveria, mas dig google.com (UDP) não funciona
  • Executando tcpdump funciona por enquanto dig, descobrimos que os pacotes UDP estão retornando
  • Executamos strace dig google.com e vemos como o dig chama corretamente sendmsg() e recvmsg(), mas o segundo é interrompido por timeout

Infelizmente, o turno acaba e somos obrigados a passar o problema para o próximo fuso horário. No entanto, o ticket despertou o interesse da nossa equipe, e um colega sugere criar o pacote DNS usando o módulo scrapy do 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())

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

O usuário executa o código, a resposta DNS é retornada e o aplicativo a recebe, confirmando a ausência de problemas no nível da rede.

Após mais uma ‘viagem ao redor do mundo’, o ticket retorna à nossa equipe, e eu o pego completamente, achando que será mais conveniente para o usuário se o ticket parar de circular.

Enquanto isso, o usuário gentilmente concorda em fornecer uma imagem do sistema. Isso é uma ótima notícia: a capacidade de testar o sistema por conta própria acelera significativamente a resolução de problemas, 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 começam a sentir um pouco de inveja. Durante o almoço, discutimos o ticket, mas ninguém tem ideias sobre o que está acontecendo. Felizmente, o próprio usuário já tomou medidas para mitigar o problema e não está com pressa, então temos tempo para dissecar o problema. E como temos a imagem, podemos realizar qualquer teste que quisermos. Ótimo!

Voltando um passo atrás

Uma das perguntas mais populares em entrevistas para engenheiros de sistemas é: 'O que acontece quando você faz ping em' www.google.com?» Вопрос шикарный, так как кандидату необходимо описать пусть от оболочки до пользовательского пространства, до ядра системы и далее к сети. Я улыбаюсь: иногда вопросы с интервью оказываются полезны и в реальной жизни…

Decido aplicar essa pergunta de RH ao problema atual. Grosso modo, quando você tenta resolver um nome DNS, acontece o seguinte:

  1. O aplicativo chama uma biblioteca do sistema, como libdns
  2. libdns verifica a configuração do sistema sobre a qual servidor DNS deve ser consultado (na diagrama é 169.254.169.254, servidor de metadados)
  3. libdns usa chamadas de sistema para criar um socket UDP (SOCKET_DGRAM) e transmitir pacotes UDP com a consulta DNS em ambas as direções
  4. Através da interface sysctl, é possível configurar a pilha UDP no nível do kernel
  5. O kernel interage com o hardware para enviar pacotes pela rede através da interface de rede
  6. O hipervisor captura e transmite o pacote para o servidor de metadados ao contatá-lo
  7. O servidor de metadados, com sua mágica, determina o nome DNS e retorna a resposta da mesma forma

História sobre pacotes DNS perdidos do suporte do Google Cloud
Lembro quais hipóteses já discutimos:

Hipótese: As bibliotecas estão corrompidas

  • Teste 1: executar strace no sistema, verificar se o dig está fazendo as chamadas de sistema corretas
  • Resultado: as chamadas de sistema estão corretas
  • Teste 2: usar srapy para verificar se conseguimos identificar nomes ignorando as bibliotecas do sistema
  • Resultado: conseguimos
  • Teste 3: executar rpm -V no pacote libdns e md5sum dos arquivos da biblioteca
  • Resultado: o código da biblioteca é idêntico ao código do sistema operacional em funcionamento
  • Teste 4: montar a imagem do sistema raiz do usuário em uma VM sem comportamento semelhante, executar chroot e verificar se o DNS funciona
  • Resultado: o DNS funciona corretamente

Conclusão com base nos testes: o problema não está nas bibliotecas

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

  • Teste 1: verificar tcpdump e observar se os pacotes DNS são enviados e recebidos corretamente após a execução do dig
  • Resultado: os pacotes estão sendo transmitidos corretamente
  • Teste 2: reverificar no servidor /etc/nsswitch.conf e /etc/resolv.conf
  • Resultado: tudo correto

Conclusão com base nos testes: o problema não está na configuração do DNS

Hipótese: o kernel está corrompido

  • Teste: instalar um novo kernel, verificar a assinatura, reiniciar
  • Resultado: comportamento análogo

Conclusão com base nos testes: o kernel não está corrompido

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

  • Teste 1: verificar as configurações do firewall
  • Resultado: o firewall permite pacotes DNS tanto no host quanto no GCP
  • Teste 2: capturar tráfego e rastrear a correção da transmissão e retorno das consultas DNS
  • Resultado: tcpdump confirma a recepção dos pacotes de retorno pelo host

Conclusão com base nos testes: o problema não está na rede

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

  • Teste 1: verificar os logs do servidor de metadados em busca de anomalias
  • Resultado: não há anomalias nos logs
  • Teste 2: contornar o servidor de metadados através de dig @8.8.8.8
  • Resultado: a resolução falha mesmo sem usar o servidor de metadados

Conclusão com base nos testes: o problema não está no servidor de metadados

Resultado: testamos todos os subsistemas, exceto configurações do ambiente de execução!

Aprofundando-se nas configurações do ambiente de execução do núcleo

Para configurar o ambiente de execução do núcleo, você pode usar as opções da linha de comando (grub) ou a interface sysctl. Eu dei uma olhada em /etc/sysctl.conf e, acredite, encontrei várias configurações personalizadas. Sentindo que estava prestes a descobrir algo, eu descartei todas as configurações que não eram de rede ou não-tcp, ficando apenas com um punhado de configurações net.core. Então, fui à pasta onde as VM armazenam as permissões do host e comecei a aplicar as configurações da VM quebrada uma a uma até encontrar o culpado:

net.core.rmem_default = 2147483647

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

A configuração do tamanho base do buffer dos pacotes DNS é feita através de net.core.rmem_default. O valor típico varia em torno de 200KiB, no entanto, se seu servidor receber muitos pacotes DNS, você pode aumentar o tamanho do buffer. Se, no momento da chegada de um novo pacote, o buffer estiver cheio, por exemplo, porque o aplicativo não o processa rapidamente o suficiente, você começará a perder pacotes. Nosso cliente aumentou corretamente o tamanho do buffer porque temia a perda de dados, uma vez que utilizava um aplicativo de coleta de métricas através de pacotes DNS. O valor que ele definiu foi o máximo possível: 231-1 (se definir 231, o núcleo retornará "ARGUMENTO INVÁLIDO").

De repente, percebi por que nmap e scapy funcionavam corretamente: eles usavam soquetes brutos! Soquetes brutos são diferentes dos convencionais: eles funcionam contornando o iptables e não têm buffer!

Mas por que um "buffer muito grande" causa problemas? Ele claramente não está funcionando como deveria.

Nesse ponto, eu poderia reproduzir o problema em vários núcleos e diversas distribuições. O problema já se manifestava no núcleo 3.x e agora também aparecia no núcleo 5.x.

De fato, ao executar

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

o DNS parava de funcionar.

Comecei a procurar valores funcionais através de um simples algoritmo de busca binária e descobri que o sistema funcionava com 2147481343, no entanto, esse número não fazia sentido para mim. Sugeri ao cliente tentar esse número, e ele respondeu que o sistema funcionou com google.com, mas ainda apresentava erro com outros domínios, então continuei minha investigação.

Instalei dropwatch, uma ferramenta que eu deveria ter usado antes: ela mostra exatamente onde o pacote atinge o núcleo. A função responsável foi identificada como udp_queue_rcv_skb. Baixei o código-fonte do núcleo e adicionei algumas funções printk para monitorar exatamente onde o pacote estava indo. Rapidamente identifiquei a condição necessária if, e por um tempo apenas fiquei olhando para ela, pois foi então que tudo finalmente se encaixou em uma imagem coesa: 231-1, um número sem sentido, um domínio não funcional… A questão estava em um pedaço de código em __udp_enqueue_schedule_skb:

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

Observe:

  • rmem é do tipo int
  • tamanho é do tipo u16 (int sem sinal 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 iguala-se ao valor em net.core.rmem_default

Quando sk_rcvbuf aproximando-se de 231, a soma do tamanho do pacote pode levar a um estouro inteiro. E como é um int, seu valor torna-se negativo, assim a condição torna-se verdadeira quando deveria ser falsa (mais sobre isso pode ser encontrado em link).

O erro é corrigido de forma trivial: convertendo para unsigned int. Apliquei a correção e reiniciei o sistema, após o que o DNS voltou a funcionar.

O gosto da vitória

Enviei minhas descobertas ao cliente e enviei o patch da LKML do núcleo. Estou satisfeito: cada pedaço do quebra-cabeça se uniu, posso explicar exatamente por que observamos o que observamos e, mais importante, conseguimos encontrar a solução para o problema através da colaboração!

Devo reconhecer que o caso foi raro e, felizmente, recebemos raramente consultas tão complexas dos usuários.

História sobre pacotes DNS perdidos do suporte do Google Cloud


Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster