/*
 *  Embedded Ping Program Examples
 *  Copyright 2003 by Floyd L. Davidson, floyd@barrow.com
 *
 *  This program is free software; you can redistribute it
 *  and/or modify it under the terms of the GNU General Public
 *  License as published by the Free Software Foundation;
 *  either version 2, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be
 *  useful, but WITHOUT ANY WARRANTY; without even the implied
 *  warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 *  PURPOSE.  See the GNU General Public License for details.
 *
 *  myping.c   --  Ping a host within a larger program.
 *
 *  $Id: myping.c,v 1.1.0.0 2003/11/05 19:29:01 floyd Exp floyd $
 */
/*
 *  Demonstrate how to ping a distant host from within a
 *  C program.
 *
 *  Note that this program must be run as root to work!
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <setjmp.h>
#include <string.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/ip_icmp.h>

jmp_buf env;

int in_cksum(unsigned short *, int);
int ping(struct sockaddr *);
int pinger(char *);
void catcher(int);


int
main(int argc, char *argv[])
{
  int	rtnvalue;

  if (argc != 2) {
    printf("Usage: ping host\n");
    exit(0);
  }

  switch (rtnvalue = pinger(argv[1])) {
  case 0:
    printf("%s is alive\n", argv[1]);
    break;
  case 1:
    printf("Invalid response from %s\n", argv[1]);
    break;
  case 2:
    printf("No response from %s\n", argv[1]);
    break;
  case 3:
    printf("Error:  unable to send packet to %s\n", argv[1]);
    break;
  case 4:
    printf("Error:  unable to create a socket.\n");
    break;
  case 5:
    printf("Error:  icmp protocol is not supported.\n");
    break;
  case 10:
    printf("Unknown host:  %s\n", argv[1]);
    break;
  case 11:
    printf("No IP address for %s\n", argv[1]);
    break;
  case 12:
    printf("DNS server error for %s\n", argv[1]);
    break;
  case 13:
    printf("Transient DNS server error for %s\n", argv[1]);
    break;
  case 14:
    printf("Unknown DNS server error for %s\n", argv[1]);
    break;
  default:
    printf("Error:  Unknown response.");
    break;
  }

  return rtnvalue;
}

/*
 * call ping to determine if a host is alive
 *
 *  This function is a wrapper that sort out all
 *  the needed information from the known information.
 *  In this demo, only a name or an IP address is
 *  known, so everything needs to be fetched and
 *  massaged properly.
 *
 *  Returns 0 on success, otherwise returns
 *
 *    error codes from ping(), or
 *
 *       10 --  Unknown host
 *       11 --  No ip address for host
 *       12 --  DNS server error
 *       13 --  Transient DNS server error
 *       14 --  Unknown DNS error
 */
int
pinger(char *host)
{
  char                  hostname[80];
  char                  hostaddr[80];
  struct sockaddr_in 	dest;
  struct hostent     	*hp;
  struct in_addr        inaddr;


  /* zero out host information */
  memset(&dest, 0, sizeof dest);

  /*
   * Initalize pointer to a hostent struct
   */	
  if (NULL == (hp = gethostbyname(host))) {
    switch (h_errno) {
    case HOST_NOT_FOUND:
      return 10;
    case NO_ADDRESS:
      return 11;
    case NO_RECOVERY:
      return 12;
    case TRY_AGAIN:
      return 13;
    }
    return 14;
  }

  /* init parts of  sockaddr_in struct */
  dest.sin_family = hp->h_addrtype;
  memcpy(&dest.sin_addr, hp->h_addr, hp->h_length);
  sprintf(hostname, "%s", hp->h_name);
  sprintf(hostaddr, "%s", inet_ntoa(dest.sin_addr));
  
  /* Print Destination host IP address */
  printf("Destination Host:    %s\n", hostname);
  printf("Destination Host IP: %s\n", hostaddr);

  /* try to get an official name for this host */	
  if (inet_aton(inet_ntoa(dest.sin_addr), &inaddr)) {
    if (NULL != (hp = gethostbyaddr(&inaddr, 4, AF_INET))) {
      sprintf(hostname, "%s", hp->h_name);
      printf("Official Host FQDN:  %s\n", hostname);
    }
  }

  return ping((struct sockaddr *) &dest);
}


