/*

  Copyright (c) 1998 by GTE Internetworking.
  
  Permission to use, copy, modify, and distribute this software for any
  purpose with or without fee is hereby granted, provided that the above
  copyright notice and this permission notice appear in all copies.
  
  THE SOFTWARE IS PROVIDED "AS IS" AND GTE INTERNETWORKING DISCLAIMS ALL
  WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
  WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL GTE
  INTERNETWORKING BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
  CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
  USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
  OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  PERFORMANCE OF THIS SOFTWARE.
  
  Author: nils mccarthy (nils@genuity.net)

*/

#include <sys/types.h>
#include <sys/time.h>
#include <stdio.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/tcp.h>
#include <netinet/if_ether.h>
#include <assert.h>
#include <netdb.h>
#include <pcap.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>

#if defined(BSD)
#define SCREWED_IP_LEN
#endif

#ifndef SCREWED_IP_LEN
static int
ip_cksum(const struct ip *ip)
{
  register const u_short *sp = (u_short *)ip;
  register u_int32_t sum = 0;
  register int count;
  
  /*
   * No need for endian conversions.
   */
  for (count = ip->ip_hl * 2; --count >= 0; )
    sum += *sp++;
  while (sum > 0xffff)
    sum = (sum & 0xffff) + (sum >> 16);
  sum = ~sum & 0xffff;
  
  return (sum);
}
#endif

static int
tcp_cksum(struct ip *ip, struct tcphdr *tcp)
{
  u_int32_t sum = 0;
  register int count;
  u_short *sp;
  int old_ttl;

  sp = (u_short *)tcp;
  for (count = sizeof(struct tcphdr)/2; --count >= 0; )
    sum += *sp++;

  sp = (u_short *)&ip->ip_src;
  for (count = 2; --count >= 0; )
    sum += *sp++;
  
  sp = (u_short *)&ip->ip_dst;
  for (count = 2; --count >= 0; )
    sum += *sp++;

  old_ttl = ip->ip_ttl;
  ip->ip_ttl = 0;
  sp = (u_short *)&ip->ip_ttl;	/* protocol comes right after this */
  for (count = 1; --count >= 0; )
    sum += *sp++;
  ip->ip_ttl = old_ttl;

  sum += htons(sizeof(struct tcphdr));

  while (sum > 0xffff)
    sum = (sum & 0xffff) + (sum >> 16);
  sum = ~sum & 0xffff;
  
  return (sum);
}

struct trace_packet_s {
  struct ip ip_hdr;
  struct tcphdr tcp_hdr;
} trace_packet;

struct trace_packet_info_s {
  int icmp_type;		/* ICMP_UNREACH code, -1 if RST reply */
  int is_done;			/* is this a final hop? */
  unsigned int hopno;
  struct timeval sent;
  struct timeval recv;		/* 0 if unreceived */
  struct in_addr hop;		/* IP address of router */
} *trace_packet_info;		/* indexed by dport - dport_start */
int trace_packet_info_size = 0;
int trace_packet_info_length = 0;

struct hop_info_s {
  struct trace_packet_info_s *last_sent; /* last packet sent or
					  * received with this ttl */
  int num_sent;
} *hop_info;
int hop_info_size = 0;
int hop_info_length = 0;

int dport_start = 25000;
int sport = 80;

int timeout_ms = 1000;		/* timeout between retries */
int retry_max = 5;		/* number of retries before giving up */
int retry_min = 1;		/* minimum number of checks per hop */
double scatter_ms = 20;		/* milleseconds between sends */
int ahead_limit = 5;		/* number of probes we can send
				 * without replies if we don't know
				 * the number of hops */
int ttl_limit=30;		/* max # hops */
int break_on_icmp = 1;		/* break on icmp other than time exceeded */
int quiet=0;
int resolve_names = 1;

struct in_addr local_address;
struct in_addr remote_address;

struct timeval last_sent, now;

int send_sock;
int skip;

unsigned short ip_id;

int initial_rtt_ms;
int num_hops = 0;

void print_host(struct in_addr addr)
{
  struct hostent *h;

  h = gethostbyaddr((void *)&addr, 4, AF_INET);
  if (h)
    printf("%s (%s)", h->h_name,
	   inet_ntoa(addr));
  else
    printf("%s", inet_ntoa(addr));
}

