#include <features.h>
#include <inttypes.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/poll.h>
#include <unistd.h>
#include <time.h>
#include <netdb.h>
#ifdef __dietlibc__
#include <write12.h>
#else
static void __write1(const char* s) {
  write(1,s,strlen(s));
}
static void __write2(const char* s) {
  write(2,s,strlen(s));
}
#endif
#include <signal.h>
#include <errno.h>

/* #define NODNS */

typedef struct {
  uint8_t type, code;
  uint16_t checksum,id,sequence;
} icmp_header;

#define ICMP_ECHO 8
#define ICMP_ECHOREPLY 0

static int in_checksum(unsigned short *buf, int len) {
  register long sum = 0;
  unsigned short answer = 0;
  while (len>1) { sum += *buf++; len -= 2; }
  if (len==1) {
    *(unsigned char*)(&answer) = *(unsigned char*)buf;
    sum += answer;
  }
  sum = (sum >> 16) + (sum & 0xffff);
  sum += (sum >> 16);
  answer = ~sum;
  return answer;
}

static unsigned int fmt_ulong(char *dest,unsigned long i) {
  register unsigned long len,tmp,len2;
  for (len=1, tmp=i; tmp>9; ++len) tmp/=10;
  if (dest)
    for (tmp=i, dest+=len, len2=len+1; --len2; tmp/=10)
      *--dest = (tmp%10)+'0';
  return len;
}

static unsigned int fmt_str(char *out,const char *in) {
  register char* s=out;
  register const char* t=in;
  for (;;) {
    if (!*t) break; *s=*t; ++s; ++t;
  }
  return s-out;
}

static unsigned int fmt_ip4(char *s,const char ip[4]) {
  unsigned int len;
  int i;
  len = 0;
  for (i=0; i<4; ++i) {
    register unsigned int j;
    len+=(j=fmt_ulong(s,(unsigned long) (unsigned char) ip[i]))+1;
    if (s && i<3) { s+=j; *s++='.'; }
  }
  return len-1;
}

static unsigned int scan_ulong(const char *src,unsigned long *dest) {
  register const char *tmp=src;
  register int l=0;
  register unsigned char c;
  while ((c=*tmp-'0')<10) {
    l=l*10+c;
    ++tmp;
  }
  *dest=l;
  return tmp-src;
}

char* peer="127.0.0.1";
int sent,received;

unsigned long dmin,dsum,dmax;

static void eintr_handler(int sig) {
  char *buf = alloca(strlen(peer)+300);
  int i,p;
  i=fmt_str(buf,"\n--- ");
  i+=fmt_str(buf+i,peer);
  i+=fmt_str(buf+i," ping statistics ---\n");
  i+=fmt_ulong(buf+i,sent);
  i+=fmt_str(buf+i," packets transmitted, ");
  i+=fmt_ulong(buf+i,received);
  i+=fmt_str(buf+i," packets received, ");
  if (received<sent) {
    p=100-(received*100/sent);
  } else p=0;
  i+=fmt_ulong(buf+i,p);
  i+=fmt_str(buf+i,"% packet loss\n");
  if (received) {
    i+=fmt_str(buf+i,"round-trip min/avg/max = ");
    i+=fmt_ulong(buf+i,dmin/10); buf[i]='.'; ++i;
    i+=fmt_ulong(buf+i,dmin%10); buf[i]='/'; ++i;
    {
      unsigned long l=dsum/received;
      i+=fmt_ulong(buf+i,l/10); buf[i]='.'; ++i;
      i+=fmt_ulong(buf+i,l%10); buf[i]='/'; ++i;
    }
    i+=fmt_ulong(buf+i,dmax/10); buf[i]='.'; ++i;
    i+=fmt_ulong(buf+i,dmax%10);
    i+=fmt_str(buf+i," ms\n");
  }
  write(1,buf,i);
  _exit(0);
}

