2011-11-27 11 views
6

¿Es posible usar sockets ICMP bajo el protocolo IP? Tal vez algo como:Enchufes ICMP (linux)

socket(PF_INET, <type>, IPPROTO_ICMP)?

¿Qué debo poner en el> campo < tipo? Vi algunos ejemplos usando SOCK_RAW, pero ¿eso no evitará que el sistema operativo haga su trabajo manejando el protocolo IP?

Y otra cosa. ¿Cómo puede el sistema operativo saber a qué proceso debe enviar los datagramas ICMP, ya que no hay puertos involucrados con el protocolo?

Respuesta

7

Sí, es posible, ya que el comando ping hace ICMP.

Para conocer las llamadas de sistema involucradas, puede strace que ordenen (en la raíz).

También puede echar un vistazo al código fuente de ese comando, p. Debian's ping

Y no es la biblioteca liboping para ayudarle a ...

14

Linux tienen un tipo especial socket ICMP puede utilizar con:

socket(PF_INET, SOCK_DGRAM IPPROTO_ICMP); 

Esto le permite enviar solamente eco ICMP pide al kernel lo manejará especialmente (coincidencia de solicitudes/respuestas, complete la suma de verificación).

Esto solo funciona si se establece special sysctl. Por defecto ni siquiera root puede usar este tipo de socket. Usted especifica los grupos de usuarios que pueden acceder a él. Para permitir que la raíz (grupo 0) para utilizar sockets ICMP, hacer:

sysctl -w net.ipv4.ping_group_range="0 0" 

Aquí es un programa de ejemplo para demostrar el uso muy básico de enviar una solicitud de eco ICMP:

#include <stdio.h> 
#include <errno.h> 
#include <string.h> 
#include <stdlib.h> 
#include <sys/socket.h> 
#include <sys/socket.h> 
#include <netinet/in.h> 
#include <netinet/ip_icmp.h> 
#include <arpa/inet.h> 
#include <sys/select.h> 

//note, to allow root to use icmp sockets, run: 
//sysctl -w net.ipv4.ping_group_range="0 0" 

void ping_it(struct in_addr *dst) 
{ 
    struct icmphdr icmp_hdr; 
    struct sockaddr_in addr; 
    int sequence = 0; 
    int sock = socket(AF_INET,SOCK_DGRAM,IPPROTO_ICMP); 
    if (sock < 0) { 
     perror("socket"); 
     return ; 
    } 

    memset(&addr, 0, sizeof addr); 
    addr.sin_family = AF_INET; 
    addr.sin_addr = *dst; 

    memset(&icmp_hdr, 0, sizeof icmp_hdr); 
    icmp_hdr.type = ICMP_ECHO; 
    icmp_hdr.un.echo.id = 1234;//arbitrary id 

    for (;;) { 
     unsigned char data[2048]; 
     int rc; 
     struct timeval timeout = {3, 0}; //wait max 3 seconds for a reply 
     fd_set read_set; 
     socklen_t slen; 
     struct icmphdr rcv_hdr; 

     icmp_hdr.un.echo.sequence = sequence++; 
     memcpy(data, &icmp_hdr, sizeof icmp_hdr); 
     memcpy(data + sizeof icmp_hdr, "hello", 5); //icmp payload 
     rc = sendto(sock, data, sizeof icmp_hdr + 5, 
         0, (struct sockaddr*)&addr, sizeof addr); 
     if (rc <= 0) { 
      perror("Sendto"); 
      break; 
     } 
     puts("Sent ICMP\n"); 

     memset(&read_set, 0, sizeof read_set); 
     FD_SET(sock, &read_set); 

     //wait for a reply with a timeout 
     rc = select(sock + 1, &read_set, NULL, NULL, &timeout); 
     if (rc == 0) { 
      puts("Got no reply\n"); 
      continue; 
     } else if (rc < 0) { 
      perror("Select"); 
      break; 
     } 

     //we don't care about the sender address in this example.. 
     slen = 0; 
     rc = recvfrom(sock, data, sizeof data, 0, NULL, &slen); 
     if (rc <= 0) { 
      perror("recvfrom"); 
      break; 
     } else if (rc < sizeof rcv_hdr) { 
      printf("Error, got short ICMP packet, %d bytes\n", rc); 
      break; 
     } 
     memcpy(&rcv_hdr, data, sizeof rcv_hdr); 
     if (rcv_hdr.type == ICMP_ECHOREPLY) { 
      printf("ICMP Reply, id=0x%x, sequence = 0x%x\n", 
          icmp_hdr.un.echo.id, icmp_hdr.un.echo.sequence); 
     } else { 
      printf("Got ICMP packet with type 0x%x ?!?\n", rcv_hdr.type); 
     } 
    } 
} 

int main(int argc, char *argv[]) 
{ 
    if (argc != 2) { 
     printf("usage: %s destination_ip\n", argv[0]); 
     return 1; 
    } 
    struct in_addr dst; 

    if (inet_aton(argv[1], &dst) == 0) { 

     perror("inet_aton"); 
     printf("%s isn't a valid IP address\n", argv[1]); 
     return 1; 
    } 

    ping_it(&dst); 
    return 0; 
} 

Tenga en cuenta que el núcleo rechazará y fallará la llamada a sendto() si los datos enviados no tienen espacio para un encabezado ICMP correcto, y el ICMP type debe ser 8 (ICMP_ECHO) y el código ICMP debe ser 0.

+0

¿No crearía un socket? ¿estar equivocado? SOCK_DRAM es parte de la capa de transporte de la pila de capa OSI/ISO 7 ... ICMP es parte de la capa 3 en la capa de red. He estado leyendo Programación de socket de Unix por Stevens y para hacer esto necesitas declarar SOCK_RAW para obtener paquetes icmp. mira este código [aquí] (http://www.binarytides.com/packet-sniffer-code-c-linux/) –

+1

@Florida_Jake SOCK_DGRAM no está vinculado a la capa 7. El libro de programación de socket Unix no describe cómo para usar los zócalos de eco ICMP específicos de Linux, el ejemplo aquí funciona. La forma tradicional de enviar/recibir mensajes ICMP es usar SOCK_RAW y crear los mensajes ICMP usted mismo. El código que publiqué usa una función alternativa, específica de Linux para enviar y recibir mensajes de eco ICMP. – nos

+0

Agradable, funcionó muy bien para mí para obtener un resultado rápidamente. Ahora es el momento de mirar el ping de IPv6. :) –

Cuestiones relacionadas