RAW sockets, enviando pacotes ethernet para a rede

Talvez você já tenha feito alguma coisa que lide diretamente com sockets, especialmente lidando com TCP/IP ou UDP/IP. Mas e se você quiser “brincar” diretamente com pacotes Ethernet? Neste caso terá que lidar com raw sockets, ou seja, sem nenhum tratamento automático de pacotes!

Vou mostrar uma maneira bem simples. Também só vou mostrar o envio de pacotes (já que isso veio de um projeto muito interessante que me foi mostrado recentemente).

A primeira coisa a fazer é criar um socket:

#include <sys/socket.h>
#include <linux/if_ether.h>

socket_t skt;

...

  if ((skt = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)) < 0)
  {
    /* Tratamento de erro:
       Não conseguimos abrir o socket! */
  }

O uso da função htons é porque sockets são implementados usando BIG ENDIAN como padrão. Os processadores Intel armazenam valores multibyte (qualquer coisa maior que um byte) de trás para frente. Por exemplo. o número 0x1234 (em hexadecimal, claro) é armazenado na sequência 0x34 0x12. Isso se chama LITTLE ENDIAN. O mesmo número em BIG ENDING é armazenado na sequência “correta”: 0x12 0x34. Tenha sempre isso em mente quando for lidar com sockets.

O header if_ether.h contém a estrutura do header do pacote Ethernet, chamada ethhdr:

O pacote Ethernet

Os endereços MAC (os dois primeiros campos) têm 6 bytes cada um. O terceiro campo (type) diz o que é que os dados representam. Segue um stream de dados (até 1500 bytes) e o pacote termina com o FCS (Frame CheckSum), que é o CRC32 de todo o pacote (frame), exceto, é claro, o próprio FCS.

Para facilitar o nosso exemplo, criaremos um array de tamanho suficiente para um pacote completo:

uint8_t buffer[ETH_FRANE_LEN + ETH_FCS_LEN];

Uma maneira de preencher esse buffer sem ter que lidar com o header byte-a-byte é usando a estrutura ethhdr:

struct ethhdr *phdr = (struct ethhdr *)buffer;
uint8_t *pdata = buffer + ETH_HLEN;
uint32_t *pfcs = (uint32_t *)(buffer + ETH_FRAME_LEN);

memcpy(phdr->h_dest, mac_dest, ETH_ALEN);
memcpy(phdr->h_source, mac_source, ETH_ALEN);
phdr->h_proto = htons(ETH_P_IP); /* Os dados correspondem a um pacote IPv4 */

memcpy(pdata, data, ETH_DATA_LEN); /* Preenche os dados */
*pfcs = htonl(calc_crc32(buffer, ETH_FRAME_LEN));

Neste exemplo estou assumindo que o ponteiro ‘data’ contém o pacote IP já montado. No caso do checksum, note que usei htonl para converter o formato do número… fiz a mesma coisa (mas com htons) para o campo type.

Uma vez que está tudo no lugar, só precisamos enviar os dados:

#include <sys/socket.h>
#include <net/if.h>
#include <linux/if_arp.h>
#include <linux/if_packet.h>

extern socket_t skt;
extern uint8 buffer[];

int SendData(void)
{
  struct sockaddr_ll sktaddr =
  {
    .sll_family   = PF_INET,
    .sll_protocol = htons(ETH_P_IP),
    .sll_ifindex  = if_nametoindex("eth0"),
    .sll_hatype   = ARPHDR_ETHER,
    .sll_pkttype  = PACKET_OTHERHOST,
    .sll_halen    = ETH_ALEN
  };

  if (sendto(skt,
        buffer, sizeof(buffer),
        0,
        (struct socketaddr *)&sktaddr, sizeof(sktaddr)) == -1)
  {
    /* Trata erro aqui:
       Erro ao enviar pacote */
  }  

  return 0;
}

E voilà! Pacote ethernet com um pacote IP embutido, criado por você, é enviado para a rede.

A única coisa que resta a fazer é fechar o socket. A função close() de unistd.h é usada para isso.

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