/*
 * sc_tptrace: driver to collect data necessary for third party traceroute
 *
 * $Id: sc_tptrace.c,v 1.34 2013/09/10 06:10:38 mjl Exp $
 *
 *       Matthew Luckie
 *       mjl@luckie.org.nz
 *
 * Copyright (C) 2013 The Regents of the University of California
 *
 * 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, version 2.
 *
 * 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 more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#ifndef lint
static const char rcsid[] =
  "$Id: sc_tptrace.c,v 1.34 2013/09/10 06:10:38 mjl Exp $";
#endif

#include <sys/time.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>

#if defined(__APPLE__)
#include <stdint.h>
#endif

#if defined(DMALLOC)
#include <dmalloc.h>
#endif

#include <assert.h>

#include "scamper_addr.h"
#include "scamper_list.h"
#include "scamper_ping.h"
#include "scamper_trace.h"
#include "scamper_dealias.h"
#include "scamper_file.h"
#include "mjl_list.h"
#include "mjl_splaytree.h"
#include "mjl_heap.h"
#include "utils.h"

typedef struct sc_target
{
  scamper_addr_t   *addr;
  int               type;
  void             *data;
  slist_t          *blocked;
  splaytree_node_t *tree_node;
} sc_target_t;

typedef struct sc_test
{
  int               type;
  void             *data;
  struct timeval    wait;
} sc_test_t;

typedef struct sc_ping
{
  scamper_addr_t   *addr;
  uint8_t           class;
  uint8_t           attempts;
} sc_ping_t;

typedef struct sc_allyconf
{
  scamper_addr_t   *a, *b;
  uint8_t           probedef;
  int               round;
} sc_allyconf_t;

typedef struct sc_ping_wait
{
  sc_ping_t        *ping;
  slist_t          *list;
  splaytree_node_t *tree_node;
} sc_ping_wait_t;

typedef struct sc_tptrace_test
{
  scamper_addr_t   *addr;
  int               attempt;
  int               mode;
  void             *data;
  uint16_t          flow;
  uint32_t          userid;
  slist_t          *hops;
} sc_tptrace_test_t;

typedef struct sc_tptrace
{
  scamper_addr_t   *addr;
  scamper_trace_t  *trace;
  sc_ping_t       **pinged;
  splaytree_node_t *tree_node;
  int               pingc;
  int               left;
} sc_tptrace_t;

typedef struct sc_link
{
  scamper_addr_t *a;
  scamper_addr_t *b;
  scamper_addr_t *ab;
} sc_link_t;

typedef struct sc_link_test
{
  sc_link_t        *link;
  uint8_t           probedef;
} sc_link_test_t;

typedef struct sc_link_wait
{
  sc_link_t        *link;
  slist_t          *list;
  splaytree_node_t *tree_node;
  uint8_t           probedef;
} sc_link_wait_t;

typedef struct sc_dump
{
  char  *descr;
  int  (*proc_trace)(scamper_trace_t *trace);
  int  (*proc_ping)(const scamper_ping_t *ping);
  int  (*proc_dealias)(const scamper_dealias_t *dealias);
  void (*finish)(void);
} sc_dump_t;

static int process_trace_1(scamper_trace_t *);
static int process_ping_1(const scamper_ping_t *);
static int process_dealias_1(const scamper_dealias_t *);

static uint32_t               options       = 0;
static char                  *address       = NULL;
static unsigned int           port          = 0;
static char                  *unix_name     = NULL;
static splaytree_t           *targets       = NULL;
static slist_t               *probelist     = NULL;
static slist_t               *waitlist      = NULL;
static heap_t                *waitheap      = NULL;
static char                  *outfile_name  = NULL;
static scamper_file_t        *outfile       = NULL;
static FILE                  *logfile       = NULL;
static scamper_file_filter_t *ffilter       = NULL;
static int                    scamper_fd    = -1;
static char                  *readbuf       = NULL;
static size_t                 readbuf_len   = 0;
static int                    data_left     = 0;
static scamper_file_t        *decode_in     = NULL;
static int                    decode_in_fd  = -1;
static int                    decode_out_fd = -1;
static int                    more          = 0;
static int                    probing       = 0;
static int                    method        = 0;
static int                    fudge         = 5000;
static int                    allyconf      = 0;
static int                    count         = 0;
static int                    count_good    = 0;
static int                    count_pinged  = 0;
static uint32_t               userid        = 1;
static struct timeval         now;
static int                    dump_id       = 0;
static char                 **dump_files;
static int                    dump_filec    = 0;
static const sc_dump_t        dump_funcs[]  = {
  {NULL, NULL, NULL, NULL},
  {"dump traces", process_trace_1, process_ping_1, process_dealias_1, NULL},
};
static int dump_funcc = sizeof(dump_funcs) / sizeof(sc_dump_t);
static splaytree_t           *tptraces      = NULL;
static splaytree_t           *link_waits    = NULL;
static splaytree_t           *ping_waits    = NULL;
static splaytree_t           *links         = NULL;
static splaytree_t           *pings         = NULL;
static splaytree_t           *allys         = NULL;

#define OPT_HELP        0x0001
#define OPT_ADDRFILE    0x0002
#define OPT_OUTFILE     0x0004
#define OPT_PORT        0x0008
#define OPT_LOG         0x0010
#define OPT_UNIX        0x0020
#define OPT_DUMP        0x0040
#define OPT_IPADDR      0x0080
#define OPT_METHOD      0x0100
#define OPT_COUNT       0x0200
#define OPT_FUDGE       0x0400
#define OPT_ALLYCONF    0x0800
#define OPT_DAEMON      0x1000

#define MODE_PING_FIRST 0x00
#define MODE_TRACE      0x01
#define MODE_PING_THRU  0x02

#define CLASS_NONE      0x00
#define CLASS_NOREP     0x01
#define CLASS_NOOPT     0x02
#define CLASS_NOTS      0x03
#define CLASS_JUN       0x04
#define CLASS_OK        0x05

#define TEST_TRACE      0x00
#define TEST_LINK       0x01
#define TEST_PING       0x02
#define TEST_ALLYCONF   0x03

#define PROBEDEF_ICMP   0x00
#define PROBEDEF_TCP    0x01
#define PROBEDEF_UDP    0x02
#define PROBEDEF_LAST   PROBEDEF_UDP

static const char *probedef_tostr[] = {"icmp-echo", "tcp-ack", "udp-dport"};

#define METHOD_UDP      0x00
#define METHOD_ICMP     0x01

#define ALLYCONF_SEC    600

static void usage(uint32_t opt_mask)
{
  fprintf(stderr,
    "usage: sc_tptrace [-D] [-a addressfile] [-A allyconf] [-c count] [-f fudge]\n"
    "                  [-i dst] [-m method] [-o warts] [-p port] [-U unix]\n"
    "                  [-l log]\n"
    "\n"
    "       sc_tptrace [-d dump] file.warts\n"
    "\n");
  return;
}

static int check_options(int argc, char *argv[])
{
  int ch; long lo;
  char *opts = "?a:A:c:d:Df:i:l:m:o:p:U:";
  char *opt_port = NULL, *opt_unix = NULL, *opt_log = NULL, *opt_dump = NULL;
  char *opt_method = NULL, *opt_count = NULL, *opt_fudge = NULL;
  char *opt_allyconf = NULL;

  while((ch = getopt(argc, argv, opts)) != -1)
    {
      switch(ch)
	{
	case 'a':
	  options |= OPT_ADDRFILE;
	  address = optarg;
	  break;

	case 'A':
	  options |= OPT_ALLYCONF;
	  opt_allyconf = optarg;
	  break;

	case 'c':
	  options |= OPT_COUNT;
	  opt_count = optarg;
	  break;

	case 'd':
	  options |= OPT_DUMP;
	  opt_dump = optarg;
	  break;

	case 'D':
	  options |= OPT_DAEMON;
	  break;

	case 'f':
	  options |= OPT_FUDGE;
	  opt_fudge = optarg;
	  break;

	case 'i':
	  options |= OPT_IPADDR;
	  address = optarg;
	  break;

	case 'l':
	  options |= OPT_LOG;
	  opt_log = optarg;
	  break;

	case 'm':
	  options |= OPT_METHOD;
	  opt_method = optarg;
	  break;

	case 'o':
	  options |= OPT_OUTFILE;
	  outfile_name = optarg;
	  break;

	case 'p':
	  options |= OPT_PORT;
	  opt_port = optarg;
	  break;

	case 'U':
	  options |= OPT_UNIX;
	  opt_unix = optarg;
	  break;

	case '?':
	default:
	  usage(0xffffffff);
	  return -1;
	}
    }

  if(options == 0)
    {
      usage(0);
      return -1;
    }

  if((options & OPT_DUMP) == 0)
    {
      if((options & OPT_OUTFILE) == 0)
	{
	  usage(OPT_OUTFILE);
	  return -1;
	}
      if((options & (OPT_PORT|OPT_UNIX)) == 0 ||
	 (options & (OPT_PORT|OPT_UNIX)) == (OPT_PORT|OPT_UNIX))
	{
	  usage(OPT_PORT|OPT_UNIX);
	  return -1;
	}
      if((options & (OPT_IPADDR|OPT_ADDRFILE)) == 0 ||
	 (options & (OPT_IPADDR|OPT_ADDRFILE)) == (OPT_IPADDR|OPT_ADDRFILE))
	{
	  usage(OPT_IPADDR|OPT_ADDRFILE);
	  return -1;
	}

      if(options & OPT_METHOD)
	{
	  if(strcasecmp(opt_method, "udp") == 0)
	    {
	      method = METHOD_UDP;
	    }
	  else if(strcasecmp(opt_method, "icmp") == 0)
	    {
	      method = METHOD_ICMP;
	    }
	  else
	    {
	      usage(OPT_METHOD);
	      return -1;
	    }
	}

      if(options & OPT_PORT)
	{
	  if(string_tolong(opt_port, &lo) != 0 || lo < 1 || lo > 65535)
	    {
	      usage(OPT_PORT);
	      return -1;
	    }
	  port = lo;
	}
      if(options & OPT_UNIX)
	{
	  unix_name = opt_unix;
	}
      if(options & OPT_COUNT)
	{
	  if(string_tolong(opt_count, &lo) != 0 || lo < 1)
	    {
	      usage(OPT_COUNT);
	      return -1;
	    }
	  count = lo;
	}
      if(options & OPT_FUDGE)
	{
	  if(string_tolong(opt_fudge, &lo) != 0 || lo < 0 || lo > 15000)
	    {
	      usage(OPT_FUDGE);
	      return -1;
	    }
	  fudge = lo;
	}
      if(options & OPT_ALLYCONF)
	{
	  if(string_tolong(opt_allyconf, &lo) != 0 || lo < 0 || lo > 10)
	    {
	      usage(OPT_ALLYCONF);
	      return -1;
	    }
	  allyconf = lo;
	}
      if(opt_log != NULL && (logfile = fopen(opt_log, "w")) == NULL)
	{
	  usage(OPT_LOG);
	  fprintf(stderr, "could not open %s\n", opt_log);
	  return -1;
	}
    }
  else
    {
      if(string_tolong(opt_dump, &lo) != 0 || lo < 1 || lo > dump_funcc)
	{
	  usage(OPT_DUMP);
	  return -1;
	}
      dump_id    = lo;
      dump_files = argv + optind;
      dump_filec = argc - optind;
    }

  return 0;
}

static int tree_to_slist(void *ptr, void *entry)
{
  if(slist_tail_push((slist_t *)ptr, entry) != NULL)
    return 0;
  return -1;
}

static void logerr(char *format, ...)
{
  va_list ap;
  char msg[131072];

  if((options & OPT_DAEMON) && logfile == NULL)
    return;

  va_start(ap, format);
  vsnprintf(msg, sizeof(msg), format, ap);
  va_end(ap);

  if((options & OPT_DAEMON) == 0)
    fprintf(stderr, "%s", msg);

  if(logfile != NULL)
    {
      fprintf(logfile, "%ld: %s", (long int)now.tv_sec, msg);
      fflush(logfile);
    }

  return;
}

static void logprint(char *format, ...)
{
  va_list ap;
  char msg[131072];

  if((options & OPT_DAEMON) && logfile == NULL)
    return;

  va_start(ap, format);
  vsnprintf(msg, sizeof(msg), format, ap);
  va_end(ap);

  if((options & OPT_DAEMON) == 0)
    printf("%s", msg);

  if(logfile != NULL)
    {
      fprintf(logfile, "%ld: %s", (long int)now.tv_sec, msg);
      fflush(logfile);
    }

  return;
}

static char *class_tostr(int class)
{
  static char *c[] = {
    "none",
    "norep",
    "noopt",
    "nots",
    "jun",
    "ok"
  };
  return c[class];
}

static void sc_link_free(sc_link_t *link)
{
  if(link == NULL)
    return;
  if(link->a != NULL) scamper_addr_free(link->a);
  if(link->b != NULL) scamper_addr_free(link->b);
  if(link->ab != NULL) scamper_addr_free(link->ab);
  free(link);
  return;
}

static sc_link_t *sc_link_alloc(scamper_addr_t *a, scamper_addr_t *b)
{
  sc_link_t *link;
  if((link = malloc_zero(sizeof(sc_link_t))) == NULL)
    return NULL;
  link->a = scamper_addr_use(a);
  link->b = scamper_addr_use(b);
  return link;
}

static int sc_link_cmp(const sc_link_t *a, const sc_link_t *b)
{
  int i;
  if((i = scamper_addr_cmp(a->a, b->a)) != 0)
    return i;
  return scamper_addr_cmp(a->b, b->b);
}

static sc_link_t *sc_link_find(scamper_addr_t *a, scamper_addr_t *b)
{
  sc_link_t fm;
  fm.a = a; fm.b = b;
  return splaytree_find(links, &fm);
}

static sc_link_t *sc_link_get(scamper_addr_t *a, scamper_addr_t *b)
{
  sc_link_t *ln;
  if((ln = sc_link_find(a, b)) != NULL)
    return ln;
  if((ln = sc_link_alloc(a, b)) == NULL || splaytree_insert(links, ln) == NULL)
    goto err;
  return ln;

 err:
  sc_link_free(ln);
  return NULL;
}

static int scamper_trace_hops2links(const scamper_trace_t *trace, slist_t *list)
{
  slist_t *lista = NULL;
  sc_link_t *link, *last_link;
  scamper_trace_hop_t *hop;
  scamper_addr_t *last = NULL;
  int i;

  assert(list != NULL && slist_count(list) == 0);

  if((lista = slist_alloc()) == NULL)
    goto err;
  for(i=trace->firsthop-1; i<trace->hop_count; i++)
    {
      if((hop = trace->hops[i]) == NULL ||
	 scamper_addr_isreserved(hop->hop_addr))
	{
	  last = NULL;
	  continue;
	}
      if(last != NULL)
	{
	  if((link = sc_link_alloc(last, hop->hop_addr)) == NULL)
	    goto err;
	  if(slist_tail_push(lista, link) == NULL)
	    goto err;
	}
      last = hop->hop_addr;
    }
  if(slist_count(lista) < 2)
    {
      slist_concat(list, lista);
      slist_free(lista);
      return 0;
    }

  slist_qsort(lista, (slist_cmp_t)sc_link_cmp);
  last_link = NULL;
  while((link = slist_head_pop(lista)) != NULL)
    {
      if(last_link == NULL || sc_link_cmp(last_link, link) != 0)
	{
	  if(slist_tail_push(list, link) == NULL)
	    goto err;
	  last_link = link;
	}
      else
	{
	  sc_link_free(link);
	}
    }
  slist_free(lista);
  return 0;

 err:
  if(lista != NULL)
    {
      while((link = slist_head_pop(lista)) != NULL)
	sc_link_free(link);
      slist_free(lista);
    }
  while((link = slist_head_pop(list)) != NULL)
    sc_link_free(link);
  return -1;
}

static int scamper_trace_hops2addrs(const scamper_trace_t *trace, slist_t *list)
{
  slist_t *lista = NULL;
  scamper_addr_t *last, *addr;
  scamper_trace_hop_t *hop;
  int i;

  assert(list != NULL && slist_count(list) == 0);

  if((lista = slist_alloc()) == NULL)
    goto err;
  for(i=trace->firsthop-1; i<trace->hop_count; i++)
    {
      if((hop = trace->hops[i]) == NULL ||
	 scamper_addr_isreserved(hop->hop_addr))
	continue;
      slist_tail_push(lista, scamper_addr_use(hop->hop_addr));
    }
  if(slist_count(lista) < 2)
    {
      slist_concat(list, lista);
      slist_free(lista);
      return 0;
    }
  
  slist_qsort(lista, (slist_cmp_t)scamper_addr_cmp);
  last = NULL;
  while((addr = slist_head_pop(lista)) != NULL)
    {
      if(last == NULL || scamper_addr_cmp(last, addr) != 0)
	{
	  slist_tail_push(list, addr);
	  last = addr;
	}
      else
	{
	  scamper_addr_free(addr);
	}
    }
  slist_free(lista);
  return 0;

 err:
  if(lista != NULL) {
    while((addr = slist_head_pop(lista)) != NULL)
      scamper_addr_free(addr);
    slist_free(lista);
  }
  while((addr = slist_head_pop(list)) != NULL)
    scamper_addr_free(addr);
  return -1;
}

static int scamper_ping_classify(const scamper_ping_t *ping)
{
  scamper_ping_reply_t *reply;
  uint16_t u16;
  for(u16=0; u16<ping->ping_sent; u16++)
    {
      if((reply = ping->ping_replies[u16]) == NULL)
	continue;

      if(SCAMPER_PING_METHOD_IS_ICMP_ECHO(ping) &&
	 SCAMPER_PING_REPLY_IS_ICMP_ECHO_REPLY(reply))
	break;
      if(SCAMPER_PING_METHOD_IS_UDP(ping) &&
	 SCAMPER_PING_REPLY_IS_ICMP_UNREACH_PORT(reply))
	break;
    }
  if(u16 == ping->ping_sent || reply == NULL)
    return CLASS_NOREP;
  else if(reply->v4ts == NULL)
    return CLASS_NOOPT;
  else if(reply->v4ts->tsc == 0)
    return CLASS_NOTS;
  else if(reply->v4ts->tsc == 4)
    return CLASS_JUN;
  return CLASS_OK;
}

static int sc_ping_cmp(const void *a, const void *b)
{
  return scamper_addr_cmp(((sc_ping_t *)a)->addr, ((sc_ping_t *)b)->addr);
}

static void sc_ping_free(sc_ping_t *ping)
{
  if(ping == NULL)
    return;
  if(ping->addr != NULL)
    scamper_addr_free(ping->addr);
  free(ping);
  return;
}

static sc_ping_t *sc_ping_alloc(scamper_addr_t *addr)
{
  sc_ping_t *ping;
  if((ping = malloc_zero(sizeof(sc_ping_t))) == NULL)
    return NULL;
  ping->addr = scamper_addr_use(addr);
  return ping;
}

static sc_ping_t *sc_ping_find(scamper_addr_t *addr)
{
  sc_ping_t fm; fm.addr = addr;
  return splaytree_find(pings, &fm);
}

static sc_ping_t *sc_ping_get(scamper_addr_t *addr)
{
  sc_ping_t *ping;
  if((ping = sc_ping_find(addr)) != NULL)
    return ping;
  if((ping = sc_ping_alloc(addr)) == NULL ||
     splaytree_insert(pings, ping) == NULL)
    {
      sc_ping_free(ping);
      return NULL;
    }
  return ping;
}

static int sc_ping_wait_cmp(const sc_ping_wait_t *a, const sc_ping_wait_t *b)
{
  return sc_ping_cmp(a->ping, b->ping);
}

static void sc_ping_wait_detach(sc_ping_wait_t *pw)
{
  if(pw != NULL && pw->tree_node != NULL)
    splaytree_remove_node(ping_waits, pw->tree_node);
  return;
}

static void sc_ping_wait_free(sc_ping_wait_t *pw)
{
  if(pw == NULL)
    return;
  if(pw->list != NULL) slist_free(pw->list);
  free(pw);
  return;
}

static sc_ping_wait_t *sc_ping_wait_find(scamper_addr_t *addr)
{
  sc_ping_wait_t fm; sc_ping_t ping;
  fm.ping = &ping; ping.addr = addr;
  return splaytree_find(ping_waits, &fm);
}

static sc_ping_wait_t *sc_ping_wait_get(scamper_addr_t *addr)
{
  sc_ping_wait_t *pw;
  if((pw = sc_ping_wait_find(addr)) != NULL)
    return pw;
  if((pw = malloc_zero(sizeof(sc_ping_wait_t))) == NULL ||
     (pw->ping = sc_ping_get(addr)) == NULL ||
     (pw->list = slist_alloc()) == NULL ||
     (pw->tree_node = splaytree_insert(ping_waits, pw)) == NULL)
    goto err;
  return pw;

 err:
  sc_ping_wait_detach(pw);
  sc_ping_wait_free(pw);
  return NULL;
}

static int sc_test_wait_cmp(const void *a, const void *b)
{
  return timeval_cmp(&((sc_test_t *)b)->wait, &((sc_test_t *)a)->wait);
}

static sc_test_t *sc_test_alloc(int type, void *data)
{
  sc_test_t *t;
  if((t = malloc_zero(sizeof(sc_test_t))) == NULL)
    return NULL;
  t->type = type;
  t->data = data;
  return t;
}

static void sc_test_free(sc_test_t *test)
{
  free(test);
  return;
}

static int sc_test_waitheap(int type, void *data, struct timeval *wait)
{
  sc_test_t *t;
  if((t = sc_test_alloc(type, data)) == NULL)
    return -1;
  timeval_cpy(&t->wait, wait);
  if(heap_insert(waitheap, t) == NULL)
    return -1;
  return 0;
}

static int sc_test_waitlist(int type, void *data)
{
  sc_test_t *t;
  if((t = sc_test_alloc(type, data)) == NULL)
    return -1;
  if(slist_tail_push(waitlist, t) == NULL)
    return -1;
  return 0;
}

static int sc_target_block(sc_target_t *tg, sc_test_t *test)
{
  if((tg->blocked == NULL && (tg->blocked = slist_alloc()) == NULL) ||
     slist_head_push(tg->blocked, test) == NULL)
    return -1;
  return 0;
}

static void sc_target_detach(sc_target_t *tg)
{
  if(tg->tree_node != NULL)
    {
      splaytree_remove_node(targets, tg->tree_node);
      tg->tree_node = NULL;
    }
  return;
}

static void sc_target_free(sc_target_t *tg)
{
  sc_test_t *bt;
  if(tg == NULL)
    return;
  assert(tg->tree_node == NULL);
  if(tg->addr != NULL)
    scamper_addr_free(tg->addr);
  if(tg->blocked != NULL)
    {
      while((bt = slist_head_pop(tg->blocked)) != NULL)
	{
	  sc_test_waitlist(bt->type, bt->data);
	  free(bt);
	}
      slist_free(tg->blocked);
    }
  free(tg);
  return;
}

static int sc_target_cmp(const sc_target_t *a, const sc_target_t *b)
{
  return scamper_addr_cmp(a->addr, b->addr);
}

static sc_target_t *sc_target_find(scamper_addr_t *addr)
{
  sc_target_t fm;
  fm.addr = addr;
  return splaytree_find(targets, &fm);
}

static sc_target_t *sc_target_get(scamper_addr_t *addr, int type, void *data)
{
  sc_target_t *tg;
  if((tg = sc_target_find(addr)) != NULL)
    return tg;
  if((tg = malloc_zero(sizeof(sc_target_t))) == NULL)
    return NULL;
  tg->addr = scamper_addr_use(addr);
  tg->type = type;
  tg->data = data;
  if((tg->tree_node = splaytree_insert(targets, tg)) == NULL)
    {
      sc_target_free(tg);
      return NULL;
    }
  return tg;
}

static void sc_link_test_free(sc_link_test_t *lt)
{
  if(lt == NULL)
    return;
  free(lt);
  return;
}

static int sc_link_test_new(sc_link_t *link)
{
  sc_link_test_t *lt;
  char a[20], b[20];

  if((lt = malloc_zero(sizeof(sc_link_test_t))) == NULL)
    goto err;
  lt->link = link;

  if(sc_test_waitlist(TEST_LINK, lt) != 0)
    {
      logerr("could not push link test %s:%s\n",
	     scamper_addr_tostr(link->a, a, sizeof(a)),
	     scamper_addr_tostr(link->b, b, sizeof(b)));
      goto err;
    }

  return 0;

 err:
  sc_link_test_free(lt);
  return -1;
}

static int sc_link_wait_cmp(const sc_link_wait_t *a, const sc_link_wait_t *b)
{
  return sc_link_cmp(a->link, b->link);
}

static void sc_link_wait_detach(sc_link_wait_t *lw)
{
  if(lw != NULL && lw->tree_node != NULL)
    splaytree_remove_node(link_waits, lw->tree_node);
  return;
}

static void sc_link_wait_free(sc_link_wait_t *lw)
{
  if(lw == NULL)
    return;
  if(lw->list != NULL) slist_free(lw->list);
  free(lw);
  return;
}

static sc_link_wait_t *sc_link_wait_find(scamper_addr_t *a, scamper_addr_t *b)
{
  sc_link_wait_t fm; sc_link_t ln; 
  fm.link = &ln; ln.a = a; ln.b = b;
  return splaytree_find(link_waits, &fm);
}

static sc_link_wait_t *sc_link_wait_get(scamper_addr_t *a, scamper_addr_t *b)
{
  sc_link_wait_t *lw;
  if((lw = sc_link_wait_find(a, b)) != NULL)
    return lw;
  if((lw = malloc_zero(sizeof(sc_link_wait_t))) == NULL ||
     (lw->link = sc_link_get(a, b)) == NULL ||
     (lw->list = slist_alloc()) == NULL ||
     (lw->tree_node = splaytree_insert(link_waits, lw)) == NULL)
    goto err;
  return lw;

 err:
  sc_link_wait_detach(lw);
  sc_link_wait_free(lw);
  return NULL;
}

static int sc_allyconf_cmp(const sc_allyconf_t *a, const sc_allyconf_t *b)
{
  int i;
  if((i = scamper_addr_cmp(a->a, b->a)) != 0)
    return i;
  if((i = scamper_addr_cmp(a->b, b->b)) != 0)
    return i;
  if(a->probedef < b->probedef)
    return -1;
  if(a->probedef > b->probedef)
    return 1;
  return 0;
}

static void sc_allyconf_free(sc_allyconf_t *ac)
{
  if(ac == NULL)
    return;
  if(ac->a != NULL) scamper_addr_free(ac->a);
  if(ac->b != NULL) scamper_addr_free(ac->b);
  free(ac);
  return;
}

static sc_allyconf_t *sc_allyconf_find(scamper_addr_t *a, scamper_addr_t *b,
				       uint8_t d)
{
  sc_allyconf_t fm;
  fm.a = a; fm.b = b; fm.probedef = d;
  return splaytree_find(allys, &fm);
}

static sc_allyconf_t *sc_allyconf_get(scamper_addr_t *a, scamper_addr_t *b,
				      uint8_t d)
{
  sc_allyconf_t *ac = NULL;

  if((ac = malloc_zero(sizeof(sc_allyconf_t))) == NULL)
    goto err;
  ac->a = scamper_addr_use(a);
  ac->b = scamper_addr_use(b);
  ac->probedef = d;
  if(splaytree_insert(allys, ac) == NULL)
    goto err;
  return ac;

 err:
  if(ac != NULL) sc_allyconf_free(ac);
  return NULL;
}

static int sc_allyconf_add(scamper_dealias_prefixscan_t *pfs)
{
  scamper_dealias_probedef_t *pd = &pfs->probedefs[0];
  sc_allyconf_t *ac = NULL;
  struct timeval tv;
  char a[20], ab[20];
  uint8_t d;

  if(pd->method == SCAMPER_DEALIAS_PROBEDEF_METHOD_UDP_DPORT)
    d = PROBEDEF_UDP;
  else if(pd->method == SCAMPER_DEALIAS_PROBEDEF_METHOD_TCP_ACK)
    d = PROBEDEF_TCP;
  else if(pd->method == SCAMPER_DEALIAS_PROBEDEF_METHOD_ICMP_ECHO)
    d = PROBEDEF_ICMP;
  else
    goto err;

  if((ac = sc_allyconf_find(pfs->a, pfs->ab, d)) != NULL)
    return 0;

  if((ac = sc_allyconf_get(pfs->a, pfs->ab, d)) == NULL)
    goto err;
  timeval_add_s(&tv, &now, ALLYCONF_SEC);
  if(sc_test_waitheap(TEST_ALLYCONF, ac, &tv) != 0)
    goto err;

  return 0;

 err:
  logerr("sc_allyconf_add failed %d %s:%s:%u", errno,
	 scamper_addr_tostr(pfs->a, a, sizeof(a)),
	 scamper_addr_tostr(pfs->ab, ab, sizeof(ab)), pd->method);
  return -1;
}

static sc_tptrace_test_t *sc_tptrace_test_free(sc_tptrace_test_t *tpt)
{
  scamper_addr_t *addr;

  if(tpt == NULL)
    return NULL;

  if(tpt->hops != NULL)
    {
      while((addr = slist_head_pop(tpt->hops)) != NULL)
	scamper_addr_free(addr);
      slist_free(tpt->hops);
    }
  if(tpt->addr != NULL)
    scamper_addr_free(tpt->addr);
  free(tpt);
  return NULL;
}

static sc_ping_t *sc_tptrace_pinged_find(const sc_tptrace_t *tp,
					 scamper_addr_t *a)
{
  sc_ping_t fm; fm.addr = a;
  return array_find((void **)tp->pinged, tp->pingc, &fm, sc_ping_cmp);
}

static void sc_tptrace_detach(sc_tptrace_t *tp)
{
  if(tp != NULL && tp->tree_node != NULL)
    splaytree_remove_node(tptraces, tp->tree_node);
  return;
}

static void sc_tptrace_free(sc_tptrace_t *tp)
{
  int i;
  if(tp == NULL)
    return;
  if(tp->addr != NULL)
    scamper_addr_free(tp->addr);
  if(tp->trace != NULL)
    scamper_trace_free(tp->trace);
  if(tp->pinged != NULL)
    {
      for(i=0; i<tp->pingc; i++)
	if(tp->pinged[i] != NULL)
	  sc_ping_free(tp->pinged[i]);
      free(tp->pinged);
    }
  free(tp);
  return;
}

static int sc_tptrace_cmp(const sc_tptrace_t *a, const sc_tptrace_t *b)
{
  return scamper_addr_cmp(a->addr, b->addr);
}

static sc_tptrace_t *sc_tptrace_find(scamper_addr_t *addr)
{
  sc_tptrace_t fm; fm.addr = addr;
  return splaytree_find(tptraces, &fm);
}

static int sc_tptrace_add(scamper_trace_t *trace)
{
  sc_tptrace_t *tp = NULL;
  sc_link_wait_t *lw;
  sc_link_t *link;
  sc_ping_wait_t *pw;
  scamper_addr_t *addr;
  slist_t *addr_list = NULL, *link_list = NULL;
  int i, count;

  if(tptraces == NULL &&
     (tptraces = splaytree_alloc((splaytree_cmp_t)sc_tptrace_cmp)) == NULL)
    goto err;

  if((addr_list = slist_alloc()) == NULL ||
     scamper_trace_hops2addrs(trace, addr_list) != 0)
    goto err;
  if((count = slist_count(addr_list)) == 0)
    {
      scamper_trace_free(trace);
      slist_free(addr_list);
      return 0;
    }

  if((tp = malloc_zero(sizeof(sc_tptrace_t))) == NULL)
    goto err;
  tp->addr = scamper_addr_use(trace->dst);

  if((tp->pinged = malloc_zero(sizeof(sc_ping_t *) * count)) == NULL)
    goto err;
  tp->pingc = count;
  for(i=0; i<count; i++)
    {
      addr = slist_head_pop(addr_list);
      if((tp->pinged[i] = sc_ping_alloc(addr)) == NULL)
	goto err;
      scamper_addr_free(addr);
      tp->left++;
      if(sc_ping_find(addr) == NULL || sc_ping_wait_find(addr) != NULL)
	{
	  if((pw = sc_ping_wait_get(addr)) == NULL ||
	     slist_tail_push(pw->list, tp) == NULL)
	    goto err;
	  tp->left++;
	}
    }
  slist_free(addr_list); addr_list = NULL;

  if((link_list = slist_alloc()) == NULL ||
     scamper_trace_hops2links(trace, link_list) != 0)
    goto err;
  while((link = slist_head_pop(link_list)) != NULL)
    {
      if(sc_link_find(link->a, link->b) == NULL ||
	 sc_link_wait_find(link->a, link->b) != NULL)
	{
	  if((lw = sc_link_wait_get(link->a, link->b)) == NULL ||
	     slist_tail_push(lw->list, tp) == NULL)
	    goto err;
	  tp->left++;
	}
      sc_link_free(link);
    }
  slist_free(link_list);

  tp->trace = trace; trace = NULL;
  if((tp->tree_node = splaytree_insert(tptraces, tp)) == NULL)
    goto err;

  return 0;

 err:
  if(addr_list != NULL)
    {
      while((addr = slist_head_pop(addr_list)) != NULL)
	scamper_addr_free(addr);
      slist_free(addr_list);
    }
  if(link_list != NULL)
    {
      while((link = slist_head_pop(link_list)) != NULL)
	sc_link_free(link);
      slist_free(link_list);
    }
  if(trace != NULL) scamper_trace_free(trace);
  return -1;
}

static int probelist_addr(char *line, void *param)
{
  struct in_addr addr;
  void *ptr = NULL;

  if(line[0] == '#' || line[0] == '\0')
    return 0;
  if(inet_aton(line, &addr) != 1 ||
     (ptr = memdup(&addr, sizeof(addr))) == NULL ||
     slist_tail_push(probelist, ptr) == NULL)
    {
      if(ptr != NULL) free(ptr);
      return -1;
    }
  return 0;
}

static void probelist_empty(void)
{
  struct in_addr *addr;
  while((addr = slist_head_pop(probelist)) != NULL)
    free(addr);
  return;
}

static sc_test_t *probelist_test(void)
{
  struct in_addr *addr = slist_head_pop(probelist);
  sc_tptrace_test_t *tpt = NULL;
  sc_test_t *t = NULL;

  assert(addr != NULL);
  if((tpt = malloc_zero(sizeof(sc_tptrace_test_t))) == NULL ||
     (tpt->addr = scamper_addr_alloc(SCAMPER_ADDR_TYPE_IPV4, addr)) == NULL)
    goto err;
  free(addr); addr = NULL;
  tpt->userid = userid++;

  if(method == METHOD_ICMP)
    {
      if(random_u16(&tpt->flow) != 0 || tpt->flow == 0)
	tpt->flow = (tpt->userid & 0xffff) | 1;
    }
  else
    {
      tpt->flow = 33435;
    }

  if((t = sc_test_alloc(TEST_TRACE, tpt)) == NULL)
    goto err;

  return t;

 err:
  if(tpt != NULL)
    {
      if(tpt->addr != NULL) scamper_addr_free(tpt->addr);
      free(tpt);
    }
  logerr("could not alloc tpt\n");
  return NULL;
}

static scamper_addr_t *do_method_trace_addr(void *data)
{
  sc_tptrace_test_t *tpt = data;
  return tpt->addr;
}

static int do_method_trace_cmd(void *data, char *cmd, size_t len)
{
  static const char *tm[] = {
    "udp-paris",
    "icmp-paris",
  };
  sc_tptrace_test_t *tpt = data;
  size_t off = 0;
  char dst[32];
  char rr[32];

  scamper_addr_tostr(tpt->addr, dst, sizeof(dst));
  if(tpt->mode == MODE_TRACE)
    {
      string_concat(cmd, len, &off, "trace -P %s -U %u -d %u %s\n",
		    tm[method], tpt->userid, tpt->flow, dst);
    }
  else
    {
      if(method == METHOD_ICMP)
	string_concat(cmd, len, &off, "ping -P icmp-echo -C %u", tpt->flow);
      else
	string_concat(cmd, len, &off, "ping -P udp -d %u", tpt->flow);
      string_concat(cmd, len, &off, " -U %u -o 1", tpt->userid);
      if(tpt->mode == MODE_PING_FIRST)
	memcpy(rr, dst, sizeof(rr));
      else
	scamper_addr_tostr(slist_head_get(tpt->hops), rr, sizeof(rr));
      string_concat(cmd, len, &off, " -T tsprespec=%s,%s,%s,%s %s\n",
		    rr, rr, rr, rr, dst);
    }

  return off;
}

static scamper_addr_t *do_method_link_addr(void *data)
{
  sc_link_test_t *lt = data;
  return lt->link->a;
}

static int do_method_link_cmd(void *data, char *cmd, size_t len)
{
  sc_link_test_t *lt = data;
  size_t off = 0;
  char a[32], b[32];

  string_concat(cmd, len, &off, "dealias -m prefixscan -W 1000 ");
  if(fudge == 0)
    string_concat(cmd, len, &off, "-O inseq ");
  else
    string_concat(cmd, len, &off, "-f %u ", fudge);
  string_concat(cmd, len, &off, "-p '-P %s' %s %s/30\n",
		probedef_tostr[lt->probedef],
		scamper_addr_tostr(lt->link->a, a, sizeof(a)),
		scamper_addr_tostr(lt->link->b, b, sizeof(b)));

  return off;
}

static scamper_addr_t *do_method_ping_addr(void *data)
{
  sc_ping_t *pt = data;
  return pt->addr;
}

static int do_method_ping_cmd(void *data, char *cmd, size_t len)
{
  sc_ping_t *pt = data;
  size_t off = 0;
  char buf[20];
  scamper_addr_tostr(pt->addr, buf, sizeof(buf));
  string_concat(cmd, len, &off, "ping -o 1 -T tsprespec=%s,%s,%s,%s %s\n",
		buf, buf, buf, buf, buf);
  return off;
}

static scamper_addr_t *do_method_allyconf_addr(void *data)
{
  sc_allyconf_t *ac = data;
  return ac->a;
}

static int do_method_allyconf_cmd(void *data, char *cmd, size_t len)
{
  sc_allyconf_t *ac = data;
  size_t off = 0;
  char a[32], b[32];

  string_concat(cmd, len, &off, "dealias -m ally -W 1000 ");
  if(fudge == 0)
    string_concat(cmd, len, &off, "-O inseq ");
  else
    string_concat(cmd, len, &off, "-f %u ", fudge);
  string_concat(cmd, len, &off, "-p '-P %s' %s %s\n",
		probedef_tostr[ac->probedef],
		scamper_addr_tostr(ac->a, a, sizeof(a)),
		scamper_addr_tostr(ac->b, b, sizeof(b)));

  return off;
}

static int do_method(void)
{
  static scamper_addr_t *(*const af[])(void *) = {
    do_method_trace_addr,
    do_method_link_addr,
    do_method_ping_addr,
    do_method_allyconf_addr,
  };
  static int (*const cf[])(void *, char *, size_t) = {
    do_method_trace_cmd,
    do_method_link_cmd,
    do_method_ping_cmd,
    do_method_allyconf_cmd,
  };
  sc_test_t *t = NULL;
  sc_target_t *tg;
  scamper_addr_t *addr;
  size_t off;
  char cmd[256];

  if(more < 1)
    return 0;

  for(;;)
    {
      if((t = heap_head_item(waitheap)) != NULL &&
	 timeval_cmp(&now, &t->wait) >= 0)
	heap_remove(waitheap);
      else if(slist_count(waitlist) > 0)
	t = slist_head_pop(waitlist);
      else if(slist_count(probelist) > 0)
	t = probelist_test();
      else
	return 0;

      if(t == NULL)
	return -1;

      addr = af[t->type](t->data);
      if((tg = sc_target_find(addr)) != NULL)
	{
	  if(tg->type == t->type && tg->data == t->data)
	    break;
	  if(sc_target_block(tg, t) != 0)
	    {
	      logerr("do_method: could not add test to blocklist");
	      return -1;
	    }
	  continue;
	}
      else if((tg = sc_target_get(addr, t->type, t->data)) == NULL)
	{
	  logerr("do_method: could not get target");
	  return -1;
	}
      break;
    }

  /* got a command, send it */
  off = cf[t->type](t->data, cmd, sizeof(cmd));
  sc_test_free(t);
  write_wrap(scamper_fd, cmd, NULL, off);
  probing++;
  more--;
  logprint("p %d, w %d, l %d : %s", probing,
	   heap_count(waitheap) + slist_count(waitlist),
	   slist_count(probelist), cmd);
  return 0;
}

