Dica: getch()

Vi uma excelente dica de um forista no Ubuntu Forum sobre a implementação da antiga função getch(), disponível em conio.h, no antigo DOS e disponível também no ncurses:

#include <stdio.h>
#include <termios.h>
#include <unistd.h>

int getch()
{
  struct termios newtios, oldtios;
  int ch;

  tcgetattr(STDIN_FILENO, &oldtios);

  /* Reusa o modo atual. */
  newtios = oldtios;

  /* Desabilita o modo canônico e o echo */
  newtios.c_lflag &= ~(ICANON | ECHO);
  tcsetattr(STDIN_FILENO, TCSANOW, &newtios);

  ch = getchar();

  /* Retorna o terminal ao normal. */
  tcsetattr(STDIN_FILENO, TCSANOW, &oldtios);

  return ch;
}

Assim vocẽ não precisa usar o ncurses!

Thanks ao forista edjin, pela dica!

Precisa de uma linha de comando? Use a GNU readline library!

Recentemente precisei criar uma aplicação, em modo texto, que emulava uma linha de comando para o usuário. Inicialmente implementei a rotina usando fgets, lendo o stream stdin. Funcionava que era uma beleza. Depois, quis implementar recursos com o history e autocompletion

Descobri a maravilhosa library GNU readline e GNU history. Dêem uma olhada no código:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <malloc.h>
#include <readline/readline.h>
#include <readline/history.h>

#define MAX_HISTORY_ENTRIES 32

/* Our history memory management structures. */
int numHistoryEntries = 0;
char *historyEntries[MAX_HISTORY_ENTRIES] = { NULL };

/* Prototypes. */
void HistoryCleanup(void);
void signalHandler(int);
void AddHistoryEntry(char *);

int main(void)
{
  struct sigaction sa;
  char *line, *p;

  /* Let us treat our own signals! */
  rl_catch_signals = 0;

  /* TIP: Disablea autocompletion binding TAB key */
  rl_bind_key('\t', rl_abort);

  /* History will keep the last 32 lines. */
  stifle_history(MAX_HISTORY_ENTRIES);

  /* Setup our signal handler.
     This method is preferable than use signal() */
  memset(&sa, 0, sizeof(sa));
  sa.sa_handler = signalHandler;
  sigaction(SIGINT, &sa, NULL);

  /* Read lines loop! */
  for (;;)
  {
    /* if a line is typed... */
    if ((line = readline("> ")) != NULL)
    {
      /* Get rid of the final '\n' char! */
      p = strchr(line, '\n');

      if (p != NULL)
        *p = '\0';

      /* QUIT or quit exits */
      if (strcasecmp(line, "quit") == 0)
        break;

      AddHistoryEntry(line);
      printf("Line: \"%s\".\n", line);
    }
  }

  /* Get rid of the last line! */
  free(line);

  /* Get rid of all lines on history. */
  HistoryCleanup();

  return 0;
}

/* Clean up all history entries */
void HistoryCleanup(void)
{
  int i;

  for (i = 0; i < numHistoryEntries; i++)
    free(historyEntries[i]);
}

/* Handles SIGINT */
void signalHandler(int signal)
{
  switch (signal)
  {
  case SIGINT:
    /* Put readline in a clean state after Ctrl+C. */
    rl_free_line_state();
    rl_cleanup_after_signal();

    /* HACK: Shows ^C and skip to next line! */
    puts("^C");
    break;
  }
}

/* This is necessary since readline allocate a buffer
   and stifled history didn't free older lines. */
void AddHistoryEntry(char *line)
{
  int index;

  index = numHistoryEntries + 1;

  /* Frees the first entry if
     buffer is full. */
  if (index == MAX_HISTORY_ENTRIES)
  {
    free(historyEntries[0]);

    /* And move the entire buffer
       1 position back! */
    memcpy(historyEntries,
           &historyEntries[1],
           sizeof(char *) * numHistoryEntries);
  }

  /* Store the line */
  historyEntries[numHistoryEntries] = line;

  if (index < MAX_HISTORY_ENTRIES)
    numHistoryEntries++;

  /* put the line in the readline history! */
  add_history(line);
}

