/* ICMP ping example in c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>

#define PAYLOAD_SIZE 64    /* 64 Byte Nutzdaten */

unsigned short csum(unsigned short *ptr, int nbytes);

int main(int argc, char **argv)
  {
  unsigned long daddr;  /* Ziel-IP-Adresse */
  unsigned long saddr;  /* Quell-IP-Adresse */
  unsigned short pid;   /* ICMP-Id */
  int packet_size;      /* Paketgroesse (wird berechnet) */
  char *packet;         /* Pointer auf das Paket */
  int sent_size;        /* Resultatwert von sendto() */
  int rcv_size;         /* Resultatwert von recvfrom() */
  int on = 1;           /* fuer setsockopt() */
  socklen_t len;        /* fuer recvfrom() */

  if (argc < 3)
    {
    printf("usage: %s <source IP> <destination IP>\n", argv[0]);
    exit(0);
    }
  saddr = inet_addr(argv[1]);
  daddr = inet_addr(argv[2]);

  /* Irgend eine ID erzeugen - dient der Erkennung des Echo Reply;
     wird mit dem Echo request gesendet */
  pid = rand();

  /* RAW-Socket anlegen - Wenn IPPROTO_ICMP angegeben wird, berechnet
     der Kernel die ICMP-Checksumme, bei IPPROTO_RAW macht er nichts */
  int sockfd = socket (AF_INET, SOCK_RAW, IPPROTO_ICMP);

  if (sockfd < 0)
    {
    perror("socket() ging schief");
    if (errno == EACCES) printf(" (EACCES)\n");
    if (errno == EAFNOSUPPORT) printf(" (EAFNOSUPPORT)\n");
    return 1;
    }

  /* IP-Header mache ich von Hand, deshalb: */
  if (setsockopt (sockfd, IPPROTO_IP, IP_HDRINCL, (const char*)&on, sizeof (on)) == -1)
    {
    perror("setsockopt() - IP-HDRINCL ging schief");
    return 1;
    }

  /* Paketgroesse berechnen und Speicher fuer den Puffer allokieren */
  packet_size = sizeof (struct iphdr) + sizeof (struct icmphdr) + PAYLOAD_SIZE;
  packet = (char *) malloc (packet_size);
  if (!packet)
    {
    perror("out of memory");
    close(sockfd);
    return 1;
    }
  /* Puffer mit Nullen fuellen */
  memset (packet, 0, packet_size);

  /* IP-Header zusammenbasteln, der Pointer ip zeigt auf den Pufferanfang */
  struct iphdr *ip = (struct iphdr *) packet;
  ip->version = 4;
  ip->ihl = 5;
  ip->tos = 0;
  ip->tot_len = htons (packet_size);
  ip->id = rand ();
  ip->frag_off = 0;
  ip->ttl = 255;
  ip->protocol = IPPROTO_ICMP;
  ip->check = 0;
  ip->saddr = saddr;
  ip->daddr = daddr;
  /* Macht der Kernel: ip->check = in_cksum ((u16 *) ip, sizeof (struct iphdr)); */

  /* ICMP-Header zusammenbasteln, der Pointer icmp zeigt auf den Puffer hinter dem IP-Header */
  struct icmphdr *icmp = (struct icmphdr *) (packet + sizeof (struct iphdr));
  icmp->type = ICMP_ECHO;
  icmp->code = 0;
  icmp->un.echo.sequence = rand();
  icmp->un.echo.id = pid;
  icmp->checksum = 0;

  /* Adress-Struktur belegen */
  struct sockaddr_in servaddr;
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = daddr;
  memset(&servaddr.sin_zero, 0, sizeof (servaddr.sin_zero));

  printf("Sending with pid=%d ...\n",pid);

  /* Nutzlast mit Zufallswerten fuellen */
  memset(packet + sizeof(struct iphdr) + sizeof(struct icmphdr), rand() % 255, PAYLOAD_SIZE);

  /* ICMP-Checksumme berechnen */
  icmp->checksum = 0;
  icmp->checksum = csum((unsigned short *)icmp, sizeof(struct icmphdr) + PAYLOAD_SIZE);

  /* Paket senden */
  servaddr.sin_addr.s_addr = daddr;
  sent_size = sendto(sockfd, packet, packet_size, 0, (struct sockaddr*) &servaddr, sizeof (servaddr));
  if (sent_size < 1)
    {
    perror("sendto ging schief\n");
    return 1;
    }
  printf("Packet sent\n");

  /* Nun wird auf die Antwort gewartet */
  len = sizeof (servaddr);
  servaddr.sin_addr.s_addr = saddr;
  rcv_size = recvfrom(sockfd, packet, sizeof(struct iphdr) + sizeof(struct icmphdr),
                      0, (struct sockaddr*) &servaddr, &len);
  if (rcv_size == -1)
    {
    perror("recvfrom ging schief\n");
    return 1;
    }
  printf("Received %d bytes\n", rcv_size);

  /* IP-Header auswerten */
  struct iphdr* ip_reply = (struct iphdr*) packet;
  printf("ID:  %d\n", ntohs(ip_reply->id));
  printf("TTL: %d\n", ip_reply->ttl);
  printf("IHL: %d\n", ip_reply->ihl);
  printf("SRC: %s\n", inet_ntoa(*(struct in_addr*)&ip_reply->saddr));
  printf("DST: %s\n", inet_ntoa(*(struct in_addr*)&ip_reply->daddr));

  /* ICMP-Header auswerten */
  struct icmphdr *icmp_r = (struct icmphdr*) (packet + ip_reply->ihl*4);
  if (icmp_r->un.echo.id == pid)
    {
    printf("ICMP: type[%d/%d] checksum[%d] id[%d] seq[%d]\n\n",
    icmp_r->type, icmp_r->code, ntohs(icmp_r->checksum),
    icmp_r->un.echo.id, icmp_r->un.echo.sequence);
    }

 free(packet);
 close(sockfd);

  return 0;
  }

unsigned short csum(unsigned short *ptr, int nbytes)
  {
  u_int32_t sum;       /* Summenvariable */
  u_int16_t oddbyte;   /* fuer Addition bei ungerader Blocklaenge */

  sum = 0;
  while(nbytes > 1)    /* ueber den Datenblock summieren */
    {
    sum += *ptr++;     /* 16-Bit-Wert summieren, daher nbytes um 2 vermindern */
    nbytes -= 2;
    }
  if(nbytes == 1)      /* ungerde Blocklaenge (eher selten) */
    {
    oddbyte = 0;       /* letztes Byte addieren */
    *((u_char*)&oddbyte) = *(u_char*)ptr;
    sum += oddbyte;
    }
  /* die oberen und unteren 16 Bit der 32-Bit-Summe addieren */
  sum = (sum >> 16) + (sum & 0xffff);
  sum = sum + (sum >> 16); /* Uebertrag beruecksichtigen */
  /* Einerkomplement bilden */
  return (u_int16_t)~sum;
  }