static int do_decoderead_ally(scamper_dealias_t *dealias)
{
  scamper_dealias_ally_t *ally = dealias->data;
  scamper_addr_t *x = ally->probedefs[0].dst;
  scamper_addr_t *y = ally->probedefs[1].dst;
  sc_target_t *tg;
  sc_allyconf_t *ac;
  struct timeval tv;
  char a[20], b[20];

  if((tg = sc_target_find(x)) == NULL)
    {
      logerr("do_decoderead_ally: could not find %s:%s\n",
	     scamper_addr_tostr(x, a, sizeof(a)),
	     scamper_addr_tostr(y, b, sizeof(b)));
      return -1;
    }
  assert(tg->type == TEST_ALLYCONF);
  ac = tg->data;
  sc_target_detach(tg); sc_target_free(tg);

  if(++ac->round < allyconf)
    {
      timeval_add_s(&tv, &now, ALLYCONF_SEC);
      if(sc_test_waitheap(TEST_ALLYCONF, ac, &tv) != 0)
	return -1;
    }

  return 0;
}

static int do_decoderead_prefixscan(scamper_dealias_t *dealias)
{
  scamper_dealias_prefixscan_t *pfs = dealias->data;
  sc_target_t *tg;
  sc_link_test_t *lt;
  struct timeval tv;
  char a[20], b[20];

  if((tg = sc_target_find(pfs->a)) == NULL)
    {
      logerr("do_decoderead_prefixscan: could not find %s:%s\n",
	     scamper_addr_tostr(pfs->a, a, sizeof(a)),
	     scamper_addr_tostr(pfs->b, b, sizeof(b)));
      return -1;
    }
  assert(tg->type == TEST_LINK);
  lt = tg->data;

  if((pfs->ab == NULL || scamper_addr_prefixhosts(pfs->b, pfs->ab) < 30) &&
     lt->probedef != PROBEDEF_LAST)
    {
      lt->probedef++;
      timeval_add_s(&tv, &now, 1);
      if(sc_test_waitheap(TEST_LINK, lt, &tv) != 0)
	return -1;
    }
  else
    {
      if(pfs->ab != NULL && scamper_addr_prefixhosts(pfs->b, pfs->ab) >= 30 &&
	 (pfs->flags & SCAMPER_DEALIAS_PREFIXSCAN_FLAG_CSA) == 0)
	{
	  if(allyconf > 0 && sc_allyconf_add(pfs) != 0)
	    return -1;
	  lt->link->ab = scamper_addr_use(pfs->ab);
	}
      sc_target_detach(tg); sc_target_free(tg);
      sc_link_test_free(lt);
    }

  return 0;
}

