Explicación paso a paso (con un ejemplo):
0. Inclusiones necesarias.
Preparamos una pequeña función para imprimir en pantalla las MAC address que procesaremos luego:
1. Buffer.
Necesitamos un buffer donde almacenar lo recibido (o capturado).
2. Crear el socket.
AF_PACKET indica capa 2 (Data Link Layer) permitiendo ver el encabezado Ethernet.
htons porque se crea en el host y se manda esta información a la red.
3. Bucle de escucha.
Dentro de un loop.
1. Crear las variables necesarias para guardar la dirección del cliente. El tipo de dato de estas variables es el necesario para que las funciones de socket como recvfrom() y sendto() funcionen.
2. Recibir los paquetes Ethernet. Por defecto requiere que el tipo de dato sea ssize_t que es un long int (%ld).
En Raw Sockets puede utilizarse tanto la función recv() como recvfrom(), pero se recomienda más esta última ya que proporciona información sobre la dirección de origen.
La función devuelve un la cantidad de bytes y es necesario guardarla con el tipo ssize_t.
Los parámetros son:
a. El file descriptor del socket (del servidor obviamente).
b. El buffer donde guardaremos lo que viene del cliente.
c. El tamaño del buffer
d. Un flag que por lo gral se deja en 0, (tiene opciones avanzadas).
c. La dirección del cliente con cast a un puntero para que sea interpretada como sockaddr
(en un principio la tuvimos que declarar como sockaddr_ll (capa 2))
Nota: en la capa 3 con servidores de socket TCP/UDP el tipo es sockaddr_in
3. Chequeamos que se haya recibido algo.
4. Extraemos la información del paquete Ethernet recibido.
Hasta ahora tenemos lo recibido en el buffer que le indicamos anteriormente a la función recvfrom(). “Todo junto”.
Para interpretar fácilmente esta información nos ayudamos de ethhdr que es una estructura definida en la biblioteca <linux/if_ether.h> en sistemas Linux. Contiene los campos básicos: [MAC_destino] [MAC_origen] [Protocolo].
En C si hacemos un cast a un puntero con un tipo de dato, no estamos cambiando el tipo de dato en si mismo, sino que nos permite decirle al compilador que para este caso interprete este valor como si fuera del tipo de dato al que estamos apuntando.
Un puntero al tipo de datos ethhdr (ethernet header), le dice al compilador que interprete los datos almacenados en buffer con este formato para acceder a ellos.
FF FF FF FF FF FF 00 11 22 33 44 55 08 00
└─ MAC Dest ──┘ └ MAC Origen ┘ └Tipo┘
Esto hace que:
a. los datos en buffer sean tratados como si se fueran del tipo de dato ethhdr
b. se guarda en un puntero llamado eth la dirección de memoria donde comienzan los datos de buffer
c. ahora se puede acceder a estos datos como si de un dato tipo ethhdr se tratase, con sus respectivos campos…
Por ejemplo:
ethhdr -> h_dest
ethhdr -> h_source
ethhdr -> h_proto
*) ethhdr tiene solo estos tres campos, entonces ¿qué pasa con el resto de lo que hay en ‘buffer’? No son interpretados. El puntero solo interpretara los primeros 14 bytes. Así que no hay problema.
5. Imprimimos en pantalla cada campo deseado
6. Así los paquetes son capturados y mostrados todos, lo que llenará la pantalla de la terminal muy rápidamente. Como es un simple programa de ejemplo, agregar una pausa al final antes de cerrar el bucle permitirá visualizar mucho mejor (aunque se pierdan muchos paquetes en el medio, no importa en este caso).
4. Cierre del socket.