A função readline aloca o buffer com a linha lida pra você. A única coisa que você tem que lembrar é de se livrar desse buffer usando a função free. O código acima usa a library GNU History para guardar as últimas 32 linhas digitadas e, para isso, tive que implementar uma rotina de memory management para não causar memory leaks. A cada linha adicionada no histórico mantenho o ponteiro do buffer numa lista (historyEntries) e me livro da primeira da lista sempre que a lista estiver cheia. Infelizmente GNU History não faz isso pra você… Tudo o que ela faz é “esquecer” os primeiros itens do histórico (cujo tamanho é indicado em stifle_history. O buffer continua alocado e é seu trabalho livrar-se dele.

Note que a rotina também implementa um tratador de sinais para lidar com o sinal SIGINT (o sinal gerado pelo Ctrl+C). Se você não informar à library GNU Readline que ela não deve tratar sinais, através da variável rl_catch_signals, vocẽ não será capaz de capturar SIGINT no seu próprio código (já que readline fará isso).

A função readline também tratará automaticamente o autocompletion, no mesmo estilo da linha de comando do bash. Para evitar isso é necessário remapear a tecla TAB para que essa não faça coisa alguma. Isso é feito com rl_bind_key. Vocẽ pode ignorar isso se implementar o seu próprio esquema de autocompletion (e existem funções para isso em GNU Readline).

Por default a função readline usará as definições contidas no arquivo de configuração /etc/inputrc, mas vocẽ pode escrever o seu e usar uma das funções de inicialização da library para usá-lo.

Para compilar o código acima é necessário instalar o pacote libreadline6-dev (instalar o meta-pacote libreadline.dev é suficiente). Para distribuir sua aplicação que use GNU Readline Library e GNU History Library é necessário colocar a dependência do pacote libreadline6 (não existe meta-pacote libreadline!). Assim, compilando o código acima:

$ sudo apt-get -y install libreadline6-dev 
$ gcc -o cmdline cmdline.c -lreadline

Dê uma olhada na documentação da GNU Readline Library aqui.

PS: Notaram que eu não usei a função signal para instalar o tratador para SIGINT? A descrição da manpage a respeito de signal (“man 2 signal“) nos avisa que devemos evitar o uso de signal e usar sigaction, porque o comportamento da primeira é dependente de arquitetura.

Fork me!

Um dos recursos próprios dos Unixes é a capacidade de criar um novo processo baseado na execução de um proceso pai. Ou seja, usar um garfo… A analogia é relacionada ao fluxo do processo. A partir do momento da execução da função fork(), o processo se divide em dois e o processo filho herda quase tudo do seu pai.

É uma maneira simples e eficiente de criar aplicações multiprocessadas (o Apache, por exemplo, por padrão, usa esse recurso). Mas é um tanto estranho para alguém que tenha vindo do mundo da Microsoft, onde o fork() não existe. O Windows é fortemente baseado em threads, não em processos. De fato, processos, no Windows, são recursos caros, pouco performáticos se usados como base para o multiprocessamento. Nos Unixes isso é padrão.

A função fork() retorna um valor inteiro correspondente à identidade do processo (pid = process identification). No processo filho fork() retorna 0, no processo pai fork() retorna o PID do filho. No processo pai fork() pode retornar -1, indicando que não foi possível forkar. Neste caso, o pai pode seguir seu curso e o filho sequer nascerá. Eis um exemplo simples de uso da função:

#include <stdio.h>
#include <unistd.h>

int main(void)
{
  pid_t pid;

  /* ... faz inicializações aqui.. */

  switch (pid = fork())
  {
  case -1:
    fprintf(stderr, "Parent: Fuck! it didn't forked!\n");
    return 1;
  case 0:
    fprintf(stderr, "Hello! I'm the child process!\n");
    break; 
  default:
    fprintf(stderr, "Hello! I'm the parent process\n"
                    "My child PID is %d.\n", pid);
    break;
  }

  /* Ambos o pai e o filho executarão deste ponto para frente. */

  return 0;
}

Reparem como é simples… Quase todos os recursos do processo pai são herdados pelo filho, incluindo arquivos abertos, memória alocada, etc… No entanto, uma vez que um novo processo foi criado, cada um segue o seu caminho (ou seja, os recursos não são compartilhados, eles são herdados). Existem, é claro, algumas restrições. Dêem uma olhada na manpage:

$ man fork

De maneira geral, fork é uma excelente ferramenta nos Unixes. Pena que não exista no mundo de Bill…

Fontes de informação II

Andei lendo o artigo sobre Padrões de projeto OO no kernel (agora liberado) e, confesso, me decepcionei um pouco.
O autor frisa bastante um padrão conhecido como vtable e que é, segundo eu entendi, base para o kernel e do artigo publicado.
Ora que bom, se todos soubessem o que é vtable ! Mas tudo bem, ele indica outro artigo dele mesmo no site onde fala sobre Padrões de projeto no kernel, onde deve explicar melhor o assunto.

Vamos por partes:
– Padrões de projeto
– Um padrão específico de projeto: vtable
– Arquitetura de Kernel
– Padrões orientados a objeto no projeto de kernel

Sinceramente, não tenho tempo para tanto.
Se é para ter aulas, então vamos direto à fonte: Axel-Tobias Schreiner.
Recomendo a literatura ali indicada e, em especial, este livro (já traduzido do alemão para inglês) que quita o assunto sobre vtables e OOC.
Ler alguns artigos do LWN, requer mais do que somente “gostar de linux e programação”.

Pegando frames de vídeos no Linux

Há algum tempo eu fiz uma pequena experiência, no Windows, usando a API vfw (video for windows) para ler um arquivo AVI, extrair os frames e usá-los como textura, numa aplicaçãozinha usando OpenGL… Nos últimos anos tenho buscado maneiras de fazer coisas que fazia no ambiente da Microsoft de maneira mais abrangente, isto é, desenvolver código que funcione nas três grandes plataformas: Windows, Linux e Mac OS/X. É suficiente dizer que, se você pretende fazer o mesmo, comece a desenvolver para ambiente Linux!

Acontece que o ambiente Mac OS/X também é derivado do Unix e no Windows temos ferramentas que possibilitam desenvolver, com poucas mudanças, quase da mesma maneira que nos outros dois ambientes. Esqueça o Visual Studio, use o MinGW32 (baixe aqui) ou o Cygwin (baixe aqui).

Para ler frames de um vídeo — qualquer um, não só AVIs — você pode usar o conjunto de bibliotecas libavformat, libavcodec, libavutil e libswscale, como mostrarei abaixo… O código exemplo que você verá aqui foi feito em 2011… Outros tutoriais que você pode achar usam rotinas obsoletas das mesmas libs… Algumas sequer existem mais… Portanto, se no futuro esse exemplo não puder mais ser compilado, procure a documentação das bibliotecas na Internet, ok?

O processo de extração de frames é o seguinte (em pseudo-código):

Inicializar as bibliotecas;
Criar o contexto do formato, abrindo o arquivo de vídeo;
Obter informações dos streams (opcional);
Procurar o stream de vídeo;
Obter o contexto do decodificador do stream selecionado;
ler um "pacote";
enquanto existirem "pacotes";
  se o pacote veio do stream escolhido; então
    decodifica o pacote para um "frame";
    converte o formato do frame para RGB;
    usa a imagem contida no frame; 
  fim-se
  ler o próximo pacote
fim-enquanto;

O código parecerá um pouco mais complicado do que isso, mas o pseudo-código mostra, em essência, o que deve ser feito. Ei-lo:

/* avcodec_example.c */
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>

#include <stdio.h>
#include <stdint.h>

/* Protótipo da função que grava um frame */
static void SaveFrame(AVFrame *pFrame, 
                      int width, int height, 
                      int iFrame);

int main (int argc, const char **argv)
{
  AVFormatContext *pFormatCtx;
  AVCodecContext  *pCodecCtx;
  AVCodec         *pCodec;
  AVFrame         *pFrame;
  AVFrame         *pFrameRGB;
  AVPacket        packet;

  int             i, videoStream;
  int             frameFinished;
  int             numBytes;
  uint8_t         *buffer;

  if (argc != 2)
  {
    fprintf(stderr, 
            "Usage: avcodec_example <video-filename>\n");
    return 1;
  }

  // Register all formats and codecs
  av_register_all();

  // Open video file
  if (av_open_input_file(
        &pFormatCtx, argv[1], NULL, 0, NULL) != 0)
  {
    fprintf(stderr, "Could not open file\n");
    return 1;
  }

  // Retrieve stream information
  if (av_find_stream_info(pFormatCtx) < 0)
  {
    fprintf(stderr, 
            "Cound not find stream information\n");
    return 1;
  }

  // Opcional. Só mostra infos do vídeo (estilo ffmpeg).
  dump_format(pFormatCtx, 0, argv[1], false);

  // Encontra o stream que queremos (o de vídeo)
  videoStream = -1;
  for (i = 0; i < pFormatCtx->nb_streams; i++)
    if (pFormatCtx->streams[i]->codec->codec_type == 
          CODEC_TYPE_VIDEO)
    {
      videoStream = i;
      break;
    }

  if (videoStream == -1)
  {
    fprintf(stderr, "Didn't find a video stream\n");
    return 1;
  }

  // Pega o contexto do codec para o stream selecinado.
  pCodecCtx = pFormatCtx->streams[videoStream]->codec;

  // Tenta encontrar o decoder para o stream.
  pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
  if (pCodec == NULL)
  {
    fprintf(stderr, "Codec not found\n");
    return 1;
  }

  // Abre o codec...
  if (avcodec_open(pCodecCtx, pCodec) < 0)
  {
    fprintf(stderr, "Cound not open codec\n");
    return 1;
  }

  // Aloca espaço para as estruturas AVFrame:
  pFrame = avcodec_alloc_frame();
  pFrameRGB = avcodec_alloc_frame();
  if ((pFrame == NULL) || (pFrameRGB == NULL))
  {
    fprintf(stderr, 
            "Could not allocate frame structures\n");
    return 1;
  }

  // pFrameRGB aponta para o frame que usaremos para obter
  // a conversão do frame original. Daí, precisamos alocar
  // espaço para o frame nós mesmos...
  numBytes = avpicture_get_size(
                PIX_FMT_RGB24, 
                pCodecCtx->width, 
                pCodecCtx->height);

  buffer = malloc(numBytes);

  // Assinala o buffer à pFrameRGB.
  avpicture_fill((AVPicture *)pFrameRGB, 
                 buffer, 
                 PIX_FMT_RGB24, 
                 pCodecCtx->width, 
                 pCodecCtx->height);

  // Loop de leitura de pacotes...
  i=0;
  while (av_read_frame(pFormatCtx, &packet) >= 0)
  {
    // O pacote lido é do stream que queremos?
    if (packet.stream_index == videoStream)
    {
      // Decodifica um frame.
      avcodec_decode_video2(
        pCodecCtx, 
        pFrame, 
        &frameFinished, 
        &packet);

      // Pegamos um frame?
      if (frameFinished)
      {
        static struct SwsContext *img_convert_ctx = NULL;

#if 0
        // Você pode achar este código em algum tutorial.
        // ELE NÂO FUNCIONA!
        img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24,
            (AVPicture*)pFrame, pCodecCtx->pix_fmt, pCodecCtx->width,
            pCodecCtx->height);
#endif

        // Converte o frame lido para RGB.
        if (img_convert_ctx == NULL)
        {
          img_convert_ctx = sws_getContext(
            pCodecCtx->width, pCodecCtx->height,
            pCodecCtx->pix_fmt,
            pCodecCtx->width, pCodecCtx->height,
            PIX_FMT_RGB24,
            SWS_BICUBIC,
            NULL, NULL, NULL);

          if (img_convert_ctx == NULL)
          {
            fprintf(stderr, 
              "Cannot initialize the conversion context!\n");
            return 1;
          }
        }

        sws_scale(img_convert_ctx,
                  (const uint8_t * const *)pFrame->data,
                  pFrame->linesize,
                  0, pCodecCtx->height,
                  pFrameRGB->data,
                  pFrameRGB->linesize);

        // Grava o frmae e incrementa contador de frames...
        SaveFrame(
          pFrameRGB, 
          pCodecCtx->width, 
          pCodecCtx->height, 
          i++);
      }
    }

    // Joga o pacote fora, não precisamos mais dele.
    av_free_packet(&packet);
  }

  // Joga fora as estruturas e buffer que não precisamos mais.
  free(buffer);
  av_free(pFrameRGB);
  av_free(pFrame);

  // Livra-se dos contextos do codec e formato,
  // fechando o arquivo de vídeo.
  avcodec_close(pCodecCtx);
  av_close_input_file(pFormatCtx);

  return 0;
}