static int do_decoderead_dealias(scamper_dealias_t *dealias)
{
  int rc = -1;
  if(SCAMPER_DEALIAS_METHOD_IS_PREFIXSCAN(dealias))
    rc = do_decoderead_prefixscan(dealias);
  else if(SCAMPER_DEALIAS_METHOD_IS_ALLY(dealias))
    rc = do_decoderead_ally(dealias);
  scamper_dealias_free(dealias);
  return rc;
}

static int do_decoderead_ping(scamper_ping_t *ping)
{
  sc_tptrace_test_t *tpt;
  scamper_addr_t *addr;
  sc_target_t *tg;
  sc_ping_t *pt;
  struct timeval tv;
  char buf[256];
  int c, rc = -1;

  if((tg = sc_target_find(ping->dst)) == NULL)
    {
      logerr("do_decoderead_ping: could not find dst %s\n",
	     scamper_addr_tostr(ping->dst, buf, sizeof(buf)));
      goto done;
    }
  assert(tg->type == TEST_TRACE || tg->type == TEST_PING);

  c = scamper_ping_classify(ping);

  if(tg->type == TEST_TRACE)
    {
      tpt = tg->data;

      if(tpt->mode == MODE_PING_FIRST)
	{
	  count_pinged++;
	  if(method == METHOD_ICMP)
	    {
	      if((pt = sc_ping_get(ping->dst)) == NULL)
		{
		  sc_target_detach(tg); sc_target_free(tg);
		  sc_tptrace_test_free(tpt);
		  goto done;
		}
	      if(pt->class == CLASS_NONE)
		pt->class = c;
	    }
	  if(c == CLASS_NOREP || c == CLASS_NOOPT)
	    {
	      if(count_good == 0 && count_pinged >= 100)
		probelist_empty();
	      sc_target_detach(tg); sc_target_free(tg);
	      sc_tptrace_test_free(tpt); rc = 0;
	      goto done;
	    }
	  tpt->mode++;
	  count_good++;
	  if(count > 0 && count_good >= count)
	    probelist_empty();
	}
      else
	{
	  assert(tpt->mode == MODE_PING_THRU);
	  tpt->attempt++;
	  if(((c == CLASS_NOREP || c == CLASS_NOOPT || c == CLASS_NOTS) &&
	      tpt->attempt == 2) || c == CLASS_JUN || c == CLASS_OK)
	    {
	      addr = slist_head_pop(tpt->hops);
	      scamper_addr_free(addr);
	      if(slist_count(tpt->hops) == 0)
		{
		  sc_target_detach(tg); sc_target_free(tg);
		  sc_tptrace_test_free(tpt); rc = 0;
		  goto done;
		}
	      tpt->attempt = 0;
	    }
	}

      timeval_add_s(&tv, &now, 1);
      if(sc_test_waitheap(TEST_TRACE, tpt, &tv) != 0)
	goto done;
    }
  else
    {
      pt = tg->data;
      pt->class = c;
      sc_target_detach(tg); sc_target_free(tg);
    }

  rc = 0;

 done:
  scamper_ping_free(ping);
  return rc;
}