void get_address(char *remote)
{
  char name[256];
  struct hostent *h;
  
  gethostname(name, 256);
  h = gethostbyname(name);
  if (!h) {
    fprintf(stderr, "Unknown host: %s\n", name);
    exit(1);
  }
  local_address = *(struct in_addr *)h->h_addr;
  
  h = gethostbyname(remote);
  if (!h) {
    fprintf(stderr, "Unknown host: %s\n", remote);
    exit(1);
  }
  remote_address = *(struct in_addr *)h->h_addr;
  if (!quiet) {
    printf("Using local address ");
    print_host(local_address);
  }
  printf("\n");
}

void open_sockets()
{
   int on=1;
#ifdef sun
   struct sockaddr_in local_bind;
#endif

#if defined(sun)
    send_sock = socket(PF_INET, SOCK_RAW, IPPROTO_IP);
#elif defined(BSD)
    send_sock = socket(PF_INET, SOCK_RAW, IPPROTO_TCP);
#else
    send_sock = socket(PF_INET, SOCK_RAW, IPPROTO_RAW);
#endif
    if (send_sock < 0) {
	perror("raw socket");
	exit(1);
    }
#ifdef IP_HDRINCL
        if (setsockopt(send_sock, IPPROTO_IP, IP_HDRINCL, (char *)&on,
            sizeof(on)) < 0) {
		perror("IP_HDRINCL");
		exit(1);
        }
#endif
#ifdef sun
    local_bind.sin_addr = local_address;
    local_bind.sin_port = 0;
    local_bind.sin_family = AF_INET;
    if (bind(send_sock, &local_bind, sizeof(local_bind))<0) {
      perror("bind");
	exit(1);
    }
#endif

    /* set up initial ip headers, etc. */
    memset(&trace_packet, 0, sizeof(trace_packet));
    trace_packet.ip_hdr.ip_v = 4;
    trace_packet.ip_hdr.ip_hl = 5;
#ifdef SCREWED_IP_LEN
    trace_packet.ip_hdr.ip_len = sizeof(trace_packet);
#else
    trace_packet.ip_hdr.ip_len = htons(sizeof(trace_packet));
#endif
#ifndef IPDEFTTL
#define IPDEFTTL 64
#endif
    trace_packet.ip_hdr.ip_ttl = IPDEFTTL;
    trace_packet.ip_hdr.ip_p = IPPROTO_TCP;

    trace_packet.tcp_hdr.th_flags = TH_ACK | TH_PUSH;
    trace_packet.tcp_hdr.th_win = htons(32768);
    trace_packet.tcp_hdr.th_off = sizeof(struct tcphdr)/4;

    srand(time(0));
    ip_id = rand()&32767;

    trace_packet_info = malloc(sizeof(struct trace_packet_s)*16);
    trace_packet_info_size = 16;

    hop_info = malloc(sizeof(struct hop_info_s)*16);
    hop_info_size = 16;
}

void send_packet(int nhop)
{
  struct sockaddr_in dest;
  int ntrace;

  last_sent = now;
  
  trace_packet.ip_hdr.ip_ttl = nhop;
  trace_packet.ip_hdr.ip_src = local_address;
  trace_packet.ip_hdr.ip_dst = remote_address;
  
  trace_packet.ip_hdr.ip_sum = 0;
#if !defined(SCREWED_IP_LEN)
  trace_packet.ip_hdr.ip_sum = ip_cksum(&trace_packet.ip_hdr);
#endif
  
  trace_packet.tcp_hdr.th_dport = htons(dport_start + trace_packet_info_length);
  trace_packet.tcp_hdr.th_sport = htons(80);
  trace_packet.tcp_hdr.th_flags = TH_ACK;
  
#if defined(sun)
  trace_packet.tcp_hdr.th_sum = htons(sizeof(struct tcphdr));
#else
  trace_packet.tcp_hdr.th_sum = 0;
  trace_packet.tcp_hdr.th_sum = tcp_cksum(&trace_packet.ip_hdr,
					  &trace_packet.tcp_hdr);
#endif
  
  dest.sin_family = AF_INET;
  dest.sin_addr = remote_address;
  dest.sin_port = 0;
  
  if (sendto(send_sock, &trace_packet, sizeof(trace_packet), 0,
	     (struct sockaddr *)&dest, sizeof(dest))<0) {
    perror("sendto");
    exit(1);
  }

  if (trace_packet_info_length >= trace_packet_info_size) {
    trace_packet_info_size *= 2;
    trace_packet_info = realloc(trace_packet_info, sizeof(struct trace_packet_info_s)*trace_packet_info_size);
  }
  
  ntrace = trace_packet_info_length++;
  printf(">%d/%d", ntrace, nhop);

  memset(&trace_packet_info[ntrace], 0, sizeof(struct trace_packet_info_s));
  trace_packet_info[ntrace].sent = now;
  trace_packet_info[ntrace].hopno = nhop;
  hop_info[nhop].last_sent = &trace_packet_info[ntrace];
  hop_info[nhop].num_sent++;
}

