Depurando uma rede usando eBPF (RHEL 8 Beta)

Tudo com os feriados passados!

Decidimos dedicar nosso primeiro artigo após as férias ao Linux, ou seja, ao nosso maravilhoso curso de Administrador Linux , que temos no grupo dos cursos mais dinâmicos, ou seja, com os materiais e práticas mais relevantes. Bem, e, consequentemente, oferecemos artigos interessantes e uma lição aberta .

Postado por Matteo Croce
Título original: Depuração de rede com eBPF (RHEL 8 Beta)

1. Introdução

O trabalho em rede é uma experiência emocionante, mas nem sempre os problemas são evitados. A solução de problemas pode ser complicada, pois está tentando reproduzir o comportamento errado que acontece “no campo”.

Felizmente, existem ferramentas que podem ajudar com isso: namespaces de rede, máquinas virtuais, tc e netfilter . Configurações simples de rede podem ser reproduzidas usando namespaces de rede e dispositivos veth, enquanto configurações mais complexas exigem a conexão de máquinas virtuais a uma ponte de software e o uso de ferramentas de rede padrão, como iptables ou tc , para simular comportamento incorreto. Se houver um problema com as respostas ICMP geradas quando o servidor SSH iptables -A INPUT -p tcp --dport 22 -j REJECT --reject-with icmp-host-unreachable , o iptables -A INPUT -p tcp --dport 22 -j REJECT --reject-with icmp-host-unreachable no namespace correto pode ajudar a resolver o problema.

Este artigo descreve como solucionar problemas complexos de rede com o eBPF (BPF estendido) , uma versão avançada do Berkeley Packet Filter. O eBPF é uma tecnologia relativamente nova, o projeto está em um estágio inicial, portanto a documentação e o SDK ainda não estão prontos. Mas esperamos melhorias, especialmente porque o XDP (eXpress Data Path) é fornecido com o Red Hat Enterprise Linux 8 Beta , que você pode baixar e executar agora.

O eBPF não resolverá todos os problemas, mas ainda é uma ferramenta poderosa de depuração de rede que merece atenção. Estou certo de que desempenhará um papel muito importante no futuro das redes.



O problema

Eu estava depurando um problema de rede Open vSwitch (OVS) que envolvia uma instalação muito complicada: alguns pacotes TCP foram espalhados e entregues na ordem errada, e a largura de banda das máquinas virtuais caiu de 6 Gb / s estáveis ​​para 2-4 Gb / s flutuantes. A análise mostrou que o primeiro pacote TCP de cada conexão com o sinalizador PSH foi enviado na ordem errada: apenas o primeiro e o único por conexão.

Tentei reproduzir essa configuração com duas máquinas virtuais e, depois de muitos artigos de ajuda e consultas de pesquisa, descobri que nem o iptables nem o nftables podem manipular sinalizadores TCP, enquanto tc pode, mas apenas substituindo os sinalizadores e interrompendo novas conexões e TCP em geral

Pode ser possível resolver o problema com uma combinação de iptables , conntrack e tc , mas decidi que esse é um ótimo trabalho para o eBPF.

O que é o eBPF?

eBPF é uma versão aprimorada do Berkeley Packet Filter. Ela traz muitas melhorias para o BPF. Em particular, ele permite que você escreva na memória, e não apenas leia, para que os pacotes possam não apenas ser filtrados, mas também editados.

Freqüentemente, o eBPF é simplesmente chamado BPF, e o próprio BPF é chamado cBPF (clássico (clássico) BPF), portanto a palavra "BPF" pode ser usada para significar as duas versões, dependendo do contexto: neste artigo eu sempre falo sobre a versão estendida.

“Under the hood” O eBPF possui uma máquina virtual muito simples que pode executar pequenos fragmentos de código de bytes e editar alguns buffers de memória. Existem limitações no eBPF que o protegem contra uso malicioso:

  • Os ciclos são proibidos para que o programa sempre termine em um horário específico;
  • Ele só pode acessar a memória através da pilha e do buffer temporário;
  • Somente funções permitidas do kernel podem ser chamadas.