static int do_decoderead_trace(scamper_trace_t *trace)
{
  sc_target_t *tg;
  struct timeval tv;
  sc_tptrace_test_t *tpt;
  slist_node_t *sn;
  scamper_addr_t *addr;
  sc_link_t *link;
  sc_ping_t *pt;
  slist_t *list = NULL;
  int rc = -1;
  char buf[32];

  if((tg = sc_target_find(trace->dst)) == NULL)
    {
      logerr("do_decoderead_trace: could not find dst %s\n",
	     scamper_addr_tostr(trace->dst, buf, sizeof(buf)));
      goto done;
    }
  assert(tg->type == TEST_TRACE);
  tpt = tg->data;

  /* list that holds links, and then addrs */
  if((list = slist_alloc()) == NULL)
    goto done;

  /* prefixscans */
  if(scamper_trace_hops2links(trace, list) != 0)
    goto done;
  if(slist_count(list) > 0)
    {
      while((link = slist_head_pop(list)) != NULL)
	{
	  if(sc_link_find(link->a, link->b) != NULL)
	    {
	      sc_link_free(link);
	      continue;
	    }

	  if(splaytree_insert(links, link) == NULL ||
	     sc_link_test_new(link) != 0)
	    goto done;
	}
    }

  /* addresses to specify in pre-specified timestamps to the destination */
  if(scamper_trace_hops2addrs(trace, list) != 0)
    goto done;
  if(slist_count(list) == 0)
    {
      sc_target_detach(tg); sc_target_free(tg);
      sc_tptrace_test_free(tpt); rc = 0;
      goto done;
    }
  slist_shuffle(list);

  for(sn=slist_head_node(list); sn != NULL; sn=slist_node_next(sn))
    {
      addr = slist_node_item(sn);
      if(sc_ping_find(addr) != NULL)
	continue;
      if((pt = sc_ping_get(addr)) == NULL ||
	 sc_test_waitlist(TEST_PING, pt) != 0)
	goto done;
    }

  tpt->hops = list; list = NULL;
  tpt->mode++;
  timeval_add_s(&tv, &now, 1);
  if(sc_test_waitheap(TEST_TRACE, tpt, &tv) != 0)
    goto done;

  rc = 0;

 done:
  if(list != NULL)
    {
      /* XXX: free items in the list */
      slist_free(list);
    }
  scamper_trace_free(trace);
  return rc;
}