/*
 * ping the host whose address is in struct sockaddr dest
 *
 *  Silently ping the distant host and return status of
 *     0 on success, otherwise
 *     1 -- invalid packet received
 *     2 -- alarm() timed out waiting for ping
 *     3 -- sendto() failed to send packet
 *     4 -- socket() failed to create socket
 *     5 -- icmp protocol is not supported
 */

/* actually only needs to be 8 bytes, minimum  */
#define PACKETSZ  (sizeof(struct icmp))

int
ping(struct sockaddr *dest)
{
  unsigned char	  	packet[PACKETSZ];
  int		  	sockfd, pid, packetsize, recv_bytes;
  struct icmp		*ic;
  struct protoent    	*pp;
  socklen_t             fromlen;
 
  pid = getpid() & 0xffff;

  /*
   * get protocol information
   */
  if (NULL == (pp = getprotobyname("icmp"))) {
    return 5;
  }

  /* build an ICMP header for the send packet */
  ic             = (struct icmp *) packet;
  ic->icmp_type  = ICMP_ECHO;   /* 1 byte  */
  ic->icmp_code  = 0; 	        /* 1 byte  */
  ic->icmp_cksum = 0;	        /* 2 bytes */
  ic->icmp_seq   = 1;	        /* 2 bytes */
  ic->icmp_id    = pid;         /* 2 bytes */

  /* as above, use only first 8 bytes */
  packetsize = 8;

  /* compute checksum */
  ic->icmp_cksum = in_cksum((unsigned short *)ic, packetsize);
	
  /* create a raw socket */
  if (0 > (sockfd = socket(AF_INET, SOCK_RAW, pp->p_proto))) {
    return 4;
  }
	
  /* send the echo request */
  if (packetsize != sendto(sockfd, packet, packetsize, 0, dest, sizeof *dest)) {
    return 3;
  }

  /* zero out host information */
  memset(dest, 0, sizeof *dest);

  /* catch alarm signal if no answer comes back */
  signal(SIGALRM, catcher);
  
  fromlen = sizeof *dest;

  if (setjmp(env)) {
    return 2;  /* no answer, return an error */
  }
  
  alarm(2);    /* wait up to 2 seconds */
  recv_bytes = recvfrom(sockfd, packet, sizeof packet, 0, dest, &fromlen);
  alarm(0);

  if (recv_bytes != packetsize + (int) sizeof (struct iphdr)) {
    return 1;
  }

  /* if all else fails, we've succeeded! */
  return 0;
}

/*
 * in_cksum --  A checksum routine for IP family headers
 *
 *   This is greatfully stolen from the original ping.c
 *   public domain source code by Mike Muuss.
 */
int
in_cksum(unsigned short *addr, int len)
{
  int            nleft  = len;
  int            sum    = 0;
  unsigned short *w     = addr;
  unsigned short answer = 0;

  /*
   * Our algorithm is simple, using a 32 bit accumulator (sum), we add
   * sequential 16 bit words to it, and at the end, fold back all the
   * carry bits from the top 16 bits into the lower 16 bits.
   */
  while (nleft > 1)  {
    sum += *w++;
    nleft -= 2;
  }

  /* mop up an odd byte, if necessary */
  if (nleft == 1) {
    *(unsigned char *)(&answer) = *(unsigned char *)w ;
    sum += answer;
  }

  /* add back carry outs from top 16 bits to low 16 bits */
  sum = (sum >> 16) + (sum & 0xffff);	/* add hi 16 to low 16 */
  sum += (sum >> 16);			/* add carry */
  answer = ~sum;			/* truncate to 16 bits */

  return(answer);
}

/*
 *  catch SIGALRM and continue
 */
void catcher(int sig) {
  longjmp(env, sig);
  return;
}