main(int argc,char* argv[]) {
  int s=socket(PF_INET,SOCK_RAW,IPPROTO_ICMP);
  icmp_header *out,*in;
  char* obuf, ibuf[1500];
  int octets;
  struct sockaddr_in sin,sout;
  int count=-1,len,sinlen;
  fd_set ifd;
  struct timeval tv,start,now;
  int _f=0,_a=0;
  unsigned long _c=0,_i=1,_q=0,_s=16;
  char* _p=0;
  int c;

  if (s<0) {
    __write2("could not create raw socket. euid==0?\n");
    return 1;
  }
  signal(SIGINT,eintr_handler);

  while ((c = getopt (argc, argv, "aqfbi:c:s:p:")) != -1)
    switch (c) {
    case 'a': _a=1; break;
    case 'f': _f=1; break;
    case 'q': _q=1; break;
    case 'b':
	      {
		int one=1;
		if (setsockopt(s,SOL_SOCKET,SO_BROADCAST,&one,sizeof one)) {
		  __write2("setsockopt(...SO_BROADCAST...) failed.\n");
		  return 3;
		}
	      }
	      break;
    case 'c': if (optarg[scan_ulong(optarg,&_c)]) goto usage; break;
    case 'i': if (optarg[scan_ulong(optarg,&_i)]) goto usage; break;
    case 's': if (optarg[scan_ulong(optarg,&_s)]) goto usage; break;
    case 'p': _p=optarg; break; 
    default:
usage:
	      __write2("usage: ping [-fb] [-c count] [-i timeout] "
		       "[-s payloadsize] [-p pattern] host\n");
	      return 0;
    }

  octets=8+_s;
  obuf=alloca(octets);
  out=(icmp_header*)obuf;

  out->type=ICMP_ECHO;
  out->code=out->checksum=0;
  out->id=23;
  if (_p) {
    int i,s=strlen(_p);
    for (i=0; i<_s; ++i) obuf[8+i]=_p[i%s];
  } else memset(obuf+8,0,_s);
  out->checksum=in_checksum((unsigned short*)out,8);

  sout.sin_family=PF_INET;
  sout.sin_port=0;
  if (argc>optind) peer=argv[optind];
  if (!inet_aton(peer,&sout.sin_addr)) {
#ifndef NODNS
    struct hostent host,*res;
    char buf[2048];
    int fnord;
    if (gethostbyname_r(peer,&host,buf,2048,&res,&fnord)) {
      __write2("unknown host: "); __write2(peer); __write2("\n");
      return 2;
    }
    sout.sin_addr=*(struct in_addr*)(host.h_addr_list[0]);
#else
    __write2("inet_aton failed\n");
    return 2;
#endif
  }
  {
    char *buf=alloca(strlen(peer)+100);
    int i,j;
    unsigned char *ip=(unsigned char*)&(sout.sin_addr);
    i=fmt_str(buf,"PING ");
    i+=fmt_str(buf+i,peer);
    i+=fmt_str(buf+i," (");
    for (j=0; ; ++j) {
      i+=fmt_ulong(buf+i,ip[j]);
      if (j==3) break;
      buf[i]='.'; ++i;
    }
    i+=fmt_str(buf+i,"): ");
    i+=fmt_ulong(buf+i,octets);
    i+=fmt_str(buf+i," octets\n");
    write(1,buf,i);
  }
  sent=received=0;
  do {
    out->sequence=htons(sent);
    out->checksum=0;
    out->checksum=in_checksum((unsigned short*)out,8);
    if (gettimeofday(&start,0)==-1) {
      __write2("gettimeofday failed\n");
      return 6;
    }
    if (_c && sent==_c) { eintr_handler(SIGINT); }
    if (sendto(s,obuf,octets,0,(struct sockaddr*)&sout,sizeof(sout))==-1) {
      if (errno==EACCES) {
	__write2("sending to broadcast address failed.  Use -b.\n");
      } else
	__write2("sendto failed\n");
      return 3;
    }
    ++sent;
    tv.tv_sec=1;
    tv.tv_usec=0;
    FD_ZERO(&ifd);
    FD_SET(s,&ifd);
    for (;;) {
      gettimeofday(&now,0);
      tv.tv_sec=now.tv_sec-start.tv_sec+_i;
      tv.tv_usec=now.tv_usec-start.tv_usec;
      if (tv.tv_usec<0) {
	--tv.tv_sec;
	tv.tv_usec+=1000000;
      }
      switch (select(s+1,&ifd,0,0,&tv)) {
      case -1: __write2("select failed\n"); return 4;
      case 0: goto next;
      }
      sinlen=sizeof(sin);
/* int recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen); */
      if ((len=recvfrom(s,ibuf,sizeof(ibuf),0,(struct sockaddr*)&sin,&sinlen))==-1) {
	__write2("recvfrom failed\n");
	return 5;
      }
      in=(icmp_header*)(ibuf+20);
      if (in->type!=ICMP_ECHOREPLY || in->code!=0 || in->id!=out->id) continue;
      {
	char ob[100];
	int i;
	++received;
	i=fmt_ulong(ob,len);
	i+=fmt_str(ob+i," octets from ");
	i+=fmt_ip4(ob+i,(char*)&sin.sin_addr);
	i+=fmt_str(ob+i,": icmp_seq=");
	i+=fmt_ulong(ob+i,ntohs(in->sequence));
	i+=fmt_str(ob+i," ttl=");
	i+=fmt_ulong(ob+i,(unsigned char)ibuf[8]);
	i+=fmt_str(ob+i," time=");
	gettimeofday(&now,0);
	{
	  unsigned long l=(now.tv_sec-start.tv_sec)*1000000+now.tv_usec-start.tv_usec;
	  l/=100;	/* 10 ms */

	  if (received==1) dmin=dmax=l; else {
	    if (l<dmin) dmin=l;
	    if (l>dmax) dmax=l;
	  }
	  dsum+=l;

	  i+=fmt_ulong(ob+i,l/10);
	  ob[i++]='.';
	  i+=fmt_ulong(ob+i,l%10);
	}
	i+=fmt_str(ob+i," ms\n");
	if (_a) ob[i++]='\a';
	if (!_q) write(1,ob,i);
      }
//      printf("%d octets from %s: icmp_seq=%d ttl=%d\n",len,inet_ntoa(sin.sin_addr),ntohs(in->sequence),(unsigned char)buf[8]);
//      select(0,0,0,0,&tv);
    }
next:
  } while (1);
}