static int do_decoderead(void)
{
  void     *data;
  uint16_t  type;

  /* try and read a traceroute from the warts decoder */
  if(scamper_file_read(decode_in, ffilter, &type, &data) != 0)
    {
      logerr("do_decoderead: scamper_file_read errno %d\n", errno);
      return -1;
    }
  if(data == NULL)
    return 0;
  probing--;

  if(scamper_file_write_obj(outfile, type, data) != 0)
    {
      logerr("do_decoderead: could not write obj %d\n", type);
      /* XXX: free data */
      return -1;
    }

  if(type == SCAMPER_FILE_OBJ_PING)
    return do_decoderead_ping(data);
  else if(type == SCAMPER_FILE_OBJ_TRACE)
    return do_decoderead_trace(data);
  else if(type == SCAMPER_FILE_OBJ_DEALIAS)
    return do_decoderead_dealias(data);

  return -1;
}

static int do_scamperread(void)
{
  ssize_t rc;
  uint8_t uu[64];
  char   *ptr, *head;
  char    buf[512];
  void   *tmp;
  long    l;
  size_t  i, uus, linelen;

  if((rc = read(scamper_fd, buf, sizeof(buf))) > 0)
    {
      if(readbuf_len == 0)
	{
	  if((readbuf = memdup(buf, rc)) == NULL)
	    {
	      logerr("do_scamperread: could not memdup %d bytes", rc);
	      return -1;
	    }
	  readbuf_len = rc;
	}
      else
	{
	  if((tmp = realloc(readbuf, readbuf_len + rc)) != NULL)
	    {
	      readbuf = tmp;
	      memcpy(readbuf+readbuf_len, buf, rc);
	      readbuf_len += rc;
	    }
	  else
	    {
	      logerr("do_scamperread: could not realloc %d bytes", readbuf_len+rc);
	      return -1;
	    }
	}
    }
  else if(rc == 0)
    {
      close(scamper_fd);
      scamper_fd = -1;
    }
  else if(errno == EINTR || errno == EAGAIN)
    {
      return 0;
    }
  else
    {
      logerr("could not read: errno %d\n", errno);
      return -1;
    }

  /* process whatever is in the readbuf */
  if(readbuf_len == 0)
    return 0;

  head = readbuf;
  for(i=0; i<readbuf_len; i++)
    {
      if(readbuf[i] == '\n')
	{
	  /* skip empty lines */
	  if(head == &readbuf[i])
	    {
	      head = &readbuf[i+1];
	      continue;
	    }

	  /* calculate the length of the line, excluding newline */
	  linelen = &readbuf[i] - head;

	  /* if currently decoding data, then pass it to uudecode */
	  if(data_left > 0)
	    {
	      uus = sizeof(uu);
	      if(uudecode_line(head, linelen, uu, &uus) != 0)
		{
		  logerr("could not uudecode_line\n");
		  return -1;
		}

	      if(uus != 0)
		write_wrap(decode_out_fd, uu, NULL, uus);

	      data_left -= (linelen + 1);
	    }
	  /* if the scamper process is asking for more tasks, give it more */
	  else if(linelen == 4 && strncasecmp(head, "MORE", linelen) == 0)
	    {
	      more++;
	      if(do_method() != 0)
		return -1;
	    }
	  /* new piece of data */
	  else if(linelen > 5 && strncasecmp(head, "DATA ", 5) == 0)
	    {
	      l = strtol(head+5, &ptr, 10);
	      if(*ptr != '\n' || l < 1)
		{
		  head[linelen] = '\0';
		  logerr("could not parse %s\n", head);
		  return -1;
		}

	      data_left = l;
	    }
	  /* feedback letting us know that the command was accepted */
	  else if(linelen >= 2 && strncasecmp(head, "OK", 2) == 0)
	    {
	      /* nothing to do */
	    }
	  /* feedback letting us know that the command was not accepted */
	  else if(linelen >= 3 && strncasecmp(head, "ERR", 3) == 0)
	    {
	      more++;
	      if(do_method() != 0)
		return -1;
	    }
	  else
	    {
	      head[linelen] = '\0';
	      logerr("unknown response '%s'\n", head);
	      return -1;
	    }

	  head = &readbuf[i+1];
	}
    }

  if(head != &readbuf[readbuf_len])
    {
      readbuf_len = &readbuf[readbuf_len] - head;
      ptr = memdup(head, readbuf_len);
      free(readbuf);
      readbuf = ptr;
    }
  else
    {
      readbuf_len = 0;
      free(readbuf);
      readbuf = NULL;
    }

  return 0;
}