double timediff_ms(struct timeval prior, struct timeval latter)
{
  return
    (latter.tv_usec - prior.tv_usec)/1000. +
    (latter.tv_sec - prior.tv_sec)*1000.;
}

void finish()
{
  int hopno, ntrace;
  int maxhop;
  
  if (num_hops) {
    maxhop = num_hops;
    /* if we got a bunch of hits for the end host, display them all */
    for (ntrace = 0; ntrace < trace_packet_info_length; ntrace++)
      if (trace_packet_info[ntrace].is_done)
	trace_packet_info[ntrace].hopno = maxhop;
  } else {
    maxhop = hop_info_length - 1;
  }


  printf("\n\nTrace to ");
  print_host(remote_address);
  printf(":\n");
  
  for (hopno = 0; hopno <= maxhop; hopno++) {
    struct in_addr last_hop;
    
    last_hop.s_addr = 0;
    
    printf("%2d ", hopno);
    for (ntrace = 0; ntrace < trace_packet_info_length; ntrace++) {
      static char *icmp_messages[] = {
	"end host",
	"unreachable net",
	"unreachable host",
	"unreachable protocol",
	"unreachable port",
	"need fragment",
	"source fail",
	"net unknown",
	"host unknown",
	"src isolated",
	"net prohib",
	"host prohib",
	"bad tos/net",
	"bad tos/hst"
      };
      if (trace_packet_info[ntrace].hopno != hopno)
	continue;

      printf(" ");
      
      if (trace_packet_info[ntrace].recv.tv_sec) {
	if (trace_packet_info[ntrace].icmp_type < -2 || 
	    trace_packet_info[ntrace].icmp_type > 12)
	  printf(" (icmp code %d)",
		 trace_packet_info[ntrace].icmp_type);
	else if (trace_packet_info[ntrace].icmp_type != -2)
	  printf(" (%s)", icmp_messages[trace_packet_info[ntrace].icmp_type+1]);
	if (last_hop.s_addr !=
	    trace_packet_info[ntrace].hop.s_addr) {
	  printf(" ");
	  print_host(trace_packet_info[ntrace].hop);
	}
	last_hop = trace_packet_info[ntrace].hop;
	printf(" %.1fms",
	       timediff_ms(trace_packet_info[ntrace].sent,
			   trace_packet_info[ntrace].recv));
      } else {
	printf(" *");
      }
    }
    printf("\n");
  }
  
  exit(0);
}

