/*
 *  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.
 *
 *  pingtest.c   --  Ping a host once, to verify connectivity
 *
 *  $Id: pingtest.c,v 1.1.0.0 2003/11/05 19:29:01 floyd Exp floyd $
 */

/*
 *  This program is intended as a demonstration of code which
 *  can be embedded into a program to verify that a network
 *  route to some given host does exist.
 *
 *  As written, there are no error messages at all, only status
 *  codes:
 *  
 *        0 if a valid response packet is received
 *        1 if no response packet is received
 *        2 if a response packet with corrupted data is received
 *       10 if icmp is not a supported protocol on this host
 *       11 if a socket cannot be opened
 *       12 if the host address/name cannot be resolved
 *
 *  Note that this program must run as root to work.
 */
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/signal.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>

#define icmphdr		icmp

#define	MAXWAIT		3	/* max seconds to wait for response */
#define	DATALEN	        24      /* default data length for test packet */
#define ICMPSIZE        sizeof (struct icmphdr) - sizeof (struct iphdr)
#define RXBUFSIZE       DATALEN + sizeof (struct icmphdr)
#define	TXBUFSIZE	DATALEN + ICMPSIZE

struct sockaddr whereto;
static int  	fd_sock;

static int  in_cksum(u_short *, int);
static void finish(int);
static void tx_packet(void);
static void rx_packet(void);
static void verify_rx_packet(char *, int, struct sockaddr_in *);

int
main(int argc, char *argv[])
{
  struct hostent     *hp;
  struct sockaddr_in *to;
  struct protoent    *proto;
  int                hold;
  char               *target;
  char               hostbuf[MAXHOSTNAMELEN];
  char               *hostname;

  if (NULL == (proto = getprotobyname("icmp"))) {
    exit(10);
  }

  if (0 > (fd_sock = socket(AF_INET, SOCK_RAW, proto->p_proto))) {
    exit(11);
  }

  if (argc > 1) {
    target = argv[1];
  } else {
    target = "127.0.0.1";
  }
  
  memset(&whereto, 0, sizeof(struct sockaddr));

  to = (struct sockaddr_in *) &whereto;
  to->sin_family = AF_INET;

  if (inet_aton(target, &to->sin_addr)) {
    hostname = target;
  } else {
    if (NULL == (hp = gethostbyname(target))) {
      exit(12);
    }

    to->sin_family = hp->h_addrtype;

    if (hp->h_length > (int) sizeof(to->sin_addr)) {
      hp->h_length = sizeof (to->sin_addr);
    }

    memcpy(&to->sin_addr, hp->h_addr, hp->h_length);
    strncpy(hostbuf, hp->h_name, sizeof(hostbuf) - 1);
    hostname = hostbuf;
  }

  hold = 5 * 1024; /* we may see packets from other processes... */
  setsockopt(fd_sock, SOL_SOCKET, SO_RCVBUF, &hold, sizeof hold);
  tx_packet();    /* send a packet */
  tx_packet();    /* send another packet, just to be sure? */
  rx_packet();    /* exits if a reply packet is received */
  return 1;       /* no valid packets received */
}

/*
 * failure exit
 */
static void
finish(int ignore)
{
  ignore = ignore;
  printf("ping failed\n");
  exit(1);
}

/*
 * Send an ICMP ECHO_REQUEST packet.
 * 
 */
static void
tx_packet(void)
{
  char           txpacketbuf[TXBUFSIZE];
  int            i;
  struct icmphdr *icp;
  static long    ntransmitted;	/* sequence # */

  memset(txpacketbuf, 0, sizeof txpacketbuf);

  /* data fill an icmp packet */
  icp             = (struct icmphdr *) txpacketbuf;
  icp->icmp_type  = ICMP_ECHO;
  icp->icmp_code  = 0;
  icp->icmp_cksum = 0;
  icp->icmp_seq   = ntransmitted++;
  icp->icmp_id    = getpid() & 0xFFFF;
  
  /* skip icmp hdr, and data fill packet with a known sequence of bytes */
  for (i = ICMPSIZE; i < DATALEN; ++i) {
    txpacketbuf[i] = i;
  }
  
  /* compute ICMP checksum here */
  icp->icmp_cksum = in_cksum( (u_short*) icp, TXBUFSIZE);

  /* send it */
  i = sendto(fd_sock, txpacketbuf, TXBUFSIZE, 0, &whereto,  sizeof(struct sockaddr));
}

void
rx_packet(void)
{
  int                size;
  size_t             fromlen;
  struct sockaddr_in from;
  char               rxpacketbuf[RXBUFSIZE];

  memset(rxpacketbuf, 0, sizeof rxpacketbuf);

  /* read received packets */
  for (;;) {
    fromlen = sizeof from;
    
    if (0 < (size = recvfrom(fd_sock, rxpacketbuf, RXBUFSIZE, 0,
			     (struct sockaddr *) &from, &fromlen))) {

#ifdef DEBUG
      printf("size:      %d\n", size);
      printf("rxbufsize: %d\n", RXBUFSIZE);
      printf("txbufsize: %d\n", TXBUFSIZE);
      printf("icmpsize:  %d\n", ICMPSIZE);
#endif

      signal(SIGALRM, finish);
      alarm(MAXWAIT);   /* set wait time */
      /* exits if a return packet is received */
      verify_rx_packet(rxpacketbuf, size, &from);
    }
  }
}


/*
 * verify_rx_packet()
 *
 *  Verify that a received packet is first an echo_reply packet,
 *  then that we sent it, then compare the send data with the receive
 *  data.
 *
 *  This routine returns if the packet is ours.  If it is our packet
 *  we exit with 0 if the data matches a send packet, and 1 otherwise.
 */

void
verify_rx_packet(char *buf, int size, struct sockaddr_in *from)
{
  char           *s;
  int            i, len;
  struct iphdr   *ip;
  struct icmphdr *icp;

  /* check the IP header */
  ip = (struct iphdr *) buf;
  len = ip->ihl << 2;

  if (size < DATALEN + ICMP_MINLEN) {
    return;
  }

  /* check the ICMP header */
  size -= len;
  icp = (struct icmphdr *) (buf + len);
  if (icp->icmp_type != ICMP_ECHOREPLY) {
    return;
  }

  if (icp->icmp_id != (getpid() & 0xFFFF)) {
    return;
  }

  printf("%d bytes from %s: icmp_seq=%u\n", size,
	 inet_ntoa(*(struct in_addr *)&from->sin_addr.s_addr),
	 icp->icmp_seq);

  /* verify received data */
  s = (char *) icp;
  for (i = ICMPSIZE; i < DATALEN; ++i) {
    if (s[i] != i) {
      exit(2);
    }
  }
  exit(EXIT_SUCCESS);
}

/*
 * in_cksum --
 *	Checksum routine for Internet Protocol family headers (C Version)
 *
 * This check sum routine is stolen from the
 * original ping program written by Mike Muuss.
 */
static int
in_cksum(u_short *addr, int len)
{
  int     nleft  = len;
  int     sum    = 0;
  u_short *w     = addr;
  u_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) {
    u_short	u = 0;

    *(u_char *)(&u) = *(u_char *)w ;
    sum += u;
  }

  /* 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;
}