libpcap: Capturando pacotes on-wire

Há alguns posts atrás falei de RAW sockets, mostrando como construir pacotes ethernet — muito superficialmente! Isso faz parte de alguns testes e projetos nos quais estou envolvido…

Para ajudar um amigo precisei analisar um código-fonte que fazia isso a nível de TCP/IP. Esse meu amigo precisava de algo parecido, mas para pacotes Ethernet… A pesquisa inicial foi feita toda por ele, mas acabei me interessando pelo assunto.

Envia pacotes on-wire é bastante simples, como vocẽ pode ver aqui. Basta criar o socket RAW, indicando o procolo Ethernet 802.3 e  dai você escreve (sendto) o buffer com o pacote montado. Mas, e se quisermos ler pacotes on-wire?

A especificação dos sockets nos diz que devemos criar um socket, ligá-lo à interface desejada (via bind()), ouví-lo (usando listen()) e aceitar novas conexões (usando accept()). Só que isso me parece meio estranho, já que eu não saberia preencher a estrutura sockaddr, que é um dos parâmetros de bind(). Ainda, já que não há uma conexão para ser aceita, como é que fica a chamada a accept()? Tem que haver uma solução mais simples… E há!

No linux temos um utilitariozinho que serve para capturar pacotes e mostrá-los, chamado tcpdump. Por exemplo:

# tcpdump -i eth0 -v -e -c 3 stp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
14:21:02.482698 00:00:00:00:00:01 (oui Unknown) > 00:00:00:00:00:02 (oui Unknown), 802.3, length 60: ...
14:21:04.483287 00:00:00:00:00:01 (oui Unknown) > 00:00:00:00:00:02 (oui Unknown), 802.3, length 60: ...
14:21:06.483723 00:00:00:00:00:01 (oui Unknown) > 00:00:00:00:00:02 (oui Unknown), 802.3, length 60: ...
3 packets captured
3 packets received by filter
0 packets dropped by kernel

Aqui estamos lendo 3 pacotes do protocolo Spanning Tree da interface eth0. Por questões de segurança modifiquei os MAC Addresses para 00:00:00:00:00:01 e 00:00:00:00:00:02 (que são totalmente inválidos).

Estou mostrando o tcpdump porque ele usa a biblioteca libpcap. E podemos usá-la em nossos códigos, se precisarmos criar um sniffer, por exemplo. Ela é bem simples:

  • Abra uma conexão via libpcap usando a função pcap_open_live(). Essa função toma 5 parâmetros: A string do dispositivo, o tamanho do buffer de recebimento de pacotes, um flag dizendo se deve colocar a interface em modo promíscuo (é uma espécie de acesso de root à interface) , segue-se o timeout (em milissegundos) e depois um ponteiro para um array de chars que receberá a mensagem de erro (se houver alguma);
  • Em seguida, registre o filtro desejado (no nosso caso, a string “stp”) usando a função pcap_compile(). Ela toma 5 parâmetros também: o handle devolvido pela chamada a pcap_open_live(), o ponteiro para a estrutura que receberá o filtro, a expressão do filtro (é uma string), um flag dizendo para a função se deve ou não otimizar o filtro e, por último, a máscara de rede a qual o filtro é aplicado (esse último parâmetro pode ser obtido chamando pcap_lookupnet(), antes de chamar pcap_open_live() – ela pega algumas características da interface de rede).
  • Depois de “compilar” o filtro, devemos aplicá-lo. A função pcap_setfilter() – que toma apenas 2 parâmetros: o handle citado acima e o ponteiro para a estrutura do filtro – faz isso;

Se você chamar pcap_next(), obterá o próximo pacote, de acordo com o filtro. Ela toma apenas o handle e o ponteiro para um header, mas pode devolver o ponteiro do buffer preenchido. Essa função tem apenas um problema: Ela pode não devolver nada quando for chamada!

Para evitar isso existe uma outra função que usa um callback para manipular os pacotes lidos. É pcap_loop():

int pcap_loop(pcap_t *handle, int cnt, pcap_handler callback, u_char *user);

Onde o primeiro parâmetro é o ponteiro para o handle que citei antes, cnt é a quantidade de pacotes (a mesma coisa que a opção -c de tcpdump) e o último parâmetro é o ponteiro para dados do usuário que serão repassados para o callback. O tipo pcap_handler é definido como:

typedef void (*pcap_handler)(u_char *args,
                             struct pcap_pkthdr *header,
                             const u_char *packet);

Ou seja, o callback receberá o conteúdo de user em args, um ponteiro para o header do pacote e outro ponteiro contendo o pacote em si. Sempre que o callback for chamado um pacote estará disponível.

O passo final é chamar pcap_close(), passando o handle para encerrar o uso da libpcap de forma graciosa.

Deixem-me lembrá-los de algumas coisas:

  1. libpcap só funciona sob o usuário root (ou se você for um sudoer);
  2. É interessante que você aprenda a mexer com o tcpdump (a documentação está disponível nas manpages);
  3. Depois de entender o tcpdump, parta para o libpcap… (sugiro este tutorial).

Assim, para ler os pacotes que estão fluindo pela rede você não precisa lidar com RAW sockets. Deixe que o libpcap faça isso proce.

Uma última informação: Existe o WinPCAP – uma versão para Windows – que é um pouco diferente da libpcap, mas muito parecida (é claro, já que é derivada de libpcap!).

Anúncios

Deixe um comentário

Faça o login usando um destes métodos para comentar:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s