static int do_scamperconnect(void)
{
  struct sockaddr_un sun;
  struct sockaddr_in sin;
  struct in_addr in;

  if(options & OPT_PORT)
    {
      inet_aton("127.0.0.1", &in);
      sockaddr_compose((struct sockaddr *)&sin, AF_INET, &in, port);
      if((scamper_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
	{
	  logerr("could not allocate new socket\n");
	  return -1;
	}
      if(connect(scamper_fd, (const struct sockaddr *)&sin, sizeof(sin)) != 0)
	{
	  logerr("could not connect to scamper process\n");
	  return -1;
	}
      return 0;
    }
  else if(options & OPT_UNIX)
    {
      if(sockaddr_compose_un((struct sockaddr *)&sun, unix_name) != 0)
	{
	  logerr("could not build sockaddr_un\n");
	  return -1;
	}
      if((scamper_fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
	{
	  logerr("could not allocate unix domain socket\n");
	  return -1;
	}
      if(connect(scamper_fd, (const struct sockaddr *)&sun, sizeof(sun)) != 0)
	{
	  logerr("could not connect to scamper process\n");
	  return -1;
	}
      return 0;
    }

  return -1;
}

static int do_files(void)
{
  int pair[2];

  if((outfile = scamper_file_open(outfile_name, 'w', "warts")) == NULL)
    return -1;

  /*
   * setup a socketpair that is used to decode warts from a binary input.
   * pair[0] is used to write to the file, while pair[1] is used by
   * the scamper_file_t routines to parse the warts data.
   */
  if(socketpair(AF_UNIX, SOCK_STREAM, 0, pair) != 0)
    return -1;

  decode_in_fd  = pair[0];
  decode_out_fd = pair[1];
  decode_in = scamper_file_openfd(decode_in_fd, NULL, 'r', "warts");
  if(decode_in == NULL)
    return -1;

  if(fcntl_set(decode_in_fd, O_NONBLOCK) == -1)
    return -1;

  return 0;
}

static int do_addresses(void)
{
  if(options & OPT_ADDRFILE)
    {
      if(file_lines(address, probelist_addr, NULL) != 0)
	return -1;
      slist_shuffle(probelist);
      return 0;
    }
  return probelist_addr(address, NULL);
}

static int tptrace_data(void)
{
  struct timeval tv, *tv_ptr;
  sc_test_t *t;
  fd_set rfds;
  int nfds;

  gettimeofday_wrap(&tv);
  srandom(tv.tv_usec);

  if((targets = splaytree_alloc((splaytree_cmp_t)sc_target_cmp)) == NULL)
    return -1;
  if((waitheap = heap_alloc(sc_test_wait_cmp)) == NULL)
    return -1;
  if((waitlist = slist_alloc()) == NULL || (probelist = slist_alloc()) == NULL)
    return -1;
  if(do_addresses() != 0)
    return -1;
  if(do_scamperconnect() != 0)
    return -1;
  if(do_files() != 0)
    return -1;

  if(write_wrap(scamper_fd, "attach\n", NULL, 7) != 0)
    {
      logerr("could not attach to scamper process\n");
      return -1;
    }

  for(;;)
    {
      nfds = 0;
      FD_ZERO(&rfds);

      if(scamper_fd < 0 && decode_in_fd < 0)
	break;

      if(scamper_fd >= 0)
	{
	  FD_SET(scamper_fd, &rfds);
	  if(nfds < scamper_fd) nfds = scamper_fd;
	}

      if(decode_in_fd >= 0)
	{
	  FD_SET(decode_in_fd, &rfds);
	  if(nfds < decode_in_fd) nfds = decode_in_fd;
	}

      /*
       * need to set a timeout on select if scamper's processing window is
       * not full and there is a trace in the waiting queue.
       */
      tv_ptr = NULL;
      if(more > 0)
	{
	  gettimeofday_wrap(&now);

	  /*
	   * if there is something ready to probe now, then try and
	   * do it.
	   */
	  t = heap_head_item(waitheap);
	  if(slist_count(probelist) > 0 || slist_count(waitlist) > 0 ||
	     (t != NULL && timeval_cmp(&t->wait, &now) <= 0))
	    {
	      if(do_method() != 0)
		return -1;
	    }

	  /*
	   * if we could not send a new command just yet, but scamper
	   * wants one, then wait for an appropriate length of time.
	   */
	  t = heap_head_item(waitheap);
	  if(more > 0 && tv_ptr == NULL && t != NULL)
	    {
	      tv_ptr = &tv;
	      if(timeval_cmp(&t->wait, &now) > 0)
		timeval_diff_tv(&tv, &now, &t->wait);
	      else
		memset(&tv, 0, sizeof(tv));
	    }
	}

      if(splaytree_count(targets) == 0 &&
	 slist_count(probelist) == 0 && heap_count(waitheap) == 0)
	{
	  logprint("done\n");
	  break;
	}

      if(select(nfds+1, &rfds, NULL, NULL, tv_ptr) < 0)
	{
	  if(errno == EINTR) continue;
	  logerr("select error\n");
	  break;
	}

      gettimeofday_wrap(&now);

      if(more > 0)
	{
	  if(do_method() != 0)
	    return -1;
	}

      if(scamper_fd >= 0 && FD_ISSET(scamper_fd, &rfds))
	{
	  if(do_scamperread() != 0)
	    return -1;
	}

      if(decode_in_fd >= 0 && FD_ISSET(decode_in_fd, &rfds))
	{
	  if(do_decoderead() != 0)
	    return -1;
	}
    }

  return 0;
}

static void tptrace_print(const sc_tptrace_t *tp)
{
  scamper_trace_t *trace = tp->trace;
  scamper_trace_hop_t *hop, *hop1;
  sc_link_t *link;
  sc_ping_t *pd;
  char buf[64];
  int i;

  printf("trace from %s", scamper_addr_tostr(trace->src, buf, sizeof(buf)));
  printf(" to %s\n", scamper_addr_tostr(trace->dst, buf, sizeof(buf)));
  for(i=trace->firsthop-1; i<trace->hop_count; i++)
    {
      printf("%3d ", i+1);
      if((hop = trace->hops[i]) == NULL)
	{
	  printf("*\n");
	  continue;
	}

      printf("%-16s", scamper_addr_tostr(hop->hop_addr, buf, sizeof(buf)));
      pd = sc_ping_find(hop->hop_addr);
      printf(" %-5s", class_tostr(pd->class));
      pd = sc_tptrace_pinged_find(tp, hop->hop_addr);
      printf(" %-5s", class_tostr(pd->class));

      if(i+1 < trace->hop_count && (hop1 = trace->hops[i+1]) != NULL &&
	 (link = sc_link_find(hop->hop_addr, hop1->hop_addr)) != NULL &&
	 link->ab != NULL)
	printf(" %s/%d", scamper_addr_tostr(link->ab, buf, sizeof(buf)),
	       scamper_addr_prefixhosts(link->b, link->ab));
      printf("\n");
    }
  printf("\n");
  return;
}

static int process_dealias_1(const scamper_dealias_t *dealias)
{
  scamper_dealias_prefixscan_t *pfs = dealias->data;
  sc_tptrace_t *tpt;
  sc_link_wait_t *lw;

  if((lw = sc_link_wait_find(pfs->a, pfs->b)) == NULL)
    return 0;

  if(pfs->ab != NULL) {
    lw->link->ab = scamper_addr_use(pfs->ab);
  } else if(lw->probedef != PROBEDEF_LAST) {
    lw->probedef++;
    return 0;
  }

  while((tpt = slist_head_pop(lw->list)) != NULL)
    {
      tpt->left--;
      if(tpt->left == 0)
	{
	  tptrace_print(tpt);
	  sc_tptrace_detach(tpt);
	  sc_tptrace_free(tpt);
	}
    }

  sc_link_wait_detach(lw);
  sc_link_wait_free(lw);

  return 0;
}

static int process_ping_1(const scamper_ping_t *ping)
{
  sc_tptrace_t *tpt;
  sc_ping_t *pd;
  sc_ping_wait_t *pw;
  int c = scamper_ping_classify(ping);

  if(ping->userid == 0 ||
     (SCAMPER_PING_METHOD_IS_ICMP_ECHO(ping) &&
      scamper_addr_cmp(ping->dst, ping->probe_tsps->ips[0]) == 0))
    {
      if((pd = sc_ping_get(ping->dst)) == NULL)
	return 0;
      pd->class = c;

      if((pw = sc_ping_wait_get(ping->dst)) != NULL)
	{
	  while((tpt = slist_head_pop(pw->list)) != NULL)
	    {
	      tpt->left--;
	      if(tpt->left == 0)
		{
		  tptrace_print(tpt);
		  sc_tptrace_detach(tpt);
		  sc_tptrace_free(tpt);
		}
	    }

	  sc_ping_wait_detach(pw);
	  sc_ping_wait_free(pw);
	}
    }

  if(ping->userid > 0)
    {
      if(ping->probe_tsps == NULL ||
	 (tpt = sc_tptrace_find(ping->dst)) == NULL ||
	 (pd = sc_tptrace_pinged_find(tpt, ping->probe_tsps->ips[0])) == NULL)
	return 0;

      pd->attempts++;
      pd->class = c;
      if(pd->class == CLASS_JUN || pd->class == CLASS_OK || pd->attempts == 2)
	tpt->left--;

      if(tpt->left == 0)
	{
	  tptrace_print(tpt);
	  sc_tptrace_detach(tpt);
	  sc_tptrace_free(tpt);
	}
    }

  return 0;
}

static int process_trace_1(scamper_trace_t *trace)
{
  return sc_tptrace_add(trace);
}

static int tptrace_read(void)
{
  scamper_file_t *in;
  char *filename;
  uint16_t type;
  void *data;
  int i;

  if((link_waits = splaytree_alloc((splaytree_cmp_t)sc_link_wait_cmp)) == NULL)
    return -1;
  if((ping_waits = splaytree_alloc((splaytree_cmp_t)sc_ping_wait_cmp)) == NULL)
    return -1;

  for(i=0; i<dump_filec; i++)
    {
      filename = dump_files[i];
      if((in = scamper_file_open(filename, 'r', NULL)) == NULL)
	{
	  fprintf(stderr,"could not open %s: %s\n", filename, strerror(errno));
	  return -1;
	}

      while(scamper_file_read(in, ffilter, &type, &data) == 0)
	{
	  /* EOF */
	  if(data == NULL)
	    break;

	  if(type == SCAMPER_FILE_OBJ_PING)
	    {
	      if(dump_funcs[dump_id].proc_ping != NULL)
		dump_funcs[dump_id].proc_ping(data);
	      scamper_ping_free(data);
	    }
	  else if(type == SCAMPER_FILE_OBJ_TRACE)
	    {
	      if(dump_funcs[dump_id].proc_trace != NULL)
		dump_funcs[dump_id].proc_trace(data);
	      else
		scamper_trace_free(data);
	    }
	  else if(type == SCAMPER_FILE_OBJ_DEALIAS)
	    {
	      if(dump_funcs[dump_id].proc_dealias != NULL)
		dump_funcs[dump_id].proc_dealias(data);
	      scamper_dealias_free(data);
	    }
	}

      scamper_file_close(in);
    }

  if(dump_funcs[dump_id].finish != NULL)
    dump_funcs[dump_id].finish();

  return 0;
}

static int tptrace_init(void)
{
  uint16_t types[] = {SCAMPER_FILE_OBJ_PING,
		      SCAMPER_FILE_OBJ_TRACE,
		      SCAMPER_FILE_OBJ_DEALIAS,
  };
  int typec   = sizeof(types) / sizeof(uint16_t);
  if((ffilter = scamper_file_filter_alloc(types, typec)) == NULL ||
     (pings = splaytree_alloc((splaytree_cmp_t)sc_ping_cmp)) == NULL ||
     (links = splaytree_alloc((splaytree_cmp_t)sc_link_cmp)) == NULL ||
     (allys = splaytree_alloc((splaytree_cmp_t)sc_allyconf_cmp)) == NULL)
    return -1;
  return 0;
}

static void cleanup(void)
{
  sc_target_t *tg;
  slist_t *list;

  if(targets != NULL)
    {
      if((list = slist_alloc()) != NULL)
	{
	  splaytree_inorder(targets, tree_to_slist, list);
	  while((tg = slist_head_pop(list)) != NULL)
	    {
	      sc_target_detach(tg);
	      sc_target_free(tg);
	    }
	  slist_free(list);
	}
      splaytree_free(targets, NULL);
      targets = NULL;
    }

  if(tptraces != NULL)
    {
      splaytree_free(tptraces, (splaytree_free_t)sc_tptrace_free);
      tptraces = NULL;
    }

  if(ping_waits != NULL)
    {
      splaytree_free(ping_waits, (splaytree_free_t)sc_ping_wait_free);
      ping_waits = NULL;
    }

  if(pings != NULL)
    {
      splaytree_free(pings, (splaytree_free_t)sc_ping_free);
      pings = NULL;
    }

  if(link_waits != NULL)
    {
      splaytree_free(link_waits, (splaytree_free_t)sc_link_wait_free);
      link_waits = NULL;
    }

  if(links != NULL)
    {
      splaytree_free(links, (splaytree_free_t)sc_link_free);
      links = NULL;
    }

  if(allys != NULL)
    {
      splaytree_free(allys, (splaytree_free_t)sc_allyconf_free);
      allys = NULL;
    }

  if(readbuf != NULL)
    {
      free(readbuf);
      readbuf = NULL;
    }

  if(probelist != NULL)
    {
      probelist_empty();
      slist_free(probelist);
      probelist = NULL;
    }

  if(waitheap != NULL)
    {
      heap_free(waitheap, NULL);
      waitheap = NULL;
    }

  if(waitlist != NULL)
    {
      slist_free(waitlist);
      waitlist = NULL;
    }

  if(outfile != NULL)
    {
      scamper_file_close(outfile);
      outfile = NULL;
    }

  if(decode_in != NULL)
    {
      scamper_file_close(decode_in);
      decode_in = NULL;
    }

  if(ffilter != NULL)
    {
      scamper_file_filter_free(ffilter);
      ffilter = NULL;
    }

  if(logfile != NULL)
    {
      fclose(logfile);
      logfile = NULL;
    }

  return;
}

int main(int argc, char *argv[])
{
#if defined(DMALLOC)
  free(malloc(1));
#endif

  atexit(cleanup);

  if(check_options(argc, argv) != 0)
    return -1;

  /* start a daemon if asked to */
  if((options & OPT_DAEMON) != 0 && daemon(1, 0) != 0)
    return -1;

  if(tptrace_init() != 0)
    return -1;

  if(options & OPT_DUMP)
    return tptrace_read();
  else
    return tptrace_data();

  return 0;
}