// Essa é a mesma função que mostrei no post anterior, mas com
// parâmetros diferentes! 
void SaveFrame(AVFrame *pFrame, 
               int width, int height, 
               int iFrame)
{
  FILE *pFile;
  char szFilename[32];
  int  y;

  // Formata nome do arquivo de imagem e cria o arquivo.
  snprintf(szFilename, 32, "frame%d.ppm", iFrame);
  pFile = fopen(szFilename, "wb");
  if (pFile == NULL)
    return;

  // Escreve header
  fprintf(pFile, "P6\n%d %d\n255\n", width, height);

  // Escreve dados.
  for (y = 0; y < height; y++)
    fwrite(pFrame->data[0]+y * pFrame->linesize[0], 
           1, width * 3, pFile);

  fclose(pFile);
}

É claro que neste exemplo deixei de fora a temporização dos frames (todo frame precisa ficar “exposto” por uma quantidade de tempo, para dar a ilusão de continuidade!) e também não mostrei como integrar isso ai com OpenGL. Também não mostrei como extrair áudio (porque é mais complicado e porque apenas o stream de vídeo me interessa agora).

Para compilar o cíodigo acima, use o seguinte makefile:

CFLAGS=-march=native -O3 
LDFLAGS=-lavformat -lavcodec -lavutil -lswscale -lz

