Todos los lenguajes de programación disponen de una manera más o menos sencilla de realizar conexiones entre un cliente y un servidor. Como mínimo todos ofrecen la posibilidad de utilizar sockets TCP y UDP. Algunos incluso ICMP. Pero cuando deseamos disponer de un acceso total al sistema de red, no hay otro remedio que acceder a la API de red con un lenguaje como C. Este acceso permite cosas como enviar paquetes ICMP, ARP, o cualquier otro totalmente personalizado, modificando cualquier flag o dato de la cabecera. Incluso, si lo deseamos, podemos crear nuestro propio protocolo.
Como primer ejemplo enviaremos un paquete ARP. Este es el formato de las cabeceras ARP y Ethernet:
ETHERNET HEADER: +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Ethernet destination address (first 32 bits) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Ethernet dest (last 16 bits) |Ethernet source (first 16 bits)| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Ethernet source address (last 32 bits) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type code | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | IP header, then TCP header, then your data | | | ... | | | end of your data | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Ethernet Checksum | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ARP HEADER: +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Hardware | Protocol | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Hw Addr len |Proto Addr len | Operation | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source hardware address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source hardware address | Source IP address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source IP address | Destination hardware address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Destination hardware address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Destination IP address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
A continuación un ejemplo de envío de paquete ARP:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <errno.h>
#include <sys/socket.h>
#include <net/ethernet.h>
#include <net/if_arp.h>
#include <arpa/inet.h>
/* Cabecera ARP */
struct arp_hdr
{
unsigned short int hardware;
unsigned short int protocol;
char hw_addr_len;
char proto_addr_len;
unsigned short operation;
char src_addr[6];
char src_ip[4];
char dst_addr[6];
char dst_ip[4];
};
int main ()
{
/* socket */
int sock;
/* Tama~o del buffer capaz de contener un paquete ARP */
unsigned int buffer_size =
sizeof(struct arp_hdr) + sizeof(struct ether_header);
/* Buffer que contendra el paquete ARP */
unsigned char buffer[buffer_size];
memset(buffer,0,buffer_size);
/* Cabecera ethernet */
struct ether_header *eth = (struct ether_header *)buffer;
/* Cabecera ARP */
struct arp_hdr *arp =
(struct arp_hdr *)(buffer + sizeof(struct ether_header));
/* Direcciones MAC */
char src_mac[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
char dst_mac[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
/* Direcciones IP */
char src_ip[] = {0x01, 0x02, 0x03, 0x04};
char dst_ip[] = {0x01, 0x02, 0x03, 0x04};
/* Dispositivo  */
char dev[5];
strncpy(dev, "eth0", 5);
/* Creacion del socket */
if ((sock = socket(AF_INET,SOCK_PACKET,htons(ETH_P_ARP)))==-1)
{
perror("socket()");
exit(EXIT_FAILURE);
}
/* Rellena la cabecera ethernet */
memcpy(eth->ether_dhost,dst_mac,ETHER_ADDR_LEN);
memcpy(eth->ether_shost,src_mac,ETHER_ADDR_LEN);
eth->ether_type = htons(ETHERTYPE_ARP);
/* Rellena la cabecera ARP */
arp->hardware = htons(ARPHRD_ETHER);
arp->protocol = htons(ETH_P_IP);
arp->hw_addr_len = 6; 
arp->proto_addr_len = 4;
arp->operation = htons(ARPOP_REPLY);
memcpy(arp->src_addr, src_mac,6);
memcpy(arp->src_ip, src_ip, 4);
memcpy(arp->dst_addr, dst_mac, 6);
memcpy(arp->dst_ip, dst_ip, 4);
/* Dispositivo utilizado "eth0" */
struct sockaddr addr;
strncpy(addr.sa_data, dev, sizeof(addr.sa_data));
/* Envio del paquete ARP */
if ((sendto(sock, buffer, buffer_size, 0,
&addr, sizeof(struct sockaddr)))==-1)
{
perror("sendto()");
exit(EXIT_FAILURE);
}
return 0;
}
De forma similar a la anterior podemos enviar un paquete ICMP. Estas son las
cabeceras utilizadas:
cabeceras utilizadas:
IP HEADER: +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |Version| IHL |Type of Service| Total Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Identification |Flags| Fragment Offset | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Time to Live | Protocol | Header Checksum | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source Address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Destination Address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | TCP header, then your data ...... | | | ICMP HEADER: +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type | Code | Checksum | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Y el ejemplo:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
int main(void)
{
/* socket */
int sock;
/* Longitud de un paquete ICMP */
unsigned int buffer_size = sizeof(struct iphdr) + sizeof(struct icmphdr);
/* Paquete capaz con capacidad para un paquete ICMP */
unsigned char buffer[buffer_size];
memset(buffer, 0, buffer_size);
/* Cabecera IP */
struct iphdr *ip = (struct iphdr *)buffer;
/* Cabecera ICMP */
struct icmphdr *icmp = (struct icmphdr *)(buffer + sizeof(struct iphdr));
/* Creacion del socket */
if ((sock = socket(AF_INET,SOCK_RAW,IPPROTO_ICMP)) == -1)
{
perror("socket()");
exit(EXIT_FAILURE);
}
/* Establece las opciones del socket */
int o = 1;
if( setsockopt(sock,IPPROTO_IP,IP_HDRINCL,&o,sizeof(o)) == -1 )
{
perror("setsockopt()");
exit(EXIT_FAILURE);
}
/* Rellena la cabecera IP */
ip->version = 4;
ip->ihl = 5;
ip->id = htonl(random());
ip->saddr = inet_addr("1.2.3.4");
ip->daddr = inet_addr("1.2.3.4");
ip->ttl = 255;
ip->protocol = IPPROTO_ICMP;
ip->tot_len = buffer_size;
ip->check = 0;
/* Rellena la cabecera ICMP */
icmp->type = 0;
icmp->code = ICMP_ECHO;
icmp->checksum = 0;
/* Rellena la estructura sockaddr_in */
struct sockaddr_in addr;
addr.sin_family = AF_INET;
/* Envio del paquete */
if ((sendto(sock, buffer, buffer_size, 0, (struct sockaddr*)&addr,
sizeof(struct sockaddr_in))) == -1 )
{
perror("send()");
exit(EXIT_FAILURE);
}
return 0;
}
En algunos casos puede ser interesante enviar paquetes TCP de uno en uno.
Por ejemplo en escaneos. Las cabeceras utilizadas en este caso son:
IP HEADER: +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |Version| IHL |Type of Service| Total Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Identification |Flags| Fragment Offset | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Time to Live | Protocol | Header Checksum | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source Address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Destination Address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | TCP header, then your data ...... | | | TCP HEADER: +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source Port | Destination Port | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Acknowledgment Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Data | |U|A|P|R|S|F| | | Offset| Reserved |R|C|S|S|Y|I| Window | | | |G|K|H|T|N|N| | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Checksum | Urgent Pointer | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | your data ... next 500 octets | | ...... |
Un ejemplo es el siguiente.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
int main(void)
{
/* socket */
int sock;
/* Tama~o del paquete TCP */
unsigned int buffer_size = sizeof(struct iphdr) + sizeof(struct tcphdr);
/* Buffer de tama~o suficiente para un paquete TCP */
unsigned char buffer[buffer_size];
memset (buffer,0,buffer_size);
/* Cabecera IP */
struct iphdr *ip = (struct iphdr *)buffer;
/* Cabecera TCP */
struct tcphdr *tcp = (struct tcphdr *)(buffer + sizeof(struct iphdr));
/* Crea el socket */
if ((sock = socket(AF_INET,SOCK_RAW,IPPROTO_TCP)) == -1)
{
perror("socket()");
exit(EXIT_FAILURE);
}
/* Establece las opciones del socket */
int o = 1;
if (setsockopt(sock,IPPROTO_IP,IP_HDRINCL,&o,sizeof(o)) == -1)
{
perror("setsockopt()");
exit(EXIT_FAILURE);
}
/* Rellena la cabecera IP */
ip->version = 4;
ip->ihl = 5;
ip->id = htonl(random());
ip->saddr = inet_addr("1.2.3.4");
ip->daddr = inet_addr("1.2.3.4");
ip->ttl = 255;
ip->protocol = IPPROTO_TCP;
ip->tot_len = buffer_size;
ip->check = 0;  /* falta calcular checksum  */
/* Rellena la cabecera TCP */
tcp->source = htons(1234);
tcp->dest = htons(1234);
tcp->seq = htonl(999999999);
tcp->ack_seq = htonl(999999999);
tcp->ack = 1;
tcp->syn = 1;
tcp->window = htons(2048);
tcp->check = 0; /* falta calcular checksum  */
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = tcp->source;
addr.sin_addr.s_addr = ip->saddr;
/* Envio del paquete */
if ((sendto(sock, buffer, buffer_size, 0, (struct sockaddr*)&addr,
sizeof(struct sockaddr_in))) == -1)
{
perror("send");
exit(1);
}
return 0;
}
Finalmente un ejemplo de UDP para completar los protocolos más utilzados. Utilizaremos las siguientes cabeceras:
IP HEADER: +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |Version| IHL |Type of Service| Total Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Identification |Flags| Fragment Offset | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Time to Live | Protocol | Header Checksum | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source Address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Destination Address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | TCP header, then your data ...... | | | UDP HEADER: +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source Port | Destination Port | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | length | Checksum | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
Y nuestro último ejemplo:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
int main(void)
{
/* socket */
int sock;
/* Tama~o del paquete UDP */
unsigned int buffer_size = sizeof(struct iphdr) + sizeof(struct udphdr);
/* Buffer de tama~o suficiente para un paquete UDP */
unsigned char buffer[buffer_size];
memset (buffer, 0, buffer_size);
/* Cabecera IP */
struct iphdr *ip = (struct iphdr *)buffer;
/* Cabecera UDP */
struct udphdr *udp = (struct udphdr *)(buffer + sizeof(struct iphdr));
/* Crea el socket */
if ((sock = socket(AF_INET,SOCK_RAW,IPPROTO_UDP)) == -1)
{
perror("socket()");
exit(EXIT_FAILURE);
}
/* Establece las opciones del socket */
int o = 1;
if (setsockopt(sock,IPPROTO_IP,IP_HDRINCL,&o,sizeof(o)) == -1)
{
perror("setsockopt()");
exit(EXIT_FAILURE);
}
/* Rellena la cabecera IP */
ip->version = 4;
ip->ihl = 5;
ip->id = htonl(random());
ip->saddr = inet_addr("1.2.3.4");
ip->daddr = inet_addr("1.2.3.4");
ip->ttl = 255;
ip->protocol = IPPROTO_UDP;
ip->tot_len = buffer_size;
ip->check = 0;
/* Rellena la cabecera UDP */
udp->source = htons(1234);
udp->dest = htons(1234);
udp->len = buffer_size;
udp->check = 0; /* falta calcular checksum  */
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = udp->source;
addr.sin_addr.s_addr = ip->saddr;
/* Envio del paquete */
if ((sendto(sock, buffer, buffer_size, 0, (struct sockaddr*)&addr,
sizeof(struct sockaddr_in))) == -1)
{
perror("send()");
exit(1);
}
return 0;
}