/* vi: set sw=4 ts=4: */
/*
 * $Id: ping.cc,v 1.6 2006/06/13 18:17:03 henthorn Exp $
 * Mini ping implementation for busybox
 *
 * Copyright (C) 1999 by Randolph Chung <tausq@debian.org>
 *
 * Adapted from the ping in netkit-base 0.10:
 * Copyright (c) 1989 The Regents of the University of California.
 * Derived from software contributed to Berkeley by Mike Muuss.
 *
 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
 */

#include <sys/param.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <sys/time.h>
#include <sys/times.h>
#include <signal.h>

#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

#include "System.h"
#include "TimeP.h"

int System::_pingsock = 0;

// Returns a socket for pinging, or a negative number upon an error
//
static int create_icmp_socket(void)
{
    struct protoent *proto;
    int sock;

    proto = getprotobyname("icmp");
    /* if getprotobyname failed, just silently force
     * proto->p_proto to have the correct value for "icmp" */
    if ((sock = socket(AF_INET, SOCK_RAW,
            (proto ? proto->p_proto : 1))) < 0) {        /* 1 == ICMP */
        if (errno == EPERM)
        {
            return(-1);
        }
        else
        {
            return(-2);
        }
    }

    /* drop root privs if running setuid */
    setuid(getuid());

    return sock;
}

/* common routines */
static int in_cksum(unsigned short *buf, int sz)
{
	int nleft = sz;
	int sum = 0;
	unsigned short *w = buf;
	unsigned short ans = 0;

	while (nleft > 1) {
		sum += *w++;
		nleft -= 2;
	}

	if (nleft == 1) {
		*(unsigned char *) (&ans) = *(unsigned char *) w;
		sum += ans;
	}

	sum = (sum >> 16) + (sum & 0xFFFF);
	sum += (sum >> 16);
	ans = ~sum;
	return (ans);
}

enum {
	DEFDATALEN = 56,
	MAXIPLEN = 60,
	MAXICMPLEN = 76,
	MAXPACKET = 65468,
	MAX_DUP_CHK = (8 * 128),
	MAXWAIT = 10,
	PINGINTERVAL = 1		/* second */
};

int System::ping(const char *host, unsigned int timeout)
{
	struct hostent *h;
	struct sockaddr_in pingaddr;
	struct icmp *pkt;
	int c;
	char packet[DEFDATALEN + MAXIPLEN + MAXICMPLEN];
	char *hostname = strdup(host);

        // Get a socket for pinging
        //printf("pingsock = %d\n", _pingsock);
        if ( 0 >= _pingsock )
        {
          //printf("Creating a socket for pinging\n");
	  _pingsock = create_icmp_socket(); // pingsock is a static member of System
          //printf("pingsock is now %d\n", _pingsock);

          if (_pingsock < 0) return _pingsock; // Could not create socket

          // Set the socket to non-blocking
          int sockfl = fcntl(_pingsock, F_GETFL);
          sockfl = fcntl(_pingsock, F_SETFL, sockfl | O_NONBLOCK);
        }

	memset(&pingaddr, 0, sizeof(struct sockaddr_in));

        // Get host information, if any
        //
        //printf("get hostent data from hostname\n");
	pingaddr.sin_family = AF_INET;
	h = gethostbyname(hostname);
        if (NULL == h)
          return(-3);   // No host info

	memcpy(&pingaddr.sin_addr, h->h_addr, sizeof(pingaddr.sin_addr));

	pkt = (struct icmp *) packet;
	memset(pkt, 0, sizeof(packet));
	pkt->icmp_type = ICMP_ECHO;
	pkt->icmp_cksum = in_cksum((unsigned short *) pkt, sizeof(packet));

        // Send a ping packet to the host
        //printf("send data packet\n");
	c = sendto(_pingsock, packet, sizeof(packet), 0,
			   (struct sockaddr *) &pingaddr, sizeof(struct sockaddr_in));

	if (c < 0 || c != sizeof(packet))
        {
          //printf("sendto() returned %d, errno = %d\n", c, errno);
		return(-4); // Could not send ping for some reason
        }

	/* listen for replies */
        //printf("listening\n");
        unsigned int elapsedTime = 0;
	struct sockaddr_in from;
	int fromlen = sizeof(from);
        unsigned int startTime = Time::milliseconds();

        int pinged = 1;
	while (elapsedTime < timeout) {
                //printf("elapsed time = %d\n", elapsedTime);
		if ((c = recvfrom(_pingsock, packet, sizeof(packet), 0,
						  (struct sockaddr *) &from, &fromlen)) >= 76) {

			/* ip + icmp */
			struct ip *iphdr = (struct ip *) packet;

			pkt = (struct icmp *) (packet + (iphdr->ip_hl << 2));	/* skip ip hdr */
			if (pkt->icmp_type == ICMP_ECHOREPLY)
                        {
				pinged = 0;  // Success!
                                break;
                        }
		}
               System::milliSleep(5);
               elapsedTime = Time::milliseconds() - startTime;
	}
        //printf("timeout\n");
	return(pinged);  // Timeout - host not responding
}