avcodec_example: avcodec_example.o

avcodec_example.o: avcodec_example.c

É isso…

A porcaria do Windows não faz isso?! Não diga! (Parte 3)

Meu amigo, e co-autor deste blog, MaRZ, me pediu para pegar leve com os insultos aos maníacos pelo Windows. Só que eu não resisto. Eis mais uma coisa que a Microsoft não colocou nele!

Suponha que vocẽ tenha feito um webservice bem simples. Seu site devolve uma string de acordo com um conjunto de critérios que vocẽ passe para ele. Só que vocẽ quer criar um script em bash para ler esse resultado sem usar nenhum programinha bobo. Ou seja, só pode usar as funções built-in do bash.

Um exemplo, do excelente post do blog de outro amigo meu, o @Kl0nEz, que pode ser lido aqui, mostra como. Antes de cair de boca no código, é necessária uma explicação:

Existem 3 handlers para dispositivos do tipo character que são padrão em qualquer distribuição unix ou derivadas. Esses handlers também estão disponíveis diretamente em qualquer programa escrito em C/C++, são eles: stdin, stdout e stderr. Esses handlers são padronizados em 0 (para stdin), 1 (para stdout) e 2 (para stderr).

A entrada padrão (stdin) é o teclado e a saída padrão (stdout) é o vídeo, assim como a saída de erros padrão (stderr). No exemplo abaixo criaremos um hadle com valor 3 justamente para não sobrepor os 3 primeiros – que são definidos pelo sistema. O que acontece quando você tenta sobrepor um dos hadles do sistema? Well… faça uma experiência!!! ;)