void check_timeouts()
{
  int nhop;
  int need_reply = 0;
  int no_reply = 0;
  int last_return = 0;

  gettimeofday(&now, NULL);
  if (timediff_ms(last_sent, now) < scatter_ms)
    return;			/* not ready to send another packet yet */
  
  for (nhop = 0; nhop < hop_info_length; nhop++) {
    if (!hop_info[nhop].num_sent) {
      send_packet(nhop);
      return;
    }
  }
  
  for (nhop = 0; nhop < hop_info_length; nhop++) {
    if (hop_info[nhop].num_sent <= retry_max &&
	!hop_info[nhop].last_sent->recv.tv_sec)
      if (timediff_ms(hop_info[nhop].last_sent->sent, now) >= timeout_ms) {
	/* we timed out waiting for this hop -- retry if we have any
	 * more tries */
	if (hop_info[nhop].num_sent < retry_max) {
	  send_packet(nhop);
	  return;
	} else
	  no_reply++;
      } else
	need_reply++;		/* we have to wait for this one to timeout */
    else
      last_return = nhop;
  }

  if (no_reply >= ahead_limit) { /* we timed out. */
    if ((last_return+3)*2 < hop_info_length)
      finish();
    /* otherwise search out for weird ttl's on return packets */
    retry_max = 1;
    retry_min = 1;
  }

  if ((!num_hops || hop_info_length < num_hops || need_reply) &&
      hop_info_length < 30) {
    if (need_reply >= ahead_limit)
      return;			/* wait for some replies before we go on */
    
    if (num_hops > 0 && hop_info_length >= num_hops)
      return;			/* we know how long the path is --
				 * wait to fill in the blanks */
    
    /* ok -- we're ready to try another hop */
    if (hop_info_length >= hop_info_size) {
      hop_info_size *= 2;
      hop_info = realloc(hop_info, sizeof(struct hop_info_s)*hop_info_size);
    }
    
    nhop = hop_info_length++;
    hop_info[nhop].last_sent = NULL;
    hop_info[nhop].num_sent = 0;
    send_packet(nhop);
  } else {
    for (nhop = 0; nhop < hop_info_length; nhop++) {
      if (hop_info[nhop].num_sent < retry_min &&
	  hop_info[nhop].num_sent <= retry_max &&
	  hop_info[nhop].last_sent->recv.tv_sec) {
	send_packet(nhop);
	return;
      }
    }
    finish();
  }
}

void recv_packet(int traceno, struct in_addr ipaddr, int icmp_type)
{
  double ms;

  if (trace_packet_info[traceno].recv.tv_sec) {
    printf("dup?\n");
    return;
  }
  printf("<%d/%d", traceno, trace_packet_info[traceno].hopno);
  trace_packet_info[traceno].recv = now;
  trace_packet_info[traceno].hop = ipaddr;
  trace_packet_info[traceno].icmp_type = icmp_type;
  if (icmp_type != -2 &&
      (!num_hops || num_hops > trace_packet_info[traceno].hopno))
    if (break_on_icmp || (icmp_type == -1)) {
      num_hops = trace_packet_info[traceno].hopno;
      trace_packet_info[traceno].is_done = 1;
    }

  /* adjust scatter if we have fast reply times */
  ms = timediff_ms(trace_packet_info[traceno].sent,
		   trace_packet_info[traceno].recv);
  scatter_ms = (scatter_ms * (ahead_limit-1) + ms) / ahead_limit;

}

void process_packet(u_char *user_data, const struct pcap_pkthdr *hdr,
		    const u_char *packet)
{
  const struct ip *ip, *orig_ip;
  const struct tcphdr *tcp;
  const struct icmp *icmp;

  check_timeouts();

  packet += skip;
  ip = (void *)packet;

  packet += 4*ip->ip_hl;
  switch (ip->ip_p) {
  case IPPROTO_ICMP:
    orig_ip = ip;
    icmp = (void *)packet;
    if (icmp->icmp_type != ICMP_UNREACH && icmp->icmp_type != ICMP_TIMXCEED)
      return;
    ip = &icmp->icmp_ip;
    if (ip->ip_p != IPPROTO_TCP)
      return;			/* not a response to our tcp probe */
    packet = (void *)ip;
    packet += 4*ip->ip_hl;
    tcp = (void *)packet;
    if (ntohs(tcp->th_dport) < dport_start ||
	ntohs(tcp->th_dport) >= dport_start + trace_packet_info_length ||
	ip->ip_src.s_addr != local_address.s_addr ||
	ip->ip_dst.s_addr != remote_address.s_addr)
      return;			/* not for us */
    
    recv_packet(ntohs(tcp->th_dport) - dport_start, orig_ip->ip_src,
		(icmp->icmp_type == ICMP_TIMXCEED)? -2 : icmp->icmp_code);
    break;
  case IPPROTO_TCP:
    /* check for RST reply */
    tcp = (void *)packet;
    if (!(tcp->th_flags & TH_RST))
      return;			/* not what we're looking rfor */
    if (ntohs(tcp->th_sport) < dport_start ||
	ntohs(tcp->th_sport) >= dport_start + trace_packet_info_length ||
	ip->ip_src.s_addr != remote_address.s_addr ||
	ip->ip_dst.s_addr != local_address.s_addr)
      return;			/* not the right connection */
    recv_packet(ntohs(tcp->th_sport) - dport_start, ip->ip_src, -1);
    return;
  }
}