Um programa pode ser carregado no kernel de várias maneiras, usando depuração e rastreamento . No nosso caso, o eBPF está interessado em trabalhar com subsistemas de rede. Existem duas maneiras de usar o programa eBPF:

  • Conectado via XDP ao início do caminho RX de uma placa de rede física ou virtual;
  • Conectado via tc ao qdisc na entrada ou na saída.

Para criar um programa eBPF para conexão, basta escrever o código C e convertê-lo em bytecode. A seguir, é apresentado um exemplo simples usando XDP:

 SEC("prog") int xdp_main(struct xdp_md *ctx) { void *data_end = (void *)(uintptr_t)ctx->data_end; void *data = (void *)(uintptr_t)ctx->data; struct ethhdr *eth = data; struct iphdr *iph = (struct iphdr *)(eth + 1); struct icmphdr *icmph = (struct icmphdr *)(iph + 1); /* sanity check needed by the eBPF verifier */ if (icmph + 1 > data_end) return XDP_PASS; /* matched a pong packet */ if (eth->h_proto != ntohs(ETH_P_IP) || iph->protocol != IPPROTO_ICMP || icmph->type != ICMP_ECHOREPLY) return XDP_PASS; if (iph->ttl) { /* save the old TTL to recalculate the checksum */ uint16_t *ttlproto = (uint16_t *)&iph->ttl; uint16_t old_ttlproto = *ttlproto; /* set the TTL to a pseudorandom number 1 < x < TTL */ iph->ttl = bpf_get_prandom_u32() % iph->ttl + 1; /* recalculate the checksum; otherwise, the IP stack will drop it */ csum_replace2(&iph->check, old_ttlproto, *ttlproto); } return XDP_PASS; } char _license[] SEC("license") = "GPL"; 

O snippet acima, sem include expressões, auxiliares e código opcional, é um programa XDP que altera o TTL das respostas de eco ICMP recebidas, ou seja, pongs, por um número aleatório. A função principal obtém a estrutura xdp_md , que contém dois ponteiros para o início e o fim do pacote.

Para compilar nosso código no bytecode do eBPF, é necessário um compilador com suporte apropriado. O Clang suporta e cria o bytecode do eBPF especificando bpf como o destino no momento da compilação:

 $ clang -O2 -target bpf -c xdp_manglepong.c -o xdp_manglepong.o 

O comando acima cria um arquivo que, à primeira vista, parece um arquivo de objeto normal, mas após uma inspeção mais detalhada, verifica-se que o tipo especificado de computador é Linux eBPF e não o tipo nativo de sistema operacional:

 $ readelf -h xdp_manglepong.o ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: REL (Relocatable file) Machine: Linux BPF <--- HERE [...] 

Após receber o invólucro de um arquivo de objeto comum, o programa eBPF está pronto para fazer o download e conectar-se ao dispositivo via XDP. Isso pode ser feito usando o ip do pacote iproute2 com a seguinte sintaxe:

 # ip -force link set dev wlan0 xdp object xdp_manglepong.o verbose 

Este comando especifica a interface wlan0 de destino e, graças à opção -force, substitui qualquer código eBPF existente que já tenha sido carregado. Após carregar o bytecode do eBPF, o sistema se comporta da seguinte maneira:

 $ ping -c10 192.168.85.1 PING 192.168.85.1 (192.168.85.1) 56(84) bytes of data. 64 bytes from 192.168.85.1: icmp_seq=1 ttl=41 time=0.929 ms 64 bytes from 192.168.85.1: icmp_seq=2 ttl=7 time=0.954 ms 64 bytes from 192.168.85.1: icmp_seq=3 ttl=17 time=0.944 ms 64 bytes from 192.168.85.1: icmp_seq=4 ttl=64 time=0.948 ms 64 bytes from 192.168.85.1: icmp_seq=5 ttl=9 time=0.803 ms 64 bytes from 192.168.85.1: icmp_seq=6 ttl=22 time=0.780 ms 64 bytes from 192.168.85.1: icmp_seq=7 ttl=32 time=0.847 ms 64 bytes from 192.168.85.1: icmp_seq=8 ttl=50 time=0.750 ms 64 bytes from 192.168.85.1: icmp_seq=9 ttl=24 time=0.744 ms 64 bytes from 192.168.85.1: icmp_seq=10 ttl=42 time=0.791 ms --- 192.168.85.1 ping statistics --- 10 packets transmitted, 10 received, 0% packet loss, time 125ms rtt min/avg/max/mdev = 0.744/0.849/0.954/0.082 ms 