Agora, vamos ao nosso exemplo… Vamos aqui obter o HTML da página do Google (http://www.google.com/) via bash:

$ exec 3<>/dev/tcp/www.google.com/80
$ echo -e "GET / HTTP/1.1\n\n" >&3
$ cat <&3
HTTP/1.1 302 Found
Location: http://www.google.com.br/
Cache-Control: private
Content-Type: text/html; charset=UTF-8
Set-Cookie: ********
Set-Cookie: ********
Date: Fri, 20 May 2011 13:58:07 GMT
Server: gws
Content-Length: 222
X-XSS-Protection: 1; mode=block

<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>302 Moved</TITLE></HEAD><BODY>
<H1>302 Moved</H1>
The document has moved
<A HREF="http://www.google.com.br/">here</A>.
</BODY></HTML>
$ exec <&3-

Lembre-se que os 3 comandos dados (exec, echo e cat) são built-in no bash! A primeira linha cria o handle 3, bidirecional, e o associa ao dispositivo de caracter /dev/tcp/url/porta. A segunda linha envia a requisição para o handle, de acordo com a RFC 2616. Tudo o que temos que fazer, na terceira linha, é ler o handle 3 para obter a resposta do site.

No final de tudo temos que fechar o handle 3. É isso o que a última linha faz.

É claro que esse exemplo ai em cima mostra a instrução de redirecionamento do google (error code 302). Acredito que @Kl0nEz tenha escolhido justamente esta URL para manter o HTML pequeno e ser usado como exemplo no post dele. Fiz o mesmo aqui.

Com esse recurso podemos obter a resposta de um site, colocá-lo numa variável e retirar aquilo que precisamos.

Agora responda: Você consegue fazer isso num arquivo .BAT, na porcaria do Windows?

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!).