AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / unix / Perguntas / 699500
Accepted
Fajela Tajkiya
Fajela Tajkiya
Asked: 2022-04-19 14:07:25 +0800 CST2022-04-19 14:07:25 +0800 CST 2022-04-19 14:07:25 +0800 CST

Entendendo o BPF

  • 772

Quando preciso capturar alguns pacotes usando tcpdump, uso comandos como:

tcpdump -i eth0 "dst host 192.168.1.0"

Eu sempre acho que a parte do dst host 192.168.1.0 é algo chamado BPF, Berkeley Packet Filter. Para mim, é uma linguagem simples para filtrar pacotes de rede. Mas hoje meu colega de quarto me diz que o BPF pode ser usado para capturar informações de desempenho. De acordo com sua descrição, é como a ferramenta perfmonno Windows. É verdade? É o mesmo BPF que mencionei no início da pergunta?

tcpdump performance
  • 2 2 respostas
  • 946 Views

2 respostas

  • Voted
  1. Best Answer
    forest
    2022-04-19T14:58:03+08:002022-04-19T14:58:03+08:00

    O que é BPF?

    BPF (ou mais comumente, a versão estendida, eBPF ) é uma linguagem que foi originalmente usada exclusivamente para filtrar pacotes, mas é capaz de muito mais. No Linux, ele pode ser usado para muitas outras coisas, incluindo filtros de chamadas do sistema para segurança e monitoramento de desempenho, como você apontou. Embora o Windows tenha adicionado suporte eBPF , não é isso que o utilitário do Windows perfmonusa. O Windows adicionou suporte apenas para compatibilidade com utilitários não Windows que dependem do suporte do SO para eBPF.

    Os programas eBPF não são executados no espaço do usuário. Em vez disso, o aplicativo cria e envia um programa eBPF para o kernel, que o executa. Na verdade, é um código de máquina para um processador virtual implementado na forma de um interpretador no kernel, embora também possa usar a compilação JIT para melhorar consideravelmente o desempenho. O programa tem acesso a algumas interfaces básicas no kernel, incluindo aquelas relacionadas a desempenho e rede. O programa eBPF então se comunica com o kernel para fornecer os resultados computacionais (como descartar um pacote).

    Restrições aos programas eBPF

    Para proteger contra ataques de negação de serviço ou travamentos acidentais, o kernel primeiro verifica o código antes de ser compilado. Antes de ser executado, o código passa por várias verificações importantes:

    • O programa consiste em não mais de 4096 instruções no total para usuários sem privilégios.

    • Saltos para trás não podem ocorrer, com exceção de loops limitados e chamadas de função.

    • Não há instruções que são sempre inalcançáveis.

    O resultado é que o verificador deve ser capaz de provar que o programa eBPF é interrompido. Ele não encontrou uma solução para o problema da parada , é claro, e é por isso que só aceita programas que sabe que vão parar. Para isso, representa o programa como um grafo acíclico direcionado . Além disso, ele tenta evitar vazamentos de informações e acesso à memória fora dos limites, impedindo que o valor real de um ponteiro seja revelado enquanto ainda permite que operações limitadas sejam executadas nele:

    • Ponteiros não podem ser comparados, armazenados ou retornados como um valor que pode ser examinado.

    • A aritmética de ponteiro só pode ser feita em um escalar (um valor não derivado de um ponteiro).

    • Nenhuma aritmética de ponteiro pode resultar em apontar para fora do mapa de memória designado.

    O verificador é bastante complexo e faz muito mais, embora ele mesmo tenha sido a fonte de sérios bugs de segurança , pelo menos quando o syscall não está desabilitado para usuários sem privilégios .bpf(2)

    Visualizando o código

    O dst host 192.168.1.0componente do comando não é BPF. Essa é apenas a sintaxe usada pelo tcpdump. No entanto, o comando que você dá é usado para gerar um programa BPF que é então enviado ao kernel. Observe que não é o eBPF que é usado neste caso, mas o cBPF mais antigo. Existem várias diferenças importantes entre os dois (embora o kernel converta internamente cBPF em eBPF). O -dsinalizador pode ser usado para ver o código cBPF que deve ser enviado ao kernel:

    # tcpdump -i eth0 "dst host 192.168.1.0" -d
    (000) ldh      [12]
    (001) jeq      #0x800           jt 2    jf 4
    (002) ld       [30]
    (003) jeq      #0xc0a80100      jt 8    jf 9
    (004) jeq      #0x806           jt 6    jf 5
    (005) jeq      #0x8035          jt 6    jf 9
    (006) ld       [38]
    (007) jeq      #0xc0a80100      jt 8    jf 9
    (008) ret      #262144
    (009) ret      #0
    

    Filtros mais complicados resultam em bytecodes mais complicados. Experimente alguns dos exemplos na página de manual e anexe o -dsinalizador para ver qual bytecode seria carregado no kernel. Para entender como ler a desmontagem, consulte a documentação do filtro BPF . Se estiver lendo um programa eBPF, você deve dar uma olhada no conjunto de instruções eBPF para a CPU virtual.

    Entendendo o código

    Para simplificar, vou supor que você especificou um IP de destino de 192.168.1.1 em vez de 192.168.1.0 e queria corresponder apenas ao IPv4, o que reduz um pouco o código, pois não precisa mais lidar com o IPv6:

    # tcpdump -i eth0 "dst host 192.168.1.1 and ip" -d
    (000) ldh      [12]
    (001) jeq      #0x800           jt 2    jf 5
    (002) ld       [30]
    (003) jeq      #0xc0a80101      jt 4    jf 5
    (004) ret      #262144
    (005) ret      #0
    

    Vamos ver o que o bytecode acima realmente faz . Cada vez que um pacote é recebido na interface especificada, o bytecode BPF é executado. O conteúdo do pacote (incluindo o cabeçalho Ethernet, se aplicável) é colocado em um buffer ao qual o código BPF tem acesso. Se o pacote corresponder ao filtro, o código retornará o tamanho do buffer de captura (262144 bytes por padrão), caso contrário, retornará 0.

    Vamos supor que você esteja executando este filtro e ele receba um pacote enviando uma mensagem ICMP com uma carga vazia de 192.168.1.142 a 192.168.1.1. O MAC de origem é aa:aa:aa:aa:aa:aa e o MAC de destino é bb:bb:bb:bb:bb:bb. O conteúdo do quadro Ethernet, em hexadecimal, é:

    aa aa aa aa aa aa bb bb bb bb bb bb 08 00 45 00
    00 1c 77 71 40 00 40 01 3f 92 c0 a8 01 8e c0 a8
    01 01 08 00 c1 c0 36 0e 00 01
    

    A primeira instrução é ldh [12]. Isso carrega uma meia palavra (dois bytes) localizada em um deslocamento de 12 bytes no pacote no registrador A. Este é o valor 0x0800 (lembre-se que os dados da rede são sempre big-endian). A segunda instrução é jeq #0x800, que irá comparar um imediato com o valor no registrador A. Se forem iguais, saltará para a instrução 2, caso contrário, 5. O valor 0x800 nesse deslocamento no quadro Ethernet especifica o protocolo IPv4. Como a comparação é avaliada como verdadeira, o código agora pula para a instrução 2. Se a carga útil não fosse IPv4, ela teria saltado para 5.

    A instrução 2 (a terceira) é ld [30]. Isso carrega uma palavra inteira de 4 bytes com um deslocamento de 30 no registrador A. Em nosso quadro Ethernet, este é 0xc0a80101. A próxima instrução, jeq #0xc0a80101, comparará um imediato com o conteúdo do registrador A e saltará para 4 se verdadeiro, caso contrário 5. Este valor é o endereço de destino (0xc0a80101 é a representação big-endian de 192.168.1.1). Os valores realmente correspondem, então o contador do programa agora está definido como 4.

    A instrução 4 é ret #262144. Isso encerra o programa BPF e retorna o inteiro 262144 para o programa de chamada. Isso informa ao programa chamador, tcpdumpneste caso, que o pacote foi capturado pelo filtro, então ele solicita o conteúdo do pacote do kernel, o decodifica mais detalhadamente e grava as informações em seu terminal. Se o endereço de destino não correspondesse ao que o filtro estava procurando ou o tipo de protocolo não fosse IPv4, o código teria saltado para a instrução 5, onde seria encontrado ret #0. Isso teria encerrado o filtro sem uma correspondência.

    Isso tudo é apenas uma maneira de retornar 262144 se a meia palavra no deslocamento 12 no pacote for 0x800 E a palavra no deslocamento 30 for 0xc0a80101 e retornar 0 caso contrário. Como tudo isso é feito no kernel (opcionalmente após ser convertido em código de máquina nativo pelo mecanismo JIT), não são necessárias trocas de contexto caras ou buffers de passagem entre o kernelspace e o userspace, portanto, o filtro é rápido .

    Exemplos mais avançados

    O código BPF não se limita a ser usado por tcpdump. Vários outros utilitários podem usá-lo. Você pode até criar uma regra iptables com um filtro BPF usando o xt_bpfmódulo! No entanto, você deve ter cuidado ao gerar o bytecode com tcpdump -dddporque espera consumir um cabeçalho de camada 2, enquanto o iptables não. Tudo o que você precisa fazer para torná-los compatíveis é ajustar os deslocamentos.

    Além disso, são fornecidas várias funções auxiliares que fornecem informações que não podem ser obtidas pela leitura do conteúdo bruto do pacote, como o comprimento do pacote, o deslocamento inicial da carga útil, a CPU na qual o pacote foi recebido, a marca NetFilter, etc. a documentação do filtro:

    O kernel do Linux também possui algumas extensões BPF que são usadas junto com a classe de instruções de carregamento “sobrecarregando” o argumento k com um deslocamento negativo + um deslocamento de extensão específico. O resultado de tais extensões BPF são carregados em A.

    As extensões BPF suportadas são:

    Extensão Descrição
    len skb->len
    proto skb->protocolo
    modelo skb->pkt_type
    capota Compensação de início de carga útil
    ifidx skb->dev->ifindex
    nla Atributo Netlink do tipo X com deslocamento A
    nlan Atributo Netlink aninhado do tipo X com deslocamento A
    marca skb->marcar
    fila skb->queue_mapping
    hatipo skb->dev->tipo
    rxhash skb->hash
    CPU raw_smp_processor_id()
    vlan_tci skb_vlan_tag_get(skb)
    vlan_avail skb_vlan_tag_present(skb)
    vlan_tpid skb->vlan_proto
    rand prandom_u32()

    Por exemplo, para corresponder a todos os pacotes recebidos na CPU 3, você pode fazer:

        ld #cpu
        jneq #3, drop
        ret #262144
    drop:
        ret #0
    

    Observe que isso está usando a sintaxe do assembly BPF compatível com bpf_asm, enquanto as outras listagens de assembly aqui estão usando a tcpdumpsintaxe. A principal diferença é que a sintaxe do primeiro usa rótulos nomeados, enquanto a sintaxe BPF do último rotula cada instrução com um número de linha. Este assembly se traduz no seguinte bytecode (instruções de delimitação de vírgulas):

    4,32 0 0 4294963236,21 0 1 1,6 0 0 262144,6 0 0 0,
    

    Isso pode ser usado com iptableso uso do xt_bpfmódulo:

    iptables -A INPUT -m bpf --bytecode "4,32 0 0 4294963236,21 0 1 1,6 0 0 262144,6 0 0 0," -j CPU3
    

    Isso pulará para a cadeia de destino CPU3para qualquer pacote recebido nessa CPU.

    Se isso parece poderoso, lembre-se de que tudo isso é cBPF. Embora o cBPF seja traduzido em eBPF internamente, tudo isso não é nada comparado ao que o eBPF bruto pode fazer!

    Para maiores informações

    Eu recomendo que você leia este artigo para entender como tcpdumpusa o cBPF.

    Depois de ler isso, leia esta explicação de como tcpdumptransforma expressões em bytecode.

    Se você quiser aprender tudo sobre isso, você sempre pode conferir o código-fonte !

    • 17
  2. Qeole
    2022-04-26T00:54:38+08:002022-04-26T00:54:38+08:00

    Os programas eBPF não são executados no espaço do usuário. Em vez disso, o aplicativo cria e envia um programa eBPF para o kernel, que o executa.

    Para complementar a boa resposta do @forest, talvez possamos elaborar um pouco como esses programas são executados.

    cBPF, como usado pelo tcpdump, tem poucos ganchos: ele pode ser anexado a sockets , para ser executado quando um pacote chega (é isso que o tcpdump faz, para filtrar os pacotes recebidos no socket, e passar apenas os desejados para o espaço do usuário ), ou eles podem ser anexados aos ganchos seccomp , para fazer alguma filtragem nas chamadas do sistema e seus argumentos.

    Uma das características importantes do eBPF é que ele pode ser anexado a uma seleção mais ampla de ganchos no kernel (embora não faça seccomp). Para redes, existem sockets , mas também ganchos TC (controle de tráfego), XDP (ganchos de nível de driver para redes rápidas) ou alguns outros. Com relação à sua pergunta: os programas também podem ser anexados a tracepoints no kernel (ganchos pré-definidos em algumas funções específicas, por exemplo, syscalls ou funções “importantes” no kernel), ou em testes do kernel ( kprobes ), tornando-os capazes de rastrear qualquer função no kernel (desde que não tenha sido embutida no momento da compilação). Em seguida, outros tiposexistem, por exemplo, LSM para casos de uso de segurança.

    O rastreamento geralmente depende de pontos de rastreamento ou kprobes para anexar um programa eBPF a uma função e executá-lo toda vez que essa função é chamada no kernel. O programa pode acessar os argumentos da função ou (se estiver anexado na saída) ao valor de retorno. Através do uso de mapas , áreas especiais de memória do kernel, como arrays ou mapas de hash, dedicados ao compartilhamento de dados entre programas eBPF e/ou espaço do usuário, os programas podem coletar métricas ou compartilhar estados entre execuções consecutivas.

    Por exemplo, opensnoop do BCC será anexado aos pontos de rastreamento na entrada e na saída das open()e openat()syscalls. Na entrada, ele coleta o caminho do arquivo que está sendo aberto e o PID do processo que o abre e o armazena em um mapa de hash. Quando a syscall é encerrada, o segundo probe coleta o valor de retorno e, com base no PID, atualiza a entrada relevante no mapa de hash. Em seguida, o espaço do usuário pode coletar e despejar todas as entradas do mapa de hash para mostrar quais arquivos foram abertos por quais processos e quais foram os valores de retorno.

    https://ebpf.io/ é um bom lugar para começar a usar o eBPF.

    • 3