Cada pacote passa pelo eBPF, o que, em última análise, faz algumas alterações e decide se deve ser descartado ou ignorado.

Como o eBPF pode ajudar

Voltando ao problema de rede original, lembramos que era necessário marcar vários sinalizadores TCP, um por conexão, e nem o iptables nem o tc poderiam fazer isso. Não é difícil escrever código para esse cenário: configure duas máquinas virtuais conectadas por uma ponte OVS e simplesmente conecte o eBPF a um dos dispositivos virtuais da VM.

Parece uma ótima solução, mas lembre-se de que o XDP suporta apenas o processamento de pacotes recebidos e a conexão do eBPF ao caminho rx da máquina virtual receptora não terá efeito no comutador.

Para resolver esse problema, o eBPF deve ser carregado usando tc e conectado ao caminho de saída da VM, porque tc pode carregar e conectar programas eBPF ao qdisk. Para marcar os pacotes que saem do host, o eBPF deve estar conectado ao qdisk de saída.

Ao carregar o programa eBPF, existem algumas diferenças entre a API XDP e a tc : por padrão, nomes de seções diferentes, o tipo de estrutura do argumento da função principal e diferentes valores de retorno. Mas isso não é um problema. Abaixo está um trecho de um programa que marca o TCP ao ingressar em uma ação tc:

 #define RATIO 10 SEC("action") int bpf_main(struct __sk_buff *skb) { void *data = (void *)(uintptr_t)skb->data; void *data_end = (void *)(uintptr_t)skb->data_end; struct ethhdr *eth = data; struct iphdr *iph = (struct iphdr *)(eth + 1); struct tcphdr *tcphdr = (struct tcphdr *)(iph + 1); /* sanity check needed by the eBPF verifier */ if ((void *)(tcphdr + 1) > data_end) return TC_ACT_OK; /* skip non-TCP packets */ if (eth->h_proto != __constant_htons(ETH_P_IP) || iph->protocol != IPPROTO_TCP) return TC_ACT_OK; /* incompatible flags, or PSH already set */ if (tcphdr->syn || tcphdr->fin || tcphdr->rst || tcphdr->psh) return TC_ACT_OK; if (bpf_get_prandom_u32() % RATIO == 0) tcphdr->psh = 1; return TC_ACT_OK; } char _license[] SEC("license") = "GPL"; 

A compilação no bytecode é feita conforme mostrado no exemplo XDP acima, usando o seguinte:

 clang -O2 -target bpf -c tcp_psh.c -o tcp_psh.o 

Mas o download é diferente:

 # tc qdisc add dev eth0 clsact # tc filter add dev eth0 egress matchall action bpf object-file tcp_psh.o 

Agora o eBPF está carregado no lugar certo e os pacotes que saem da VM estão marcados. Depois de verificar os pacotes recebidos na segunda VM, veremos o seguinte:



tcpdump confirma que o novo código eBPF está funcionando e aproximadamente 1 em cada 10 pacotes TCP possui o sinalizador PSH definido. Foram necessárias apenas 20 linhas de código C para marcar seletivamente os pacotes TCP que saem da máquina virtual, reproduzir o erro que ocorre "em batalha" e tudo sem recompilar ou mesmo reiniciar! Isso simplificou bastante a verificação da correção Open vSwitch , que era impossível de obter com outras ferramentas.

Conclusão

O eBPF é uma tecnologia relativamente nova e a comunidade tem uma opinião clara sobre sua implementação. Também é importante notar que os projetos baseados no eBPF, por exemplo, bpfilter , estão se tornando mais populares e, como resultado, muitos fornecedores de equipamentos estão começando a implementar o suporte ao eBPF diretamente nas placas de rede.

O eBPF não resolverá todos os problemas; portanto, não abuse, mas continua sendo uma ferramenta muito poderosa para depuração de rede e merece atenção. Estou certo de que desempenhará um papel importante no futuro das redes.

O FIM

Estamos aguardando seus comentários aqui e também convidamos você a visitar nossa lição aberta , onde você também pode fazer perguntas.

Source: https://habr.com/ru/post/pt436528/


All Articles