void usage(char *prog)
{
  fprintf(stderr, "Usage: %s [<options>] <hostname>\n"
"Options are:\n"
"  -d <dport>       destination port number to start on\n"
"  -m <min>         minimum number of queries to send per host\n"
"  -M <max>         maximum number of queries to send per host\n"
"  -a <ahead>       number of hops forward to query before receiving replies\n"
"  -s <scatter ms>  minimum number of milliseconds between subsequent queries\n"
"  -t <timeout ms>  maximum RTT before assuming packet was dropped\n"
"  -l <ttl>         maximum TTL to use on outgoing packets\n"
"  -i               don't stop on ICMP other than TTL expired\n"
"  -v               show version information\n"
"\n"
" Default is: %s -d %d -m %d -M %d -a %d -s %.0f -t %d -l %d\n"
, prog, prog,
	  dport_start,
	  retry_min,
	  retry_max,
	  ahead_limit,
	  scatter_ms,
	  timeout_ms,
	  ttl_limit);
  exit(1);
}

void version()
{
  fprintf(stderr, "\n"
"FFT version $Id: fft.c,v 1.6 1998/02/25 04:23:12 nils Exp $\n"
"Compilation options:\n"
#if defined(BSD)
"   BSD\n"
#endif
#if defined(linux)
"   linux\n"
#endif
#if defined(sun)
"   sun\n"
#endif
#if !defined(sun) && !defined(linux) && !defined(BSD)
"   (unknown arch)\n"
#endif
#if defined(SCREWED_IP_LEN)
"   backwards IP length field\n"
#else
"   forwards IP length field\n"
#endif
#if defined(IP_HDRINCL)
"   IP_HDRINCL\n"
#else
"   no IP_HDRINCL\n"
#endif
	  );
  exit(0);
}

void main(int argc, char **argv)
{
  char ebuf[PCAP_ERRBUF_SIZE];
  static pcap_t *pd;
  char *pcap_dev;
  int ch;

  setbuf(stdout, NULL);

  while ((ch = getopt(argc, argv, "d:M:m:a:s:t:vi")) != EOF)
    switch(ch) {
    case 'd':
      dport_start = atoi(optarg);
      break;
    case 'm':
      retry_min = atoi(optarg);
      break;
    case 'M':
      retry_max = atoi(optarg);
      break;
    case 'a':
      ahead_limit = atoi(optarg);
      break;
    case 's':
      scatter_ms = atoi(optarg);
      break;
    case 't':
      timeout_ms = atoi(optarg);
      break;
    case 'l':
      ttl_limit = atoi(optarg);
      break;
    case 'i':
      break_on_icmp = 0;
      break;
    case 'v':
      version();
      break;
    case 'q':
      quiet = 1;
      break;
    default:
      usage(argv[0]);
    }

  if ((argc-optind) != 1)
    usage(argv[0]);

  pcap_dev = pcap_lookupdev(ebuf);
  if (pcap_dev == NULL) {
    fprintf(stderr, "%s\n", ebuf);
    exit(1);
  }
  /* fix pcap's linux implementation */
  if (!strncmp(pcap_dev, "lo:", 3) || !strcmp(pcap_dev, "lo")) {
    pcap_dev = "ppp0";
    skip = 0;
  } else if (!strncmp(pcap_dev, "ppp", 3))
    skip = 0;
  else /* assume ethernet */
    skip = sizeof (struct ether_header);
  if (!quiet) printf("using device %s\n", pcap_dev);
  pd = pcap_open_live(pcap_dev, 1600, 0, 20, ebuf);
  if (!pd) {
    fprintf(stderr, "%s\n", ebuf);
    exit(1);
  }
  get_address(argv[optind]);
  open_sockets();
  setuid(getuid());
  while (pcap_dispatch(pd, -1, process_packet, 0) >= 0)
    check_timeouts();
}