relate perguntas

  • Por que `sync + drop_caches` não está descartando caches?

  • Por que `strace` não mostra que este processo está esperando por algo?

  • Tempo de criação do processo, shell script e overhead de chamada do sistema

  • Usando tcpdump para registrar todas as atividades de rede passando por um servidor roteador

  • tcpdump --- captura pacotes para um arquivo não rotativo

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Possível firmware ausente /lib/firmware/i915/* para o módulo i915

    • 3 respostas
  • Marko Smith

    Falha ao buscar o repositório de backports jessie

    • 4 respostas
  • Marko Smith

    Como exportar uma chave privada GPG e uma chave pública para um arquivo

    • 4 respostas
  • Marko Smith

    Como podemos executar um comando armazenado em uma variável?

    • 5 respostas
  • Marko Smith

    Como configurar o systemd-resolved e o systemd-networkd para usar o servidor DNS local para resolver domínios locais e o servidor DNS remoto para domínios remotos?

    • 3 respostas
  • Marko Smith

    apt-get update error no Kali Linux após a atualização do dist [duplicado]

    • 2 respostas
  • Marko Smith

    Como ver as últimas linhas x do log de serviço systemctl

    • 5 respostas
  • Marko Smith

    Nano - pule para o final do arquivo

    • 8 respostas
  • Marko Smith

    erro grub: você precisa carregar o kernel primeiro

    • 4 respostas
  • Marko Smith

    Como baixar o pacote não instalá-lo com o comando apt-get?

    • 7 respostas
  • Martin Hope
    user12345 Falha ao buscar o repositório de backports jessie 2019-03-27 04:39:28 +0800 CST
  • Martin Hope
    Carl Por que a maioria dos exemplos do systemd contém WantedBy=multi-user.target? 2019-03-15 11:49:25 +0800 CST
  • Martin Hope
    rocky Como exportar uma chave privada GPG e uma chave pública para um arquivo 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Evan Carroll status systemctl mostra: "Estado: degradado" 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim Como podemos executar um comando armazenado em uma variável? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S Por que /dev/null é um arquivo? Por que sua função não é implementada como um programa simples? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 Como ver as últimas linhas x do log de serviço systemctl 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - pule para o final do arquivo 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla Por que verdadeiro e falso são tão grandes? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis Substitua a string em um arquivo de texto enorme (70 GB), uma linha 2017-12-30 06:58:33 +0800 CST

Hot tag

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve