/*
 *  lft_lib.c
 *  Layer Four Traceroute
 *
 *  This file is part of LFT.
 *
 *  The LFT software provided in this Distribution is
 *  Copyright 2010 VOSTROM Holdings, Inc.
 *
 *  The full text of our legal notices is contained in the file called
 *  COPYING, included with this Distribution.
 *
 */

#define _USE_32BIT_TIME_T

#include "lft_lib.h"
#include "lft_btcptrace.h"
#include "lft_icmptrace.h"
#ifndef WIN32
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <unistd.h>
#include <fcntl.h>
#endif

/*---------------------------------------------------------------------------*/
/* GraphViz output defines */
#define GVGRAPHNAME				"lftpath"
/*
#define GVHOPSTYLE_BASE				"shape = rect, penwidth=1"
#define GVHOPSTYLE_SOURCE			"shape = rect, style=filled, fillcolor=lightskyblue, color=skyblue, penwidth=2"
#define GVHOPSTYLE_TARGET_OPEN			"shape = rect, style=filled, fillcolor=palegreen, color=mediumspringgreen, penwidth=2"
#define GVHOPSTYLE_TARGET_CLOSED		"shape = rect, style=filled, fillcolor=tomato, color=orangered, penwidth=2"
#define GVHOPSTYLE_TARGET_FILTERED		"shape = rect, style=filled, fillcolor=grey, color=mediumspringgreen, penwidth=2"
#define GVHOPSTYLE_HOLE				"shape = note, style=filled, fillcolor=lightgrey, color=slategrey, penwidth=2"
#define GVHOPSTYLE_ANOMALY1			"shape = rect, style=filled, fillcolor=salmon, skew=.4, color=orangered, penwidth=2"
#define GVHOPSTYLE_ANOMALY2			"shape = rect, style=filled, fillcolor=lemonchiffon, skew=.4, color=orangered, penwidth=2"
#define GVHOPSTYLE_ANOMALY3			"shape = rect, style=filled, fillcolor=lightgrey, color=slategrey, skew=.4, penwidth=2"
*/
#define GVHOPSTYLE_BASE					"shape = none"
#define GVHOPSTYLE_SOURCE				"shape = none"
#define GVHOPSTYLE_TARGET_OPEN			"shape = none"
#define GVHOPSTYLE_TARGET_CLOSED		"shape = none"
#define GVHOPSTYLE_TARGET_FILTERED		"shape = none"
#define GVHOPSTYLE_HOLE					"shape = none"
/* Firewall - The next gateway may statefully inspect packets */
#define GVHOPSTYLE_ANOMALY1				"shape = none"
/* Firewall - The next gateway may implement a flag-based state filter */
#define GVHOPSTYLE_ANOMALY2				"shape = none"
/* 4.2-3 BSD bug - The next gateway may errantly reply with reused TTLs */
#define GVHOPSTYLE_ANOMALY3				"shape = none"

#define GV_ANOMALY1_TEXT	"Stateful Firewall"
#define GV_ANOMALY2_TEXT	"Flag-based Firewall"
#define GV_ANOMALY3_TEXT	"BSD-stack TTL Reused"

#define GVFONTSIZE				"12"
#define GVFONTNAME				"Helvetica"

static const char GVNTBEG[]="<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\"><tr><td><img src=\"";
static const char GVNTMID[]="\"/></td></tr><tr><td>";
static const char GVNTEND[]="</td></tr></table>";
static const char GVNIMG_SOURCE[]="source.png";
static const char GVNIMG_TRGOPEN[]="dest.png";
static const char GVNIMG_TRGCLOSED[]="dest_closed.png";
static const char GVNIMG_TRGFILTERED[]="dest_prohibited.png";
static const char GVNIMG_ANOMALY1[]="firewall_smart.png";
static const char GVNIMG_ANOMALY2[]="firewall_stupid.png";
static const char GVNIMG_ANOMALY3[]="firewall_stupid.png";
static const char GVNIMG_REGULAR[]="router.png";
static const char GVNIMG_HOLE[]="router_cloaked.png";
static const char GVNIMG_SEAM[]="router_seam.png";
#if defined(WIN32) || defined(_WIN32)
static const char DIRECTORY_SPLITTER='\\';
#else
static const char DIRECTORY_SPLITTER='/';
#endif
/*---------------------------------------------------------------------------*/
static const int start_dport = 33434;  /* starting port for UDP tracing (traceroute 
                                   compatibility for some targets that care */
const int maxpacklen = 16 * 1024;  /* maximum user-supplied probe length */
                                    /* Ethernet=1500, GigEther(jumbo)=9000 */
static const int minpacklen = 60;     /* minimum user-supplied probe length */

const char * icmp_messages[] = {
    "endpoint",
    "net unreachable",
    "host unreachable",
    "protocol unreachable",
    "port unreachable",
    "need fragment",
    "source fail",
    "net unknown",
    "host unknown",
    "src isolated",
    "net prohib",
    "host prohib",
    "bad tos/net",
    "bad tos/hst",
    "prohibited",
    "precedence violation",
    "precedence cutoff"
};

/* Returns a title-case ICMP name for -VV verbose output.
 * stored_icmp_type is trace_packet_info_s->icmp_type:
 *   -2 = TTL exceeded (type 11 code 0)
 *   -1 = target reached (RST/open — not an ICMP unreachable)
 *    0-15 = ICMP type-3 UNREACH sub-code */
static const char *
lft_icmp_verbose_name(int t)
{
    switch (t) {
    case -2: return "Time Exceeded";
    case  0: return "Net Unreachable";
    case  1: return "Host Unreachable";
    case  2: return "Protocol Unreachable";
    case  3: return "Port Unreachable";
    case  4: return "Fragmentation Needed";
    case  5: return "Source Route Failed";
    case  6: return "Net Unknown";
    case  7: return "Host Unknown";
    case  8: return "Source Isolated";
    case  9: return "Net Prohibited";
    case 10: return "Host Prohibited";
    case 11: return "Net Unreachable for TOS";
    case 12: return "Host Unreachable for TOS";
    case 13: return "Communication Prohibited";
    case 14: return "Precedence Violation";
    case 15: return "Precedence Cutoff";
    default: return "Unknown";
    }
}

/* Map raw on-wire ICMP type + code to a human name.
 * Used by EVT_RCVD_ICMP_ICMP/Echo where the raw struct is available. */
static const char *
lft_icmp_name_raw(int raw_type, int raw_code)
{
    if (raw_type == ICMP_TIMXCEED)  return "Time Exceeded";
    if (raw_type == ICMP_UNREACH)   return lft_icmp_verbose_name(raw_code);
    if (raw_type == ICMP_ECHOREPLY) return "Echo Reply";
    return "ICMP";
}

static char time_format[] = "%d-%b-%y %H:%M:%S %Z"; /* time display format */
static char tbuf[128];
const char *appname = "LFT";
static const int hop_info_size = 256;  /* can't be more than this */

static const unsigned int max_net_dev_input = 64; /* only take this much input */

static const int def_payload_len = 10;     /* default payload length for UDP packets */

#if defined( __CYGWIN__ ) || defined( WIN32 ) || defined(_WIN32)
static int sock = -1;
#endif

static int global_output_style=0;	/* 0 - ordinary output, 1 - xml output, 2 - GraphViz output */

void GraphVizOutput(lft_session_params * sess);
void ASCIIOutput(lft_session_params * sess);

/* =========================================================================
 * -o mode: live probe counter + SIGALRM spinner
 *
 * Display format (written to /dev/tty every 250 ms):
 *   Tracing icmp://x.x.x.x   ↓14  ↑3  ⬡6  ◎✗   ⠹
 *
 *   ↓  replies received      (bold green)
 *   ↑  in-flight probes      (cyan→yellow→red heat scale; hidden when 0)
 *   ⬡  hops discovered       (magenta hex + bold-white number)
 *   ◎✗ target not yet reached (dim red)  → ◎✔ reached (bold green)
 *   ⠹  braille spinner       (dim; pulses bold-green on target reply or
 *                             when in-flight drops to 0 with target reached)
 * ========================================================================= */
#ifndef WIN32

/* Unicode multi-byte literals */
#define OS_DOWN     "\xe2\x86\x93"   /* ↓  U+2193 */
#define OS_UP       "\xe2\x86\x91"   /* ↑  U+2191 */
#define OS_HEX      "\xe2\xac\xa1"   /* ⬡  U+2B21 */
#define OS_BULL     "\xe2\x97\x8e"   /* ◎  U+25CE */
#define OS_CROSS    "\xe2\x9c\x97"   /* ✗  U+2717 */
#define OS_CHECK    "\xe2\x9c\x94"   /* ✔  U+2714 */

/* ANSI colour escapes — mirror --watch's ncurses palette so the -o
 * progress spinner (and any other temporarily-displayed status bar)
 * has the same look as the watch-mode footer. */
#define OS_BGREEN   "\033[1;32m"
#define OS_BCYAN    "\033[1;36m"
#define OS_BBLUE    "\033[1;34m"
#define OS_BYELLOW  "\033[1;33m"
#define OS_BRED     "\033[1;31m"
#define OS_BMAGENTA "\033[1;35m"
#define OS_BWHITE   "\033[1;37m"
#define OS_DIM      "\033[2m"
#define OS_DIMRED   "\033[2;31m"
#define OS_SENT     "\033[1;38;5;75m"   /* bold medium-slate-blue (matches watch CP_SENT) */
#define OS_RST      "\033[0m"
#define OS_CLEOL    "\033[K"

static const char *os_braille[] = {
    "\xe2\xa0\x8b", "\xe2\xa0\x99", "\xe2\xa0\xb9", "\xe2\xa0\xb8",
    "\xe2\xa0\xbc", "\xe2\xa0\xb4", "\xe2\xa0\xa6", "\xe2\xa0\xa7",
    "\xe2\xa0\x87", "\xe2\xa0\x8f"
};
#define OS_BRAILLE_N 10

static volatile sig_atomic_t g_o_active        = 0;
static volatile sig_atomic_t g_o_replies       = 0;
static volatile sig_atomic_t g_o_sent          = 0;  /* cumulative probes sent */
static volatile sig_atomic_t g_o_hops          = 0;
static volatile sig_atomic_t g_o_target        = 0;
static volatile sig_atomic_t g_o_timeout_flash = 0;
static volatile sig_atomic_t g_o_target_flash  = 0;
static volatile sig_atomic_t g_o_spin_frame    = 0;

/* ---- inline progress spinner (default output style 0) ---- */
static volatile sig_atomic_t g_inline_spinning  = 0;  /* 1 = spinner char is on stdout */
static volatile sig_atomic_t g_inline_frame     = 0;  /* animation frame index */
static int g_inline_armed  = 0;   /* 1 = SIGALRM armed for inline spinner */
static int g_inline_utf8   = 0;   /* 1 = braille (3-byte), 0 = ASCII |/-\ (1-byte) */

/* ---- cursor hide/show during inline spinner ---- */
static volatile sig_atomic_t g_cursor_hidden   = 0;  /* 1 = cursor hidden via \033[?25l */
static int                   g_cursor_sigs_set = 0;  /* 1 = termination signal handlers installed */
static int                   g_cursor_atexit   = 0;  /* 1 = atexit handler already registered */
static struct sigaction      g_old_sa_sigint;
static struct sigaction      g_old_sa_sigterm;
static struct sigaction      g_old_sa_sighup;
static struct sigaction      g_old_sa_sigquit;

static const char g_ascii_spin[] = "|/-\\";
#define G_ASCII_SPIN_N 4

static int  g_o_tty_fd = -1;
static char g_o_prefix[160];       /* "Tracing <url>  " prefix string  */
static char g_o_seen_hops[256];    /* non-zero once hopno has replied   */

static struct sigaction g_o_sa_old;  /* saved SIGALRM disposition for -o spinner */

/* Saved termination-signal dispositions for the -o spinner's Ctrl-C guard */
static struct sigaction      g_o_old_sa_sigint;
static struct sigaction      g_o_old_sa_sigterm;
static struct sigaction      g_o_old_sa_sighup;
static struct sigaction      g_o_old_sa_sigquit;
static volatile sig_atomic_t g_o_sigs_set = 0;  /* 1 = handlers installed */
static int                   g_o_atexit   = 0;   /* 1 = atexit registered  */

/* Format integer with comma thousands separator into buf[size].
 * Returns chars written (not including NUL).  No heap, no stdio — safe from
 * signal handlers (where we also use snprintf, a common practical compromise). */
static int fmt_comma(char *buf, int size, int val)
{
    char tmp[24];
    int  n, i, j, next_comma;
    if (size <= 1) { if (size == 1) buf[0] = '\0'; return 0; }
    n = snprintf(tmp, sizeof(tmp), "%d", val);
    next_comma = n % 3;
    if (next_comma == 0) next_comma = 3;
    j = 0;
    for (i = 0; i < n; i++) {
        if (i == next_comma && j < size - 1) {
            buf[j++] = ',';
            next_comma += 3;
        }
        if (j < size - 1)
            buf[j++] = tmp[i];
    }
    if (j < size) buf[j] = '\0';
    return j;
}

static void o_spinner_sigalrm(int sig)
{
    (void)sig;

    /* Inline progress spinner for default output style (style 0).
     * Overwrites the single spinner glyph in-place on stdout using
     * backspace + next frame.  All I/O via write() (async-signal-safe). */
    if (g_inline_spinning) {
        int frame = (int)(g_inline_frame++);
        char buf[8];
        int  n;
        if (g_inline_utf8) {
            const char *sp = os_braille[frame % OS_BRAILLE_N];
            buf[0] = '\b'; buf[1] = '\b';   /* back over trailing space + glyph */
            memcpy(buf + 2, sp, 3);
            buf[5] = ' ';                    /* re-emit trailing space */
            n = 6;
        } else {
            buf[0] = '\b'; buf[1] = '\b';   /* back over trailing space + glyph */
            buf[2] = g_ascii_spin[frame % G_ASCII_SPIN_N];
            buf[3] = ' ';                    /* re-emit trailing space */
            n = 4;
        }
        write(STDOUT_FILENO, buf, (size_t)n);
        return;
    }

    if (!g_o_active || g_o_tty_fd < 0) return;

    int frame   = (int)(g_o_spin_frame++);
    int replies = (int)g_o_replies;
    int sent    = (int)g_o_sent;
    int hops    = (int)g_o_hops;
    int tgt     = (int)g_o_target;
    int tfail   = (int)g_o_timeout_flash;
    int treply  = (int)g_o_target_flash;

    /* consume one-frame flash flags */
    if (tfail)  g_o_timeout_flash = 0;
    if (treply) g_o_target_flash  = 0;

    /* format counts with comma thousands separators */
    char c_replies[16], c_sent[16], c_hops[16];
    fmt_comma(c_replies, sizeof(c_replies), replies);
    fmt_comma(c_sent,    sizeof(c_sent),    sent);
    fmt_comma(c_hops,    sizeof(c_hops),    hops);

    char buf[512];
    int  n = 0;

    /* Rewrite whole line from column 0 */
    n += snprintf(buf + n, sizeof(buf) - n, "\r%s", g_o_prefix);

    /* ↓ replies (bold green) */
    n += snprintf(buf + n, sizeof(buf) - n,
                  OS_BGREEN OS_DOWN "%s" OS_RST, c_replies);

    /* ↑ total sent (medium slate blue — matches watch-mode CP_SENT) */
    n += snprintf(buf + n, sizeof(buf) - n,
                  "  " OS_SENT OS_UP "%s" OS_RST, c_sent);

    /* ⬡ hops (bold cyan — glyph + space + number unified) */
    n += snprintf(buf + n, sizeof(buf) - n,
                  "  " OS_BCYAN OS_HEX " %s" OS_RST, c_hops);

    /* target status: ✗ bold-red until reached, ✔ bold-green once reached
     * (matches --watch's sticky target mark in the status-bar counter row). */
    if (tgt)
        n += snprintf(buf + n, sizeof(buf) - n,
                      "  " OS_BGREEN OS_CHECK OS_RST);
    else
        n += snprintf(buf + n, sizeof(buf) - n,
                      "  " OS_BRED OS_CROSS OS_RST);

    /* spinner: bright white always; brief red flash on timeout */
    const char *spin = os_braille[frame % OS_BRAILLE_N];
    const char *spin_color = tfail ? OS_BRED : OS_BWHITE;
    n += snprintf(buf + n, sizeof(buf) - n,
                  "  %s%s" OS_RST, spin_color, spin);

    /* clear remainder of line */
    n += snprintf(buf + n, sizeof(buf) - n, OS_CLEOL);

    write(g_o_tty_fd, buf, (size_t)n);
}

/* Common init shared by lft_o_spinner_start and lft_o_spinner_start_prefix. */
static void o_spinner_arm_sigalrm(void)
{
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = o_spinner_sigalrm;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;
    sigaction(SIGALRM, &sa, &g_o_sa_old);

    struct itimerval itv;
    itv.it_value.tv_sec     = 0; itv.it_value.tv_usec    = 250000;
    itv.it_interval.tv_sec  = 0; itv.it_interval.tv_usec = 250000;
    setitimer(ITIMER_REAL, &itv, NULL);
}

/* Signal handler: Ctrl-C (and SIGTERM/HUP/QUIT) while the -o spinner is live.
 * Stops the spinner (erases the status line, restores the cursor), reinstates
 * the previous handlers, then re-raises so the process exits with the right
 * status (SIG_DFL termination, coredump for SIGQUIT, etc.).
 * All calls here are async-signal-safe: lft_o_spinner_stop uses only
 * write(), close(), setitimer(), sigaction(), and volatile flag stores. */
static void o_spinner_sig(int sig)
{
    lft_o_spinner_stop();   /* erase line + restore cursor before we die */
    if (g_o_sigs_set) {
        sigaction(SIGINT,  &g_o_old_sa_sigint,  NULL);
        sigaction(SIGTERM, &g_o_old_sa_sigterm, NULL);
        sigaction(SIGHUP,  &g_o_old_sa_sighup,  NULL);
        sigaction(SIGQUIT, &g_o_old_sa_sigquit, NULL);
        g_o_sigs_set = 0;
    }
    raise(sig);
}

/* atexit backstop: catches exit() paths not reached by the signal handler
 * (e.g. a fatal error in LFTExecute that calls exit() directly). */
static void o_spinner_atexit(void)
{
    lft_o_spinner_stop();
}

/* Start the spinner with a caller-supplied prefix string (e.g. watch mode
 * startup message).  The prefix is copied into g_o_prefix as-is. */
void lft_o_spinner_start_prefix(const char *prefix)
{
    if (!isatty(STDOUT_FILENO)) return;   /* stdout redirected — no spinner */

    snprintf(g_o_prefix, sizeof(g_o_prefix), "%s", prefix);

    /* reset all counters */
    g_o_replies = g_o_sent = g_o_hops = 0;
    g_o_target  = g_o_timeout_flash = g_o_target_flash = 0;
    g_o_spin_frame = 0;
    memset(g_o_seen_hops, 0, sizeof(g_o_seen_hops));

    g_o_tty_fd = open("/dev/tty", O_WRONLY);
    if (g_o_tty_fd < 0) return;   /* no tty — skip spinner silently */

    /* Hide the block cursor while the spinner runs, and install handlers for
     * SIGINT/SIGTERM/SIGHUP/SIGQUIT so the cursor is restored before the
     * process terminates (Ctrl-C, kill, hangup, etc.). */
    write(g_o_tty_fd, "\033[?25l", 6);

    if (!g_o_sigs_set) {
        struct sigaction sa;
        memset(&sa, 0, sizeof(sa));
        sa.sa_handler = o_spinner_sig;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = 0;
        sigaction(SIGINT,  &sa, &g_o_old_sa_sigint);
        sigaction(SIGTERM, &sa, &g_o_old_sa_sigterm);
        sigaction(SIGHUP,  &sa, &g_o_old_sa_sighup);
        sigaction(SIGQUIT, &sa, &g_o_old_sa_sigquit);
        g_o_sigs_set = 1;
    }
    if (!g_o_atexit) {
        atexit(o_spinner_atexit);
        g_o_atexit = 1;
    }

    /* print initial placeholder; SIGALRM handler will overwrite from \r */
    printf("%s" OS_BWHITE "-" OS_RST, g_o_prefix);
    fflush(stdout);

    o_spinner_arm_sigalrm();
    g_o_active = 1;
}

void lft_o_spinner_start(lft_session_params *sess)
{
    /* Use hostname (as typed by user); remote_address may be 0.0.0.0 before
     * LFTExecute runs DNS resolution */
    const char *tgt = (sess->hostname && sess->hostname[0])
                      ? sess->hostname : inet_ntoa(sess->remote_address);
    char url[80];
    switch (sess->protocol) {
        case 1:
            snprintf(url, sizeof(url), "udp://%s:%d",  tgt, sess->dport); break;
        case 2: case 3:
            snprintf(url, sizeof(url), "icmp://%s",    tgt);               break;
        default:
            snprintf(url, sizeof(url), "tcp://%s:%d",  tgt, sess->dport); break;
    }
    char prefix[200];
    snprintf(prefix, sizeof(prefix), "Tracing %s  ", url);
    lft_o_spinner_start_prefix(prefix);
}

void lft_o_spinner_stop(void)
{
    if (!g_o_active) return;
    g_o_active = 0;

    struct itimerval zero;
    memset(&zero, 0, sizeof(zero));
    setitimer(ITIMER_REAL, &zero, NULL);
    sigaction(SIGALRM, &g_o_sa_old, NULL);

    /* Restore termination signal handlers installed in lft_o_spinner_start_prefix */
    if (g_o_sigs_set) {
        sigaction(SIGINT,  &g_o_old_sa_sigint,  NULL);
        sigaction(SIGTERM, &g_o_old_sa_sigterm, NULL);
        sigaction(SIGHUP,  &g_o_old_sa_sighup,  NULL);
        sigaction(SIGQUIT, &g_o_old_sa_sigquit, NULL);
        g_o_sigs_set = 0;
    }

    if (g_o_tty_fd >= 0) {
        /* erase the spinner line completely, restore cursor */
        write(g_o_tty_fd, "\r" OS_CLEOL "\033[?25h",
              sizeof("\r" OS_CLEOL "\033[?25h") - 1);
        close(g_o_tty_fd);
        g_o_tty_fd = -1;
    }
}

/* Enable counter accumulation without arming SIGALRM.
 * Used by watch mode: lft_o_counters_activate() before LFTExecute,
 * watch_spinner_arm() for the /dev/tty spinner — two separate steps. */
void lft_o_counters_activate(void)
{
    g_o_replies = g_o_sent = g_o_hops = 0;
    g_o_target  = g_o_timeout_flash = g_o_target_flash = 0;
    g_o_spin_frame = 0;
    memset(g_o_seen_hops, 0, sizeof(g_o_seen_hops));
    g_o_active = 1;
}

/* Disable counter accumulation (g_o_active → 0).  Values are preserved
 * so callers can read them via lft_o_get_*() immediately after. */
void lft_o_counters_deactivate(void)
{
    g_o_active = 0;
}

/* Getters — trivial reads of volatile sig_atomic_t globals. */
int lft_o_get_replies(void) { return (int)g_o_replies; }
int lft_o_get_sent(void)    { return (int)g_o_sent;    }
int lft_o_get_hops(void)    { return (int)g_o_hops;    }
int lft_o_get_target(void)  { return (int)g_o_target;  }

/* Read and clear the timeout-flash flag (one-shot consumption). */
int lft_o_consume_tfail(void)
{
    int v = (int)g_o_timeout_flash;
    if (v) g_o_timeout_flash = 0;
    return v;
}

#endif /* !WIN32 */

/* Called from send paths in lft_icmptrace.c / lft_btcptrace.c — must be
   non-static so other translation units can link to it.  No-op on Windows. */
void lft_o_probe_sent(void)
{
#ifndef WIN32
    if (g_o_active) g_o_sent++;
#endif
}

/*---------------------------------------------------------------------------*/
static void LFTDefaultErrorHandler(lft_session_params * sess, int code, const void * params)
{
    const WrnBadHopStateParam * wbhsp;
    switch(code)
    {
    case WRN_CANT_SETUP_FIN:
		if(global_output_style==1)
			printf("<warning code=\"%d\">%s</warning>",code,
				   "TCP flags are selected automatically using (-E) option.\n\t\t\tIgnoring FINs-only (-F) option and using adaptive Engine.\n");
		else
			fprintf (stderr,
					 "LFT warning: TCP flags are selected automatically using (-E) option.\n\t\t\tIgnoring FINs-only (-F) option and using adaptive Engine.\n");
        break;
    case WRN_CANT_DISP_HOST_NAMES:
		if(global_output_style==1)
			printf("<warning code=\"%d\">%s</warning>",code,
				   "I can't display hostnames (-h) unless I resolve them.\n\t\t\tIgnoring your request to display hostnames exclusively.\n");
		else
			fprintf (stderr,
					 "LFT warning: I can't display hostnames (-h) unless I resolve them.\n\t\t\tIgnoring your request to display hostnames exclusively.\n");
        break;
    case WRN_ADAPTIVE_DISABLED_BY_UDP:
		if(global_output_style==1)
			printf("<warning code=\"%d\">%s</warning>",code,"Disabling adaptive mode while using UDP.");
		else
			fprintf (stderr,"LFT warning: Disabling adaptive mode while using UDP.\n");
        break;
    case WRN_FIN_DISABLED_BY_UDP:
		if(global_output_style==1)
			printf("<warning code=\"%d\">%s</warning>",code,"Disabling FINs-only mode while using UDP.");
		else
			fprintf (stderr,"LFT warning: Disabling FINs-only mode while using UDP.\n");
        break;
    case WRN_ONLY_ONE_ASN_LOOKUP:
		if(global_output_style==1)
			printf("<warning code=\"%d\">%s",code,"Only one ASN lookup source may be used--you selected ");
		else
			fprintf (stderr,"LFT warning: Only one ASN lookup source may be used--you selected ");
		if(global_output_style!=1)
		{
			if(sess->use_cymru)
				fprintf (stderr,"Cymru.\n\t\t\tIgnoring your request to use ");
			if(sess->use_ris)
				fprintf (stderr,"RIPE NCC RIS.\n\t\t\tIgnoring your request to use ");
			if(sess->use_radb)
				fprintf (stderr,"RADB.\n\t\t\tIgnoring your request to use ");
			switch(*((const int *)params))
			{
				case ASN_LOOKUP_RIS:
					fprintf (stderr,"RIPE NCC RIS.\n");
					break;
				case ASN_LOOKUP_RADB:
					fprintf (stderr,"RADB.\n");
					break;
				case ASN_LOOKUP_CYMRU:
					fprintf (stderr,"Cymru.\n");
					break;
			}
			if(global_output_style==1)
				printf("</warning>");
		}
		break;
    case WRN_UDP_PORT_TOO_HIGH:
			if(global_output_style==1)
				printf("<warning code=\"%d\">Starting UDP port %d is too high.  Will start with %d instead.</warning>",
					   code, *((const int *)params), sess->dport);
			else
				fprintf (stderr,
						 "LFT warning: Starting UDP port %d is too high.  Will start with %d instead.\n", *((const int *)params), sess->dport);
			break;
    case WRN_PACKET_LENGTH_TOO_HIGH:
			if(global_output_style==1)
				printf("<warning code=\"%d\">Packet length %d is too high.  Will use %d instead.</warning>",
					   code, *((const int *)params), maxpacklen);
			else
				fprintf (stderr,
						 "LFT warning: Packet length %d is too high.  Will use %d instead.\n", *((const int *)params), maxpacklen);
			break;
    case WRN_PACKET_LENGTH_TOO_LOW:
			if(global_output_style==1)
				printf("<warning code=\"%d\">Packet length %d is too low.  Will use %d instead.</warning>",
					   code, *((const int *)params), minpacklen);
			else
				fprintf (stderr,
						 "LFT warning: Packet length %d is too low.  Will use %d instead.\n", *((const int *)params), minpacklen);
			break;
    case WRN_CANT_DISABLE_RESOLVER:
			if(global_output_style==1)
				printf("<warning code=\"%d\">%s</warning>",code,
					   "LFT warning: I can't display hostnames (-h) unless I resolve them.\n\t\t\tIgnoring your request not to resolve DNS.\n");
			else
				fprintf (stderr,
						 "LFT warning: I can't display hostnames (-h) unless I resolve them.\n\t\t\tIgnoring your request not to resolve DNS.\n");
			break;
    case WRN_ALREADY_RANDOM_SPORT:
			if(global_output_style==1)
				printf("<warning code=\"%d\">%s</warning>",code,
					   "LFT warning: You already asked to use a random source port.\n\t\t\tIgnoring request to set specific source port.\n");
			else
				fprintf (stderr,
						 "LFT warning: You already asked to use a random source port.\n\t\t\tIgnoring request to set specific source port.\n");
			break;
    case WRN_ADAPTIVE_DISABLED_BY_FIN:
			if(global_output_style==1)
				printf("<warning code=\"%d\">%s</warning>",code,
					   "LFT warning: TCP flags are selected automatically using (-E) option.\n\t\t\tIgnoring adaptive Engine (-E) option and using FINs-only (-F).\n");
			else
				fprintf (stderr,
						 "LFT warning: TCP flags are selected automatically using (-E) option.\n\t\t\tIgnoring adaptive Engine (-E) option and using FINs-only (-F).\n");
			break;
    case ERR_DEVNAME_TOO_LONG:
			if(global_output_style==1)
				printf("<error code=\"%d\">Net interface names are limited to %d characters. (e.g., \"eth0\" or \"ppp0\" or \"10.10.10.10\")</error>",code,
					   max_net_dev_input);
			else
				fprintf (stderr,
						 "Net interface names are limited to %d characters. (e.g., \"eth0\" or \"ppp0\" or \"10.10.10.10\")\n",
						 max_net_dev_input);
			sess->exit_state=-1;
			break;
    case WRN_UNABLE_SETUP_UTC:
			if(global_output_style==1)
				printf("<warning code=\"%d\">%s</warning>",code,"Unable to set TZ to UTC.");
			else
				fprintf(stderr, "LFT: Unable to set TZ to UTC.\n");
			break;
    case ERR_UNKNOWN_HOST:
			if(global_output_style==1)
				printf("<error code=\"%d\">Unknown host: %s</error>",code,(const char *)params);
			else
				fprintf (stderr, "LFT: Unknown host: %s\n", (const char *)params);
			sess->exit_state=-2;
			break;
    case WRN_GETIFFORREMOTE_SOCKET:
			if(global_output_style==1)
#if defined( __CYGWIN__ ) || defined( WIN32 ) || defined(_WIN32)
				printf("<warning code=\"%d\">socket: %s</warning>",code,strerror(errno));
#else
			printf("<warning code=\"%d\">Socket trouble; unable to determine interface</warning>",code);
#endif
			else
#if defined( __CYGWIN__ ) || defined( WIN32 ) || defined(_WIN32)
				perror("socket");
#else
			perror("LFT: Socket trouble; unable to determine interface");
#endif
			break;
    case WRN_GETIFFORREMOTE_CONNECT:
			if(global_output_style==1)
				printf("<warning code=\"%d\">UDP connect(); unable to determine interface: %s</warning>",code,strerror(errno));
			else
				perror("LFT: UDP connect(); unable to determine interface");
			break;
    case WRN_GETIFFORREMOTE_SOCKNAME:
			if(global_output_style==1)
				printf("<warning code=\"%d\">getsockname: %s</warning>",code,strerror(errno));
			else
				perror("getsockname");
			break;
    case ERR_RAW_SOCKET:
			if(global_output_style==1)
				printf("<error code=\"%d\">raw socket: %s</error>",code,strerror(errno));
			else {
				perror ("LFT: raw socket");
#if defined(linux) || defined(__linux__)
				fprintf(stderr, "LFT: On Linux, grant capabilities with:\n"
				                "       setcap cap_net_raw,cap_net_admin=eip /usr/sbin/lft\n"
				                "     or install lft setuid root (chown root lft && chmod u+s lft)\n");
#else
				fprintf(stderr, "LFT: Install lft setuid root (chown root lft && chmod u+s lft)\n");
#endif
			}
			sess->exit_state=-3;
			break;
    case ERR_SOCKET_BIND:
			if(global_output_style==1)
				printf("<error code=\"%d\">bind: %s</error>",code,strerror(errno));
			else
				perror ("LFT: bind");
			sess->exit_state=-4;
			break;
    case WRN_WSAIOCTL:
			if(global_output_style==1)
				printf("<warning code=\"%d\">WSAIoctl: %s</warning>",code,strerror(errno));
			else
				perror("LFT: WSAIoctl");
			break;
    case ERR_IP_HDRINCL:
			if(global_output_style==1)
				printf("<error code=\"%d\">IP_HDRINCL: %s</error>",code,strerror(errno));
			else
				perror ("LFT: IP_HDRINCL");
			sess->exit_state=-5;
			break;
    case ERR_NOT_ENOUGH_MEM:
			if(global_output_style==1)
				printf("<error code=\"%d\">malloc(): %s</error>",code,strerror(errno));
			else
				perror("malloc");
			sess->exit_state=-6;
			break;
    case ERR_RAW_TCP_DISABLED:
		if(global_output_style==1)
		{
			printf("<error code=\"%d\">sendto: %s\n",code,strerror(errno));
#if defined( __CYGWIN__ ) || defined( WIN32 ) || defined(_WIN32)
			if (!sess->protocol) {
				printf("Your platform may prevent you from using raw TCP sockets.\n");
				printf("Try UDP-based tracing instead using the \"-u\" option.\n");
			}
#else
			printf("Your platform may prevent you from using raw TCP sockets.\n");
			printf("This could be the result of a local (host-based) firewall\n");
			printf("or a permissions problem on this binary or the BPF device.\n");
			if(sess->adaptive)
				printf("You can try TCP-based tracing without using adaptive mode.\n");
			printf("You can try UDP-based tracing using the \"-u\" option.\n");
#endif
			printf("</error>\n");
		}
		else
		{
#if defined( __CYGWIN__ ) || defined( WIN32 ) || defined(_WIN32)
			perror ("sendto");
			if (!sess->protocol) {
				fprintf(stderr,"LFT:  Your platform may prevent you from using raw TCP sockets.\n");
				fprintf(stderr,"      Try UDP-based tracing instead using the \"-u\" option.\n");
			}
#else
			perror ("sendto");
			fprintf(stderr,"LFT:  Your platform may prevent you from using raw TCP sockets.\n");
			fprintf(stderr,"      This could be the result of a local (host-based) firewall\n");
			fprintf(stderr,"      or a permissions problem on this binary or the BPF device.\n");
			if (sess->adaptive)
				fprintf(stderr,"      You can try TCP-based tracing without using adaptive mode.\n");
			fprintf(stderr,"      You can try UDP-based tracing using the \"-u\" option.\n");
#endif
		}
		sess->exit_state=-7;
        break;
    case WRN_BAD_HOP_STATE:
			wbhsp=(const WrnBadHopStateParam *)params;
			if(global_output_style==1)
				printf("<warning code=\"%d\">Bad state %#x for hop %d</warning>", code, wbhsp->h->state, wbhsp->nhop);
			else
				fprintf(stderr,"Bad state %#x for hop %d\n", wbhsp->h->state, wbhsp->nhop);
			break;
    case WRN_NS_LOOKUP_FAILED:
			if(global_output_style==1)
			{
				printf("<warning code=\"%d\">", code);
				if(sess->use_radb)
					printf ("The RADB lookup failed.");
				else
					if(sess->use_cymru)
						printf ("The Cymru lookup failed.");
					else
						if(sess->use_ris)
							printf ("The RIPE NCC RIS lookup failed.");
						else
							printf ("The Prefix WhoIs lookup failed.");
				printf("</warning>");
			}
			else
			{
				if(sess->use_radb)
					fprintf(stderr,"The RADB lookup failed.\n");
				else
					if(sess->use_cymru)
						fprintf(stderr,"The Cymru lookup failed.\n");
					else
						if(sess->use_ris)
							fprintf(stderr,"The RIPE NCC RIS lookup failed.\n");
						else
							fprintf(stderr,"The Prefix WhoIs lookup failed.\n");
			}
			break;
    case ERR_WIN_SELECT:
			if(global_output_style==1)
				printf("<error code=\"%d\">select: %s</error>",code,strerror(errno));
			else
				perror("select");
			sess->exit_state=-8;
			break;
    case ERR_WIN_RECV:
			if(global_output_style==1)
				printf("<error code=\"%d\">read: %s</error>",code,strerror(errno));
			else
				perror("read");
			sess->exit_state=-9;
			break;
    case ERR_WIN_WSASTARTUP:
			if(global_output_style==1)
				printf("<error code=\"%d\">WSAStartup: %s</error>",code,strerror(errno));
			else
				perror("WSAStartup");
			sess->exit_state=-10;
			break;
    case ERR_PCAP_ERROR:
			if(global_output_style==1)
				printf("<error code=\"%d\">%s</error>",code,(const char *)params);
			else
				fprintf (stderr, "LFT: %s\n", (const char *)params);
			sess->exit_state=-11;
			break;
    case ERR_DISCOVER_INTERFACE:
			if(global_output_style==1)
				printf("<error code=\"%d\">Failed to discover an appropriate interface</error>",code);
			else
				fprintf (stderr, "LFT: Failed to discover an appropriate interface.\n");
			sess->exit_state=-12;
			break;
    case ERR_UNKNOWN_INTERFACE:
			if(global_output_style==1)
				printf("<error code=\"%d\">Unable to locate a local interface with IP address %s</error>",code,sess->userdev);
			else
				fprintf (stderr, "LFT: Unable to locate a local interface with IP address %s\n", sess->userdev);
			sess->exit_state=-13;
			break;
    case ERR_PCAP_DEV_UNAVAILABLE:
			if(global_output_style==1)
				printf("<error code=\"%d\">The network device \"%s\" isn\'t available to LFT.  Try another or fix:\nERROR: %s</error>",code,sess->pcap_dev,(const char *)params);
			else
				fprintf (stderr, "The network device \"%s\" isn\'t available to LFT.  Try another or fix:\nERROR: %s\n", sess->pcap_dev, (const char *)params);
			sess->exit_state=-14;
			break;
	case WRN_BIOCIMMEDIATE:
			if(global_output_style==1)
				printf("<warning code=\"%d\">BIOCIMMEDIATE: %s</warning>", code, (const char *)params);
			else
				fprintf(stderr, "BIOCIMMEDIATE: %s\n",(const char *)params);
			sess->exit_state=-34;
			break;
	case WRN_OCHECK_OPEN_SOCK:
		if(global_output_style!=1 && sess->noisy>1)
			fprintf(stderr, "LFT: Error opening socket.\n");
		if(global_output_style==1)
			printf("<warning code=\"%d\">Error opening socket</warning>", code);
        break;
	case WRN_OCHECK_IOCTL:
		if(global_output_style!=1 && sess->noisy>1)
			fprintf(stderr, "LFT: Error setting nonblocking mode (IOCTL).\n");
		if(global_output_style==1)
			printf("<warning code=\"%d\">Error setting nonblocking mode (IOCTL)</warning>", code);
        break;
	case WRN_OCHECK_SELECT:
		if(global_output_style!=1 && sess->noisy>1)
			fprintf(stderr, "LFT: Error on socket select call.\n");
		if(global_output_style==1)
			printf("<warning code=\"%d\">Error on socket select call</warning>", code);
        break;
	case WRN_OCHECK_GETERROR:
		if(global_output_style!=1 && sess->noisy>1)
			fprintf(stderr, "LFT: Error trying to read socket error.\n");
		if(global_output_style==1)
			printf("<warning code=\"%d\">Error trying to read socket error</warning>", code);
        break;
	case WRN_OCHECK_SOCKERROR:
		if(global_output_style!=1 && sess->noisy>1)
			fprintf(stderr, "LFT: Error on socket.\n");
		if(global_output_style==1)
			printf("<warning code=\"%d\">Error on socket</warning>", code);
        break;
	case WRN_OCHECK_TIMEOUT:
		if(global_output_style!=1 && sess->noisy>1)
			fprintf(stderr, "LFT: Timeout on socket.\n");
		if(global_output_style==1)
			printf("<warning code=\"%d\">Timeout on socket</warning>", code);
        break;
	case WRN_OCHECK_FCNTLGET:
		if(global_output_style!=1 && sess->noisy>1)
			fprintf(stderr, "LFT: Error setting nonblocking mode (FCNTL GET).\n");
		if(global_output_style==1)
			printf("<warning code=\"%d\">Error setting nonblocking mode (FCNTL GET)</warning>", code);
        break;
	case WRN_OCHECK_FCNTLSET:
		if(global_output_style!=1 && sess->noisy>1)
			fprintf(stderr, "LFT: Error setting nonblocking mode (FCNTL SET).\n");
		if(global_output_style==1)
			printf("<warning code=\"%d\">Error setting nonblocking mode (FCNTL SET)</warning>", code);
        break;
	case WRN_OCHECK_CONNECTERR:
		if(global_output_style!=1 && sess->noisy>1)
			fprintf(stderr, "LFT: Error trying socket connect.\n");
		if(global_output_style==1)
			printf("<warning code=\"%d\">Error trying socket connect</warning>", code);
        break;
	case ERR_PCAP_NONBLOCK_ERROR:
			if(global_output_style==1)
				printf("<error code=\"%d\">%s</error>",code,(const char *)params);
			else
				fprintf (stderr, "LFT: Failed to set nonblocking mode.\n     %s\n", (const char *)params);
			sess->exit_state=-35;
			break;
	case ERR_PCAP_ACTIVATE_ERROR:
			if(global_output_style==1)
				printf("<error code=\"%d\">Failed to activate capture on device \"%s\": %s</error>",code,sess->pcap_dev,(const char *)params);
			else
				fprintf(stderr, "LFT: Failed to activate capture on device \"%s\".  Try another or fix:\n     %s\n",sess->pcap_dev,(const char *)params);
			sess->exit_state=-36;
			break;
	case WRN_PCAP_ACTIVATE:
			if(global_output_style==1)
				printf("<warning code=\"%d\">Warning activating capture on device \"%s\": %s</warning>",code,sess->pcap_dev,(const char *)params);
			else
				fprintf(stderr, "LFT: Warning activating capture on device \"%s\": %s\n",sess->pcap_dev,(const char *)params);
			/* non-fatal: do not set exit_state */
			break;
    }
}
/*---------------------------------------------------------------------------*/
/* c-ares async DNS helpers
 *
 * Design summary:
 *  - lft_ares_fire()    is called whenever a new hop IP is first seen; it adds
 *                       an entry to the session DNS cache (addr + empty name)
 *                       and kicks off an ares_gethostbyaddr() PTR query.
 *  - lft_ares_callback()fires when c-ares completes a query; it fills the name
 *                       field in the cache entry and decrements ares_pending.
 *  - lft_ares_poll()    is called on every main-loop iteration (non-blocking
 *                       select + ares_process) to harvest completed replies.
 *  - lft_ares_wait()    is called once after the trace loop exits; it blocks
 *                       up to 1.2 s for any still-pending queries (queries
 *                       fired for the last few hops may not have finished yet).
 *  - print_host()       checks the cache first; if a name is present it uses
 *                       it, otherwise falls back to the numeric IP address.
 *                       The synchronous getnameinfo()+alarm() path is still
 *                       compiled in as a fallback for -Q / no-c-ares builds.
 *
 * Timeout policy: TIMEOUTMS=500ms, TRIES=2 (one retry for dropped UDP).
 * Total worst-case per query: 1 000 ms — same ceiling as the alarm(1) path,
 * but since queries run concurrently with probing the cost is largely free. */
#ifdef HAVE_CARES

typedef struct {
    lft_session_params *sess;
    struct in_addr      addr;
} lft_ares_ud_t;

static void
lft_ares_callback(void *arg, int status, int timeouts __attribute__((unused)),
                  struct hostent *host)
{
    lft_ares_ud_t      *ud   = (lft_ares_ud_t *)arg;
    lft_session_params *sess = ud->sess;
    int i;

    if (status == ARES_SUCCESS && host && host->h_name) {
        for (i = 0; i < sess->dns_cache_count; i++) {
            if (sess->dns_cache[i].addr.s_addr == ud->addr.s_addr) {
                strncpy(sess->dns_cache[i].name, host->h_name,
                        sizeof(sess->dns_cache[i].name) - 1);
                sess->dns_cache[i].name[sizeof(sess->dns_cache[i].name)-1] = '\0';
                break;
            }
        }
    }
    sess->ares_pending--;
    free(ud);
}

/* Fire an async PTR query for addr if not already queued.  No-op if full. */
static void
lft_ares_fire(lft_session_params *sess, struct in_addr addr)
{
    lft_ares_ud_t *ud;
    int i;

    if (!sess->ares_chan || sess->async_dns_disabled || !sess->resolve_names)
        return;
    for (i = 0; i < sess->dns_cache_count; i++)
        if (sess->dns_cache[i].addr.s_addr == addr.s_addr)
            return;   /* already queued or resolved */
    if (sess->dns_cache_count >= LFT_DNS_CACHE_SIZE)
        return;       /* cache full — shouldn't happen within ttl_limit */

    sess->dns_cache[sess->dns_cache_count].addr    = addr;
    sess->dns_cache[sess->dns_cache_count].name[0] = '\0';
    sess->dns_cache_count++;

    ud = malloc(sizeof(lft_ares_ud_t));
    if (!ud) return;
    ud->sess = sess;
    ud->addr = addr;

    sess->ares_pending++;
    ares_gethostbyaddr(sess->ares_chan, &addr, sizeof(addr), AF_INET,
                       lft_ares_callback, ud);
}

/* Public wrapper so other translation units (lft_icmptrace.c, lft_btcptrace.c)
 * can queue PTR lookups without needing access to the static lft_ares_fire(). */
void
lft_ares_queue(lft_session_params *sess, struct in_addr addr)
{
    lft_ares_fire(sess, addr);
}

/* Socket-state callback: c-ares calls this whenever it opens, changes interest
 * on, or closes a socket.  We maintain ares_fd_table[] so lft_ares_poll() and
 * lft_ares_wait() know exactly which fds to select() on without calling the
 * deprecated ares_fds(). */
static void
lft_ares_sock_state_cb(void *data, ares_socket_t fd, int readable, int writable)
{
    lft_session_params *sess = (lft_session_params *)data;
    int i;

    /* Update an existing entry */
    for (i = 0; i < sess->ares_nfds; i++) {
        if (sess->ares_fd_table[i].fd == fd) {
            if (!readable && !writable) {
                /* c-ares no longer needs this fd — remove by swap with last */
                sess->ares_fd_table[i] = sess->ares_fd_table[--sess->ares_nfds];
            } else {
                sess->ares_fd_table[i].events =
                    (readable ? (unsigned)ARES_FD_EVENT_READ  : 0u) |
                    (writable ? (unsigned)ARES_FD_EVENT_WRITE : 0u);
            }
            return;
        }
    }
    /* New fd */
    if (!readable && !writable) return;  /* shouldn't happen; ignore */
    if (sess->ares_nfds >= LFT_ARES_MAX_FDS) return;  /* table full */
    sess->ares_fd_table[sess->ares_nfds].fd     = fd;
    sess->ares_fd_table[sess->ares_nfds].events =
        (readable ? (unsigned)ARES_FD_EVENT_READ  : 0u) |
        (writable ? (unsigned)ARES_FD_EVENT_WRITE : 0u);
    sess->ares_nfds++;
}

/* Non-blocking poll: harvest any c-ares replies that have arrived.
 * Uses ares_process_fds() with the fd table maintained by the sock_state_cb. */
void
lft_ares_poll(lft_session_params *sess)
{
    fd_set rfd, wfd;
    struct timeval tv = {0, 0};
    ares_fd_events_t events[LFT_ARES_MAX_FDS];
    size_t nevents = 0;
    int i, nfds = 0;

    if (!sess->ares_chan || sess->ares_pending == 0 || sess->ares_nfds == 0)
        return;

    FD_ZERO(&rfd); FD_ZERO(&wfd);
    for (i = 0; i < sess->ares_nfds; i++) {
        if (sess->ares_fd_table[i].events & ARES_FD_EVENT_READ)
            FD_SET(sess->ares_fd_table[i].fd, &rfd);
        if (sess->ares_fd_table[i].events & ARES_FD_EVENT_WRITE)
            FD_SET(sess->ares_fd_table[i].fd, &wfd);
        if ((int)sess->ares_fd_table[i].fd + 1 > nfds)
            nfds = (int)sess->ares_fd_table[i].fd + 1;
    }
    if (nfds == 0) return;
    select(nfds, &rfd, &wfd, NULL, &tv);  /* non-blocking: tv = {0, 0} */

    for (i = 0; i < sess->ares_nfds; i++) {
        unsigned int ev = ARES_FD_EVENT_NONE;
        if (FD_ISSET(sess->ares_fd_table[i].fd, &rfd)) ev |= ARES_FD_EVENT_READ;
        if (FD_ISSET(sess->ares_fd_table[i].fd, &wfd)) ev |= ARES_FD_EVENT_WRITE;
        if (ev != ARES_FD_EVENT_NONE) {
            events[nevents].fd     = sess->ares_fd_table[i].fd;
            events[nevents].events = ev;
            nevents++;
        }
    }
    ares_process_fds(sess->ares_chan, events, nevents, 0);
}

/* Post-trace blocking wait: drain remaining queries up to a hard deadline.
 * Worst-case wait = TIMEOUTMS * TRIES = 500 * 2 = 1 000 ms; the 1.2 s
 * ceiling gives 200 ms of headroom for scheduler jitter.
 * Time spent here is stored in sess->ares_wait_ms for -T display. */
void
lft_ares_wait(lft_session_params *sess)
{
    struct timeval deadline, now, remaining, ares_tv, *atvp, wait_start;
    ares_fd_events_t events[LFT_ARES_MAX_FDS];
    int i, nfds;

    sess->ares_wait_ms = 0.0;
    if (!sess->ares_chan || sess->ares_pending == 0)
        return;

    gettimeofday(&wait_start, NULL);
    gettimeofday(&deadline, NULL);
    deadline.tv_sec  += 1;
    deadline.tv_usec += 200000;   /* 1.2 s total ceiling */
    if (deadline.tv_usec >= 1000000) {
        deadline.tv_sec++;
        deadline.tv_usec -= 1000000;
    }

    while (sess->ares_pending > 0) {
        fd_set rfd, wfd;
        size_t nevents = 0;

        gettimeofday(&now, NULL);
        timersub(&deadline, &now, &remaining);
        if (remaining.tv_sec < 0)
            break;   /* deadline passed — display remaining hops numerically */

        if (sess->ares_nfds == 0) {
            /* No fds yet registered; drive timeout processing only */
            ares_process_fds(sess->ares_chan, NULL, 0, 0);
            break;
        }

        FD_ZERO(&rfd); FD_ZERO(&wfd);
        nfds = 0;
        for (i = 0; i < sess->ares_nfds; i++) {
            if (sess->ares_fd_table[i].events & ARES_FD_EVENT_READ)
                FD_SET(sess->ares_fd_table[i].fd, &rfd);
            if (sess->ares_fd_table[i].events & ARES_FD_EVENT_WRITE)
                FD_SET(sess->ares_fd_table[i].fd, &wfd);
            if ((int)sess->ares_fd_table[i].fd + 1 > nfds)
                nfds = (int)sess->ares_fd_table[i].fd + 1;
        }
        if (nfds == 0) break;

        atvp = ares_timeout(sess->ares_chan, &remaining, &ares_tv);
        select(nfds, &rfd, &wfd, NULL, atvp);

        for (i = 0; i < sess->ares_nfds; i++) {
            unsigned int ev = ARES_FD_EVENT_NONE;
            if (FD_ISSET(sess->ares_fd_table[i].fd, &rfd)) ev |= ARES_FD_EVENT_READ;
            if (FD_ISSET(sess->ares_fd_table[i].fd, &wfd)) ev |= ARES_FD_EVENT_WRITE;
            if (ev != ARES_FD_EVENT_NONE) {
                events[nevents].fd     = sess->ares_fd_table[i].fd;
                events[nevents].events = ev;
                nevents++;
            }
        }
        ares_process_fds(sess->ares_chan, events, nevents, 0);
    }
    gettimeofday(&now, NULL);
    sess->ares_wait_ms = timediff_ms(wait_start, now);
}
#endif /* HAVE_CARES */

/*---------------------------------------------------------------------------*/
/* Aggressive reverse-DNS timeout for the trace hot path.
 * _res.retrans=1 / _res.retry=1 covers Linux/glibc where getnameinfo()
 * uses the same resolver machinery.  On macOS, getnameinfo() may route
 * through mDNSResponder which ignores _res, so alarm(1) provides a hard
 * 1-second ceiling regardless of platform.  LFT is single-threaded so
 * SIGALRM is safe here; there are no other SIGALRM users in the process. */
#ifndef WIN32
static volatile sig_atomic_t lft_revdns_timed_out;
static void lft_sigalrm(int sig __attribute__((unused))) { lft_revdns_timed_out = 1; }
#define LFT_REVDNS_TIMEOUT 1
#else
/* Windows: thread-based 1-second timeout for getnameinfo() on the
 * synchronous fallback path (used when c-ares is not compiled in or -Q).  */
struct lft_gni_args {
    struct sockaddr *sa;
    socklen_t        salen;
    char            *hbuf;
    size_t           hbufsz;
    int              flags;
    int              rc;
};
static DWORD WINAPI lft_gni_thread(LPVOID arg)
{
    struct lft_gni_args *a = (struct lft_gni_args *)arg;
    a->rc = getnameinfo(a->sa, a->salen, a->hbuf, (DWORD)a->hbufsz,
                        NULL, 0, a->flags);
    return 0;
}
static int
lft_getnameinfo_timed(struct sockaddr *sa, socklen_t salen,
                      char *hbuf, size_t hbufsz, int flags)
{
    struct lft_gni_args a;
    HANDLE th;
    DWORD  tid;
    a.sa = sa; a.salen = salen; a.hbuf = hbuf; a.hbufsz = hbufsz;
    a.flags = flags; a.rc = EAI_AGAIN;
    th = CreateThread(NULL, 0, lft_gni_thread, &a, 0, &tid);
    if (!th) return EAI_AGAIN;
    if (WaitForSingleObject(th, 1000) == WAIT_TIMEOUT)
        TerminateThread(th, 0);
    CloseHandle(th);
    return a.rc;
}
#endif /* WIN32 */

static void
print_host (lft_session_params * sess, struct in_addr addr)
{
    char hbuf[NI_MAXHOST];
    struct sockaddr_in sa;
    int rc;

#ifdef HAVE_CARES
    /* When c-ares is active, DNS results are pre-populated in the session
     * cache during the trace; no blocking call is needed at display time. */
    if (sess->ares_chan && !sess->async_dns_disabled) {
        const char *name = NULL;
        int i;
        for (i = 0; i < sess->dns_cache_count; i++) {
            if (sess->dns_cache[i].addr.s_addr == addr.s_addr) {
                if (sess->dns_cache[i].name[0])
                    name = sess->dns_cache[i].name;
                break;
            }
        }
        if (name) {
            if (global_output_style == 1)
                printf (" host=\"%s\"", name);
            else
                printf ("%s", name);
            if (!sess->hostnames_only) {
                if (global_output_style == 1)
                    printf (" ip=\"%s\"", inet_ntoa (addr));
                else
                    printf (" (%s)", inet_ntoa (addr));
            }
        } else {
            if (global_output_style == 1)
                printf (" ip=\"%s\"", inet_ntoa (addr));
            else
                printf ("%s", inet_ntoa (addr));
        }
        return;
    }
#endif /* HAVE_CARES */

    /* Synchronous fallback: used when c-ares is not compiled in or -Q is set. */
    if (!sess->resolve_names) {
        if (global_output_style == 1)
            printf (" ip=\"%s\"", inet_ntoa (addr));
        else
            printf ("%s", inet_ntoa (addr));
        return;
    }
    memset(&sa, 0, sizeof(sa));
    sa.sin_family = AF_INET;
    sa.sin_addr   = addr;
#ifndef WIN32
    lft_revdns_timed_out = 0;
    signal(SIGALRM, lft_sigalrm);
    alarm(LFT_REVDNS_TIMEOUT);
    rc = getnameinfo((struct sockaddr *)&sa, sizeof(sa),
                     hbuf, sizeof(hbuf), NULL, 0, NI_NAMEREQD);
    alarm(0);
    signal(SIGALRM, SIG_DFL);
    if (lft_revdns_timed_out) rc = EAI_AGAIN;
#else
    rc = lft_getnameinfo_timed((struct sockaddr *)&sa, sizeof(sa),
                               hbuf, sizeof(hbuf), NI_NAMEREQD);
#endif
    if (rc == 0) {
        if (global_output_style == 1)
            printf (" host=\"%s\"", hbuf);
        else
            printf ("%s", hbuf);
        if (!sess->hostnames_only) {
            if (global_output_style == 1)
                printf (" ip=\"%s\"", inet_ntoa (addr));
            else
                printf (" (%s)", inet_ntoa (addr));
        }
    } else {
        if (global_output_style == 1)
            printf (" ip=\"%s\"", inet_ntoa (addr));
        else
            printf ("%s", inet_ntoa (addr));
    }
}
/*---------------------------------------------------------------------------*/
double timediff_ms (struct timeval prior, struct timeval latter)
{
    return
    (latter.tv_usec - prior.tv_usec) / 1000. +
    (latter.tv_sec - prior.tv_sec) * 1000.;
}
/* Per-hop RTT statistics (used by text/XML output and ASCII chart) */
typedef struct {
    double rtt_min;
    double rtt_avg;
    double rtt_max;
    double rtt_stddev;
    int    n;          /* number of RTT samples */
} hop_rtt_stats_t;

static hop_rtt_stats_t
compute_hop_rtt_stats(lft_session_params *sess, int hopno)
{
    hop_rtt_stats_t s;
    struct trace_packet_info_s *tp;
    double sum = 0.0, sum2 = 0.0;
    int n = 0;
    double rtt_min = 0.0, rtt_max = 0.0;

    memset(&s, 0, sizeof(s));
    if (hopno < 0 || hopno >= sess->hop_info_length) return s;

    SLIST_FOREACH(tp, &sess->hop_info[hopno].packets, next_by_hop) {
        if (!tp->recv.tv_sec) continue;
        {
            double rtt = timediff_ms(tp->sent, tp->recv);
            if (n == 0 || rtt < rtt_min) rtt_min = rtt;
            if (n == 0 || rtt > rtt_max) rtt_max = rtt;
            sum  += rtt;
            sum2 += rtt * rtt;
            n++;
        }
    }
    if (n == 0) return s;
    s.n        = n;
    s.rtt_min  = rtt_min;
    s.rtt_max  = rtt_max;
    s.rtt_avg  = sum / n;
    s.rtt_stddev = (n > 1) ? sqrt(sum2/n - (sum/n)*(sum/n)) : 0.0;
    if (s.rtt_stddev < 0.0) s.rtt_stddev = 0.0;
    return s;
}
/*---------------------------------------------------------------------------*/
static void EvtPacketInfoDefaultHandler(lft_session_params * sess, const EvtPacketInfoParam * ehip)
{
    char ind=' ';

    /* rtt_stats: skip duplicate-hop packets (only first probe's host info is emitted;
     * RTT suppressed here — stats are printed at EVT_RPT_PACKET_LIST_END) */
    if (sess->rtt_stats && ehip->last_hop.s_addr == ehip->tp->hopaddr.s_addr)
        return;

    if(ehip->tp->recv.tv_sec) {
        if (ehip->last_hop.s_addr != ehip->tp->hopaddr.s_addr) {
            /* Fix #1: check whether this IP was already annotated at an earlier hop.
             * Suppresses repeated ASN/netname for routers that respond at multiple
             * TTL levels (CGNAT gateways, -i probe-through-firewall mode, etc.). */
            int _hop_seen = 0;
            {
                int _j;
                for (_j = 0; _j < sess->n_shown_asn_ips; _j++) {
                    if (sess->shown_asn_ips[_j].s_addr == ehip->tp->hopaddr.s_addr) {
                        _hop_seen = 1;
                        break;
                    }
                }
            }

            if (!_hop_seen) {
                if (sess->do_aslookup) {
                    if(global_output_style==1)
                        printf(" asn=\"%d\"", ehip->asnumber);
                    else
                    {
                        /* Check IANA special-purpose table first — catches RFC-reserved
                         * addresses (RFC1918, RFC6598 CGNAT, etc.) that may have a wrong
                         * ASN carried over from a prior hop's ipaslist slot. */
                        char _irfc[16], _iname[64];
                        if (lft_iana_v4_lookup(ehip->tp->hopaddr,
                                               _irfc, sizeof(_irfc),
                                               _iname, sizeof(_iname)))
                            printf(" [%s]", _irfc);
                        else if (ehip->asnumber)
                            printf(" [%d]", ehip->asnumber);
                        else
                            printf(" [AS?]");
                    }
                }
                if (sess->do_netlookup) {
                    if(global_output_style==1)
                        printf(" net=\"%s\"", ehip->netname);
                    else
                    {
                        /* Check IANA first (same reason as ASN block above) */
                        char _irfc[16], _iname[64];
                        if (lft_iana_v4_lookup(ehip->tp->hopaddr,
                                               _irfc, sizeof(_irfc),
                                               _iname, sizeof(_iname)))
                            printf(" [%s]", _iname);
                        else if(ehip->netname && strlen(ehip->netname)>0
                                && strcmp(ehip->netname,"NULL")!=0)
                            printf(" [%s]", ehip->netname);
                        else
                            printf(" [NET?]");
                    }
                }
                /* Register this IP as annotated for subsequent hops */
                if (sess->n_shown_asn_ips < 256)
                    sess->shown_asn_ips[sess->n_shown_asn_ips++] = ehip->tp->hopaddr;
            } /* !_hop_seen */

            /* Fix #2: ICMP message label.
             * For a router already seen at a prior hop, ICMP type-3 codes
             * (port/host/net unreachable) mean the router is filtering the probe
             * rather than being the destination — label it [filtered] for clarity.
             * For a new router, show the specific ICMP code as before. */
            if (ehip->tp->icmp_type < -2 || ehip->tp->icmp_type > 17)
            {
                if(global_output_style==1)
                    printf(" icmpcode=\"%d\"", ehip->tp->icmp_type);
                else
                    printf (" [icmp code %d]", ehip->tp->icmp_type);
            }
            else
                if (ehip->tp->icmp_type >= 0)
                {
                    const char *_icmplabel = (_hop_seen)
                        ? "filtered"
                        : icmp_messages[ehip->tp->icmp_type + 1];
                    if(global_output_style==1)
                        printf (" icmpmsg=\"%s\"", _icmplabel);
                    else
                        printf (" [%s]", _icmplabel);
                }
                    
            if (ehip->tp->icmp_type == -1) {
				if(global_output_style==1)
					printf(" trgstate=\"");
				else
					printf(" [target");
				if(sess->protocol==0 || sess->protocol==4)
				{
					if(!(global_output_style==1))
						printf(" ");
					if (sess->target_open > 0)
						printf("open");
					else if (sess->pmtud)
						/* RST to an oversized SYN does not confirm the port is
						 * closed — the server may be rejecting the malformed probe
						 * rather than the connection.  Report what was observed. */
						printf("RST");
					else
					{
						if(sess->target_filtered > 0)
							printf("filtered");
						else
							printf("closed");
					}
				}
				else if (sess->protocol == 2 || sess->protocol == 3)
				{
					/* ICMP mode: target sent Echo Reply — no TCP port state */
					if(global_output_style != 1)
						printf(" replied");
				}
				if(global_output_style==1)
					printf("\"");
				else
					printf("]");
            }
			if(ehip->is_asseam)
			{
				if(global_output_style==1)
					printf(" asseam=\"1");
				else
					printf(" (AS-Method Seam");
			}
			if(ehip->is_netseam)
			{
				if(global_output_style==1)
					printf(" netseam=\"1");
				else
				{
					if(ehip->is_asseam)
					   printf(", Network-Method Seam");
					else
					   printf(" (Network-Method Seam");
				}
			}
			if(ehip->is_asseam || ehip->is_netseam)
			{
				if(ehip->seam_traced)
				{
					if(global_output_style==1)
						printf("\" seamstate=\"");
					else
						printf(": ");
					if(ehip->is_open)
						printf("OPEN");
					else
					{
						if(ehip->is_filtered)
							printf("FILTERED");
						else
							printf("CLOSED");
					}
				}
				if(global_output_style==1)
					printf("\"");
				else
					printf(")");
			}
			if(global_output_style!=1)
				printf(" ");
            print_host (sess, ehip->tp->hopaddr);
            if (ehip->tp->icmp_type == -1 && (sess->protocol<2 || sess->protocol>3))
			{
				if(global_output_style==1)
					printf(" port=\"%d\"",sess->dport);
				else
					printf(":%d",sess->dport);
            }
            /* PMTUD annotation: only show when a PTB actually forced a step-down
             * below the starting probe size.  When path_mtu == pmtud_initial_mtu
             * no bottleneck was found — showing [MTU: 1500] would imply a
             * constraint was discovered when it wasn't. */
            if (sess->pmtud) {
                if (ehip->path_mtu > 0 &&
                    ehip->path_mtu < sess->pmtud_initial_mtu) {
                    if(global_output_style==1)
                        printf(" pathmtu=\"%u\"", (unsigned)ehip->path_mtu);
                    else
                        printf(" [MTU: %u]", (unsigned)ehip->path_mtu);
                }
            }
        }
        else
		{
			if(global_output_style==1)
				ind=';';
			else
				ind='/';
		}
        /* Individual RTT — suppressed when rtt_stats active (stats at EVT_RPT_PACKET_LIST_END) */
        if (!sess->rtt_stats) {
            if(global_output_style==1)
            {
                if(ind==';')
                    printf (";%.1f", timediff_ms(ehip->tp->sent, ehip->tp->recv));
                else
                    printf (" timems=\"%.1f", timediff_ms(ehip->tp->sent, ehip->tp->recv));
            }
            else
                printf ("%c%.1f", ind, timediff_ms(ehip->tp->sent, ehip->tp->recv));
        }
    }
}

/* ---- inline spinner helpers (style-0 default output, non-Windows) -------- */
#ifndef WIN32

/* ---- cursor visibility helpers (async-signal-safe: write() only) ---- */

/* Hide the terminal text cursor via DECTCEM while the spinner is running.
 * Only called when isatty(STDOUT_FILENO) is already confirmed. */
static void inline_cursor_hide(void)
{
    write(STDOUT_FILENO, "\033[?25l", 6);
    g_cursor_hidden = 1;
}

/* Restore the cursor.  Idempotent — safe to call multiple times. */
static void inline_cursor_show(void)
{
    if (g_cursor_hidden) {
        write(STDOUT_FILENO, "\033[?25h", 6);
        g_cursor_hidden = 0;
    }
}

/* Signal handler for SIGINT / SIGTERM / SIGHUP / SIGQUIT: restore the cursor,
 * put back each original handler, then re-raise the signal so the process
 * terminates with the correct exit status (and coredumps for SIGQUIT). */
static void inline_cursor_sig(int sig)
{
    inline_cursor_show();
    if (g_cursor_sigs_set) {
        sigaction(SIGINT,  &g_old_sa_sigint,  NULL);
        sigaction(SIGTERM, &g_old_sa_sigterm, NULL);
        sigaction(SIGHUP,  &g_old_sa_sighup,  NULL);
        sigaction(SIGQUIT, &g_old_sa_sigquit, NULL);
        g_cursor_sigs_set = 0;
    }
    raise(sig);
}

/* atexit backstop: covers exit() paths not reached by the signal handler
 * (e.g. a fatal error path that calls exit() directly). */
static void inline_cursor_atexit(void)
{
    inline_cursor_show();
}

/* Emit one progress character, replacing the current spinner glyph and
 * advancing to the next animation frame.  Must NOT be called from a signal
 * handler.  Blocks SIGALRM around the write so the handler cannot race us. */
static void inline_spin_emit(char c)
{
    if (!g_inline_spinning) {
        /* spinner not running — plain emit */
        printf("%c", c);
        return;
    }
    sigset_t blk, old;
    sigemptyset(&blk);
    sigaddset(&blk, SIGALRM);
    sigprocmask(SIG_BLOCK, &blk, &old);

    int  frame = (int)(++g_inline_frame);
    char buf[8];
    int  n = 0;
    buf[n++] = '\b';    /* back over trailing space (1 display col) */
    buf[n++] = '\b';    /* back over current spinner glyph (1 display col) */
    buf[n++] = c;       /* leave result character behind */
    if (g_inline_utf8) {
        const char *sp = os_braille[frame % OS_BRAILLE_N];
        memcpy(buf + n, sp, 3); n += 3;
    } else {
        buf[n++] = g_ascii_spin[frame % G_ASCII_SPIN_N];
    }
    buf[n++] = ' ';     /* re-emit trailing space after new spinner glyph */
    write(STDOUT_FILENO, buf, (size_t)n);

    sigprocmask(SIG_UNBLOCK, &blk, &old);
}

/* Tear down the inline spinner, emit one optional suffix (e.g. "T\n" or " \n"),
 * and restore the previous SIGALRM disposition.  Safe to call even if spinner
 * was never started (g_inline_armed == 0). */
static void inline_spin_finish(const char *suffix)
{
    if (!g_inline_armed) {
        if (suffix) printf("%s", suffix);
        return;
    }
    sigset_t blk, old;
    sigemptyset(&blk);
    sigaddset(&blk, SIGALRM);
    sigprocmask(SIG_BLOCK, &blk, &old);

    /* disarm timer, restore saved SIGALRM handler */
    struct itimerval zero;
    memset(&zero, 0, sizeof(zero));
    setitimer(ITIMER_REAL, &zero, NULL);
    sigaction(SIGALRM, &g_o_sa_old, NULL);
    g_inline_armed    = 0;
    g_inline_spinning = 0;

    /* restore cursor and termination signal handlers before printing */
    inline_cursor_show();
    if (g_cursor_sigs_set) {
        sigaction(SIGINT,  &g_old_sa_sigint,  NULL);
        sigaction(SIGTERM, &g_old_sa_sigterm, NULL);
        sigaction(SIGHUP,  &g_old_sa_sighup,  NULL);
        sigaction(SIGQUIT, &g_old_sa_sigquit, NULL);
        g_cursor_sigs_set = 0;
    }

    if (suffix && suffix[0]) {
        /* backspace over trailing space + spinner glyph, then emit suffix */
        char buf[32];
        int  n = 0;
        buf[n++] = '\b'; /* back over trailing space */
        buf[n++] = '\b'; /* back over spinner glyph  */
        size_t slen = strlen(suffix);
        if (n + (int)slen <= (int)(sizeof(buf) - 1)) {
            memcpy(buf + n, suffix, slen);
            n += (int)slen;
        }
        write(STDOUT_FILENO, buf, (size_t)n);
    }

    sigprocmask(SIG_UNBLOCK, &blk, &old);
}

#endif  /* !WIN32 */
/*---------------------------------------------------------------------------*/
static void LFTDefaultEventHandler(lft_session_params * sess, int code, const void * params)
{
    const EvtSentPacketParam * spparam;
    const EvtNoReplyParam * nrparam;
    const struct trace_packet_s * packet;
    const EvtDebugCheckpoint1Param * edcpparam;
    const EvtNonSeqPacketParam * enspparam;
    const EvtRecvPacketParam * erpparam;
    const EvtIncomingICMPUDPParam * eiiuparam;
    const EvtIncomingICMPTCPParam * eiitparam;
	const EvtIncomingICMPEchoParam * eiiiparam;
	const EvtIncomingICMPICMPParam * eicmparam;
    const struct tcphdr *tcp;
    const struct udphdr *udp;
	const struct icmp_echo_header_s * echo;
    const struct ip * ip;
	const struct icmp * icmp;

	if(global_output_style==1 && code!=EVT_RPT_PACKET_LIST_END && code!=EVT_RPT_PACKET_INFO)
		printf("<event code=\"%d\"",code);
    switch(code)
    {
	case EVT_AUTOCONFIGURED_TO_PORTS:
		if(global_output_style<2)
		{
			if(global_output_style)
				printf(" sport=\"%d\" dport=\"%d\" />\n",sess->sport,sess->dport);
			else
				printf ("Autoconfigured to source port %d, destination port %d.\n",sess->sport, sess->dport);
		}
        break;
    case EVT_ADDRESS_INITIALIZED:
		if(global_output_style<2)
		{
			print_host (sess, sess->local_address);
			if(global_output_style==1)
			{
				printf(" protocol=\"%d\"",sess->protocol);
				if(sess->protocol!=2 && sess->protocol!=3)
				{
					if (sess->random_source)
						printf (" rndsport=\"1\" sport=\"%d\"", sess->sport);
					else 
						printf (" rndsport=\"0\" sport=\"%d\"", sess->sport);
				}
				printf(" />\n");
			}
			else
			{
				if(!global_output_style)
				{
					if(sess->protocol==2 || sess->protocol==3)
						printf("\n");
					else
						if (sess->random_source)
							printf (":%d (pseudo-random)\n", sess->sport);
						else 
							printf (":%d\n", sess->sport);
				}
			}
		}
        break;
    case EVT_SENT_PACKET:
        spparam=(const EvtSentPacketParam *)params;
		if(global_output_style==1)
		{
			printf(" protocol=\"%d\" ttl=\"%d\"", sess->protocol, spparam->nhop+1);
			if(!sess->protocol || sess->protocol==4)
			{
				int flcnt=0;
				printf(" seq=\"%u\" xflags=\"%#x\" flags=\"", spparam->tseq, spparam->flags);
				if (spparam->flags & TH_RST)
				{
					printf ("RST");
					flcnt++;
				}
				if (spparam->flags & TH_ACK)
				{
					if(flcnt)
						printf(";");
					printf("ACK");
					flcnt++;
				}
				if (spparam->flags & TH_SYN)
				{
					if(flcnt)
						printf(";");
					printf ("SYN");
					flcnt++;
				}
				if (spparam->flags & TH_FIN)
				{
					if(flcnt)
						printf(";");
					printf ("FIN");
					flcnt++;
				}
				if(!flcnt)
					printf("none");
				printf("\" />\n");
			}
			else if(sess->protocol==1)
			{
				printf(" dport=\"%d\" ipid=\"%u\" />\n",
				       sess->dport,
				       (spparam->tseq & 0xFFFF) ? (unsigned)(spparam->tseq & 0xFFFF) : 0xFFFFu);
			}
			else
			{
				printf(" />\n");
			}
		}
		else if(!global_output_style)
		{
			if(!sess->protocol || sess->protocol==4)
			{
				printf("SENT TCP  TTL=%d SEQ=%u FLAGS=%#x ( ", spparam->nhop+1, spparam->tseq, spparam->flags);
				if (spparam->flags & TH_RST)
					printf ("RST ");
				if (spparam->flags & TH_ACK)
					printf ("ACK ");
				if (spparam->flags & TH_SYN)
					printf ("SYN ");
				if (spparam->flags & TH_FIN)
					printf ("FIN ");
				if (sess->pmtud) {
#ifdef SCREWED_IP_LEN
					int pkt_len = (int)sess->trace_packet.ip_hdr.ip_len;
#else
					int pkt_len = (int)ntohs(sess->trace_packet.ip_hdr.ip_len);
#endif
					printf(") LEN=%d [PMTUD%s]\n", pkt_len,
					       sess->pmtud_verifying ? " verify" : "");
				} else {
					printf(")\n");
				}
			}
			else if(sess->protocol==1)
			{
				if (sess->pmtud) {
#ifdef SCREWED_IP_LEN
					int pkt_len = (int)sess->trace_packet.ip_hdr.ip_len;
#else
					int pkt_len = (int)ntohs(sess->trace_packet.ip_hdr.ip_len);
#endif
					printf("SENT UDP  TTL=%d DPORT=%d IPID=%u LEN=%d [PMTUD%s]\n",
					       spparam->nhop+1, sess->dport,
					       (spparam->tseq & 0xFFFF) ? (unsigned)(spparam->tseq & 0xFFFF) : 0xFFFFu,
					       pkt_len, sess->pmtud_verifying ? " verify" : "");
				} else {
					printf("SENT UDP  TTL=%d DPORT=%d IPID=%u\n",
					       spparam->nhop+1, sess->dport,
					       (spparam->tseq & 0xFFFF) ? (unsigned)(spparam->tseq & 0xFFFF) : 0xFFFFu);
				}
			}
			else
			{
				if (sess->pmtud)
					printf("SENT ICMP  TTL=%d SEQ=%u LEN=%d [PMTUD%s]\n",
					       spparam->nhop+1, (unsigned)spparam->tseq, sess->userlen,
					       sess->pmtud_verifying ? " verify" : "");
				else
					printf("SENT ICMP  TTL=%d\n", spparam->nhop+1);
			}
		}
        break;
    case EVT_SHOW_PAYLOAD:
        packet=(const struct trace_packet_s *)params;
		if(global_output_style==1)
		{
			if(!packet->payload_len)
				printf(" payloadlen=\"%d\" />\n", packet->payload_len);
			else
				printf(" payloadlen=\"%d\">%s</event>\n", packet->payload_len, packet->payload);
		}
		else if(!global_output_style)
		{
			if(!packet->payload_len)
				printf("Payload:  Length=%d  Contents=EMPTY\n", packet->payload_len);
			else
				printf("Payload:  Length=%d  Contents=\"%s\"\n", packet->payload_len, packet->payload);
		}
        break;
    case EVT_SHOW_UDP_CHECKSUM:
        packet=(const struct trace_packet_s *)params;
		if(global_output_style==1)
			printf(" udpsum=\"%#x\"/>\n",packet->udp_hdr.uh_sum);
		else if(!global_output_style)
			printf("UDP Checksum = %#x\n",packet->udp_hdr.uh_sum);
        break;
    case EVT_SHOW_TCP_CHECKSUM:
        packet=(const struct trace_packet_s *)params;
		if(global_output_style==1)
			printf(" tcpsum=\"%#x\"/>\n",packet->tcp_hdr.th_sum);
		else if(!global_output_style)
			printf("TCP Checksum = %#x\n",packet->tcp_hdr.th_sum);
        break;
    case EVT_SHOW_HOPS:
		if(global_output_style==1)
			printf(" uphops=\"%d\" />\n", (int)(*((const short *)params)));
		else if(!global_output_style)
			printf("Upping states of the hops following %d\n", (int)(*((const short *)params)));
        break;
    case EVT_SHOW_NUM_HOPS:
		if(global_output_style<2)
		{
			if(global_output_style)
				printf(" numhops=\"%d\" />\n", (sess->num_hops+1));
			else
				printf ("Concluding with %d hops.\n", (sess->num_hops+1));
		}
        break;
    case EVT_TRACE_COMPLETED:
		if(global_output_style<2)
		{
			if(global_output_style)
			{
				printf(" />\n");
			}
			else
			{
				if (sess->num_hops && !sess->nostatus && !sess->noisy) {
#ifndef WIN32
					/* spinner eats the T: \b + T + \n */
					if (g_inline_armed) inline_spin_finish("T\n");
					else
#endif
					printf ("T\n");
				} else if (!sess->noisy && !sess->nostatus) {
#ifndef WIN32
					/* spinner: \b + space (erase glyph) + \n */
					if (g_inline_armed) inline_spin_finish(" \n");
					else
#endif
					printf ("\n");
				}
			}
		}
        break;
    case EVT_ON_RESOLUTION:
		if(global_output_style<2)
		{
			if(global_output_style)
			{
				printf(" asresolutiontype=\"");
				if(sess->use_radb)
					printf ("RADB");
				else
					if(sess->use_cymru)
						printf ("Cymru");
					else
						if(sess->use_ris)
							printf ("RIPE");
						else
							printf ("PWhoIs");
				printf("\" />\n");
			}
			else
			{
				if(sess->use_radb)
					printf ("Using RADB for in-line AS resolution...\n");
				else
					if(sess->use_cymru)
						printf ("Using Cymru for bulk AS resolution...\n");
					else
						if(sess->use_ris)
							printf ("Using RIPE NCC RIS for bulk AS resolution...\n");
						else
							printf ("Using Prefix WhoIs for bulk AS resolution...\n");
			}
		}
        break;
    case EVT_TRACE_REPORT_START:
		if(global_output_style<2)
		{
			if(!global_output_style)
				printf ("TTL LFT trace to ");
			print_host (sess, sess->remote_address);
			if(global_output_style)
			{
				printf(" protocol=\"%d\"",sess->protocol);
				switch(sess->protocol) 
				{
					case 0:
						printf(" protocolname=\"TCP\" dport=\"%d\" />\n",sess->dport);
						break;
					case 4:
						printf(" protocolname=\"TCP\" dportrange=\"%d-%d\" />\n",sess->dport,(sess->dport + (*((const int *)params))));
						break;
					case 1:
						printf(" protocolname=\"UDP\" dport=\"%d\" />\n",sess->dport);
						break;
					case 2:
						printf(" protocolname=\"ICMP\" />\n");
						break;
					case 3:
						printf(" protocolname=\"RFC1393\" />\n");
						break;
				}
			}
			else
			{
				switch(sess->protocol) 
				{
					case 0:
						printf(":%d/tcp\n",sess->dport);
						break;
					case 4:
						printf(":%d-%d/tcp\n",sess->dport, (sess->dport + (*((const int *)params))));
						break;
					case 1:
						printf(":%d/udp\n",sess->dport);
						break;
					case 2:
						printf("/icmp\n");
						break;
					case 3:
						printf("/rfc1393\n");
						break;
				}
			}
		}
        break;
    case EVT_RPT_NO_REPLY:
		if(global_output_style<2)
		{
			nrparam=(const EvtNoReplyParam *)params;
			if(global_output_style)
			{
				printf(" firstholehop=\"%d\" lastholehop=\"%d\" />\n", nrparam->hopno - nrparam->noreply + 1, nrparam->hopno);
			}
			else
			{
				if (nrparam->noreply == 1)
					printf("**  [neglected] no reply packets received from TTL %d\n", nrparam->hopno);
				if (nrparam->noreply > 1)
					printf("**  [neglected] no reply packets received from TTLs %d through %d\n", nrparam->hopno - nrparam->noreply + 1, nrparam->hopno);
			}
		}
        break;
    case EVT_RPT_FRW_INSPECT_PACKS:
		if(global_output_style<2)
		{
			if(global_output_style)
				printf(" />\n");
			else
				printf("**  [firewall] the next gateway may statefully inspect packets\n");
		}
        break;
    case EVT_RPT_FRW_STATE_FILTER:
		if(global_output_style<2)
		{
			if(global_output_style)
				printf(" />\n");
			else
				printf("**  [firewall] the next gateway may implement a flag-based state filter\n");
		}
        break;
    case EVT_RPT_BSD_BUG:
		if(global_output_style<2)
		{
			if(global_output_style)
				printf(" />\n");
			else
				printf("**  [4.2-3 BSD bug?] the next gateway may errantly reply with reused TTLs\n");
		}
        break;
    case EVT_RPT_PACKET_INFO:
		if(global_output_style<2)
		{
			EvtPacketInfoDefaultHandler(sess, (const EvtPacketInfoParam *)params);
		}
        break;
    case EVT_RPT_PACKET_LIST_END:
		if(global_output_style<2)
		{
            if (sess->rtt_stats && sess->_rtt_stat_hopno >= 0) {
                hop_rtt_stats_t rs = compute_hop_rtt_stats(sess, sess->_rtt_stat_hopno);
                if (rs.n > 0) {
                    if (global_output_style==1)
                        printf(" timems_min=\"%d\" timems_avg=\"%d\" timems_max=\"%d\""
                               " timems_jitter=\"%d\" />\n",
                               (int)round(rs.rtt_min), (int)round(rs.rtt_avg),
                               (int)round(rs.rtt_max), (int)round(rs.rtt_stddev));
                    else
                        printf(" %d/%d/%d/%dms\n",
                               (int)round(rs.rtt_min), (int)round(rs.rtt_avg),
                               (int)round(rs.rtt_max), (int)round(rs.rtt_stddev));
                } else {
                    if (global_output_style==1)
                        printf(" />\n");
                    else
                        printf("\n");
                }
            } else {
                if(global_output_style==1)
                    printf("\" />\n");
                else
                    printf ("ms\n");
            }
		}
        break;
    case EVT_RPT_HOP_INFO_START:
        sess->_rtt_stat_hopno = *((const int *)params);   /* track for rtt_stats */
		if(global_output_style<2)
		{
			if(global_output_style)
				printf (" index=\"%2d\"", (*((const int *)params)) + 1);
			else
				printf ("%2d ", (*((const int *)params)) + 1);
		}
        break;
    case EVT_RPT_NO_HOPS:
		if(global_output_style<2)
		{
			if(global_output_style)
			{
				printf(" protocol=\"%d\"",sess->protocol);
				if(sess->protocol==0 || sess->protocol==4)
					printf(" protocolname=\"TCP\"");
				else
					if(sess->protocol==1)
						printf(" protocolname=\"UDP\"");
				else
					printf(" protocolname=\"ICMP\"");
				if(sess->target_anomaly)
					printf(" targetanomaly=\"1\"");
				if(sess->target_anomaly || sess->protocol==0)
					printf(" dport=\"%d\"",sess->dport);
				if(!sess->target_anomaly && sess->protocol==4)
					printf(" dportrange=\"%d-%d\"",sess->dport,(sess->dport + (*((const int *)params))));
				if(!sess->target_anomaly && sess->protocol==1)
					printf(" dport=\"%d\"",sess->dport);
					printf(" />\n");
			}
			else
			{
				if (sess->target_anomaly)
					printf("**  [%d/tcp sequence anomaly from target]  Try advanced options (use -VV to see packets).\n", sess->dport);
				else if (sess->protocol==1)
					printf("**  [%d/udp no reply from target]  Use -VV to see packets.\n", sess->dport);
				else if (sess->protocol==0)
					printf("**  [%d/tcp no reply from target]  Try advanced options (use -VV to see packets).\n", sess->dport);
				else if (sess->protocol==4)
					printf("**  [%d-%d/tcp no reply from target]  Use -VV to see packets.\n", sess->dport, (sess->dport + (*((const int *)params))));
				else
					printf("**  [icmp no reply from target]  Try advanced options (use -VV to see packets).\n");
			}
		}
        break;
    case EVT_RPT_TIME_TRACE:
		if(global_output_style<2)
		{
			gettimeofday (&(sess->now), NULL);
#if defined( __CYGWIN__ ) || defined( WIN32 ) || defined(_WIN32)
			if(!sess->UseLocalTime)
				(void)strftime(tbuf, sizeof(tbuf), time_format, (struct tm *) gmtime((time_t *) &(sess->now.tv_sec)));
			else
#endif
			(void)strftime(tbuf, sizeof(tbuf), time_format, (struct tm *)localtime((time_t *) &(sess->now.tv_sec)));
			if(global_output_style)
			{
				printf (" finishtime=\"%s\"", tbuf);
				printf (" elapsed=\"%.2f\"",(timediff_ms(sess->begin_time, sess->now) / 1000));
				printf (" tracingtime=\"%.2f\"", 
						(timediff_ms(sess->begin_time, sess->trace_done_time) / 1000));
				if (sess->resolve_names || sess->do_aslookup || sess->do_netlookup) {
#ifdef HAVE_CARES
					if (sess->ares_chan && !sess->async_dns_disabled)
						printf(" resolvingtime=\"%.2f\" asyncdns=\"1\"", sess->ares_wait_ms / 1000);
					else
#endif
					printf(" resolvingtime=\"%.2f\"", (timediff_ms(sess->trace_done_time, sess->now) / 1000));
				}
				printf(" />\n");
			}
			else
			{
				printf ("LFT trace finished at %s", tbuf);
				printf (" (%.2fs elapsed)",(timediff_ms(sess->begin_time, sess->now) / 1000));
				if (sess->noisy) {
					printf ("\nTime spent tracing: %.2fs", 
							(timediff_ms(sess->begin_time, sess->trace_done_time) / 1000));
					if (sess->resolve_names || sess->do_aslookup || sess->do_netlookup) {
#ifdef HAVE_CARES
						if (sess->ares_chan && !sess->async_dns_disabled)
							printf(", resolving: %.2fs (async)", sess->ares_wait_ms / 1000);
						else
#endif
						printf(", resolving: %.2fs", (timediff_ms(sess->trace_done_time, sess->now) / 1000));
					}
				}
				printf("\n");
			}
		}
        break;
    case EVT_ON_EXIT:
		if(global_output_style==1)
			printf(" />\n");
        sess->exit_state=-100;
		if(global_output_style==2)
			GraphVizOutput(sess);
		if(global_output_style==3)
			ASCIIOutput(sess);
        break;
    case EVT_TTL_NO_REPLY:
		if(global_output_style<2)
		{
			if(global_output_style)
				printf(" ttl=\"%d\" />\n", (*((const int *)params)));
			else
				printf("No reply on TTL %d\n", (*((const int *)params)));
		}
        break;
    case EVT_PROGRESS_NO_REPLY:
		if(global_output_style<2)
		{
			if(global_output_style)
				printf(" />\n");
			else {
#ifndef WIN32
				inline_spin_emit('*');
#else
				printf("*");
#endif
			}
		}
        break;
	case EVT_PROGRESS_SKIP_PACKET:
		if(global_output_style<2)
		{
			if(global_output_style)
				printf(" />\n");
			else {
#ifndef WIN32
				inline_spin_emit('?');
#else
				printf("?");
#endif
			}
		}
		break;
    case EVT_TTL_TOUT_RESEND:
		if(global_output_style<2)
		{
			if(global_output_style)
				printf(" ttl=\"%d\" />\n", (*((const int *)params)));
			else
				printf("TTL %d timed out, (resending)\n",(*((const int *)params)));
		}
        break;
    case EVT_TTL_TOUT_GIVINGUP:
#ifndef WIN32
        if (g_o_active) g_o_timeout_flash = 1;
#endif
		if(global_output_style<2)
		{
			if(global_output_style)
				printf(" ttl=\"%d\" />\n", (*((const int *)params)));
			else
				printf("TTL %d timed out, (giving up)\n",(*((const int *)params)));
		}
        break;
    case EVT_DBG_CHECKPOINT1:
		if(global_output_style<2)
		{
			edcpparam=(const EvtDebugCheckpoint1Param *)params;
			if(global_output_style)
			{
				printf (" hilength=\"%d\" last_return=\"%d\"", sess->hop_info_length, edcpparam->last_return);
				printf (" no_reply=\"%d\" ahead_limit=\"%d\"", edcpparam->no_reply, sess->ahead_limit);
				printf (" num_hops=\"%d\" need_reply=\"%d\" />\n", sess->num_hops, edcpparam->need_reply);
			}
			else
			{
				printf ("| hilength %d, last_return %d\n", sess->hop_info_length, edcpparam->last_return);
				printf ("| no_reply %d, ahead_limit %d\n", edcpparam->no_reply, sess->ahead_limit);
				printf ("| num_hops %d, need_reply %d\n", sess->num_hops, edcpparam->need_reply);
			}
		}
        break;
    case EVT_CANT_RELIABLY_RTRIP:
		if(global_output_style<2)
		{
			if(global_output_style)
				printf(" />\n");
			else
				printf("LFT can\'t reliably round-trip.  Close-proximity filter in the way?\n");
		}
        break;
    case EVT_HAVE_UNANSWERRED_HOPS:
		if(global_output_style<2)
		{
			if(global_output_style)
				printf(" />\n");
			else
				printf("I still have unanswered hops.\n");
		}
        break;
    case EVT_TOO_FAR_AHEAD:
		if(global_output_style<2)
		{
			if(global_output_style)
				printf(" />\n");
			else
				printf("I\'m too far ahead, returning.\n");
		}
        break;
    case EVT_HAVE_GAPS:
		if(global_output_style<2)
		{
			if(global_output_style)
				printf(" />\n");
			else
				printf("I know the distance to the target, but I have gaps to fill.  Returning...\n");
		}
        break;
    case EVT_EITHER_RESP_OR_TOUT:
		if(global_output_style<2)
		{
			if(global_output_style)
				printf(" />\n");
			else
				printf("Everyone either responded or timed out.  ");
		}
        break;
    case EVT_LOOKFOR_UNINC_ACK:
		if(global_output_style<2)
		{
			if(global_output_style)
				printf(" />\n");
			else
				printf("\nNo match in sequence check, looking for a match inside seq-.\n");
		}
        break;
    case EVT_LOOKFOR_OFF_BY_LEN:
		if(global_output_style<2)
		{
			if(global_output_style)
				printf(" />\n");
			else
				printf("No match in seq-, looking for a match inside seq+len.\n");
		}
        break;
    case EVT_LOOKFOR_LAST_RESORT:
		if(global_output_style<2)
		{
			if(global_output_style)
				printf(" />\n");
			else
				printf("No match in seq+len, looking for a match inside last resort loop.\n");
		}
        break;
    case EVT_SKIP_PACKET:
		if(global_output_style<2)
		{
			if(global_output_style)
				printf(" />\n");
			else
				printf("(packet not meant for us, skip)\n");
		}
        break;
    case EVT_ACK_WAS_NOT_INC:
		if(global_output_style<2)
		{
			enspparam=(const EvtNonSeqPacketParam *)params;
			if(global_output_style)
				printf (" src=\"%s\" pttl=\"%d\" />\n", inet_ntoa (enspparam->ipaddr), enspparam->tp->hopno+1);
			else
			{
				printf ("SRC=%s PTTL=%d\n", inet_ntoa (enspparam->ipaddr), enspparam->tp->hopno+1);
				printf ("Target\'s ACK was not incremented.  ");
			}
		}
        break;
    case EVT_RST_REL_TO_ISN:
		if(global_output_style<2)
		{
			enspparam=(const EvtNonSeqPacketParam *)params;
			if(global_output_style)
			{
				printf (" src=\"%s\" pttl=\"%d\"", inet_ntoa (enspparam->ipaddr), enspparam->tp->hopno+1);
				printf (" payloadlen=\"%d\" />\n", sess->payloadlen);
			}
			else
			{
				printf ("SRC=%s PTTL=%d\n", inet_ntoa (enspparam->ipaddr), enspparam->tp->hopno+1);
				printf ("Target\'s RST relates to the ISN + payload length (%d).  ", sess->payloadlen);
			}
		}
        break;
    case EVT_ACK_WAS_WAY_OFF:
		if(global_output_style<2)
		{
			enspparam=(const EvtNonSeqPacketParam *)params;
			if(global_output_style)
				printf (" src=\"%s\" pttl=\"%d\" />\n", inet_ntoa (enspparam->ipaddr), enspparam->tp->hopno+1);
			else
			{
				printf ("SRC=%s PTTL=%d\n", inet_ntoa (enspparam->ipaddr), enspparam->tp->hopno+1);
				printf ("Target\'s ACK was way off.  ");
			}
		}
        break;
    case EVT_DUPLICATE_PACKET:
		if(global_output_style<2)
		{
			if(global_output_style)
				printf(" />\n");
			else
				printf ("(duplicate packet, skip)\n");
		}
        break;
    case EVT_PROGRESS_DUPLICATE:
		if(global_output_style<2)
		{
			if(global_output_style)
				printf(" />\n");
			else {
#ifndef WIN32
				inline_spin_emit('!');
#else
				printf("!");
#endif
			}
		}
        break;
    case EVT_RECV_PACKET:
#ifndef WIN32
        /* Guard: sub-sessions (seam-check via -y) fire events while the main
         * session's spinner is still active.  Their hop_info is tiny and would
         * clobber g_o_hops / g_o_replies for the main session display. */
        if (g_o_active && !sess->is_graphviz_subquery) {
            /* EVT_RECV_PACKET fires when noisy>1 (watch mode forces noisy=2 so
             * verbose output feeds the log drawer).  EVT_PROGRESS_OK fires in the
             * non-verbose path.  Both represent a valid reply, so both must
             * increment g_o_replies for the ↓ counter in watch mode. */
            g_o_replies++;
            erpparam = (const EvtRecvPacketParam *)params;
            if (erpparam && erpparam->tp) {
                /* Recount hops from sess->hop_info, capping at num_hops+1
                 * once the target is known.  Same logic as EVT_PROGRESS_OK,
                 * keeps the ⬡ counter consistent and ensures probes that
                 * overshot the target (-a ahead-limit replies at TTL >
                 * target hop) do not inflate the count. */
                int _h, _hcount = 0;
                int _max = sess->hop_info_length;
                if (sess->num_hops > 0 && sess->num_hops < _max)
                    _max = sess->num_hops + 1;
                for (_h = 0; _h < _max; _h++)
                    if (sess->hop_info[_h].all_rcvd > 0)
                        _hcount++;
                g_o_hops = (sig_atomic_t)_hcount;
                /* icmp_type == -1 means target reached (no TTL-exceeded) */
                if (erpparam->tp->icmp_type == -1) {
                    g_o_target       = 1;
                    g_o_target_flash = 1;
                }
            }
        }
#endif
		if(global_output_style<2)
		{
			erpparam=(const EvtRecvPacketParam *)params;
			if(global_output_style) {
				/* For TCP/UDP/BCP modes, add icmptype/icmpcode/icmpname when
				 * this is an ICMP error hop (tp->icmp_type correctly set early). */
				int _is_icmp_engine = (sess->protocol == 2 || sess->protocol == 3);
				if (!_is_icmp_engine && erpparam->tp
				    && erpparam->tp->icmp_type != -1) {
					int _rt = (erpparam->tp->icmp_type == -2) ? 11 : 3;
					int _rc = (erpparam->tp->icmp_type == -2) ?  0 : erpparam->tp->icmp_type;
					printf (" icmptype=\"%d\" icmpcode=\"%d\" icmpname=\"%s\""
					        " src=\"%s\" pttl=\"%d\" pseq=\"%u\" />\n",
					        _rt, _rc,
					        lft_icmp_verbose_name(erpparam->tp->icmp_type),
					        inet_ntoa (erpparam->ipaddr),
					        erpparam->tp->hopno+1, erpparam->seq);
				} else {
					printf (" src=\"%s\" pttl=\"%d\" pseq=\"%u\" />\n",
					        inet_ntoa (erpparam->ipaddr), erpparam->tp->hopno+1, erpparam->seq);
				}
			}
			else {
				/* Routing:
				 *  ICMP-engine modes (protocol 2/3):
				 *    EVT_RCVD_ICMP_ICMP/Echo already printed
				 *    "RCVD ICMP type=N code=N NAME='...' " on this line.
				 *    Just append SRC/PTTL/PSEQ to close it.
				 *  TCP/UDP/BCP modes (protocol 0/1/4):
				 *    EVT_RCVD_ICMP_TCP/UDP is suppressed.
				 *    For ICMP error hops print the full normalized line here.
				 *    For TCP RST/SYN-ACK (icmp_type==-1) just append the tail
				 *    to the "RCVD TCP FLAGS=... SEQ=N ACK=N " line from
				 *    EVT_RCVD_TCP. */
				int _is_icmp_engine = (sess->protocol == 2 || sess->protocol == 3);
				if (!_is_icmp_engine && erpparam->tp
				    && erpparam->tp->icmp_type != -1) {
					int _rt = (erpparam->tp->icmp_type == -2) ? 11 : 3;
					int _rc = (erpparam->tp->icmp_type == -2) ?  0 : erpparam->tp->icmp_type;
					printf ("RCVD ICMP type=%d code=%d NAME='%s' SRC=%s PTTL=%d PSEQ=%u\n",
					        _rt, _rc,
					        lft_icmp_verbose_name(erpparam->tp->icmp_type),
					        inet_ntoa (erpparam->ipaddr),
					        erpparam->tp->hopno+1, erpparam->seq);
				} else {
					/* ICMP-engine tail, or TCP RST continuation */
					printf ("SRC=%s PTTL=%d PSEQ=%u\n",
					        inet_ntoa (erpparam->ipaddr), erpparam->tp->hopno+1, erpparam->seq);
				}
			}
		}
        break;
    case EVT_PROGRESS_OK:
#ifndef WIN32
        if (g_o_active && !sess->is_graphviz_subquery) {
            int _h, _hcount = 0;
            g_o_replies++;
            /* Count unique responding hops; cap at num_hops+1 to avoid
             * counting extra target replies at probed-beyond-target TTLs */
            int _max = sess->hop_info_length;
            if (sess->num_hops > 0 && sess->num_hops < _max)
                _max = sess->num_hops + 1;
            for (_h = 0; _h < _max; _h++)
                if (sess->hop_info[_h].all_rcvd > 0)
                    _hcount++;
            g_o_hops = (sig_atomic_t)_hcount;
            /* Target detection: num_hops set when target replies (ICMP & TCP) */
            if (!g_o_target && sess->num_hops > 0) {
                g_o_target       = 1;
                g_o_target_flash = 1;
            }
        }
#endif
		if(global_output_style<2)
		{
			if(global_output_style)
				printf(" />\n");
			else {
#ifndef WIN32
				inline_spin_emit('.');
#else
				printf(".");
#endif
			}
		}
        break;
    case EVT_TCP_PORT_CLOSED:
#ifndef WIN32
        if (g_o_active && !sess->is_graphviz_subquery) { g_o_target = 1; g_o_target_flash = 1; }
#endif
		if(global_output_style<2)
		{
			if(global_output_style)
				printf (" dport=\"%d\" />\n", sess->dport);
			else if (sess->pmtud)
				printf ("Port %d/tcp responded to our oversized probes in PMTUD"
				        " mode; target sent RST.\n", sess->dport);
			else
				printf ("Port %d/tcp appears to be closed; target sent RST.\n", sess->dport);
		}
        break;
    case EVT_TCP_PORT_OPEN:
#ifndef WIN32
        if (g_o_active && !sess->is_graphviz_subquery) { g_o_target = 1; g_o_target_flash = 1; }
#endif
		if(global_output_style<2)
		{
			if(global_output_style)
				printf (" dport=\"%d\" />\n", sess->dport);
			else if (sess->pmtud)
				printf ("Port %d/tcp responded to our oversized probes in PMTUD"
				        " mode; target attempted handshake.\n", sess->dport);
			else
				printf ("Port %d/tcp open; target attempted handshake.\n", sess->dport);
		}
        break;
    case EVT_PROCESS_PACKET_START:
		if(global_output_style<2)
		{
			if(global_output_style)
				printf(" />\n");
			else
				printf("Received new data from packet capture; processing.\n");
		}
        break;
    case EVT_UDP_NOT_FOR_US:
		if(global_output_style<2)
		{
			if(global_output_style)
				printf(" />\n");
			else
			{
				if (sess->noisy>3)
					printf("Not for us\n");
				else
					printf("?");
			}
		}
        break;
	case EVT_INCOMING_ICMP_ICMP:
		if(global_output_style<2)
		{
			eicmparam=(const EvtIncomingICMPICMPParam *)params;
			if(global_output_style)
			{
				printf(" src=\"%s\" proto=\"%d\" ttl=\"%d\"", inet_ntoa(eicmparam->ip->ip_src), eicmparam->ip->ip_p, eicmparam->ip->ip_ttl);
				printf(" protocolname=\"ICMP\"");
				if (eicmparam->icmp->icmp_type == ICMP_TIMXCEED) 
					printf(" icmptype=\"ICMP_TIMXCEED\"");
				else if (eicmparam->icmp->icmp_type == ICMP_UNREACH)
					printf(" icmptype=\"ICMP_UNREACH\"");
				printf(" echoid=\"%d\" echoseq=\"%d\"",(int)eicmparam->echo->id, (int)eicmparam->echo->sequence);
				printf(" />\n");
			}
			else
			{
				printf("INCOMING IP:  SRC=%s PROTO=%d TTL=%d\n", inet_ntoa(eicmparam->ip->ip_src), eicmparam->ip->ip_p, eicmparam->ip->ip_ttl);
				printf("\\->ICMP+ICMP:  ");
				if (eicmparam->icmp->icmp_type == ICMP_TIMXCEED) 
					printf("TTL exceeded; ");
				else if (eicmparam->icmp->icmp_type == ICMP_UNREACH)
					printf("unreachable; ");
				printf("ICMP echo ID=%d SEQ=%d\n",(int)eicmparam->echo->id, (int)eicmparam->echo->sequence);
			}
		}
		break;
	case EVT_INCOMING_ICMP_Echo:
		if(global_output_style<2)
		{
			eiiiparam=(const EvtIncomingICMPEchoParam *)params;
			if(global_output_style)
			{
				printf(" src=\"%s\" proto=\"%d\" ttl=\"%d\"", inet_ntoa(eiiiparam->ip->ip_src), eiiiparam->ip->ip_p, eiiiparam->ip->ip_ttl);
				printf(" protocolname=\"ECHO\" echoid=\"%d\" echoseq=\"%d\"",(int)eiiiparam->echo->id, (int)eiiiparam->echo->sequence);
				printf(" />\n");
			}
			else
			{
				printf("INCOMING IP:  SRC=%s PROTO=%d TTL=%d\n", inet_ntoa(eiiiparam->ip->ip_src), eiiiparam->ip->ip_p, eiiiparam->ip->ip_ttl);
				printf("\\->ICMP+ICMP echo:  ");
				printf("ICMP echo ID=%d SEQ=%d\n",(int)eiiiparam->echo->id, (int)eiiiparam->echo->sequence);
			}
		}
        break;
    case EVT_INCOMING_ICMP_UDP:
		if(global_output_style<2)
		{
			eiiuparam=(const EvtIncomingICMPUDPParam *)params;
			if(global_output_style)
			{
				printf(" src=\"%s\" proto=\"%d\" ttl=\"%d\"", inet_ntoa (eiiuparam->orig_ip->ip_src), eiiuparam->ip->ip_p, eiiuparam->ip->ip_ttl);
				if (eiiuparam->icmp->icmp_type == ICMP_TIMXCEED) 
					printf(" icmptype=\"ICMP_TIMXCEED\"");
				else if (eiiuparam->icmp->icmp_type == ICMP_UNREACH)
					printf(" icmptype=\"ICMP_UNREACH\"");
				printf(" protocolname=\"UDP\" sport=\"%d\" dport=\"%d\"",(ntohs (eiiuparam->udp->uh_sport)), (ntohs (eiiuparam->udp->uh_dport)));
				printf(" />\n");
			}
			else
			{
				printf("INCOMING IP:  SRC=%s PROTO=%d TTL=%d\n", inet_ntoa (eiiuparam->orig_ip->ip_src), eiiuparam->ip->ip_p, eiiuparam->ip->ip_ttl);
				printf("\\->ICMP+UDP:  ");
				if (eiiuparam->icmp->icmp_type == ICMP_TIMXCEED) 
					printf("TTL exceeded; ");
				else if (eiiuparam->icmp->icmp_type == ICMP_UNREACH)
					printf("unreachable; ");
				printf("UDP SPORT=%d DPORT=%d\n", (ntohs (eiiuparam->udp->uh_sport)), (ntohs (eiiuparam->udp->uh_dport)));
			}
		}
        break;
    case EVT_INCOMING_ICMP_TCP:
		if(global_output_style<2)
		{
			eiitparam=(const EvtIncomingICMPTCPParam *)params;
			if(global_output_style)
			{
				printf(" src=\"%s\" proto=\"%d\" ttl=\"%d\"", inet_ntoa (eiitparam->orig_ip->ip_src), eiitparam->ip->ip_p, eiitparam->ip->ip_ttl);
				if (eiitparam->icmp->icmp_type == ICMP_TIMXCEED) 
					printf(" icmptype=\"ICMP_TIMXCEED\"");
				else if (eiitparam->icmp->icmp_type == ICMP_UNREACH)
					printf(" icmptype=\"ICMP_UNREACH\"");
				printf(" protocolname=\"TCP\" sport=\"%d\" dport=\"%d\"",(ntohs (eiitparam->tcp->th_sport)), (ntohs (eiitparam->tcp->th_dport)));
				printf(" />\n");
			}
			else
			{
				printf("INCOMING IP:  SRC=%s PROTO=%d TTL=%d\n", inet_ntoa (eiitparam->orig_ip->ip_src), eiitparam->ip->ip_p, eiitparam->ip->ip_ttl);
				printf("\\->ICMP+TCP:  ");
				if (eiitparam->icmp->icmp_type == ICMP_TIMXCEED) 
					printf("TTL exceeded; ");
				else if (eiitparam->icmp->icmp_type == ICMP_UNREACH)
					printf("unreachable; ");
				printf("TCP SPORT=%d DPORT=%d\n", (ntohs (eiitparam->tcp->th_sport)), (ntohs (eiitparam->tcp->th_dport)));
			}
		}
        break;
	case EVT_RCVD_ICMP_Echo:
		if(global_output_style<2)
		{
			if(global_output_style)
			{
				echo=(const struct icmp_echo_header_s *)params;
				/* Keep id/seq for backward compat; add type/code/name for parity. */
				printf(" icmptype=\"%d\" icmpcode=\"0\" icmpname=\"%s\""
				       " id=\"%d\" seq=\"%d\" />\n",
				       ICMP_ECHOREPLY,
				       lft_icmp_name_raw(ICMP_ECHOREPLY, 0),
				       (int)echo->id, (int)echo->sequence);
			}
			else
				printf("RCVD ICMP type=%d code=%d NAME='%s' ",
				       ICMP_ECHOREPLY, 0,
				       lft_icmp_name_raw(ICMP_ECHOREPLY, 0));
		}
        break;
	case EVT_RCVD_ICMP_ICMP:
		if(global_output_style<2)
		{
			icmp=(const struct icmp *)params;
			if(global_output_style)
				printf(" icmptype=\"%d\" icmpcode=\"%d\" icmpname=\"%s\" />\n",
				       (int)icmp->icmp_type, (int)icmp->icmp_code,
				       lft_icmp_name_raw(icmp->icmp_type, icmp->icmp_code));
			else
				printf("RCVD ICMP type=%d code=%d NAME='%s' ",
				       (int)icmp->icmp_type, (int)icmp->icmp_code,
				       lft_icmp_name_raw(icmp->icmp_type, icmp->icmp_code));
		}
        break;
    case EVT_RCVD_ICMP_UDP:
		if(global_output_style<2)
		{
			udp=(const struct udphdr *)params;
			if(global_output_style)
				printf(" dport=\"%u\" />\n", (unsigned int)ntohs(udp->uh_dport));
			/* plain text: suppressed — EVT_RECV_PACKET prints the full normalized line */
		}
        break;
    case EVT_RCVD_ICMP_TCP:
		if(global_output_style<2)
		{
			tcp=(const struct tcphdr *)params;
			if(global_output_style)
				printf(" seq=\"%u\" />\n",ntohl (tcp->th_seq));
			/* plain text: suppressed — EVT_RECV_PACKET prints the full normalized line */
		}
        break;
    case EVT_RCVD_TCP:
		if(global_output_style<2)
		{
			tcp=(const struct tcphdr *)params;
			if(global_output_style)
			{
				int flcnt=0;
				printf(" xflags=\"%#x\" flags=\"", tcp->th_flags);
				if (tcp->th_flags & TH_RST)
				{
					printf ("RST");
					flcnt++;
				}
				if (tcp->th_flags & TH_ACK)
				{
					if(flcnt)
						printf(";");
					printf("ACK");
					flcnt++;
				}
				if (tcp->th_flags & TH_SYN)
				{
					if(flcnt)
						printf(";");
					printf ("SYN");
					flcnt++;
				}
				if (tcp->th_flags & TH_FIN)
				{
					if(flcnt)
						printf(";");
					printf ("FIN");
					flcnt++;
				}
				if(!flcnt)
					printf("none\"");
				else
					printf("\"");
				printf (" seq=\"%u\" ack=\"%u\"", ntohl (tcp->th_seq), ntohl (tcp->th_ack));
				printf(" />\n");
			}
			else
			{
				printf("RCVD TCP  FLAGS=%#x ( ",tcp->th_flags);
				
				if (tcp->th_flags & TH_SYN)
					printf ("SYN ");
				if (tcp->th_flags & TH_ACK)
					printf ("ACK ");
				if (tcp->th_flags & TH_FIN)
					printf ("FIN ");
				if (tcp->th_flags & TH_RST)
					printf ("RST ");
				
				printf (") SEQ=%u ACK=%u ", ntohl (tcp->th_seq), ntohl (tcp->th_ack));
			}
		}
        break;
    case EVT_RCVD_UNKNOWN:
		if(global_output_style<2)
		{
			ip=(const struct ip *)params;
			if(global_output_style)
			{
				printf(" protocolcode=\"%d\"", ip->ip_p);
#if defined(WIN32) || defined(_WIN32)
				printf(" ipfrom=\"%d.%d.%d.%d\" ipto=\"%d.%d.%d.%d\"", 
					   (int)ip->ip_src.s_net,(int)ip->ip_src.s_host,(int)ip->ip_src.s_lh,(int)ip->ip_src.s_impno,
					   (int)ip->ip_dst.s_net,(int)ip->ip_dst.s_host,(int)ip->ip_dst.s_lh,(int)ip->ip_dst.s_impno);
#endif
				printf(" />\n");
			}
			else
			{
				printf("Incoming datagram contains unsupported protocol %d.\n", ip->ip_p);
#if defined(WIN32) || defined(_WIN32)
				if (sess->noisy > 4)
					printf("\t(from %d.%d.%d.%d to %d.%d.%d.%d)\n", 
						   (int)ip->ip_src.s_net,(int)ip->ip_src.s_host,(int)ip->ip_src.s_lh,(int)ip->ip_src.s_impno,
						   (int)ip->ip_dst.s_net,(int)ip->ip_dst.s_host,(int)ip->ip_dst.s_lh,(int)ip->ip_dst.s_impno);
#endif
			}
		}
        break;
    case EVT_DEVICE_SELECTED:
		if(global_output_style<2)
		{
			if(global_output_style)
				printf (" device=\"%s\" linktype=\"%d\" linktypename=\"%s\" />\n", sess->pcap_dev, sess->pcap_datalink, pcap_datalink_val_to_name(sess->pcap_datalink));
			else 
				printf ("Receiving on %s, type %d (%s), transmitting on %s as ", sess->pcap_dev, sess->pcap_datalink, pcap_datalink_val_to_name(sess->pcap_datalink), sess->pcap_send_dev);
		}
        break;
    case EVT_SHOW_INITIAL_SEQNUM:
		if(global_output_style<2)
		{
			if(global_output_style)
				printf (" seqstart=\"%d\" />\n", sess->seq_start);
			else
			{
				printf ("Receive link type is %s (%d), skipping %0d bytes\n", pcap_datalink_val_to_name(sess->pcap_datalink),sess->pcap_datalink,sess->skip_header_len);
				printf ("Transmit Initial Sequence Number (ISN) will be %d\n", sess->seq_start);
			}
		}
        break;
    case EVT_TRACE_START:
		if(global_output_style<2)
		{
			if(global_output_style)
			{
				memset(&tbuf, 0, sizeof(tbuf));
#if defined( __CYGWIN__ ) || defined( WIN32 ) || defined(_WIN32)
				if(!sess->UseLocalTime)
					(void)strftime(tbuf, sizeof(tbuf), time_format, (struct tm *) gmtime((time_t *) &(sess->begin_time.tv_sec)));
				else
#endif
					(void)strftime(tbuf, sizeof(tbuf), time_format, (struct tm *) localtime((time_t *) &(sess->begin_time.tv_sec)));
				printf (" begtime=\"%s\" />\n", tbuf);
			}
			else
			{
				if (sess->timetrace) {
					memset(&tbuf, 0, sizeof(tbuf));
#if defined( __CYGWIN__ ) || defined( WIN32 ) || defined(_WIN32)
					if(!sess->UseLocalTime)
						(void)strftime(tbuf, sizeof(tbuf), time_format, (struct tm *) gmtime((time_t *) &(sess->begin_time.tv_sec)));
					else
#endif
						(void)strftime(tbuf, sizeof(tbuf), time_format, (struct tm *) localtime((time_t *) &(sess->begin_time.tv_sec)));
					printf ("LFT trace started at %s\n", tbuf);
				}
				if (!sess->nostatus && !sess->noisy) {
					printf("Tracing ");
#ifndef WIN32
					/* Start inline progress spinner when:
					 *   - default text output (style 0)
					 *   - stdout is a real terminal (not piped/redirected)
					 *   - not silent (-S) and not verbose (-V/-v) */
					if (!global_output_style && isatty(STDOUT_FILENO)) {
						const char *term = getenv("TERM");
						const char *lang = getenv("LANG");
						const char *lc   = getenv("LC_ALL");
						/* Use braille if terminal isn't "dumb" and locale
						 * hints UTF-8.  Default to braille on unknown locales
						 * since virtually all modern terminals handle it. */
						g_inline_utf8 = 1;
						if (term && (strncmp(term, "dumb", 4) == 0 ||
						             strncmp(term, "unknown", 7) == 0))
							g_inline_utf8 = 0;
						else if ((lang && strstr(lang, "8859")) ||
						         (lc   && strstr(lc,   "8859")))
							g_inline_utf8 = 0; /* Latin-1 locale — ASCII safe */
						g_inline_frame    = 0;
						g_inline_spinning = 0;
						g_inline_armed    = 0;
						o_spinner_arm_sigalrm();
						g_inline_armed    = 1;
						/* emit the first spinner frame + trailing space so
						 * the block cursor sits one column clear of the glyph */
						if (g_inline_utf8)
							printf("%s ", os_braille[0]);
						else
							printf("| ");
						fflush(stdout);
						g_inline_spinning = 1;
						/* Hide the cursor while the spinner is live.
						 * Install SIGINT/SIGTERM/SIGHUP/SIGQUIT handlers that
						 * restore it before re-raising the signal, and a one-time
						 * atexit backstop for any exit() path. */
						if (!g_cursor_sigs_set) {
							struct sigaction sa_cur;
							memset(&sa_cur, 0, sizeof(sa_cur));
							sa_cur.sa_handler = inline_cursor_sig;
							sigemptyset(&sa_cur.sa_mask);
							sa_cur.sa_flags = 0;
							sigaction(SIGINT,  &sa_cur, &g_old_sa_sigint);
							sigaction(SIGTERM, &sa_cur, &g_old_sa_sigterm);
							sigaction(SIGHUP,  &sa_cur, &g_old_sa_sighup);
							sigaction(SIGQUIT, &sa_cur, &g_old_sa_sigquit);
							g_cursor_sigs_set = 1;
						}
						if (!g_cursor_atexit) {
							atexit(inline_cursor_atexit);
							g_cursor_atexit = 1;
						}
						inline_cursor_hide();
					}
#endif
				}
			}
		}
        break;
    case EVT_DBG_CHECKPOINT2:
		if(global_output_style<2)
		{
			if(global_output_style)
				printf(" />\n");
			else
				printf("Left dispatch.\n");
		}
        break;
    case EVT_DBG_LOG_MESSAGE:
		if(global_output_style<2)
		{
			if(global_output_style)
				printf(">%s</event>\n",(const char *)params);
			else
				printf("%s",(const char *)params);
		}
        break;
	case EVT_OCHECK_START:
		if(global_output_style<2)
		{
			if(global_output_style)
			{
				print_host (sess, sess->remote_address);
				printf(" dport=\"%d\" />\n",sess->dport);
			}
			else
				if(sess->noisy)
				{
					printf("TCP open checking started for ");
					print_host (sess, sess->remote_address);
					printf(" port %d.\n",sess->dport);
				}
		}
        break;
	case EVT_OCHECK_OPEN:
		if(global_output_style<2)
		{
			if(global_output_style)
			{
				print_host (sess, sess->remote_address);
				printf(" dport=\"%d\" />\n",sess->dport);
			}
			else
				if(sess->noisy)
				{
					printf("TCP port %d is open on ",sess->dport);
					print_host (sess, sess->remote_address);
					printf(".\n");
				}
		}
        break;
    }
}
/*---------------------------------------------------------------------------*/
static LFT_CALLBACK LFTErrHandler=LFTDefaultErrorHandler;
static LFT_CALLBACK LFTEvtHandler=LFTDefaultEventHandler;
/*---------------------------------------------------------------------------*/
void LFTInitializeCallbacks(LFT_CALLBACK error_handler, LFT_CALLBACK event_handler)
{
    if(error_handler)
        LFTErrHandler=error_handler;
    else
        LFTErrHandler=LFTDefaultErrorHandler;
    if(event_handler)
        LFTEvtHandler=event_handler;
    else
        LFTEvtHandler=LFTDefaultEventHandler;
}
/*---------------------------------------------------------------------------*/
lft_session_params * LFTSessionOpen(void)
{
    lft_session_params * sess= (lft_session_params *)malloc(sizeof(lft_session_params));
    memset(sess, 0,sizeof(lft_session_params));
    sess->scatter_ms = 20;
    sess->ttl_min = 0;
    sess->hop_info_length = 0;
    sess->hop_info=NULL;
    sess->tcp_flags = TH_SYN;
    sess->use_fins = 0;
    sess->seq_start = 0;       /* generate ISN internally by default */
    sess->dport = 443;          /* set default destination to tcp/443 HTTP */
    sess->sport = rand()%16384+49152; /* set default source to universal random-dynamic */
    sess->auto_ports = 1;      /* enable port autoselection by default */
    sess->random_source = 0;   /* disable random source port by default */
    sess->set_tos = 0;         /* disable set ToS bit by default */
    sess->userlen = 0;         /* user-requested packet length */
    sess->payloadlen = 0;      /* the final probe payloadlength */
    sess->payload = NULL;
    sess->win_len = 32768;

    sess->timeout_ms = 250;	   /* timeout between retries */
    sess->retry_max = 2;       /* number of retries before giving up */
    sess->retry_min = 1;       /* minimum number of checks per hop */
    sess->ahead_limit = 5;     /* number of probes we can send
                                * without replies if we don't know
                                * the number of hops */
    sess->dflag = 0;

    sess->ttl_limit = 30;      /* max # hops to traverse (highest TTL) */

    /* LFT-AUTOTUNE — topology-inheritance defaults (zero learned data,
     * 2-TTL margin above learned path length, feature enabled). */
    memset(&sess->learned, 0, sizeof(sess->learned));
    sess->no_autotune  = 0;
    sess->learn_margin = 2;

    sess->break_on_icmp = 1;   /* break on icmp other than time exceeded */
    sess->noisy = 0;           /* disable verbose debug by default */
    sess->nostatus = 0;        /* print status bar by default */
    sess->userdevsel = 0;      /* by default, we'll select the device */
    sess->senddevsel = 0;      /* by default, we won't use a spoof device */
    sess->resolve_names = 1;   /* dns resolution enabled by default */
    sess->hostnames_only = 0;  /* disable printing of IP addresses */
    sess->timetrace = 0;       /* disable tracer timing by default */
    sess->adaptive = 0;		   /* disable state engine by default */
    sess->protocol = 0;         /* use UDP instead of TCP */
    sess->do_netlookup = 0;    /* disable netname lookup by default */
    sess->do_aslookup = 0;     /* disable asn lookup by default */
    sess->use_radb = 0;        /* use RADB instead of pwhois */
    sess->use_cymru = 0;       /* use Cymru instead of pwhois */
    sess->use_ris = 0;         /* use RIPE NCC RIS instead of pwhois */

    sess->num_hops = 0;
    /*sess->num_sent = 0;*/
    sess->num_rcvd = 0;
    sess->target_open = 0;
    sess->target_filtered = 0;
    sess->target_anomaly = 0;

    sess->hostname = NULL;
    sess->hostname_lsrr_size = 0;
    SLIST_INIT(&(sess->trace_packets));
    sess->trace_packets_num = 0;
    sess->pcap_dev = NULL;
    sess->pcap_datalink = -1;
    sess->pcap_send_dev = NULL;
    sess->userdev = NULL;
    sess->senddev = NULL;

    sess->send_sock = 0;

    sess->btcpmap = NULL;
    sess->btcpmapsize = 0;
    sess->btcpdpucnt = 0;
    sess->trg_probe_is_sent = 0;
	
    sess->icmp_packet.packet = NULL;

#if defined( __CYGWIN__ ) || defined( WIN32 ) || defined(_WIN32)
    sess->recv_sock = 0;
    sess->wsastarted = 0;
#else
    sess->pcapdescr = 0;
#endif
    sess->UseLocalTime=1;
    sess->exit_state=0;
	
	sess->is_graphviz_subquery=0;
	sess->check_seam=0;
	sess->graphviz_icon_path=NULL;
    
    /*sess->wsess=w_init();*/
    return sess;
}
/*---------------------------------------------------------------------------*/
void LFTSessionClose(lft_session_params * sess)
{
    struct trace_packet_info_s 	*tp;
#ifdef HAVE_CARES
    if (sess->ares_chan) {
        ares_destroy(sess->ares_chan);
        sess->ares_chan = NULL;
    }
#endif
    if(sess->hop_info)
        free(sess->hop_info);
    if(sess->send_sock > 0)
#if defined(WIN32) || defined(_WIN32)
        closesocket(sess->send_sock);
#else
        close(sess->send_sock);
#endif
#if defined(WIN32) || defined(_WIN32)
    if(sess->pcap_dev != NULL)
        free(sess->pcap_dev);
    if(sess->recv_sock > 0)
        closesocket(sess->send_sock);
#else
    if(sess->pcapdescr != 0)
	{
        pcap_close(sess->pcapdescr);
		sess->pcapdescr=0;
	}
#endif
    while(SLIST_FIRST(&(sess->trace_packets)))
    {
        tp=SLIST_FIRST(&(sess->trace_packets));
        SLIST_REMOVE_HEAD(&(sess->trace_packets), next);
        free(tp);
    }
    if(sess->payload)
        free(sess->payload);
	if(sess->icmp_packet.packet)
		free(sess->icmp_packet.packet);
	if(sess->btcpmap)
		free(sess->btcpmap);
    free(sess);
}
/*---------------------------------------------------------------------------*/
/*Use TCP FIN packets exclusively (defaults are SYN)*/
int LFTSetupFIN(lft_session_params * sess)
{
    if (sess->adaptive) {
        LFTErrHandler(sess, WRN_CANT_SETUP_FIN, NULL);
        return 0;
    } 
    sess->tcp_flags = TH_FIN;
    sess->use_fins = 1;
    sess->dport = 25000;
    sess->auto_ports = 0;
    return 1;
}
/*---------------------------------------------------------------------------*/
/* =========================================================================
 * IANA Special-Purpose Address Registry lookup (IPv4 + IPv6)
 *
 * Sorted most-specific-first so the first match wins. Prefix octets are
 * in network (big-endian) order; comparison uses ntohl() to work in host
 * byte order regardless of machine endianness.
 * ========================================================================= */

static const struct {
    uint8_t  p[4];   /* prefix octets, network order */
    int      len;    /* prefix length in bits         */
    const char *rfc;
    const char *name;
} g_iana_v4[] = {
    /* /32 — most specific */
    {{ 0,  0,  0,  0},  32, "RFC1122", "This host on this network"},
    {{192,  0,  0,  8}, 32, "RFC7600", "IPv4 dummy address"},
    {{192,  0,  0,  9}, 32, "RFC7723", "Port Control Protocol Anycast"},
    {{192,  0,  0, 10}, 32, "RFC8155", "Traversal Using Relays around NAT Anycast"},
    {{192,  0,  0,170}, 32, "RFC8880", "NAT64/DNS64 Discovery"},
    {{192,  0,  0,171}, 32, "RFC8880", "NAT64/DNS64 Discovery"},
    {{192, 88, 99,  2}, 32, "RFC6751", "6a44 relay anycast address"},
    {{255,255,255,255}, 32, "RFC8190", "Limited Broadcast"},
    /* /29 */
    {{192,  0,  0,  0}, 29, "RFC7335", "IPv4 Service Continuity Prefix"},
    /* /24 */
    {{192,  0,  0,  0}, 24, "RFC6890", "IETF Protocol Assignments"},
    {{192,  0,  2,  0}, 24, "RFC5737", "Documentation (TEST-NET-1)"},
    {{192, 31,196,  0}, 24, "RFC7535", "AS112-v4"},
    {{192, 52,193,  0}, 24, "RFC7450", "AMT"},
    {{192, 88, 99,  0}, 24, "RFC7526", "Deprecated (6to4 Relay Anycast)"},
    {{192,175, 48,  0}, 24, "RFC7534", "Direct Delegation AS112 Service"},
    {{198, 51,100,  0}, 24, "RFC5737", "Documentation (TEST-NET-2)"},
    {{203,  0,113,  0}, 24, "RFC5737", "Documentation (TEST-NET-3)"},
    /* /16 */
    {{169,254,  0,  0}, 16, "RFC3927", "Link Local"},
    {{192,168,  0,  0}, 16, "RFC1918", "Private-Use"},
    /* /15 */
    {{198, 18,  0,  0}, 15, "RFC2544", "Benchmarking"},
    /* /12 */
    {{172, 16,  0,  0}, 12, "RFC1918", "Private-Use"},
    /* /10 */
    {{100, 64,  0,  0}, 10, "RFC6598", "Shared Address Space"},
    /* /8 */
    {{  0,  0,  0,  0},  8, "RFC791",  "This network"},
    {{ 10,  0,  0,  0},  8, "RFC1918", "Private-Use"},
    {{127,  0,  0,  0},  8, "RFC1122", "Loopback"},
    /* /4 */
    {{240,  0,  0,  0},  4, "RFC1112", "Reserved"},
};
#define G_IANA_V4_COUNT ((int)(sizeof(g_iana_v4)/sizeof(g_iana_v4[0])))

/* LFT-AUTOTUNE — apply learned-path knowledge to trace knobs.  Called by the
 * caller (currently watch_run_one_cycle) BEFORE each cycle's LFTExecute when
 * the previous cycle's results are available.  Three guards keep this from
 * doing harm:
 *   1. sess->no_autotune set    → skip entirely (--no-learn flag)
 *   2. learned.valid == 0       → no prior cycle data, leave defaults
 *   3. stable_cycles < 3        → path length unstable, don't narrow
 *   4. last_protocol != current → protocol changed, defaults safer
 *
 * When all guards pass, narrow ttl_limit, widen ahead_limit to the full
 * known path width, and shrink the cloaked-hop timeout.  Cap ahead_limit
 * at 16 to stay friendly to per-source ICMP rate limits. */
void lft_auto_tune_from_learned(lft_session_params *sess)
{
    if (!sess || sess->no_autotune)
        return;
    if (!sess->learned.valid)
        return;
    if (sess->learned.stable_cycles < 3)
        return;
    if (sess->learned.last_protocol != sess->protocol)
        return;
    if (sess->learned.num_hops <= 0)
        return;

    /* Narrow ttl_limit to known path length plus a small margin so we
     * still notice if the path lengthens. */
    int new_ttl_limit = sess->learned.num_hops + sess->learn_margin;
    if (new_ttl_limit < 4)  new_ttl_limit = 4;
    if (new_ttl_limit > 30) new_ttl_limit = 30;
    sess->ttl_limit = new_ttl_limit;

    /* Widen ahead_limit to do the entire path in one parallel burst,
     * but cap at 16 to keep ICMP rate-limit triggers at bay. */
    int new_ahead = sess->learned.num_hops;
    if (new_ahead < 4)  new_ahead = 4;
    if (new_ahead > 16) new_ahead = 16;
    sess->ahead_limit = new_ahead;

    /* Adaptive timeout: 8 × max RTT seen, floor 200ms. */
    int new_timeout = sess->learned.max_rtt_ms * 8;
    if (new_timeout < 200) new_timeout = 200;
    if (new_timeout > sess->timeout_ms) new_timeout = sess->timeout_ms;
    sess->timeout_ms = new_timeout;

    /* Tighter probe pacing — routers have already seen our flow. */
    if (sess->scatter_ms > 5)
        sess->scatter_ms = 5;
}

int lft_iana_v4_lookup(struct in_addr addr, char *rfc_out, size_t rfc_sz,
                        char *name_out, size_t name_sz)
{
    uint32_t a = ntohl(addr.s_addr);   /* address in host byte order */
    int i;
    for (i = 0; i < G_IANA_V4_COUNT; i++) {
        const uint8_t *pb = g_iana_v4[i].p;
        int            pl = g_iana_v4[i].len;
        uint32_t prefix = ((uint32_t)pb[0] << 24) | ((uint32_t)pb[1] << 16)
                        | ((uint32_t)pb[2] <<  8) |  (uint32_t)pb[3];
        uint32_t mask   = (pl == 0) ? 0u : (0xFFFFFFFFu << (32 - pl));
        if ((a & mask) == (prefix & mask)) {
            if (rfc_out  && rfc_sz  > 0) snprintf(rfc_out,  rfc_sz,  "%s", g_iana_v4[i].rfc);
            if (name_out && name_sz > 0) snprintf(name_out, name_sz, "%s", g_iana_v4[i].name);
            return 1;
        }
    }
    return 0;
}

/* IPv6 special-purpose table — prefix bytes + prefix length in bits */
static const struct {
    uint8_t  p[16];
    int      len;
    const char *rfc;
    const char *name;
} g_iana_v6[] = {
    /* /128 */
    {{0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,1},128,"RFC4291","Loopback"},
    {{0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0},128,"RFC4291","Unspecified Address"},
    /* /96 */
    {{0,0,0,0, 0,0,0,0, 0,0,0xFF,0xFF,0,0,0,0},96,"RFC4291","IPv4-mapped Address"},
    {{0,0x64,0xFF,0x9B, 0,0,0,0, 0,0,0,0, 0,0,0,0},96,"RFC6052","IPv4-IPv6 Translation"},
    /* /64 */
    {{0,0x64,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0},64,"RFC6666","Discard-Only Address Block"},
    /* /48 */
    {{0x20,0x01,0,0x02, 0,0,0,0, 0,0,0,0, 0,0,0,0},48,"RFC5180","Benchmarking"},
    {{0x20,0x01,0,0x04, 0x01,0x12,0,0, 0,0,0,0, 0,0,0,0},48,"RFC7535","AS112-v6"},
    {{0x26,0x20,0,0x4F, 0x80,0,0,0, 0,0,0,0, 0,0,0,0},48,"RFC7534","Direct Delegation AS112 Service"},
    /* /32 */
    {{0x20,0x01,0,0,   0,0,0,0, 0,0,0,0, 0,0,0,0},32,"RFC4380","TEREDO"},
    {{0x20,0x01,0,0x03, 0,0,0,0, 0,0,0,0, 0,0,0,0},32,"RFC7450","AMT"},
    {{0x20,0x01,0x0D,0xB8, 0,0,0,0, 0,0,0,0, 0,0,0,0},32,"RFC3849","Documentation"},
    /* /28 */
    {{0x20,0x01,0,0x10, 0,0,0,0, 0,0,0,0, 0,0,0,0},28,"RFC4843","Deprecated (ORCHID)"},
    {{0x20,0x01,0,0x20, 0,0,0,0, 0,0,0,0, 0,0,0,0},28,"RFC7343","ORCHIDv2"},
    {{0x20,0x01,0,0x30, 0,0,0,0, 0,0,0,0, 0,0,0,0},28,"RFC9374","Drone Remote ID Entity Tags"},
    /* /23 */
    {{0x20,0x01,0,0,    0,0,0,0, 0,0,0,0, 0,0,0,0},23,"RFC2928","IETF Protocol Assignments"},
    /* /20 */
    {{0x3F,0xFF,0,0,    0,0,0,0, 0,0,0,0, 0,0,0,0},20,"RFC9637","Documentation"},
    /* /16 */
    {{0x20,0x02,0,0,    0,0,0,0, 0,0,0,0, 0,0,0,0},16,"RFC3056","6to4"},
    {{0x5F,0,0,0,       0,0,0,0, 0,0,0,0, 0,0,0,0},16,"RFC9602","Segment Routing (SRv6) SIDs"},
    /* /10 */
    {{0xFE,0x80,0,0,    0,0,0,0, 0,0,0,0, 0,0,0,0},10,"RFC4291","Link-Local Unicast"},
    /* /7 */
    {{0xFC,0,0,0,       0,0,0,0, 0,0,0,0, 0,0,0,0}, 7,"RFC4193","Unique-Local"},
};
#define G_IANA_V6_COUNT ((int)(sizeof(g_iana_v6)/sizeof(g_iana_v6[0])))

int lft_iana_v6_lookup(const struct in6_addr *addr, char *rfc_out, size_t rfc_sz,
                        char *name_out, size_t name_sz)
{
    const uint8_t *ab = addr->s6_addr;
    int i;
    for (i = 0; i < G_IANA_V6_COUNT; i++) {
        const uint8_t *pb = g_iana_v6[i].p;
        int            pl = g_iana_v6[i].len;
        int whole_bytes   = pl / 8;
        int partial_bits  = pl % 8;
        int match         = 1;
        int j;

        for (j = 0; j < whole_bytes && match; j++)
            if (ab[j] != pb[j]) match = 0;

        if (match && partial_bits) {
            uint8_t mask = (uint8_t)(0xFF << (8 - partial_bits));
            if ((ab[whole_bytes] & mask) != (pb[whole_bytes] & mask)) match = 0;
        }

        if (match) {
            if (rfc_out  && rfc_sz  > 0) snprintf(rfc_out,  rfc_sz,  "%s", g_iana_v6[i].rfc);
            if (name_out && name_sz > 0) snprintf(name_out, name_sz, "%s", g_iana_v6[i].name);
            return 1;
        }
    }
    return 0;
}

/*---------------------------------------------------------------------------*/
/*Display hosts symbolically; suppress IP address display*/
int LFTSetupDispSymbHost(lft_session_params * sess)
{
    if(!sess->resolve_names) {
        LFTErrHandler(sess, WRN_CANT_DISP_HOST_NAMES, NULL);
        return 0;
    }
    sess->hostnames_only = 1;
    return 1;
}
/*---------------------------------------------------------------------------*/
int LFTSetupUDPMode(lft_session_params * sess)
{
    int prev_protocol;
    char tmp[30];
    prev_protocol = sess->protocol;
    sess->protocol = 1;
    if(sess->adaptive) {
        LFTErrHandler(sess, WRN_ADAPTIVE_DISABLED_BY_UDP, NULL);
        sess->adaptive = 0;
    }
    if(sess->use_fins) {
        LFTErrHandler(sess, WRN_FIN_DISABLED_BY_UDP, NULL);
        sess->use_fins = 0;
    }
    if(!sess->dflag)
        sess->dport = start_dport;
    else
    {
        if(!prev_protocol)
        {
            sprintf(tmp,"%d",sess->dport);
            LFTSetupDestinationPort(sess, tmp);
        }
    }
    sess->auto_ports = 0;
    return 1;
}
/*---------------------------------------------------------------------------*/
int LFTSetupRISLookup(lft_session_params * sess)
{
    int asnlkptp=ASN_LOOKUP_RIS;
    if(sess->use_cymru || sess->use_radb){
        LFTErrHandler(sess, WRN_ONLY_ONE_ASN_LOOKUP, &asnlkptp);
        return 0;
    }
    sess->use_radb = 0;
    sess->use_ris = 1;
    sess->use_cymru = 0;
    sess->do_aslookup = 1;
    return 1;
}
/*---------------------------------------------------------------------------*/
int LFTSetupRADBLookup(lft_session_params * sess)
{
    int asnlkptp=ASN_LOOKUP_RADB;
    if(sess->use_cymru || sess->use_ris){
        LFTErrHandler(sess, WRN_ONLY_ONE_ASN_LOOKUP, &asnlkptp);
        return 0;
    }
    sess->use_radb = 1;
    sess->use_ris = 0;
    sess->use_cymru = 0;
    sess->do_aslookup = 1;
    return 1;
}
/*---------------------------------------------------------------------------*/
int LFTSetupCYMRULookup(lft_session_params * sess)
{
    int asnlkptp=ASN_LOOKUP_CYMRU;
    if(sess->use_radb || sess->use_ris){
        LFTErrHandler(sess, WRN_ONLY_ONE_ASN_LOOKUP, &asnlkptp);
        return 0;
    }
    sess->use_radb = 0;
    sess->use_ris = 0;
    sess->use_cymru = 1;
    sess->do_aslookup = 1;
    return 1;
}
/*---------------------------------------------------------------------------*/
int lft_resolve_port(lft_session_params * sess, const char *strport)
{
	struct addrinfo hint, *ai;
	struct sockaddr_in addr;
	char *end;
	unsigned long port;

	/*
	 * Check if this is numeric, if so simply convert.
	 */
	port = strtoul(strport, &end, 10);
	if (*end == '\0') {
		if (port > 65535) {
			/* XXX Error.  Nothing checks for error, though.  */
			return (-1);
		}
		return (port);
	}

	/*
	 * This is a named service, look it up.
	 */
	memset(&hint, 0, sizeof hint);

	hint.ai_family = AF_INET;

	if (sess->protocol == 1) {
		hint.ai_socktype = SOCK_DGRAM;
		hint.ai_protocol = IPPROTO_UDP; /* UDP */
	} else {
		hint.ai_socktype = SOCK_STREAM;
		hint.ai_protocol = IPPROTO_TCP; /* TCP */
	}

	if (getaddrinfo(NULL, strport, &hint, &ai) != 0) {
		/* XXX Error.  Nothing checks for error, though.  */
		return (-1);
	}

	if (ai->ai_addrlen != sizeof addr) {
		/* XXX Error.  Nothing checks for error, though.  */
		return (-1);
	}

	memcpy(&addr, ai->ai_addr, ai->ai_addrlen);

	return (ntohs(addr.sin_port));
}
/*---------------------------------------------------------------------------*/
int LFTSetupDestinationPort(lft_session_params * sess, char * userport)
{
/*
    char strport[50];
    sprintf(strport,"%u",userport);
*/
    sess->dflag++;
    if(sess->protocol==1)
    { 
	if(lft_resolve_port(sess, userport) > 65534)
	{
            sess->dport = 65534;
            LFTErrHandler(sess, WRN_UDP_PORT_TOO_HIGH, &userport);
        }
	else
            sess->dport = lft_resolve_port(sess, userport);
    } 
    else
	{
        sess->dport = lft_resolve_port (sess, userport);
    }
    sess->auto_ports = 0;
    return 1;
}
/*---------------------------------------------------------------------------*/
int LFTSetupLengthOfPacket(lft_session_params * sess, int plen)
{
    if(plen > maxpacklen) {
        LFTErrHandler(sess, WRN_PACKET_LENGTH_TOO_HIGH, &plen);
        sess->userlen = maxpacklen;
    } else if(plen < minpacklen) {
        LFTErrHandler(sess, WRN_PACKET_LENGTH_TOO_LOW, &plen);
        sess->userlen = 0;
    } else 
        sess->userlen = plen;
    return 1;
}
/*---------------------------------------------------------------------------*/
int LFTSetupDisableResolver(lft_session_params * sess)
{
    if(!sess->hostnames_only) {
        sess->resolve_names = 0; 
    } else {
        LFTErrHandler(sess, WRN_CANT_DISABLE_RESOLVER, NULL);
    }
    return 1;
}
/*---------------------------------------------------------------------------*/
int LFTSetupSourcePort(lft_session_params * sess, int port)
{
    if(sess->random_source) {
        LFTErrHandler(sess, WRN_ALREADY_RANDOM_SPORT, NULL);

        return 0;
    }
    sess->sport = port;
    return 1;
}
/*---------------------------------------------------------------------------*/
int LFTSetupAdaptiveMode(lft_session_params * sess)
{
    if (sess->protocol==1) {
        LFTErrHandler(sess, WRN_ADAPTIVE_DISABLED_BY_UDP, NULL);
        return 0;
    }                        
    if (sess->use_fins) {
        LFTErrHandler(sess, WRN_ADAPTIVE_DISABLED_BY_FIN, NULL);
        return 0;
    }
    sess->adaptive = 1;
    sess->break_on_icmp = 0;  /* adaptive probes through firewalls; don't stop on unreachables */
    return 1;
}
/*---------------------------------------------------------------------------*/
int LFTSetupDevice(lft_session_params * sess,char * udev)
{
    if (strlen (udev) > max_net_dev_input) {
        LFTErrHandler(sess, ERR_DEVNAME_TOO_LONG, NULL);
        return 0;
    }
    sess->userdevsel = 1;
    sess->userdev = udev;
    return 1;
}
/*---------------------------------------------------------------------------*/
int LFTSetupSendDevice(lft_session_params * sess,char * sdev)
{
    if (strlen (sdev) > max_net_dev_input) {
        LFTErrHandler(sess, ERR_DEVNAME_TOO_LONG, NULL);
        return 0;
    }
    sess->senddevsel = 1;
    sess->senddev = sdev;
    return 1;
}
/*---------------------------------------------------------------------------*/
int LFTSetupUTCTimes(lft_session_params * sess)
{
#if defined(sun)
    if (putenv("TZ=GMT0") == -1) {
        LFTErrHandler(sess, WRN_UNABLE_SETUP_UTC, NULL);
    }                
#else
#if !defined(WIN32) && !defined(_WIN32)
    if (setenv("TZ", "GMT0", 1) == -1) {
        LFTErrHandler(sess, WRN_UNABLE_SETUP_UTC, NULL);
    }
#endif
#endif
    sess->UseLocalTime=0;
    sess->timetrace = 1;
    return 1;
}
/*---------------------------------------------------------------------------*/
/*                          Refactored part of code                          */
/*---------------------------------------------------------------------------*/
unsigned int new_seq(lft_session_params * sess)
{
    if (sess->adaptive) {
        return rand();
    } else {
        return sess->seq_start + sess->trace_packets_num;
    }
}
/*---------------------------------------------------------------------------*/
#ifndef SCREWED_IP_LEN 
u_int32_t
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
/*---------------------------------------------------------------------------*/
/* A standardized way of calculating checksums */
static unsigned short 
udp_cksum (unsigned short *addr, signed int len) {
    
    unsigned short answer = 0;
    register unsigned short *w = addr;
    register int nleft = len;
    register int sum = 0; 
    
    while(nleft > 1) {
        sum += *w++;
        nleft -= 2;
    }
    
    if(nleft == 1) {
        *(unsigned char *)(&answer) = *(unsigned char *)w;
        sum += answer;
    }
    
    sum = (sum>>16) + (sum&0xffff);
    sum += (sum>>16);
    answer = ~sum;
    return (answer);
}
/*---------------------------------------------------------------------------*/
u_int32_t tcp_cksum (struct ip *ip, struct tcphdr *tcp, const char * payload, int payload_len)
{
    u_int32_t sum = 0; 
    register int count;
    u_short *sp;
	u_short *temptcp = (u_short *)malloc(sizeof(struct tcphdr)+payload_len+4);
	u_int32_t tempip;
    
	memset(temptcp, 0,sizeof(struct tcphdr)+payload_len+4);
	memcpy(temptcp, (char *)tcp, sizeof(struct tcphdr));
	memcpy((u_char *)temptcp+sizeof(struct tcphdr),payload,payload_len);
	sp = temptcp;
    for(count = (sizeof(struct tcphdr)+payload_len+(sizeof(struct tcphdr)+payload_len)%2)/2; --count >= 0;)
        sum += *sp++;
	free(temptcp);

	memcpy(&tempip, &ip->ip_src, sizeof ip->ip_src);
	sum += (tempip >> 16) + (tempip & 0xffff);
    
	memcpy(&tempip, &ip->ip_dst, sizeof ip->ip_dst);
	sum += (tempip >> 16) + (tempip & 0xffff);
    
	sum += ip->ip_p << 8;
    
    sum += htons (sizeof (struct tcphdr)+payload_len);
    
    while (sum > 0xffff)
        sum = (sum & 0xffff) + (sum >> 16);
    sum = ~sum & 0xffff;
    
    return (sum);
}
/*---------------------------------------------------------------------------*/
static char * 
strtolower (char *input) {
    char *iter = input;
    
    if (input == NULL)
        return NULL;
    
    while (*iter) {
        *iter = tolower (*iter);
        iter++;
    }
    return input;
}
/*---------------------------------------------------------------------------*/
static void
do_auto_ports (lft_session_params * sess, char *hostname, int init_dport)
{
    /*
     *  Provides rudimentary auto-select of source and destination ports
     *  based on parsing the hostname supplied by the user
     *
     *  EXPECTS a string like "mail.example.com" and RETURNS the 
     *  auto-selected src and dst ports if changed or NULL if unchanged
     *
     *  Currently operates on user's input only as a call to
     *  getnameinfo() before tracing could slow us down too much
     */
    
    if (strlen(hostname) > 5) {
        
        const char *mailservers[] = { "mail", "smtp", "mx", "relay", "inbound" };
        int cnt = 0;
        hostname = (char *) strtolower(hostname);
        
        for (cnt = 0 ; cnt < 5 ; cnt++)
        {
            if (strstr(hostname,mailservers[cnt]))
            {
                sess->dport = 25;
                sess->sport = 52025;
                break;
            }
        }
        
        if (strncmp (hostname,"ftp",3) == 0) {
            sess->dport = 21;
            sess->sport = 52021;
        } else if (strncmp (hostname,"whois",5) == 0) {
            sess->dport = 43;
            sess->sport = 52043;
        } else if (strncmp (hostname,"imap",4) == 0) {
            sess->dport = 143;
            sess->sport = 52143;
        } else if (strncmp (hostname,"pop",3) == 0) {
            sess->dport = 110;
            sess->sport = 52110;
        } else if ((strncmp (hostname,"dns",3) == 0) || (strncmp (hostname,"ns",2) == 0) || (strncmp (hostname,"udns",4) == 0)) {
            sess->dport = 53;
            sess->sport = 53;
        } else if ((strncmp (hostname,"ntp",3) == 0) || (strncmp (hostname,"clock",5) == 0)) {
            sess->dport = 123;
            sess->sport = 123;
        } else if ((strncmp (hostname,"ike",3) == 0) || (strncmp (hostname,"isakmp",6) == 0)) {
            sess->dport = 500;
            sess->sport = 500;
        } else if ((strncmp (hostname,"ike",3) == 0) || (strncmp (hostname,"isakmp",6) == 0) || (strncmp (hostname,"ipsec",5) == 0)) {
            sess->dport = 500;
            sess->sport = 500;
        }
        
        if (sess->noisy && (sess->dport != init_dport)) 
            LFTEvtHandler(sess,EVT_AUTOCONFIGURED_TO_PORTS, NULL);
    }
}
/*---------------------------------------------------------------------------*/
unsigned int
get_address(lft_session_params * sess, const char *host)
{
	struct addrinfo hint, *ai;
	struct sockaddr_in addr;

	/*
	 * Check if this is numeric, if so simply convert.
	 */
	if (inet_aton(host, &addr.sin_addr) == 1)
		return (addr.sin_addr.s_addr);

	/*
	 * This is a named host, look it up.
	 */
	memset(&hint, 0, sizeof hint);

	hint.ai_family = AF_INET;

	if (getaddrinfo(host, NULL, &hint, &ai) != 0) {
		LFTErrHandler(sess, ERR_UNKNOWN_HOST, host);
		return (0);
	}

	if (ai->ai_addrlen != sizeof addr) {
		/*
		 * XXX
		 * Is there a better error?
		 */
		LFTErrHandler(sess, ERR_UNKNOWN_HOST, host);
		return (0);
	}

	memcpy(&addr, ai->ai_addr, ai->ai_addrlen);

	return (addr.sin_addr.s_addr);
}
/*---------------------------------------------------------------------------*/
#if defined( __CYGWIN__ ) || defined( WIN32 ) || defined(_WIN32)
/*
 The Windows/Cygwin version of this function uses the winsock built-in 
 WSAIoctl SIO_ROUTING_INTERFACE_QUERY to find the appropriate interface.
 */
static char *
lft_getifforremote(lft_session_params * sess, const char *remote)
{
	struct sockaddr_in in, out;
	unsigned long nBytesReturned;
    
	/* Only do this once of course */
	if (sock < 0) {
		if ((sock = socket (AF_INET, SOCK_DGRAM, 0)) < 0) {
            		LFTErrHandler(sess, WRN_GETIFFORREMOTE_SOCKET, NULL);
    			if(sess->wsess != NULL) {
				free(sess->wsess);
				sess->wsess = NULL;
			}
			return NULL;
		}
	}
    
	/* Ask what i/f the packet should go out on */
	in.sin_family = AF_INET;
	in.sin_port = 0;
	in.sin_addr.s_addr = get_address(sess, (char *)remote);
    	if(sess->exit_state < 0){
    		if(sess->wsess != NULL) {
			free(sess->wsess);
			sess->wsess = NULL;
		}
		return NULL;
    	}
	if (SOCKET_ERROR != WSAIoctl(sock, SIO_ROUTING_INTERFACE_QUERY, &in, sizeof(in), &out, sizeof(out), &nBytesReturned, NULL, NULL))
		return lft_getifname(out.sin_addr);
	/* not found */
    	if(sess->wsess != NULL) {
		free(sess->wsess);
		sess->wsess = NULL;
	}
	return NULL;
}
#else
/* 
 The non-Windows version of this function uses connect() to 
 acquire a (UDP) socket to the target and uses the OS's decision 
 as to what interface is appropriate. 
 */
static char *
lft_getifforremote(lft_session_params * sess, const char *remote)
{
    int sd;
    struct sockaddr_in sock;
    uint32_t socklen;
    uint16_t p1;
    
    p1 = rand()%16384+49152;
    
    if ((sd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
    {
        LFTErrHandler(sess, WRN_GETIFFORREMOTE_SOCKET, NULL); 
  	if(sess->wsess != NULL) {
		free(sess->wsess);
		sess->wsess = NULL;
	}
        return NULL;
    }
    sock.sin_family = AF_INET;
    sock.sin_addr.s_addr = get_address(sess, remote);
    if(sess->exit_state < 0){
 	if(sess->wsess != NULL) {
		free(sess->wsess);
		sess->wsess = NULL;
	}
        return NULL;
    }
    sock.sin_port = htons(p1);
    if (connect(sd, (const struct sockaddr *)(const void *)&sock, sizeof sock) == -1)
    {
        LFTErrHandler(sess, WRN_GETIFFORREMOTE_CONNECT, NULL);
#if defined(WIN32) || defined(_WIN32)
        closesocket(sd);
#else
        close(sd);
#endif
    	if(sess->wsess != NULL) {
		free(sess->wsess);
		sess->wsess = NULL;
	}
        return NULL;
    }
    
    socklen = sizeof sock;
    if (getsockname(sd, (struct sockaddr *)(void *)&sock, &socklen) == -1) {
        LFTErrHandler(sess, WRN_GETIFFORREMOTE_SOCKNAME, NULL);
#if defined(WIN32) || defined(_WIN32)
        closesocket(sd);
#else
        close(sd);
#endif
    	if(sess->wsess != NULL) {
		free(sess->wsess);
		sess->wsess = NULL;
	}
        return NULL;
    }
    
#if defined(WIN32) || defined(_WIN32)
    closesocket(sd);
#else
    close(sd);
#endif
    
    return lft_getifname(sock.sin_addr);
}
#endif
/*---------------------------------------------------------------------------*/
static void
init_address (lft_session_params * sess, char *remote, const char *pcap_dev)
{

    (void)pcap_dev;
    
    /* this is now set inside device selection routines */
    /* sess->local_address.s_addr = lft_getifaddr (pcap_dev); */	
    
    if (sess->noisy) {
        LFTEvtHandler(sess, EVT_ADDRESS_INITIALIZED, NULL);
        if(sess->exit_state < 0){
        	if(sess->wsess != NULL) {
			free(sess->wsess);
			sess->wsess = NULL;
		}
		return;
	}
    }
    
    sess->remote_address.s_addr = get_address(sess, remote);
}
/*---------------------------------------------------------------------------*/
/* RFC 1191 plateau table — used when a router sends PTB with nextmtu == 0  */
static const uint16_t pmtud_plateaus[] = {
    65535, 32000, 17914, 8166, 4352, 2002, 1492, 1006, 508, 296, 68, 0
};

/* Return the next plateau value strictly below current_mtu, or 68 (minimum). */
static uint16_t
lft_pmtud_next_plateau(uint16_t current_mtu)
{
    int i;
    for (i = 0; pmtud_plateaus[i] != 0; i++) {
        if (pmtud_plateaus[i] < current_mtu)
            return pmtud_plateaus[i];
    }
    return 68;
}

/*
 * Handle an ICMP "fragmentation needed" (type 3, code 4) PTB response.
 * Called from process_packet() and tcp_base_recv_packet() BEFORE recv_packet()
 * so the hop is NOT marked as answered — probing continues with a smaller packet.
 *
 * seq       — TCP seq or UDP dport value from the inner quoted packet
 * from      — IP address of the router that sent the PTB
 */
void
lft_pmtud_handle_ptb(lft_session_params *sess, const struct icmp *icmp,
                     unsigned int seq, struct in_addr from)
{
    struct trace_packet_info_s *tp = NULL;
    uint16_t next_mtu, new_mtu;
    int nhop;
    int hdr_size;

    /* Find the probe that triggered this PTB */
    SLIST_FOREACH(tp, &(sess->trace_packets), next) {
        if (tp->seq == seq)
            break;
    }
    if (tp == NULL)
        return; /* not our probe */

    nhop = tp->hopno;
    if (nhop < 0 || nhop >= 256)
        return;

    /* Determine the new MTU */
    next_mtu = ntohs(icmp->icmp_nextmtu);
    if (next_mtu > 0 && next_mtu < sess->pmtud_current_mtu) {
        new_mtu = next_mtu;
    } else {
        /* Pre-RFC-1191 router or bogus value: step down via plateau table */
        new_mtu = lft_pmtud_next_plateau(sess->pmtud_current_mtu);
    }

    /* Guard: must be a real reduction and >= minimum IP packet */
    if (new_mtu >= sess->pmtud_current_mtu || new_mtu < 68)
        return;

    /* Record the MTU limit at the responding hop */
    sess->hop_info[nhop].path_mtu = new_mtu;
    sess->pmtud_current_mtu = new_mtu;
    sess->pmtud_ptb_mtu = new_mtu;

    if (sess->noisy > 1 && !global_output_style)
        printf("RCVD PTB   SRC=%s SEQ=%u NEXTMTU=%u NEWMTU=%u\n",
               inet_ntoa(from), seq, (unsigned)next_mtu, (unsigned)new_mtu);

    /* Resize the probe template — payload shrinks, IP length shrinks */
    if (sess->protocol == 1)
        hdr_size = (int)(sizeof(struct ip) + sizeof(struct udphdr));
    else
        hdr_size = (int)(sizeof(struct ip) + sizeof(struct tcphdr));
    if (sess->trace_packet.lsrr.ipl_len > 0)
        hdr_size += sess->trace_packet.lsrr.ipl_len + 1;

    if ((int)new_mtu <= hdr_size)
        return; /* degenerate — can't shrink further */

    sess->trace_packet.payload_len = (int)new_mtu - hdr_size;
    /* ip_len is stored in network byte order after open_sockets() finishes */
#ifdef SCREWED_IP_LEN
    sess->trace_packet.ip_hdr.ip_len = (u_short)new_mtu;
#else
    sess->trace_packet.ip_hdr.ip_len = htons((u_short)new_mtu);
#endif

    (void)from; /* recorded via tp->hopno; kept for future use */
}

/*
 * Handle an ICMP PTB for ICMP probe mode (-p).
 * Same logic as lft_pmtud_handle_ptb() but resizes via sess->userlen (the
 * total desired packet size picked up by generateICMPPacket()) rather than
 * the TCP/UDP trace_packet template fields.
 *
 * seq  — ICMP echo sequence number from the inner quoted echo request
 * from — IP address of the router that sent the PTB
 */
void
lft_pmtud_icmp_handle_ptb(lft_session_params *sess, const struct icmp *icmp,
                           unsigned int seq, struct in_addr from)
{
    struct trace_packet_info_s *tp = NULL;
    uint16_t next_mtu, new_mtu;
    int nhop;
    /* ICMP probe header: IP header + ICMP echo header */
    const int hdr_size = (int)(sizeof(struct ip) + sizeof(struct icmp_echo_header_s));

    /* Find the probe that triggered this PTB */
    SLIST_FOREACH(tp, &(sess->trace_packets), next) {
        if (tp->seq == seq)
            break;
    }
    if (tp == NULL)
        return; /* not our probe */

    nhop = tp->hopno;
    if (nhop < 0 || nhop >= 256)
        return;

    /* Determine the new MTU */
    next_mtu = ntohs(icmp->icmp_nextmtu);
    if (next_mtu > 0 && next_mtu < sess->pmtud_current_mtu) {
        new_mtu = next_mtu;
    } else {
        /* Pre-RFC-1191 router or bogus value: step down via plateau table */
        new_mtu = lft_pmtud_next_plateau(sess->pmtud_current_mtu);
    }

    /* Guard: must be a real reduction and >= minimum IP packet */
    if (new_mtu >= sess->pmtud_current_mtu || new_mtu < 68)
        return;

    /* Record the MTU limit at the responding hop */
    sess->hop_info[nhop].path_mtu = new_mtu;
    sess->pmtud_current_mtu = new_mtu;
    sess->pmtud_ptb_mtu = new_mtu;

    if (sess->noisy > 1 && !global_output_style)
        printf("RCVD PTB   SRC=%s SEQ=%u NEXTMTU=%u NEWMTU=%u\n",
               inet_ntoa(from), seq, (unsigned)next_mtu, (unsigned)new_mtu);

    if ((int)new_mtu <= hdr_size)
        return; /* degenerate — can't shrink further */

    /* Resize ICMP probe via sess->userlen (picked up by generateICMPPacket) */
    sess->userlen = (int)new_mtu;
}

/*
 * Enable active Path MTU Discovery.  Called from lft.c when -K / --mtu is seen.
 * The actual probe size is set in open_sockets() once the interface is known.
 */
int
LFTSetupPMTUD(lft_session_params *sess)
{
    sess->pmtud = 1;
    sess->pmtud_current_mtu = 1500; /* updated in open_sockets() via SIOCGIFMTU */
    sess->pmtud_initial_mtu = 1500; /* updated in open_sockets() via SIOCGIFMTU */
    sess->pmtud_ptb_pending = 0;
    sess->pmtud_ptb_mtu = 0;
    return 0;
}
/*---------------------------------------------------------------------------*/
int LFTSetupRTTStats(lft_session_params *sess, int n)
{
    if (n < 1) n = 1;
    sess->rtt_stats = n;
    return 0;
}
/*---------------------------------------------------------------------------*/
static void
open_sockets (lft_session_params * sess)
{
	struct timeval tforid;
#if defined( __CYGWIN__ ) || defined( WIN32 ) || defined(_WIN32)
    int optval = 1;
    DWORD dwBytesRet = 2048;
    int rmem = 120832;
    int wmem = 120832;
    BOOL blnFlag=TRUE; 
	int ioctlret;
#endif
#ifdef IP_HDRINCL
    int on = 1;
#endif
    int i;
#if defined(sun) || defined(__CYGWIN__) || defined( WIN32 ) || defined(_WIN32)
    struct sockaddr_in local_bind;
#endif

#ifdef HAVE_NCURSES
    /* In watch mode, the raw socket was opened as root before privilege drop
     * and is reused across all sub-session cycles.  Skip creation if already open. */
    if (sess->send_sock <= 0)
#endif
    {
#if defined(sun)
	sess->send_sock = socket (PF_INET, SOCK_RAW, IPPROTO_IP);
#elif defined(BSD_IP_STACK)
	switch(sess->protocol)
	{
	case 0:		/*TCP*/
	case 4:		/*TCP basic*/
		sess->send_sock = socket (PF_INET, SOCK_RAW, IPPROTO_TCP);
		break;
	case 1:		/*UDP*/
        sess->send_sock = socket (PF_INET, SOCK_RAW, IPPROTO_UDP);
		break;
	case 2:		/*ICMP basic*/
	case 3:		/*RFC1393*/
		sess->send_sock = socket (PF_INET, SOCK_RAW, IPPROTO_RAW);
		break;
	}
#elif defined(__CYGWIN__) || defined( WIN32 ) || defined(_WIN32)
	switch(sess->protocol)
	{
	case 0:		/*TCP*/
	case 1:		/*UDP*/
	case 4:		/*TCP basic*/
		sess->send_sock = socket (PF_INET, SOCK_RAW, IPPROTO_RAW);
		break;
	case 2:		/*ICMP basic*/
	case 3:		/*RFC1393*/
		sess->send_sock = socket (PF_INET, SOCK_RAW, IPPROTO_ICMP);
		break;
	}
    sess->recv_sock = socket (PF_INET, SOCK_RAW, IPPROTO_IP);

    if (sess->recv_sock < 0) {
        LFTErrHandler(sess, ERR_RAW_SOCKET, NULL);
        return;
    }
    local_bind.sin_addr = sess->local_address;
    local_bind.sin_port = 0;
    local_bind.sin_family = AF_INET;
    if (bind(sess->recv_sock, (const struct sockaddr *)(const void *)&local_bind, sizeof(local_bind)) < 0) {
        LFTErrHandler(sess, ERR_SOCKET_BIND, NULL);
        return;
    }
    /* apparently the cygwin include files don't define this: */
#ifndef SIO_RCVALL
# define SIO_RCVALL 0x98000001
#endif
	ioctlret = WSAIoctl(sess->recv_sock, SIO_RCVALL, &optval, sizeof(optval), NULL, 0, &dwBytesRet, NULL, NULL);
	if (ioctlret < 0)
	{
		LFTErrHandler(sess, WRN_WSAIOCTL, NULL);
		if(sess->exit_state<0)
			return;
	}
#else
	switch(sess->protocol)
	{
	case 0:		/*TCP*/
	case 1:		/*UDP*/
	case 4:		/*TCP basic*/
		sess->send_sock = socket (PF_INET, SOCK_RAW, IPPROTO_RAW);
		break;
	case 2:		/*ICMP basic*/
	case 3:		/*RFC1393*/
		sess->send_sock = socket (PF_INET, SOCK_RAW, IPPROTO_ICMP);
		break;
	}
#endif
    if (sess->send_sock < 0) {
        LFTErrHandler(sess, ERR_RAW_SOCKET, NULL);
	if(sess->wsess) {
		free(sess->wsess);
		sess->wsess = NULL;
	}
        return;
    }
#ifdef IP_HDRINCL
	if (setsockopt (sess->send_sock, IPPROTO_IP, IP_HDRINCL, (char *) &on, sizeof (on)) < 0) {
		LFTErrHandler(sess, ERR_IP_HDRINCL, NULL);
		return;
	}
#endif
#ifdef sun
    local_bind.sin_addr = sess->local_address;
    local_bind.sin_port = 0;
    local_bind.sin_family = AF_INET;
    if (bind (sess->send_sock, (const struct sockaddr *)&local_bind, sizeof (local_bind)) < 0) {
        LFTErrHandler(sess, ERR_SOCKET_BIND, NULL);
        return;
    }
#endif
    } /* end if (sess->send_sock <= 0) */
#ifndef IPDEFTTL
#define IPDEFTTL 200
#endif
	/* if(sess->protocol==4)
	{
		if(sess->userlen == 0)
			sess->userlen=def_payload_len;
		if(!(sess->payload = malloc(sess->userlen)))
		{
			LFTErrHandler(sess, ERR_NOT_ENOUGH_MEM, NULL);
			return;
		}
		memset(sess->payload, 0,sess->userlen);
		sess->trace_packet.payload_len = sess->userlen;      
		sess->payloadlen = sess->trace_packet.payload_len;
		sess->trace_packet.payload = sess->payload;
	}
	else */
    if(sess->protocol==2 || sess->protocol==3)		/*ICMP base or ICMP RFC 1393*/
	{
		/* PMTUD: query interface MTU and set initial probe size */
		if (sess->pmtud) {
#if !defined(__CYGWIN__) && !defined(WIN32) && !defined(_WIN32)
			if (sess->pcap_dev) {
				struct ifreq pmtu_ifr;
				int pmtu_fd = socket(AF_INET, SOCK_DGRAM, 0);
				if (pmtu_fd >= 0) {
					memset(&pmtu_ifr, 0, sizeof(pmtu_ifr));
					strncpy(pmtu_ifr.ifr_name, sess->pcap_dev, IFNAMSIZ - 1);
					if (ioctl(pmtu_fd, SIOCGIFMTU, &pmtu_ifr) == 0 &&
					    pmtu_ifr.ifr_mtu >= 68 && pmtu_ifr.ifr_mtu <= 65535) {
						sess->pmtud_current_mtu = (uint16_t)pmtu_ifr.ifr_mtu;
						sess->pmtud_initial_mtu = sess->pmtud_current_mtu;
					}
					close(pmtu_fd);
				}
			}
#endif
			sess->userlen = (int)sess->pmtud_current_mtu;
		}
		u_short ptr2id[sizeof tforid / sizeof (u_short)];
		u_short icmpid=0;
		unsigned int ptridx;
		gettimeofday(&tforid,NULL);
		memcpy(ptr2id, &tforid, sizeof ptr2id);
		for(ptridx=0;ptridx<sizeof(tforid)/sizeof(u_short);ptridx++)
			icmpid^=ptr2id[ptridx];
		generateICMPPacket(sess,
			LFTErrHandler,
			LFTEvtHandler,
			&sess->icmp_packet,
			1,
			ptr2id[0],
			1);
	}
	else				/*TCP and UDP*/
	{
		/* PMTUD: query interface MTU and set initial probe size to fill the path */
		if (sess->pmtud) {
#if !defined(__CYGWIN__) && !defined(WIN32) && !defined(_WIN32)
			if (sess->pcap_dev) {
				struct ifreq pmtu_ifr;
				int pmtu_fd = socket(AF_INET, SOCK_DGRAM, 0);
				if (pmtu_fd >= 0) {
					memset(&pmtu_ifr, 0, sizeof(pmtu_ifr));
					strncpy(pmtu_ifr.ifr_name, sess->pcap_dev, IFNAMSIZ - 1);
					if (ioctl(pmtu_fd, SIOCGIFMTU, &pmtu_ifr) == 0 &&
					    pmtu_ifr.ifr_mtu >= 68 && pmtu_ifr.ifr_mtu <= 65535) {
						sess->pmtud_current_mtu = (uint16_t)pmtu_ifr.ifr_mtu;
						sess->pmtud_initial_mtu = sess->pmtud_current_mtu;
					}
					close(pmtu_fd);
				}
			}
#endif
			/* Override userlen so payload construction sets probe size to MTU */
			sess->userlen = (int)sess->pmtud_current_mtu;
		}
		/* Prepare the trace packet (probe) */
		memset (&(sess->trace_packet), 0, sizeof (sess->trace_packet));
	    
		if (sess->userlen == 0 && (sess->protocol==0 || sess->protocol==4)) {
			/* If user doesn't supply length, default to zero-payload for TCP packets */
			sess->trace_packet.payload_len = 0;
			sess->trace_packet.payload = NULL;
		}
		else if (sess->userlen == 0) {
			if(!(sess->payload = malloc(def_payload_len)))
			{
				LFTErrHandler(sess, ERR_NOT_ENOUGH_MEM, NULL);
				return;
			}
			memset(sess->payload, 0, def_payload_len);
			sess->trace_packet.payload_len = def_payload_len;
		} else {
			if (sess->hostname_lsrr_size > 0) {
				if (sess->protocol==1) 
					sess->userlen -= (sizeof (struct ip) + sizeof(struct udphdr) + sess->trace_packet.lsrr.ipl_len + 1);
				else
					sess->userlen -= (sizeof (struct ip) + sizeof(struct tcphdr) + sess->trace_packet.lsrr.ipl_len + 1);
			} else {
				if (sess->protocol==1) 
					sess->userlen -= (sizeof (struct ip) + sizeof(struct udphdr));
				else
					sess->userlen -= (sizeof (struct ip) + sizeof(struct tcphdr));            
			}
			if (!(sess->payload = malloc(sess->userlen+4)))
			{
				LFTErrHandler(sess, ERR_NOT_ENOUGH_MEM, NULL);
				return;
			}
			memset(sess->payload, 0, sess->userlen+4);
			sess->trace_packet.payload_len = sess->userlen;      
		} 
	    
		sess->payloadlen = sess->trace_packet.payload_len;
		sess->trace_packet.payload = sess->payload;
	    
		/* set up initial ip headers, etc. */
	        
		if (sess->hostname_lsrr_size > 0) {
			for (i = 0; i < sess->hostname_lsrr_size; i++) {
				sess->trace_packet.lsrr.data[i] = get_address(sess, sess->hostname_lsrr[i]);
				if(sess->exit_state<0)
				{
					if(sess->payload)
					{
						free(sess->payload);
						sess->payload=NULL;
					}
					return;
				}
			}
			sess->trace_packet.lsrr.ipl_code = IPOPT_LSRR;
			sess->trace_packet.lsrr.ipl_len = sess->hostname_lsrr_size * 4 + 3;
			sess->trace_packet.lsrr.ipl_ptr = 4;
		}
		sess->trace_packet.ip_hdr.ip_v = 4;
		if (sess->hostname_lsrr_size > 0) {
			sess->trace_packet.ip_hdr.ip_hl = 6 + sess->hostname_lsrr_size; /* 5 + 3byte lsrr + addresses + padding */
			if (sess->protocol==1) 
				sess->trace_packet.ip_hdr.ip_len = sizeof (struct ip) + sizeof(struct udphdr) + sess->trace_packet.lsrr.ipl_len + 1 + sess->trace_packet.payload_len;
			else 
				sess->trace_packet.ip_hdr.ip_len = sizeof (struct ip) + sizeof(struct tcphdr) + sess->trace_packet.lsrr.ipl_len + 1 + sess->trace_packet.payload_len;
		} else {
			sess->trace_packet.ip_hdr.ip_hl = 5;
			if (sess->protocol==1) 
				sess->trace_packet.ip_hdr.ip_len = sizeof (struct ip) + sizeof(struct udphdr) + sess->trace_packet.payload_len;
			else
				sess->trace_packet.ip_hdr.ip_len = sizeof (struct ip) + sizeof(struct tcphdr) + sess->trace_packet.payload_len;
		}
		sess->trace_packet.ip_hdr.ip_off = IP_DF;
	    
#ifdef SCREWED_IP_LEN
		/*  trace_packet.ip_hdr.ip_len = sizeof (struct ip) + sizeof(struct tcphdr); */
#else
		sess->trace_packet.ip_hdr.ip_len = htons (sess->trace_packet.ip_hdr.ip_len);
		sess->trace_packet.ip_hdr.ip_off = htons(sess->trace_packet.ip_hdr.ip_off);
#endif
		sess->trace_packet.ip_hdr.ip_ttl = IPDEFTTL;
		if (sess->protocol==1) 
			sess->trace_packet.ip_hdr.ip_p = IPPROTO_UDP;
		else {
			sess->trace_packet.ip_hdr.ip_p = IPPROTO_TCP;
			if (sess->set_tos) 
				sess->trace_packet.ip_hdr.ip_tos = TOSMINDELAY;
			sess->trace_packet.tcp_hdr.th_win = htons (sess->win_len);
			sess->trace_packet.tcp_hdr.th_off = sizeof (struct tcphdr) / 4;
		}
	}
    
    /* 
     * Init hop storage (array)
     * Init global packet info storage (SLIST)
     * Init per-hop packet info storage (SLIST)
     */
    
    sess->hop_info = (struct hop_info_s *)malloc (sizeof(struct hop_info_s) * hop_info_size);
    for (i = 0; i < 256; i++) {
        memset(&(sess->hop_info[i]), 0, sizeof(struct hop_info_s));
        sess->hop_info[i].state = 0;
		sess->hop_info[i].done_packet = NULL;
        SLIST_INIT(&(sess->hop_info[i].packets));
    }
    
    SLIST_INIT(&(sess->trace_packets));
}
/*---------------------------------------------------------------------------*/
static unsigned int
send_packet (lft_session_params * sess, short nhop, unsigned short ttl, unsigned int seq, unsigned char flags)
{
    struct sockaddr_in dest;
    unsigned int tseq=0;
    unsigned short tttl=0;
    u_short udp_ip_id=0;           /* probe ID for UDP mode, embedded in IP ID field */
    struct sumh sum;
    char *buf;
    char *bptr = NULL;
    int blen = 0;
    char *s;
    EvtSentPacketParam espparam;
    
    /* we'll use gettimeofday() from checktimeouts 
     * instead of doing it again here               */
    /* gettimeofday (&now, NULL); */
    
    struct trace_packet_info_s	*pinfo = NULL;
    struct trace_packet_s *packet = NULL;

#if defined( __CYGWIN__ ) || defined( WIN32 ) || defined(_WIN32)
    buf=(char *)_alloca(maxpacklen+64);
#else
    buf=(char *)alloca(maxpacklen+64);
#endif
    
    if (!(pinfo = (struct trace_packet_info_s *)malloc(sizeof(struct trace_packet_info_s)))) {
        LFTErrHandler(sess, ERR_NOT_ENOUGH_MEM, NULL);
        return 0;
    }
    
    memset(pinfo, 0, sizeof(struct trace_packet_info_s));
    packet = &(pinfo->u.packet);
    memcpy(packet, &(sess->trace_packet), sizeof(struct trace_packet_s));
    
    bptr = buf;
    
    dest.sin_family = AF_INET;
    dest.sin_addr = sess->remote_address;
    dest.sin_port = 0;    
    
    if (seq == 0)
        tseq = new_seq(sess);
    else
        tseq = seq;
    
    if (nhop == -1)
        tttl = ttl;
    else 
        tttl = nhop + 1;
    
    /*There is no place we use this variable*/
    /*sess->num_sent++;*/
    
    sess->ts_last_sent = sess->now;
    
    packet->ip_hdr.ip_ttl = tttl;
    packet->ip_hdr.ip_src = sess->local_address;
    packet->ip_hdr.ip_dst = sess->remote_address;

    /* UDP: encode probe sequence in the IP Identification field so each
     * probe can be matched to its TTL-exceeded reply via the echoed inner
     * IP header.  The destination and source ports are kept constant for
     * all probes, giving every probe the same 5-tuple and therefore the
     * same ECMP hash.  IP ID is not part of the ECMP hash, so varying
     * it here does not affect path selection. */
    if (sess->protocol == 1) {
        /* Use the low 16 bits of tseq as a pseudorandom probe ID embedded in
         * the IP ID field.  tseq starts from a random seq_start (same as the
         * TCP ISN mechanism) so the ID is unpredictable and session-unique.
         * Avoid ip_id=0: some kernels replace a zero IP ID in raw socket sends
         * with their own counter, which would break probe matching on reply. */
        udp_ip_id = (u_short)(tseq & 0xFFFF);
        if (!udp_ip_id) udp_ip_id = 0xFFFF;
        packet->ip_hdr.ip_id = htons(udp_ip_id);
    }

    espparam.flags=flags;
    espparam.nhop=nhop;
    espparam.tseq=tseq;
    espparam.tttl=tttl;
    lft_o_probe_sent();   /* spinner: count in-flight probe */
    if (sess->noisy > 1)
    {
        LFTEvtHandler(sess,EVT_SENT_PACKET, &espparam);
        if(sess->exit_state<0)
        {
            free(pinfo);
            return 0;
        }
    }

    packet->ip_hdr.ip_sum = 0;
#if !defined(SCREWED_IP_LEN)
    packet->ip_hdr.ip_sum = ip_cksum(&packet->ip_hdr);
#endif
        
    memcpy(bptr, &(packet->ip_hdr), sizeof(struct ip));
    bptr += sizeof(struct ip);
    if (packet->lsrr.ipl_len > 0) {
        memcpy(bptr, &(packet->lsrr), packet->lsrr.ipl_len + 1);
        bptr += (packet->lsrr.ipl_len + 1); /* PADDING !!! */
    }
    
    /* Layer-4 preparation */
    
    if (sess->protocol==1) {
        
        if (sess->noisy > 5) 
        {
            LFTEvtHandler(sess,EVT_SHOW_PAYLOAD, packet);
            if(sess->exit_state<0)
            {
                free(pinfo);
                return 0;
            }
        }
        /* Construct UDP (with payload).
         * Both ports are fixed for the entire session so that all probes
         * share the same 5-tuple and are forwarded by load balancers on
         * the same path.  The probe sequence is tracked via the IP ID
         * field (set above before ip_sum) rather than the destination port. */
        packet->udp_hdr.uh_dport = htons(sess->dport);
        packet->udp_hdr.uh_sport = htons (sess->sport);
        packet->udp_hdr.uh_ulen = htons ((sizeof (struct udphdr)) + packet->payload_len);
        /* truncated-udplength 0
            packet->udp_hdr.uh_ulen = 0;   
        packet->udp_hdr.th_flags = flags;
        */
                
        /* create pseudo-header for checksum calculation */
        sum.src=sess->local_address.s_addr;
        sum.dst=sess->remote_address.s_addr;
        sum.fill=0;
        sum.protocol=IPPROTO_UDP;
        sum.len=htons(sizeof(struct udphdr) + packet->payload_len);

        if(!( s = (char *)malloc(sizeof(struct sumh)+sizeof(struct udphdr)+packet->payload_len)))
        {
            LFTErrHandler(sess, ERR_NOT_ENOUGH_MEM, NULL);
            free(pinfo);
            return 0;
        }
        memset(s,0,(sizeof(struct sumh)+sizeof(struct udphdr)+packet->payload_len));
        memcpy(s,&sum,sizeof(struct sumh));
        memcpy(s+sizeof(struct sumh),&(packet->udp_hdr),sizeof(struct udphdr));
        memcpy(s+sizeof(struct sumh)+sizeof(struct udphdr),
               packet->payload,packet->payload_len);
        
        packet->udp_hdr.uh_sum=udp_cksum((unsigned short *)(void *)s, 
                                    sizeof(struct sumh)+sizeof(struct udphdr)+packet->payload_len);
        free(s);
        
        if (sess->noisy > 5)
        {
            LFTEvtHandler(sess,EVT_SHOW_UDP_CHECKSUM, packet);
            if(sess->exit_state<0)
            {
                free(pinfo);
                return 0;
            }
        }
        
#if defined(SOLARIS_LENGTH_IN_CHECKSUM)
        packet->udp_hdr.uh_sum = htons (sizeof (struct udphdr) + packet->payload_len);
#endif
        memcpy(bptr, &(packet->udp_hdr), sizeof(struct udphdr));
        bptr += sizeof(packet->udp_hdr);
        memcpy(bptr, packet->payload, packet->payload_len);
        blen = sizeof(struct ip) + packet->lsrr.ipl_len + sizeof(struct udphdr) + packet->payload_len;
    }
    
    else {
        /* Construct TCP (no payload needed) */
        if (sess->noisy > 5) 
        {
            LFTEvtHandler(sess,EVT_SHOW_PAYLOAD, packet);
            if(sess->exit_state<0)
            {
                free(pinfo);
                return 0;
            }
        }
        packet->tcp_hdr.th_dport = htons (sess->dport);
        /*
         trace_packet.tcp_hdr.th_seq = htonl (seq_start + trace_packet_info_length);
         */
        packet->tcp_hdr.th_seq = htonl (tseq);
        packet->tcp_hdr.th_sport = htons (sess->sport);
        packet->tcp_hdr.th_flags = flags;
        
#if defined(SOLARIS_LENGTH_IN_CHECKSUM)
        packet->tcp_hdr.th_sum = htons (sizeof (struct tcphdr)) + packet->payload_len;
#else
        packet->tcp_hdr.th_sum = 0;
        packet->tcp_hdr.th_sum = tcp_cksum(&packet->ip_hdr, &packet->tcp_hdr, packet->payload, packet->payload_len);
#endif
        if (sess->noisy > 5)
        {
            LFTEvtHandler(sess,EVT_SHOW_TCP_CHECKSUM, packet);
            if(sess->exit_state<0)
            {
                free(pinfo);
                return 0;
            }
        }
        
        memcpy(bptr, &(packet->tcp_hdr), sizeof(struct tcphdr));
        bptr += sizeof(packet->tcp_hdr);
        memcpy(bptr, packet->payload, packet->payload_len);
        blen = sizeof(struct ip) + packet->lsrr.ipl_len + sizeof(struct tcphdr) + packet->payload_len;
    }
    
    /* Packet is ready, fire away */
    if (sendto (sess->send_sock, buf, blen, 0, (const struct sockaddr *)(const void *)&dest, sizeof (dest)) < 0) {
        LFTErrHandler(sess, ERR_RAW_TCP_DISABLED, NULL);
        free(pinfo);
        return 0;
    }
    pinfo->hopno = nhop;
    if (sess->protocol==1)
        pinfo->seq = udp_ip_id;  /* matches ip->ip_id in the echoed inner header */
    else if(!sess->protocol)
        pinfo->seq = tseq;
    pinfo->sent = sess->now;
    SLIST_INSERT_HEAD(&(sess->trace_packets), pinfo, next);
    sess->trace_packets_num++;
    
    if (nhop != -1) {
        SLIST_INSERT_HEAD(&(sess->hop_info[nhop].packets), pinfo, next_by_hop);
        sess->hop_info[nhop].num_sent++;
        sess->hop_info[nhop].all_sent++;
        sess->hop_info[nhop].ts_last_sent = sess->now;
    }
    
    return tseq;
}
/*---------------------------------------------------------------------------*/
static unsigned int
send_hop (lft_session_params * sess, short nhop)
{
    WrnBadHopStateParam wbhsp;
    struct hop_info_s *h = &(sess->hop_info[nhop]);
    
    if (!sess->adaptive)
        return send_packet (sess, nhop , 0, 0, sess->tcp_flags);
    
    if (h->state == HS_SEND_FIN) {
        return send_packet(sess, nhop, 0, 0, TH_FIN);
    }
    
    if (h->state == HS_SEND_SYN) {
        return send_packet(sess, nhop, 0, 0, TH_SYN);
    }
    
    if (h->state == HS_SEND_SYN_ACK) {
        return send_packet(sess, nhop, 0, 0, HS_SEND_SYN_ACK);
    }    

    wbhsp.h=h;
    wbhsp.nhop=nhop;
    LFTErrHandler(sess, WRN_BAD_HOP_STATE, &wbhsp);
    return -1;
}
/*---------------------------------------------------------------------------*/
int hop_state_up (lft_session_params * sess, short nhop)
{
    struct hop_info_s *h = &(sess->hop_info[nhop]);
    
    if (h->state == HS_MAX)
        return -1;
    
    /* 1st try FIN, then SYN_ACK, then SYN, then fail (set to MAX) */
    if (h->state == HS_SEND_FIN) 
        h->state = HS_SEND_SYN_ACK;
    else if (h->state == HS_SEND_SYN_ACK)                     
        h->state = HS_SEND_SYN;
    else 
        h->state = HS_MAX;
    
    h->num_sent = 0; /* for this state that is */
    return 0;
}
/*---------------------------------------------------------------------------*/
int hop_state_copy(lft_session_params * sess, short nhop)
{
    int i;
	
    if (sess->noisy > 4)
        LFTEvtHandler(sess,EVT_SHOW_HOPS, &nhop);
    if(sess->exit_state>=0)
    {
        for (i = (nhop+1); i <= 255; i++)
            if (sess->hop_info[i].state < sess->hop_info[nhop].state) {
                sess->hop_info[i].state = sess->hop_info[nhop].state;
                sess->hop_info[i].num_sent = 0;
            }
    }
            
    return 0;
}
/*---------------------------------------------------------------------------*/
/* Returns 1 if every received packet at hop 'hopno' comes from an IP that was
 * already annotated at an earlier hop AND carries an ICMP dest-unreachable code
 * (icmp_type >= 0, meaning ICMP type 3 of some code).  Returns 0 if any packet
 * is from a new/unseen IP, carries a normal TTL-exceeded response, or if no
 * packets were received at this hop at all. */
int
hop_is_filtered_repeat (const lft_session_params *sess, int hopno)
{
    const struct trace_packet_info_s *tp;
    int has_reply = 0;
    SLIST_FOREACH(tp, &(sess->hop_info[hopno].packets), next_by_hop) {
        if (tp->recv.tv_sec || tp->recv.tv_usec) {
            int k, found = 0;
            has_reply = 1;
            for (k = 0; k < sess->n_shown_asn_ips; k++) {
                if (sess->shown_asn_ips[k].s_addr == tp->hopaddr.s_addr) {
                    found = 1;
                    break;
                }
            }
            if (!found || tp->icmp_type < 0)
                return 0;  /* new IP or TTL-exceeded — not a filtered repeat */
        }
    }
    return has_reply;
}

/* Return the address of the first received packet at hop 'hopno'. */
struct in_addr
hop_first_rcvd_ip (const lft_session_params *sess, int hopno)
{
    const struct trace_packet_info_s *tp;
    struct in_addr zero;
    zero.s_addr = 0;
    SLIST_FOREACH(tp, &(sess->hop_info[hopno].packets), next_by_hop) {
        if (tp->recv.tv_sec || tp->recv.tv_usec)
            return tp->hopaddr;
    }
    return zero;
}

/* Returns the icmp_type of the first received packet at hopno.
 * Used to carry the ICMP name into the compressed filtered-run line. */
int
hop_first_rcvd_icmp_type(const lft_session_params *sess, int hopno)
{
    const struct trace_packet_info_s *tp;
    SLIST_FOREACH(tp, &(sess->hop_info[hopno].packets), next_by_hop) {
        if (tp->recv.tv_sec || tp->recv.tv_usec) return tp->icmp_type;
    }
    return 3;   /* default: port unreachable */
}

/* Emit the compressed filtered-run range line, mirroring EVT_RPT_NO_REPLY format.
 * icmp_type is trace_packet_info_s->icmp_type (0-15 = UNREACH code);
 * icmp_messages[icmp_type+1] gives the human-readable name.
 * Format: **  [filtered] A.B.C.D replied <name> for TTL[s] N [through M] */
void
print_filtered_run (int start_ttl, int end_ttl, struct in_addr ip, int icmp_type)
{
    const char *name = (icmp_type >= 0 && icmp_type <= 15)
                       ? icmp_messages[icmp_type + 1]
                       : "unreachable";
    if (start_ttl == end_ttl)
        printf("**  [filtered] %s replied %s for TTL %d\n",
               inet_ntoa(ip), name, start_ttl);
    else
        printf("**  [filtered] %s replied %s for TTLs %d through %d\n",
               inet_ntoa(ip), name, start_ttl, end_ttl);
}

/*---------------------------------------------------------------------------*/
static void
finish (lft_session_params * sess)
{
    int hopno;
    int maxhop;
    int reply, noreply;
    int as_for_hop = 0;
    struct trace_packet_info_s 	*tp;
    char *netname; 
	/*int ocres;*/
    char *myApp = (char *)malloc((strlen(version)+1 * sizeof(char)) + (strlen(appname) * sizeof(char)));
    struct ip_list_array *ipaslist = (struct ip_list_array *)malloc(sizeof(struct ip_list_array));
	/* Variables for seam detection */
	int icmpcode;
	int asseam_hopno=-1;
	struct in_addr asseam_hopaddr;
	int netseam_hopno=-1;
	struct in_addr netseam_hopaddr;
	struct in_addr classbmask;
	struct in_addr masked_target;
	int prevasn=-1;
	struct in_addr prevasn_hopaddr;
	int prevasn_hopno;
	int lastishole;
	int netreached=0;
	int isseam;
	/* ---------------------------- */
	inet_aton("255.255.0.0", &classbmask);
	masked_target.s_addr=sess->remote_address.s_addr & classbmask.s_addr;

    EvtPacketInfoParam ehip;
    
    memset(ipaslist, 0, sizeof(struct ip_list_array));
    memset(&tbuf, 0, sizeof(tbuf));
    gettimeofday (&(sess->trace_done_time), NULL);
	/*if(sess->protocol==0)
	{
		ocres = open_check(sess, LFTErrHandler, LFTEvtHandler);
		LFTEvtHandler(sess,EVT_OPEN_CHECK_RESULT,&ocres);
		if(ocres==1)
		{
			sess->target_open=1;
			sess->target_filtered=0;
		}
		else
		{
			sess->target_open=0;
			if(ocres<0)
				sess->target_filtered=0;
			else
				sess->target_filtered=1;
		}
	}*/
    if (sess->noisy > 3)
    {
        LFTEvtHandler(sess, EVT_SHOW_NUM_HOPS, NULL);
        if(sess->exit_state <  0)
        {
	    free(ipaslist);
            free(myApp);
            return;
        }
    }
    if (sess->num_hops || (sess->hop_info[0].flags & HF_ENDPOINT)) {
        maxhop = sess->num_hops;
        /* display all packets we received from this host */
        SLIST_FOREACH(tp, &(sess->trace_packets), next)
            if (tp->is_done)
                tp->hopno = maxhop;
    } else {
        maxhop = sess->hop_info_length - 1;
    }
    
    LFTEvtHandler(sess, EVT_TRACE_COMPLETED, NULL);
    if(sess->exit_state < 0)
    {
    	free(ipaslist);
        free(myApp);
        return;
    }
    
    /* if user wants ASN resolution from pwhois/cymru/riswhois, do it in bulk */
    if (sess->do_aslookup || sess->do_netlookup) {
        if(sess->noisy > 1)
        {
            LFTEvtHandler(sess,EVT_ON_RESOLUTION, NULL);
            if(sess->exit_state < 0)
            {
	    	free(ipaslist);
                free(myApp);
                return;
            }
        }
        if (!sess->use_radb) {
            /* populate bulk ip_addr_list structure */
            for (hopno = sess->ttl_min; hopno <= maxhop; hopno++) {
                SLIST_FOREACH(tp, &(sess->hop_info[hopno].packets), next_by_hop)  {
                    if (tp->recv.tv_usec) {
                        (*ipaslist).ipaddr[as_for_hop] = tp->hopaddr;
                        as_for_hop++;
                        (*ipaslist).numItems = (as_for_hop);
                        break;
                    }
                }
            }
            if (sess->use_cymru) {         /* use cymru bulk service */
                if (w_lookup_as_cymru_bulk(sess->wsess, &(*ipaslist)) != 0)
                    if (sess->noisy) LFTErrHandler(sess, WRN_NS_LOOKUP_FAILED, NULL);
            } else if (sess->use_ris) {    /* use RIPE NCC RIS service */
                if (w_lookup_all_riswhois_bulk(sess->wsess, &(*ipaslist)) != 0)
                    if (sess->noisy) LFTErrHandler(sess, WRN_NS_LOOKUP_FAILED, NULL);
            } else {                 /* use pwhois bulk service */
                if ((strlen(version) * sizeof(char)) + 1 + (strlen(appname) * sizeof(char)) < 254) {
                    *myApp = '\0';
                    strcat(myApp, appname); 
		    strcat(myApp, " "); 
		    strcat(myApp, version);
                    strncpy((*ipaslist).application,myApp,511);
                }
                if (w_lookup_all_pwhois_bulk(sess->wsess, &(*ipaslist)) != 0)
                    if (sess->noisy)
			LFTErrHandler(sess, WRN_NS_LOOKUP_FAILED, NULL);
                /* Bulk pWhoIs omits org-name; fall back to individual queries
                 * for any hop that has a valid ASN but no orgName yet.
                 * This mirrors the per-hop query path used by watch mode. */
                {
                    int _fh;
                    for (_fh = 0; _fh < (*ipaslist).numItems; _fh++) {
                        if ((*ipaslist).asn[_fh] && !(*ipaslist).orgName[_fh][0]) {
                            char _ipstr[INET_ADDRSTRLEN];
                            strncpy(_ipstr, inet_ntoa((*ipaslist).ipaddr[_fh]),
                                    sizeof(_ipstr) - 1);
                            _ipstr[sizeof(_ipstr) - 1] = '\0';
                            sess->wsess->consolidated_orgname[0] = '?';
                            sess->wsess->consolidated_orgname[1] = '\0';
                            if (w_lookup_all_pwhois(sess->wsess, _ipstr) == 0 &&
                                sess->wsess->consolidated_orgname[0] != '?')
                                strncpy((*ipaslist).orgName[_fh],
                                        sess->wsess->consolidated_orgname,
                                        sizeof((*ipaslist).orgName[_fh]) - 1);
                        }
                    }
                }
            }
            if(sess->exit_state < 0)
            {
                free(ipaslist);
		free(myApp);
                return;
            }
        }
    } 

	free(myApp);

	LFTEvtHandler(sess,EVT_TRACE_REPORT_START, &maxhop);
	if(sess->exit_state < 0){
		free(ipaslist);
		return;
	}

	/* seam detection */
	as_for_hop=0;
	for(hopno = sess->ttl_min; hopno < sess->hop_info_length; hopno++)
	{
		struct in_addr last_hop;
		icmpcode=-100;
		last_hop.s_addr = 0;
		if(sess->hop_info[hopno].all_rcvd)
		{
			lastishole=0;
            SLIST_FOREACH(tp, &(sess->hop_info[hopno].packets), next_by_hop)
			{
                if(tp->recv.tv_sec)
				{
					if(hopno<=maxhop)
						icmpcode=tp->icmp_type;
					if(last_hop.s_addr != tp->hopaddr.s_addr)
					{
						if((tp->hopaddr.s_addr & classbmask.s_addr) == masked_target.s_addr)
							netreached=1;
						else
						{
							netseam_hopno=hopno;
							netseam_hopaddr=tp->hopaddr;
						}
						if(sess->do_aslookup || sess->do_netlookup)
						{
							if (sess->use_radb)
							{ 
								/* using RADB/IRR */
								tp->asnumber = w_lookup_as(sess->wsess, inet_ntoa(tp->hopaddr));
							}
							else
							{
								/* using pwhois by default */
								tp->asnumber = (*ipaslist).asn[as_for_hop];
							}
							if(prevasn==-1)
							{
								if(tp->asnumber)
								{
									prevasn=tp->asnumber;
									prevasn_hopno=hopno;
									prevasn_hopaddr=tp->hopaddr;
								}
							}
							else
							{
								if(tp->asnumber)
								{
									if(tp->asnumber!=prevasn)
									{
										asseam_hopno=prevasn_hopno;
										asseam_hopaddr=prevasn_hopaddr;
									}
									prevasn=tp->asnumber;
									prevasn_hopno=hopno;
									prevasn_hopaddr=tp->hopaddr;
								}
							}
						}
						last_hop=tp->hopaddr;
					}
				}
			}
			as_for_hop++;
		}
		else
			lastishole=1;
		if(icmpcode==-1)
			break;
	}
	if(!netreached)
		netseam_hopno=-1;
	if(lastishole)
		asseam_hopno=-1;
	/* -------------- */
noreply = 0;
reply = 0;
as_for_hop = 0;            /* this correlates the hopno to the asn stored in ipaslist */
{
    int found_target = 0;  /* suppress duplicate target hops from ahead-limit overshoot */
    /* Filtered-run state: consecutive hops where the same already-seen IP
     * responds with an ICMP dest-unreachable code.  Compressed into one line. */
    int filt_run_start    = 0;  /* first TTL of active filtered run; 0 = none */
    int filt_run_end      = 0;  /* last TTL of active filtered run */
    int filt_run_icmp_type = 3; /* icmp_type of the filtering IP (default: port unreachable) */
    struct in_addr filt_run_ip;
    filt_run_ip.s_addr = 0;

for (hopno = sess->ttl_min; hopno <= maxhop; hopno++) {
    struct in_addr last_hop;

    /* Suppress duplicate target-hop output caused by ahead-limit overshoot */
    if (found_target && (sess->hop_info[hopno].flags & HF_ENDPOINT)) {
        reply = 0;
        continue;
    }

    /* Detect filtered-repeat hops: same already-seen IP responding with ICMP
     * type-3 (dest unreachable / port blocked) at multiple TTL levels.
     * Compress into a single range line, mirroring the noreply treatment. */
    {
        int _is_filt = (sess->hop_info[hopno].all_rcvd != 0) &&
                       hop_is_filtered_repeat(sess, hopno);
        if (!_is_filt && filt_run_start > 0) {
            print_filtered_run(filt_run_start, filt_run_end, filt_run_ip, filt_run_icmp_type);
            filt_run_start = 0;
        }
        if (_is_filt) {
            /* Flush any pending noreply range before extending filtered run */
            if (noreply >= 1) {
                EvtNoReplyParam _nrp;
                _nrp.hopno = hopno;
                _nrp.noreply = noreply;
                LFTEvtHandler(sess, EVT_RPT_NO_REPLY, &_nrp);
                if (sess->exit_state < 0) { free(ipaslist); return; }
            }
            if (filt_run_start == 0) {
                filt_run_start     = hopno;
                filt_run_ip        = hop_first_rcvd_ip(sess, hopno);
                filt_run_icmp_type = hop_first_rcvd_icmp_type(sess, hopno);
            }
            filt_run_end = hopno;
            reply = 1;
        } else { /* !_is_filt — normal rendering path */

    if (sess->hop_info[hopno].all_rcvd != 0) {
        if (noreply >= 1)
        {
            if (sess->pmtud && global_output_style == 0) {
                /* Per-hop output when PMTUD is active.
                 * [ICMP filtered]: hop suppresses TTL-exceeded for large probes
                 *   but forwards them (confirmed because a downstream hop or the
                 *   target responded to the same probe size).
                 * [MTU Limit]: hop did not respond to the DF probe and we cannot
                 *   confirm the packet was forwarded — likely a true dropper.
                 * [neglected]: hop ignored probes of all sizes. */
                int bh;
                for (bh = hopno - noreply; bh < hopno; bh++) {
                    if (sess->hop_info[bh].pmtud_icmp_suppressor)
                        printf("**  [ICMP filtered] hop %d suppressed TTL-exceeded"
                               " for DF probe (%u bytes) — packet confirmed"
                               " forwarded\n",
                               bh + 1,
                               (unsigned)sess->hop_info[bh].path_mtu);
                    else if (sess->hop_info[bh].pmtud_blackhole)
                        printf("**  [MTU Limit] hop %d did not respond to DF probe"
                               " (%u bytes, no PTB received)\n",
                               bh + 1,
                               (unsigned)sess->hop_info[bh].path_mtu);
                    else
                        printf("**  [neglected] no reply packets received"
                               " from TTL %d\n", bh + 1);
                }
            } else {
                EvtNoReplyParam nrp;
                nrp.hopno=hopno;
                nrp.noreply=noreply;
                LFTEvtHandler(sess,EVT_RPT_NO_REPLY, &nrp);
                if(sess->exit_state < 0)
                {
                    free(ipaslist);
                    return;
                }
            }
        }
    }
    
    last_hop.s_addr = 0;
    if ((sess->hop_info[hopno].state == HS_SEND_FIN) && (sess->hop_info[hopno+1].state == HS_SEND_SYN) && (sess->hop_info[hopno+1].ts_last_recv.tv_sec)) {
        LFTEvtHandler(sess,EVT_RPT_FRW_INSPECT_PACKS, NULL);
        if(sess->exit_state < 0){
        	free(ipaslist);
		return;
	}
    }

    if ((sess->hop_info[hopno].state != HS_SEND_SYN_ACK) && (sess->hop_info[hopno+1].state == HS_SEND_SYN_ACK) && (hopno == (sess->num_hops - 1))) {
        LFTEvtHandler(sess,EVT_RPT_FRW_STATE_FILTER, NULL);
        if(sess->exit_state < 0){
        	free(ipaslist);
		return;
	}
    }    
    
    if ((sess->hop_info[hopno].flags & HF_ENDPOINT) && (noreply >= ((maxhop - sess->ttl_min)/2)) && sess->num_hops > 3) {
        LFTEvtHandler(sess,EVT_RPT_BSD_BUG, NULL);
        if(sess->exit_state < 0){
        	free(ipaslist);
		return;
	}
    }
    
    if (sess->hop_info[hopno].all_rcvd == 0) {
        reply = 0;
    } else {
        LFTEvtHandler(sess,EVT_RPT_HOP_INFO_START,&hopno);
        if(sess->exit_state < 0){
        	free(ipaslist);
		return;
	}

        SLIST_FOREACH(tp, &(sess->hop_info[hopno].packets), next_by_hop) {
            
            if (tp->recv.tv_sec) {
                reply = 1;
                                                                
                if (last_hop.s_addr != tp->hopaddr.s_addr) {
                    ehip.asnumber = 0; 	/* init/clear the ASN */
                    if (sess->do_aslookup) {
                        if (sess->use_radb) { 
                            /* using RADB/IRR */
                            ehip.asnumber = w_lookup_as(sess->wsess, inet_ntoa(tp->hopaddr));
                        } else {
                            /* using pwhois by default */
                            ehip.asnumber = (*ipaslist).asn[as_for_hop];
                        }
                    }
					tp->asnumber=ehip.asnumber;
					ehip.netname=NULL;
                    if (sess->do_netlookup) {
                        if (!sess->do_aslookup || (sess->do_aslookup && !sess->use_cymru && !sess->use_radb)) {
                            netname = (*ipaslist).netName[as_for_hop];
                        } else {
                            netname = w_lookup_netname(sess->wsess, inet_ntoa(tp->hopaddr));
                        }
                        ehip.netname=netname;
                    }
					if(ehip.netname)
						strncpy(tp->netname, ehip.netname, 511);
					else
						tp->netname[0]=0;

                    /* Orgname — pwhois bulk only (not available via cymru/radb).
                     * Fire whenever pwhois ran, which is do_aslookup OR do_netlookup. */
                    tp->orgname[0] = 0;
                    if ((sess->do_aslookup || sess->do_netlookup) && !sess->use_cymru && !sess->use_radb)
                        strncpy(tp->orgname, (*ipaslist).orgName[as_for_hop],
                                sizeof(tp->orgname) - 1);

                }
                ehip.last_hop=last_hop;
				tp->last_hop=ehip.last_hop;
                last_hop = tp->hopaddr;
            }
            ehip.tp=tp;
			/* seam processing */
			isseam=0;
			ehip.is_asseam=0;
			ehip.is_netseam=0;
			ehip.is_open=0;
			ehip.is_filtered=0;
			ehip.seam_traced=0;
			if(sess->check_seam && hopno==asseam_hopno && tp->hopaddr.s_addr==asseam_hopaddr.s_addr)
			{
				isseam=1;
				ehip.is_asseam=1;
			}
			if(sess->check_seam && hopno==netseam_hopno && tp->hopaddr.s_addr==netseam_hopaddr.s_addr)
			{
				isseam=1;
				ehip.is_netseam=1;
			}
			if(isseam)
			{
				if(sess->check_seam)
				{
					int curroutputstyle=global_output_style;
					char hostname[100];
					ehip.seam_traced=1;
					global_output_style=2;
					lft_session_params * subsess=LFTSessionOpen();
					strncpy(hostname, inet_ntoa(tp->hopaddr),100);
					subsess->senddevsel = sess->senddevsel;
					subsess->senddev = sess->senddev;
					subsess->auto_ports=0;
					subsess->dport=179;
					subsess->seq_start=30;
					subsess->retry_min=1;
					subsess->retry_max=1;
					subsess->resolve_names=0;
					subsess->ahead_limit=1;
					subsess->break_on_icmp = 0;
					subsess->is_graphviz_subquery=1;
					subsess->hostname=hostname;
					subsess->hostname_lsrr_size = 0;
					LFTExecute(subsess);
					ehip.is_open=subsess->target_open;
					ehip.is_filtered=subsess->target_filtered;
					LFTSessionClose(subsess);
					global_output_style=curroutputstyle;
				}
			}
			/* PMTUD annotations */
			ehip.path_mtu              = sess->hop_info[hopno].path_mtu;
			ehip.pmtud_blackhole       = sess->hop_info[hopno].pmtud_blackhole;
			ehip.pmtud_icmp_suppressor = sess->hop_info[hopno].pmtud_icmp_suppressor;
			/* --------------- */
            LFTEvtHandler(sess,EVT_RPT_PACKET_INFO,&ehip);
	    if(sess->exit_state < 0){
            	free(ipaslist);
	    	return;
	    }
        }
        LFTEvtHandler(sess,EVT_RPT_PACKET_LIST_END,NULL);
        if(sess->exit_state < 0){
        	free(ipaslist);
		return;
	}
        /* Record that we've printed the first target hop so the guard above
         * can suppress any duplicate endpoint hops from ahead-limit overshoot. */
        if (sess->hop_info[hopno].flags & HF_ENDPOINT)
            found_target = 1;
    }
        } /* !_is_filt */
    } /* filtered-repeat detection block */
    if (reply) {
        noreply = 0;
        as_for_hop++;
    } else
        noreply++;

    reply = 0;
} /* for(...) */
/* Flush any trailing filtered run (e.g. filtered hops at the end of the trace) */
if (filt_run_start > 0)
    print_filtered_run(filt_run_start, filt_run_end, filt_run_ip, filt_run_icmp_type);
} /* found_target block */

/* num_hops==0 is ambiguous: it means both "not set" and "target replied at hopno=0
 * (TTL=1)".  Guard against the latter before reporting no hops found. */
if (!sess->num_hops && !(sess->hop_info[0].flags & HF_ENDPOINT)){
    LFTEvtHandler(sess, EVT_RPT_NO_HOPS, &maxhop);
}
if (sess->timetrace){
    LFTEvtHandler(sess, EVT_RPT_TIME_TRACE, NULL);
}
LFTEvtHandler(sess, EVT_ON_EXIT, NULL);
free(ipaslist);
return;
}

/*---------------------------------------------------------------------------*/
/* forward declaration — pcap_process_packet is defined later in this file */
#if !defined(__CYGWIN__) && !defined(WIN32) && !defined(_WIN32)
static void pcap_process_packet(u_char *, const struct pcap_pkthdr *, const u_char *);
#endif

/*---------------------------------------------------------------------------*/
/*
 * lft_pmtud_verify_blackholes — called just before finish() when -K is active.
 *
 * Every hop tentatively flagged pmtud_blackhole=1 could be either:
 *   (a) a genuine PMTUD black hole: drops oversized DF probes silently, but
 *       WILL respond to small probes with ICMP TTL-exceeded, or
 *   (b) a plain non-responder that ignores TTL-exceeded entirely.
 *
 * We send one small (128-byte) probe at each suspect TTL and wait up to
 * timeout_ms for a reply.  If a TTL-exceeded comes back the hop is a
 * confirmed black hole; if nothing comes back we clear the flag so the hop
 * is rendered as ordinary [neglected].
 *
 * State cleanup: both ts_last_recv and all_rcvd are reset to zero after the
 * probe so finish() still treats the hop as no-reply regardless of outcome;
 * pmtud_blackhole is the discriminator used in output.
 */
#if !defined(__CYGWIN__) && !defined(WIN32) && !defined(_WIN32)
void
lft_pmtud_verify_blackholes(lft_session_params *sess)
{
    int nhop;
    const uint16_t verify_mtu = 128;   /* small enough to pass any real link */

    if (sess->protocol == 2 || sess->protocol == 3) {
        /* ICMP probe mode: resize via sess->userlen */
        int save_userlen = sess->userlen;
        sess->userlen = (int)verify_mtu;

        for (nhop = sess->ttl_min; nhop < sess->hop_info_length; nhop++) {
            struct timeval deadline, now;
            int confirmed;

            if (!sess->hop_info[nhop].pmtud_blackhole)
                continue;

            /* Clear reply state so a fresh reply is detectable */
            sess->hop_info[nhop].ts_last_recv.tv_sec  = 0;
            sess->hop_info[nhop].ts_last_recv.tv_usec = 0;

            /* Set verifying flag before send so EVT_SENT_PACKET shows [PMTUD verify] */
            sess->pmtud_verifying = 1;
            if (sess->noisy > 1 && !global_output_style)
                printf("PMTUD      hop %d: sending verify probe (LEN=%u)\n",
                       nhop + 1, (unsigned)verify_mtu);
            icmp_pmtud_send_verify_hop(sess, nhop);

            gettimeofday(&deadline, NULL);
            deadline.tv_usec += (long)sess->timeout_ms * 1000L;
            while (deadline.tv_usec >= 1000000L) {
                deadline.tv_sec++;
                deadline.tv_usec -= 1000000L;
            }

            confirmed = 0;
            do {
                pcap_dispatch(sess->pcapdescr, 1, pcap_icmp_process_packet, (u_char *)sess);
                if (sess->hop_info[nhop].ts_last_recv.tv_sec) {
                    confirmed = 1;
                    break;
                }
                gettimeofday(&now, NULL);
            } while (timercmp(&now, &deadline, <));
            sess->pmtud_verifying = 0;

            sess->hop_info[nhop].ts_last_recv.tv_sec  = 0;
            sess->hop_info[nhop].ts_last_recv.tv_usec = 0;
            sess->hop_info[nhop].all_rcvd = 0;

            if (!confirmed) {
                if (sess->noisy > 1 && !global_output_style)
                    printf("PMTUD      hop %d: verify timed out, reclassified as [neglected]\n",
                           nhop + 1);
                sess->hop_info[nhop].pmtud_blackhole = 0;
                sess->hop_info[nhop].path_mtu        = 0;
            } else {
                if (sess->noisy > 1 && !global_output_style)
                    printf("PMTUD      hop %d: verify confirmed DF black hole (path_mtu=%u)\n",
                           nhop + 1, (unsigned)sess->hop_info[nhop].path_mtu);
            }
        }

        sess->userlen = save_userlen;

        /* Post-verification pass: if the target was reached by the oversized
         * probes (no PTB received, so pmtud_current_mtu never stepped down from
         * the initial value), then every confirmed black hole on the path
         * forwarded those probes and is actually suppressing ICMP TTL-exceeded
         * by size policy — not dropping the packets.  Reclassify accordingly. */
        if (sess->num_hops > 0 && sess->pmtud_ptb_mtu == 0) {
            for (nhop = sess->ttl_min; nhop < sess->hop_info_length; nhop++) {
                if (sess->hop_info[nhop].pmtud_blackhole) {
                    sess->hop_info[nhop].pmtud_blackhole       = 0;
                    sess->hop_info[nhop].pmtud_icmp_suppressor = 1;
                    if (sess->noisy > 1 && !global_output_style)
                        printf("PMTUD      hop %d: reclassified as [ICMP filtered]"
                               " — oversized probe confirmed forwarded\n", nhop + 1);
                }
            }
        }
        return;
    }

    /* TCP / UDP probe mode: resize via trace_packet template */
    {
        int hdr_size;
        int save_payload_len;
        u_short save_ip_len;

        if (sess->protocol == 1)
            hdr_size = (int)(sizeof(struct ip) + sizeof(struct udphdr));
        else
            hdr_size = (int)(sizeof(struct ip) + sizeof(struct tcphdr));
        if (sess->trace_packet.lsrr.ipl_len > 0)
            hdr_size += sess->trace_packet.lsrr.ipl_len + 1;

        if ((int)verify_mtu <= hdr_size)
            return;   /* degenerate — skip */

        save_payload_len = sess->trace_packet.payload_len;
        save_ip_len      = sess->trace_packet.ip_hdr.ip_len;

        sess->trace_packet.payload_len = (int)verify_mtu - hdr_size;
#ifdef SCREWED_IP_LEN
        sess->trace_packet.ip_hdr.ip_len = (u_short)verify_mtu;
#else
        sess->trace_packet.ip_hdr.ip_len = htons((u_short)verify_mtu);
#endif

        for (nhop = sess->ttl_min; nhop < sess->hop_info_length; nhop++) {
            struct timeval deadline, now;
            int confirmed;

            if (!sess->hop_info[nhop].pmtud_blackhole)
                continue;

            /* Clear reply state so a fresh reply is detectable */
            sess->hop_info[nhop].ts_last_recv.tv_sec  = 0;
            sess->hop_info[nhop].ts_last_recv.tv_usec = 0;

            /* Set verifying flag before send so EVT_SENT_PACKET shows [PMTUD verify] */
            sess->pmtud_verifying = 1;
            if (sess->noisy > 1 && !global_output_style)
                printf("PMTUD      hop %d: sending verify probe (LEN=%u)\n",
                       nhop + 1, (unsigned)verify_mtu);
            send_hop(sess, nhop);

            gettimeofday(&deadline, NULL);
            deadline.tv_usec += (long)sess->timeout_ms * 1000L;
            while (deadline.tv_usec >= 1000000L) {
                deadline.tv_sec++;
                deadline.tv_usec -= 1000000L;
            }

            /* Guard: prevent the check_timeouts() call inside process_packet()
             * from re-entrantly calling finish() while we are still verifying. */
            confirmed = 0;
            do {
                pcap_dispatch(sess->pcapdescr, 1, pcap_process_packet, (u_char *)sess);
                if (sess->hop_info[nhop].ts_last_recv.tv_sec) {
                    confirmed = 1;
                    break;
                }
                gettimeofday(&now, NULL);
            } while (timercmp(&now, &deadline, <));
            sess->pmtud_verifying = 0;

            /* Always restore no-reply state: finish() must treat this as a hole.
             * pmtud_blackhole is the sole discriminator for [MTU Limit] vs [neglected]. */
            sess->hop_info[nhop].ts_last_recv.tv_sec  = 0;
            sess->hop_info[nhop].ts_last_recv.tv_usec = 0;
            sess->hop_info[nhop].all_rcvd = 0;

            if (!confirmed) {
                /* Small probe also timed out — plain non-responder, not PMTUD-related */
                if (sess->noisy > 1 && !global_output_style)
                    printf("PMTUD      hop %d: verify timed out, reclassified as [neglected]\n",
                           nhop + 1);
                sess->hop_info[nhop].pmtud_blackhole = 0;
                sess->hop_info[nhop].path_mtu        = 0;
            } else {
                /* Confirmed PMTUD black hole — leave pmtud_blackhole=1, path_mtu set */
                if (sess->noisy > 1 && !global_output_style)
                    printf("PMTUD      hop %d: verify confirmed DF black hole (path_mtu=%u)\n",
                           nhop + 1, (unsigned)sess->hop_info[nhop].path_mtu);
            }
        }

        sess->trace_packet.payload_len   = save_payload_len;
        sess->trace_packet.ip_hdr.ip_len = save_ip_len;

        /* Post-verification pass: reclassify confirmed black holes as ICMP
         * suppressors when the target was reached without any PTB step-down,
         * proving the oversized probes were forwarded through those hops. */
        if (sess->num_hops > 0 && sess->pmtud_ptb_mtu == 0) {
            for (nhop = sess->ttl_min; nhop < sess->hop_info_length; nhop++) {
                if (sess->hop_info[nhop].pmtud_blackhole) {
                    sess->hop_info[nhop].pmtud_blackhole       = 0;
                    sess->hop_info[nhop].pmtud_icmp_suppressor = 1;
                    if (sess->noisy > 1 && !global_output_style)
                        printf("PMTUD      hop %d: reclassified as [ICMP filtered]"
                               " — oversized probe confirmed forwarded\n", nhop + 1);
                }
            }
        }
    }
}
#endif /* !CYGWIN/WIN32 */

/*---------------------------------------------------------------------------*/
static int check_timeouts (lft_session_params * sess)
{
    int nhop;
    int need_reply = 0;
    int no_reply = 0;
    int last_return = 0;
    
    gettimeofday (&(sess->now), NULL);
    if (timediff_ms (sess->ts_last_sent, sess->now) < sess->scatter_ms)
        return 0;			/* not ready to send another packet yet */
    
    for (nhop = sess->ttl_min; nhop < sess->hop_info_length; nhop++) {
        if (!sess->hop_info[nhop].num_sent) {
            send_hop(sess, nhop);
            return 0;
        }
    }
    
    for (nhop = sess->ttl_min; nhop < sess->hop_info_length; nhop++) {
        if (sess->hop_info[nhop].num_sent <= sess->retry_max && !sess->hop_info[nhop].ts_last_recv.tv_sec) {
            if (sess->noisy > 4)
            {
                LFTEvtHandler(sess,EVT_TTL_NO_REPLY,&nhop);
                if(sess->exit_state<0)
                    return 0;
            }
            if (timediff_ms (sess->hop_info[nhop].ts_last_sent, sess->now) >= sess->timeout_ms) {
                /* we timed out waiting for this hop -- retry if we have any
                * more tries */
                if (sess->hop_info[nhop].num_sent < sess->retry_max) {
                    if (!sess->noisy && !sess->nostatus)
                        LFTEvtHandler(sess,EVT_PROGRESS_NO_REPLY,NULL);
                    if (sess->noisy > 2)
                        LFTEvtHandler(sess,EVT_TTL_TOUT_RESEND,&nhop);
                    if(sess->exit_state<0)
                        return 0;
                    send_hop(sess, nhop);
                    return 0;
                } else {
                    if (!sess->adaptive || hop_state_up(sess, nhop)) {
                        if (sess->noisy > 3)
                            LFTEvtHandler(sess,EVT_TTL_TOUT_GIVINGUP,&nhop);
                        if(sess->exit_state<0)
                            return 0;
                        /* PMTUD: DF probe timed out — silent black hole.
                         * Store the probe size that was rejected so output
                         * can report the exact byte count to the user. */
                        if (sess->pmtud) {
                            sess->hop_info[nhop].pmtud_blackhole = 1;
                            sess->hop_info[nhop].path_mtu =
                                sess->pmtud_current_mtu;
                        }
                        no_reply++;
                    }
                }
            } else {
                need_reply++;		/* we have to wait for this one to timeout */
            }
        } else { /* have reply */
            last_return = nhop;
        }
    }
    
    if (sess->noisy > 4) {
        EvtDebugCheckpoint1Param edcp;
        edcp.last_return=last_return;
        edcp.need_reply=need_reply;
        edcp.no_reply=no_reply;
        LFTEvtHandler(sess,EVT_DBG_CHECKPOINT1,&edcp);
        if(sess->exit_state<0)
            return 0;
    }
    if (no_reply >= sess->ahead_limit) {	/* we timed out. */
        if ((last_return + 3) * 2 < sess->hop_info_length) {
            if (sess->pmtud_verifying)
                return 0;   /* re-entrant call during verification — do nothing */
            if ((need_reply < 3) && (sess->num_rcvd < 2))
                LFTEvtHandler(sess,EVT_CANT_RELIABLY_RTRIP,NULL);
            if(sess->exit_state<0)
                return 0;
#if !defined(__CYGWIN__) && !defined(WIN32) && !defined(_WIN32)
            if (sess->pmtud)
                lft_pmtud_verify_blackholes(sess);
#endif
            finish (sess);
            return 1;
        }
    }

	if ((!sess->num_hops || sess->hop_info_length < sess->num_hops || need_reply) && sess->hop_info_length < sess->ttl_limit) { 
	    if (sess->noisy > 4) 
	        LFTEvtHandler(sess,EVT_HAVE_UNANSWERRED_HOPS,NULL);
	    if (need_reply >= sess->ahead_limit) {
	        if (sess->noisy > 4)
	            LFTEvtHandler(sess,EVT_TOO_FAR_AHEAD,NULL);
	        return 0;			/* wait for some replies before we go on */
	    }
	    if(sess->exit_state<0)
	        return 0;
    
	    if (sess->num_hops > 0 && sess->hop_info_length >= sess->num_hops) {
	        if (sess->noisy > 3)
	            LFTEvtHandler(sess,EVT_HAVE_GAPS,NULL);
	        return 0;			/* we know how long the path is --
	                             * wait to fill in the blanks      */
	    }
    
	    nhop = sess->hop_info_length++;
	    send_hop(sess, nhop);
	} else
	{ 
	    if (sess->noisy >= 4)
	    {
	        LFTEvtHandler(sess,EVT_EITHER_RESP_OR_TOUT,NULL);
	        if(sess->exit_state<0)
	            return 0;
	    }
	    for (nhop = sess->ttl_min; nhop < sess->hop_info_length; nhop++)
	    {
	        if (sess->hop_info[nhop].num_sent < sess->retry_min && sess->hop_info[nhop].num_sent <= sess->retry_max)
	        {
	            send_hop(sess, nhop);
	            return 0;
	        }
	    }
	    	    
	    /* If we're adaptive and target appears closed, increment state and try again */
	    if ((sess->adaptive) && (sess->target_open < 1) && (sess->hop_info[nhop].state != HS_MAX))
	    {
	       hop_state_up(sess, nhop);
	       send_hop(sess, nhop);
	       return 0;
	    }

	    if (sess->pmtud_verifying)
	        return 0;   /* re-entrant call during verification — do nothing */
#if !defined(__CYGWIN__) && !defined(WIN32) && !defined(_WIN32)
	    if (sess->pmtud)
	        lft_pmtud_verify_blackholes(sess);
#endif
	    finish (sess);
	    return 1;
	}
	return 0;
}
/*---------------------------------------------------------------------------*/
static void
recv_packet (lft_session_params * sess, unsigned int seq, struct in_addr ipaddr, int icmp_type, const struct pcap_pkthdr *hdr)
{
    double ms;
    struct trace_packet_info_s *tp = NULL;
    EvtNonSeqPacketParam ensp;
    
    /* Depending on the platform, we can use
     * the pcap header's timeval or we must call
       gettimeofday() for each packet  */
    
#if defined( __CYGWIN__ ) || defined( WIN32 ) || defined(_WIN32) || defined( USE_GTOD )
    (void)hdr;

    gettimeofday (&(sess->now), NULL);
#else
    sess->now.tv_sec = hdr->ts.tv_sec; 
    sess->now.tv_usec = hdr->ts.tv_usec;
    /* gettimeofday (&now, NULL); */
#endif
    
    /* First, search every probe to find an exact sequence match */
    SLIST_FOREACH(tp, &(sess->trace_packets), next) {
        if (tp->seq == seq) {
            break;
        }
    } 

    /* Next, if no probes have an exact sequence match, look for an unincremented ACK */
    if (tp == NULL) {
        if (sess->noisy > 3)
        {
            LFTEvtHandler(sess,EVT_LOOKFOR_UNINC_ACK,NULL);
            if(sess->exit_state<0)
                return;
        }
        SLIST_FOREACH(tp, &(sess->trace_packets), next) {       
            if (((tp->seq) == (seq +1)) && (icmp_type == -1))
                    break;
        }
    }
    
    /* Next, if no probes have an exact sequence match, look for an off-by-len */
    if (tp == NULL) {
        if (sess->noisy > 3)
        {
            LFTEvtHandler(sess,EVT_LOOKFOR_OFF_BY_LEN,NULL);
            if(sess->exit_state<0)
                return;
        }
        SLIST_FOREACH(tp, &(sess->trace_packets), next) {       
            if (((tp->seq) == (seq - sess->payloadlen)) && (icmp_type == -1))
                break;
        }
    }    
    
    /* Last resort.  Catch any response from the target */
    if (tp == NULL) {
        if (sess->noisy > 3)
        {
            LFTEvtHandler(sess,EVT_LOOKFOR_LAST_RESORT,NULL);
            if(sess->exit_state<0)
                return;
        }
        SLIST_FOREACH(tp, &(sess->trace_packets), next) {       
            /* Special case: look for a response to our SYN_ACK */
            if (tp->u.packet.tcp_hdr.th_flags == HS_SEND_SYN_ACK) {
                if (!tp->recv.tv_sec) {
                    break;
                } 
            }            
            /* Truly the last resort: packet from the target with a wacky ACK sequence */
            if ((ipaddr.s_addr == sess->remote_address.s_addr) && (tp->hopaddr.s_addr == 0) && (icmp_type == -1)) {
                sess->target_anomaly = 1;
            }
        }
    }
    
    /* This packet is not even close, drop it and move on */
    if (!tp) {
        if (sess->noisy)
            LFTEvtHandler(sess,EVT_SKIP_PACKET,NULL);
        else
            if (!sess->nostatus) 
				LFTEvtHandler(sess,EVT_PROGRESS_SKIP_PACKET,NULL);
        return;
    }
    
    if (tp->seq != seq) {
        ensp.ipaddr=ipaddr;
        ensp.tp=tp;
        if (((tp->seq) == (seq + 1)) && (icmp_type == -1)) {
            if (sess->noisy > 1) {
                LFTEvtHandler(sess,EVT_ACK_WAS_NOT_INC,&ensp);
                if(sess->exit_state<0)
                    return;
            }
            /* return; */
        } else if (((tp->seq) == (seq - sess->payloadlen)) && (icmp_type == -1)) {
            if (sess->noisy > 1) {
                LFTEvtHandler(sess,EVT_RST_REL_TO_ISN,&ensp);
                if(sess->exit_state<0)
                    return;
            }
            /* return; */
        } else if ((ipaddr.s_addr == sess->remote_address.s_addr) && (icmp_type == -1)) {
            if (sess->noisy > 1) {
                LFTEvtHandler(sess,EVT_ACK_WAS_WAY_OFF,&ensp);
                if(sess->exit_state<0)
                    return;
            }
            /* return; */
        }
    }
    
    if (tp->recv.tv_sec) {
        if (sess->noisy)
            LFTEvtHandler(sess,EVT_DUPLICATE_PACKET, NULL);
        else
            if (!sess->nostatus)
                LFTEvtHandler(sess,EVT_PROGRESS_DUPLICATE,NULL);
        return;
    }
    
    /* Set icmp_type before firing EVT_RECV_PACKET so the handler sees the
     * correct value (race-condition fix: it was being set 30 lines later). */
    tp->icmp_type = icmp_type;

    if (sess->noisy > 1)
    {
        EvtRecvPacketParam erpp;
        erpp.ipaddr=ipaddr;
        erpp.seq=seq;
        erpp.tp=tp;
        LFTEvtHandler(sess,EVT_RECV_PACKET,&erpp);
    }
    else {
        if (!sess->nostatus)
            LFTEvtHandler(sess,EVT_PROGRESS_OK,NULL);
    }
    if(sess->exit_state<0)
        return;

    /* increment received packet counter */
    sess->num_rcvd++;

    tp->recv = sess->now;
    if (tp->hopno != -1) {
        sess->hop_info[tp->hopno].ts_last_recv = sess->now;
        sess->hop_info[tp->hopno].all_rcvd++;
        hop_state_copy(sess, tp->hopno);
        /* indicate this hop has a sequence anomaly */

        if (icmp_type == -1)
            sess->hop_info[tp->hopno].flags |= HF_ENDPOINT;
    }

    tp->hopaddr = ipaddr;
    /* tp->icmp_type already set above, before EVT_RECV_PACKET */
#ifdef HAVE_CARES
    lft_ares_fire(sess, ipaddr);   /* queue PTR lookup for this hop (deduped) */
#endif
    if (icmp_type != -2 && (!sess->num_hops || sess->num_hops > tp->hopno))
        if ((sess->break_on_icmp && lft_icmp_stops_trace(icmp_type)) || (icmp_type == -1)) {
            if (tp->hopno != -1) { /* we set fake type -1 when we get actual
                * tcp packet in return - meaning destination */
                sess->num_hops = tp->hopno;
                tp->is_done = 1;
                if (sess->noisy > 1 && sess->target_open < 1)
                    LFTEvtHandler(sess,EVT_TCP_PORT_CLOSED,NULL);
                else 
                    if (sess->noisy > 1 && sess->target_open > 0)
                        LFTEvtHandler(sess,EVT_TCP_PORT_OPEN,NULL);
                if(sess->exit_state<0)
                    return;
            }
        }
            
        /* adjust scatter if we have fast reply times */
        ms = timediff_ms (tp->sent, tp->recv);
        sess->scatter_ms = (sess->scatter_ms * (sess->ahead_limit - 1) + ms) / sess->ahead_limit;
    
}
/*---------------------------------------------------------------------------*/
void lft_printf(lft_session_params * sess, const char *templ, ...)
{
  va_list ap;
  char buf[1024];

  va_start (ap, templ);
  vsprintf(buf, templ, ap);
  va_end (ap);
  LFTEvtHandler(sess, EVT_DBG_LOG_MESSAGE, buf);
}
/*---------------------------------------------------------------------------*/
static void process_packet (lft_session_params * sess, const u_char *packet, const struct pcap_pkthdr *hdr)
{
    const struct ether_header *eptr;
    const struct ip *ip, *orig_ip;
    const struct tcphdr *tcp;
    const struct udphdr *udp;
    const struct icmp *icmp;
        
    if (sess->noisy > 4)
    {
        LFTEvtHandler(sess,EVT_PROCESS_PACKET_START,NULL);
        if(sess->exit_state<0)
            return;
    }
    check_timeouts (sess);
    if(sess->exit_state<0) 
        return;
     
    /* Test EtherType to adjust Ethernet header length 
    802.1q VLAN Ethernet frame (dot1q) */
    eptr = (const struct ether_header *) packet;
    if ((sess->skip_header_len == sizeof (struct ether_header)) && (ntohs (eptr->ether_type) == ETHERTYPE_VLAN)) {
        sess->skip_header_len += 4;
    }
        
    packet += sess->skip_header_len;
    ip = (const void *)packet;
            
    packet += 4 * ip->ip_hl;
                
    switch (ip->ip_p) {
        case IPPROTO_ICMP:
            orig_ip = ip;
            icmp = (const void *)packet;
            if (icmp->icmp_type != ICMP_UNREACH && icmp->icmp_type != ICMP_TIMXCEED) {
                return;
            }
                ip = &icmp->icmp_ip;
            if (sess->protocol==1) {
                if (ip->ip_p != IPPROTO_UDP)
                    return;			/* not a response to our udp probe */
            } else {
                if (ip->ip_p != IPPROTO_TCP)
                    return;			/* not a response to our tcp probe */                
            }
            packet = (const u_char *)ip;
            packet += 4 * ip->ip_hl;
            
            if (sess->protocol==1) {
                udp = (const void *)packet;
                if (ntohs (udp->uh_sport) != sess->sport || ip->ip_src.s_addr != sess->local_address.s_addr || ip->ip_dst.s_addr != sess->remote_address.s_addr) {
                    LFTEvtHandler(sess,EVT_UDP_NOT_FOR_US,NULL);
                    return;			/* not for us */      
                }
                if (sess->noisy > 2) {
                    EvtIncomingICMPUDPParam eiiup;
                    eiiup.icmp=icmp;
                    eiiup.ip=ip;
                    eiiup.orig_ip=orig_ip;
                    eiiup.udp=udp;
                    LFTEvtHandler(sess,EVT_INCOMING_ICMP_UDP,&eiiup);
                    if(sess->exit_state<0)
                        return;
                }
                if (sess->noisy > 1)
                {
                    LFTEvtHandler(sess,EVT_RCVD_ICMP_UDP,udp);
                    if(sess->exit_state<0)
                        return;
                }
                /* PMTUD: intercept PTB before marking the hop answered */
                if (sess->pmtud && icmp->icmp_type == ICMP_UNREACH &&
                    icmp->icmp_code == ICMP_UNREACH_NEEDFRAG) {
                    /* Match via IP ID embedded in the inner (echoed) IP header —
                     * ip points to the inner IP after the ip = &icmp->icmp_ip
                     * reassignment above; orig_ip is the outer router IP. */
                    lft_pmtud_handle_ptb(sess, icmp,
                                         (unsigned int)ntohs(ip->ip_id), orig_ip->ip_src);
                    return;
                }
                /* Match the reply to its sent probe via the IP ID embedded in
                 * the inner (echoed) IP header.  ip was reassigned to the inner
                 * header via ip = &icmp->icmp_ip; orig_ip is the outer router IP. */
                recv_packet (sess, (unsigned int)ntohs(ip->ip_id), orig_ip->ip_src,
                             (icmp->icmp_type == ICMP_TIMXCEED) ? -2 : icmp->icmp_code, hdr);
                if(sess->exit_state<0)
                    return;
            } else {
                tcp = (const void *)packet;
                if (ntohs (tcp->th_dport) != sess->dport || ip->ip_src.s_addr != sess->local_address.s_addr || ip->ip_dst.s_addr != sess->remote_address.s_addr)
                    return;			/* not for us */
                if (sess->noisy > 2) {
                    EvtIncomingICMPTCPParam eiitp;
                    eiitp.icmp=icmp;
                    eiitp.ip=ip;
                    eiitp.orig_ip=orig_ip;
                    eiitp.tcp=tcp;
                    LFTEvtHandler(sess,EVT_INCOMING_ICMP_TCP,&eiitp);
                    if(sess->exit_state<0)
                        return;
                }
                if (sess->noisy > 1)
                {
                    LFTEvtHandler(sess,EVT_RCVD_ICMP_TCP,tcp);
                    if(sess->exit_state<0)
                        return;
                }
                /* PMTUD: intercept PTB before marking the hop answered */
                if (sess->pmtud && icmp->icmp_type == ICMP_UNREACH &&
                    icmp->icmp_code == ICMP_UNREACH_NEEDFRAG) {
                    lft_pmtud_handle_ptb(sess, icmp,
                                         ntohl(tcp->th_seq), orig_ip->ip_src);
                    return;
                }
                recv_packet (sess, ntohl (tcp->th_seq) , orig_ip->ip_src,
                             (icmp->icmp_type == ICMP_TIMXCEED) ? -2 : icmp->icmp_code, hdr);
                if(sess->exit_state<0)
                    return;
            }
            return;
            
        case IPPROTO_TCP:
            /* check for RST reply */
            tcp = (const void *)packet;
            if (!(tcp->th_flags & TH_RST) && !(tcp->th_flags & TH_ACK) && !(tcp->th_flags & TH_SYN)) 
                return;			/* not what we're looking for */

            if (ntohs (tcp->th_sport) != sess->dport || ip->ip_src.s_addr != sess->remote_address.s_addr || ip->ip_dst.s_addr != sess->local_address.s_addr) {
                return;			/* not the right connection */
            }
                    
            if (sess->noisy > 1) {
                LFTEvtHandler(sess,EVT_RCVD_TCP,tcp);
                if(sess->exit_state<0)
                    return;
            }
                    
            /*if (ntohl(tcp->th_ack) < seq_start || ntohl(tcp->th_ack) > seq_start + trace_packet_info_length + 1)
            return; * not for us */
                    
            /* Check for SYN,ACK in response to determine if target is listening */
            if ((tcp->th_flags & TH_ACK) && (tcp->th_flags & TH_SYN) && !(tcp->th_flags & TH_RST)) 
            {
                sess->target_open++;
            }
            /* It's wrong, because it can reset status of port after it already detected as open
            if ((tcp->th_flags & TH_ACK) && !(tcp->th_flags & TH_SYN) && (tcp->th_flags & TH_RST))
            {
                sess->target_open = 0;
            }
            */
                                    
            recv_packet (sess, ntohl (tcp->th_ack) - 1, ip->ip_src, -1, hdr);
            if(sess->exit_state<0)
                return;
             /*Could be nice to host and send a RESET here*/
             /*
             send_packet(-1, IPDEFTTL, ntohl(tcp->th_ack) + 1, TH_RST);
             */
            return;
            
        case IPPROTO_UDP:
            /* could be a probe we sent or something we don't support */
                return;			
            
        default:
            if (sess->noisy > 3)
                LFTEvtHandler(sess,EVT_RCVD_UNKNOWN,ip);
    }
}
/*---------------------------------------------------------------------------*/
#if !defined(__CYGWIN__) && !defined(WIN32) && !defined(_WIN32)
static void
pcap_process_packet (u_char * user_data, const struct pcap_pkthdr *hdr,
                     const u_char * packet)
{
    lft_session_params * sess=(lft_session_params *)(void *)user_data;
	if(sess->exit_state<0)
		return;
    process_packet(sess, packet, hdr);
}
#endif
/*---------------------------------------------------------------------------*/
#if defined( __CYGWIN__ ) || defined( WIN32 ) || defined(_WIN32)
void cygwin_process(lft_session_params * sess)
{
    fd_set fds;
    struct timeval tm;
	int wsaerr;
    tm.tv_sec = 0;
    tm.tv_usec = 100000;

    FD_ZERO(&fds);
    FD_SET(sess->recv_sock, &fds);
    if (select(sess->recv_sock+1, &fds, 0, 0, &tm) < 0) {
		wsaerr=WSAGetLastError();
        LFTErrHandler(sess, ERR_WIN_SELECT, NULL);
        return;
    }
    if (FD_ISSET(sess->recv_sock, &fds)) {
        /* read packet */
        char packetbuf[2048];
        int nread;
        memset(packetbuf, 0, sizeof(packetbuf));
        nread = recv(sess->recv_sock, packetbuf, sizeof(packetbuf), 0);
        if (nread <= 0) {
            LFTErrHandler(sess, ERR_WIN_RECV, NULL);
            return;
        }
        process_packet(sess, packetbuf, NULL);
    }
}

#endif
/*---------------------------------------------------------------------------*/
/*               Replacement for deprecated pcap_lookupdev                   */
/*---------------------------------------------------------------------------*/
char * lft_pcap_lookupdev(char *ebuf)
{
    pcap_if_t *alldevs;
    char *dev = NULL;
    int res = pcap_findalldevs(&alldevs, ebuf);
    if (!res && alldevs != NULL) {
        dev = strdup(alldevs->name);
        pcap_freealldevs(alldevs);
    }
    return dev;
}
/*---------------------------------------------------------------------------*/
/*                                  Main of lft                              */
/*---------------------------------------------------------------------------*/
void LFTExecute(lft_session_params * sess)
{
#if !defined(__CYGWIN__) && !defined(WIN32) && !defined(_WIN32)
    char ebuf[PCAP_ERRBUF_SIZE];
    /*static pcap_t *pd;*/
#endif

    sess->exit_state = 0;

#ifdef HAVE_CARES
    /* Initialize c-ares channel now that all session options (including
     * async_dns_disabled) have been set by the caller.
     * TIMEOUTMS=500ms per attempt, TRIES=2 (one retry for dropped UDP):
     * total worst-case 1 000 ms per query — same as the alarm(1) ceiling
     * but queries run concurrently with probing so cost is largely hidden.
     * In watch mode LFTExecute is called once per cycle; destroy any channel
     * left open from the previous cycle before creating a fresh one. */
    if (sess->ares_chan) {
        ares_destroy(sess->ares_chan);
    }
    sess->ares_chan      = NULL;
    sess->ares_pending  = 0;
    /* In watch mode the dns_cache is pre-seeded by watch_inject_cached_hostnames
     * before each LFTExecute call.  Resetting it here would discard those entries
     * and force lft_ares_fire to re-query every IP every cycle.  The existing
     * dedup check in lft_ares_fire() prevents duplicate entries, so the cache
     * only grows when genuinely new IPs appear (path changes). */
    if (!sess->watch_mode)
        sess->dns_cache_count = 0;
    if (!sess->async_dns_disabled && sess->resolve_names) {
        struct ares_options aopts;
        int                 amask;
        memset(&aopts, 0, sizeof(aopts));
        aopts.timeout            = 500;    /* ms per attempt        */
        aopts.tries              = 2;      /* total attempts        */
        aopts.sock_state_cb      = lft_ares_sock_state_cb;
        aopts.sock_state_cb_data = sess;
        sess->ares_nfds = 0;
        memset(sess->ares_fd_table, 0, sizeof(sess->ares_fd_table));
        amask = ARES_OPT_TIMEOUTMS | ARES_OPT_TRIES | ARES_OPT_SOCK_STATE_CB;
        if (ares_init_options(&sess->ares_chan, &aopts, amask) != ARES_SUCCESS)
            sess->ares_chan = NULL;         /* fall back to getnameinfo */
    }
#endif /* HAVE_CARES */

    if(sess->auto_ports != 0)
    {
        do_auto_ports(sess, sess->hostname, sess->dport);
        if(sess->exit_state < 0){
	/* if(sess->hostname != NULL)
		free(sess->hostname); */
	if(sess->wsess != NULL) {
		free(sess->wsess);
		sess->wsess = NULL;
	} 
        return;
	}
    }
    
    if ((sess->do_netlookup != 0) || (sess->do_aslookup != 0))
    {
        sess->wsess = w_init();                                                 /* initialize the whois framework */
        sess->wsess->logprintfCookie = sess; /*Parameter for lft_printf*/
    }
    
    /* if not given network interface, select one automatically */
    if (!sess->userdevsel) {
        sess->pcap_dev = lft_getifforremote(sess, sess->hostname);
        if(sess->exit_state < 0){
            	if(sess->wsess != NULL) {
			free(sess->wsess);
			sess->wsess = NULL;
		}
        	/* if (sess->hostname != NULL)
			free(sess->hostname); */
		return;
	}
#if !defined(__CYGWIN__) && !defined(WIN32) && !defined(_WIN32)
        if (sess->pcap_dev == NULL) 
            sess->pcap_dev = lft_pcap_lookupdev (ebuf);
        if (sess->pcap_dev == NULL) {
            LFTErrHandler(sess, ERR_PCAP_ERROR, ebuf);
	    if(sess->wsess != NULL) {
	    	free(sess->wsess);
		sess->wsess = NULL;
	    }
            /* if (sess->hostname != NULL)
		free(sess->hostname); */
            return;
        }
#else 
        if (sess->pcap_dev == NULL) {
            LFTErrHandler(sess, ERR_DISCOVER_INTERFACE, NULL);
            if(sess->wsess != NULL) {
	    	free(sess->wsess);
		sess->wsess = NULL;
	    }
	     return;
        }
#endif
        /* we have a receive device, set the source address */
        sess->pcap_send_dev = sess->pcap_dev;
        sess->local_address.s_addr = lft_getifaddr(sess->pcap_dev);

    } else {
        struct in_addr addr;
        if (inet_aton(sess->userdev, &addr)) {
            /* specified by ip address -- look up device. */
            sess->pcap_dev = lft_getifname(addr);
            if (sess->pcap_dev != 0) {
                LFTErrHandler(sess, ERR_UNKNOWN_INTERFACE, NULL);
	    	if(sess->wsess != NULL) {
	    		free(sess->wsess);
			sess->wsess = NULL;
		}
                return;
            }
            /* we have a receive device, set the source address */
            sess->local_address.s_addr = lft_getifaddr(sess->pcap_dev);
        } else
            sess->pcap_dev = sess->userdev;
            sess->pcap_send_dev = sess->userdev;
            sess->local_address.s_addr = lft_getifaddr(sess->pcap_dev);
    };
    
    /* if user wants a different/spoof interface, facilitate  */
    if (sess->senddevsel > 0) {
        struct in_addr addr;
        if (inet_aton(sess->senddev, &addr)) {
            /* specified by ip address -- force using default device */
            /*sess->pcap_send_dev = lft_getifname(addr);*/
            sess->pcap_send_dev = lft_getifforremote(sess, sess->hostname);
            if (sess->pcap_send_dev == 0) {
                LFTErrHandler(sess, ERR_UNKNOWN_SEND_INTERFACE, NULL);
                return;
            }
            /* we have a send IP address, set the source address */
            sess->local_address.s_addr = get_address(sess,sess->senddev);
        } else {
            sess->pcap_send_dev = sess->senddev;
            if (sess->pcap_send_dev == 0) {
                LFTErrHandler(sess, ERR_UNKNOWN_SEND_INTERFACE, NULL);
                return;
            }
            /* we have a send device, set the source address */
            sess->local_address.s_addr = lft_getifaddr(sess->pcap_dev);
            }
    };

#if !defined(__CYGWIN__) && !defined(WIN32) && !defined(_WIN32)
#ifdef HAVE_NCURSES
    /* In watch mode the pcap handle is opened once (as root) and reused across
     * all sub-session cycles.  Skip pcap_create/activate when already set. */
    if (sess->pcapdescr == NULL)
#endif
    {
    sess->pcapdescr = pcap_create(sess->pcap_dev, ebuf);
    if (sess->pcapdescr == NULL) {
        LFTErrHandler(sess, ERR_PCAP_DEV_UNAVAILABLE, ebuf);
        if(sess->wsess != NULL) {
            free(sess->wsess);
            sess->wsess = NULL;
        }
        return;
    }
    pcap_set_snaplen(sess->pcapdescr, 1600);
    pcap_set_promisc(sess->pcapdescr, 0);
    pcap_set_timeout(sess->pcapdescr, 20);
    {
        int pcap_status = pcap_activate(sess->pcapdescr);
        if (pcap_status < 0) {
            LFTErrHandler(sess, ERR_PCAP_ACTIVATE_ERROR, pcap_geterr(sess->pcapdescr));
            pcap_close(sess->pcapdescr);
            sess->pcapdescr = NULL;
            if(sess->wsess != NULL) {
                free(sess->wsess);
                sess->wsess = NULL;
            }
            return;
        } else if (pcap_status > 0) {
            LFTErrHandler(sess, WRN_PCAP_ACTIVATE, pcap_geterr(sess->pcapdescr));
        }
    }
    /* retain and inform data link type */
    sess->pcap_datalink = pcap_datalink(sess->pcapdescr);
#if defined( __CYGWIN__ ) || defined( WIN32 ) || defined(_WIN32)
    sess->skip_header_len = 0;
#else
    /* use pcap datalink type to determine skip_header_len */
    if (sess->pcap_datalink == DLT_RAW)
        sess->skip_header_len = 0;
    else if (sess->pcap_datalink == DLT_PPP)
       sess->skip_header_len += 4;
    else if (sess->pcap_datalink == DLT_NULL)
       sess->skip_header_len += 4;
    else if (sess->pcap_datalink == DLT_PPP_ETHER)
       sess->skip_header_len += (8 + (sizeof (struct ether_header)));
    else if (sess->pcap_datalink == DLT_LINUX_SLL)
       sess->skip_header_len += 16;
    else                             /* assume ethernet: linktype EN10MB */
        sess->skip_header_len = sizeof (struct ether_header);
    /* if we're on what looks like a serial link, up the timeout (scatter will take care of itself) */
    /* if ((sess->pcap_datalink == DLT_PPP || sess->pcap_datalink == DLT_LINUX_SLL) && (sess->timeout_ms == DEFAULT_TIMEOUT_MS))
        sess->timeout_ms += 5000; */
#endif
    if (sess->noisy)
    {
        LFTEvtHandler(sess,EVT_DEVICE_SELECTED,NULL);
        if(sess->exit_state < 0){
        if(sess->wsess != NULL) {
                free(sess->wsess);
		sess->wsess = NULL;
	}
        return;
        }
    }
#ifdef BSD_IP_STACK
#ifndef NETBSD
    uint32_t bpfimmflag = 1;
    /* Instruct device to return packets immediately */
    if (ioctl(pcap_fileno(sess->pcapdescr), BIOCIMMEDIATE, &bpfimmflag) < 0) {
        LFTErrHandler(sess, WRN_BIOCIMMEDIATE, pcap_strerror(errno));
        if(sess->exit_state < 0){
	if(sess->wsess != NULL) {
		free(sess->wsess);
		sess->wsess = NULL;
	}
        if(sess->pcapdescr != 0)
		{
			pcap_close(sess->pcapdescr);
			sess->pcapdescr=0;
		}
        return;
	}
    }
#endif
#endif
    /* Set pcap non-blocking mode */
    if (pcap_setnonblock(sess->pcapdescr, 1, ebuf) < 0) {
        LFTErrHandler(sess, ERR_PCAP_NONBLOCK_ERROR, ebuf);
        if(sess->exit_state < 0){
	if(sess->wsess != NULL) {
	    	free(sess->wsess);
		sess->wsess = NULL;
	}
        /* if(sess->hostname != NULL)
		free(sess->hostname); */
        return;
	}
    }
    } /* end if (sess->pcapdescr == NULL) */
#endif
    
    if (sess->senddevsel > 0)
      init_address (sess, sess->hostname, sess->pcap_send_dev);
    else
      init_address (sess, sess->hostname, sess->pcap_dev);
    if(sess->exit_state < 0){
	if(sess->wsess != NULL) {
	    	free(sess->wsess);
		sess->wsess = NULL;
	}
        /* if(sess->hostname != NULL)
		free(sess->hostname); */
        return;
    }

    if (!sess->seq_start) {
        sess->seq_start = rand();   /* pseudorandom ISN for TCP; probe-ID base for UDP */
    }

    if (sess->noisy > 3 || (sess->noisy > 0 && sess->seq_start))
    {
        LFTEvtHandler(sess, EVT_SHOW_INITIAL_SEQNUM, NULL);
        if(sess->exit_state < 0){
	if(sess->wsess != NULL) {
	    	free(sess->wsess);
		sess->wsess = NULL;
	}
        /* if (sess->hostname != NULL)
		free(sess->hostname); */
        return;
	}
    }
    
    open_sockets(sess);
    if(sess->exit_state < 0)
    {
#ifdef HAVE_NCURSES
        /* In watch mode sub-sessions, send_sock and pcapdescr are borrowed from
         * proto_sess — never close them here; they will be reused next cycle. */
        if (!sess->watch_mode)
#endif
        {
        if(sess->send_sock > 0)
        {
#if defined(WIN32) || defined(_WIN32)
            closesocket(sess->send_sock);
#else
            close(sess->send_sock);
#endif
            sess->send_sock = 0;
        }
#if defined(WIN32) || defined(_WIN32)
        if(sess->recv_sock > 0)
        {

            closesocket(sess->recv_sock);
            sess->recv_sock = 0;
        }
#else
    	if(sess->pcapdescr != 0)
		{
        	pcap_close(sess->pcapdescr);
			sess->pcapdescr=0;
		}
#endif
        } /* end if (!sess->watch_mode) */
        return;
    }
    
#if !defined(__CYGWIN__) && !defined(WIN32) && !defined(_WIN32)
#ifndef LFT_DONT_USE_SAFE_UID
	if(!sess->check_seam && !sess->is_graphviz_subquery)
		setuid (getuid ());
#endif
#endif
    
    if (sess->adaptive) {
        if (sess->retry_min < 2)
            sess->retry_min = 2;
    }
    
    if (sess->protocol==1) {
        if (sess->retry_min > 2)
            sess->retry_min = 2;
    }

    if (sess->retry_max < sess->retry_min)
        sess->retry_max = sess->retry_min;
    
#ifdef HAVE_CARES
    /* Pre-fire PTR queries for the two addresses we already know about.
     * Hop addresses are queued as they are discovered in recv_packet(). */
    lft_ares_fire(sess, sess->local_address);
    lft_ares_fire(sess, sess->remote_address);
#endif

    gettimeofday (&(sess->begin_time), NULL);
    LFTEvtHandler(sess,EVT_TRACE_START,NULL);
    if(sess->exit_state<0)
    {
        if(sess->send_sock > 0)
        {
#if defined(WIN32) || defined(_WIN32)
            closesocket(sess->send_sock);
#else
            close(sess->send_sock);
#endif
            sess->send_sock = 0;
        }
#if defined(WIN32) || defined(_WIN32)
        if(sess->recv_sock > 0)
        {
            closesocket(sess->recv_sock);
            sess->recv_sock = 0;
        }
#endif
        return;
    }
    if(sess->protocol<2)  /*UDP or TCP*/
	{
#if defined( __CYGWIN__ ) || defined( WIN32 ) || defined(_WIN32)
	    for (;;) {
	        cygwin_process(sess);
	        if(sess->exit_state<0)
	            break;
	        if(check_timeouts(sess))
	            break;
	        if(sess->exit_state<0)
	            break;
	    }
#else
	    while (pcap_dispatch (sess->pcapdescr, -1, pcap_process_packet, (u_char *)sess) >= 0) {
	        if(sess->exit_state<0)
	            break;
	        if (sess->noisy > 6)
	        {
	            LFTEvtHandler(sess,EVT_DBG_CHECKPOINT2,NULL);
	            if(sess->exit_state < 0)
	                break;
	        }
#ifdef HAVE_CARES
	        lft_ares_poll(sess);   /* harvest any completed PTR replies */
#endif
	        if(check_timeouts (sess))
	            break;
	        if(sess->exit_state < 0)
	            break;
	    }
#ifdef HAVE_CARES
	    lft_ares_wait(sess);   /* drain remaining queries before display */
#endif
#endif
	}
	else
	{
        if(sess->protocol<4)
            icmp_trace_main_loop(sess, LFTErrHandler, LFTEvtHandler);   /*ICMP traces*/
        else
            tcp_base_trace_main_loop(sess, LFTErrHandler, LFTEvtHandler);
#ifdef HAVE_CARES
        lft_ares_wait(sess);   /* drain remaining PTR queries before display */
#endif
	}
	/*{
		int i;
		printf("\n");
		for(i=0;i<sess->debugmapidx;i++)
		{
			if(sess->debugmap[i].type)
			{
				printf("%3d <<<", i);
				printf("%3d %5d phop=%2d %d.%d.%d.%d\n",
					sess->debugmap[i].hop,
					sess->debugmap[i].port,
					sess->debugmap[i].phop,
					(int)sess->debugmap[i].ip.S_un.S_un_b.s_b1,
					(int)sess->debugmap[i].ip.S_un.S_un_b.s_b2,
					(int)sess->debugmap[i].ip.S_un.S_un_b.s_b3,
					(int)sess->debugmap[i].ip.S_un.S_un_b.s_b4);
			}
			else
			{
				printf("%3d -->", i);
				printf("%3d %5d\n",
					sess->debugmap[i].hop,
					sess->debugmap[i].port);
			}
		}
	}*/
#ifdef HAVE_NCURSES
    /* In watch mode sub-sessions, the socket is borrowed from proto_sess and
     * must not be closed here — it is reused across all watch cycles. */
    if (!sess->watch_mode)
#endif
    {
    if(sess->send_sock > 0)
    {
#if defined(WIN32) || defined(_WIN32)
        closesocket(sess->send_sock);
#else
        close(sess->send_sock);
#endif
        sess->send_sock = 0;
    }
#if defined(WIN32) || defined(_WIN32)
    if(sess->recv_sock > 0)
    {
        closesocket(sess->recv_sock);
        sess->recv_sock = 0;
    }
#endif
    } /* end if (!sess->watch_mode) */
}
/*---------------------------------------------------------------------------*/
void setOutputStyle(int nstyle)
{
#ifdef HAVE_NCURSES
	if(nstyle<0 || nstyle>4)
#else
	if(nstyle<0 || nstyle>3)
#endif
		global_output_style=0;
	else
		global_output_style=nstyle;
}

int getOutputStyle(void)
{
	return global_output_style;
}

int outputStyleIsXML(void)
{
	if(global_output_style==1)
		return 1;
	return 0;
}

int outputStyleIsGraphViz(void)
{
	if(global_output_style==2)
		return 1;
	return 0;
}

int outputStyleIsASCII(void)
{
	if(global_output_style==3)
		return 1;
	return 0;
}
/*---------------------------------------------------------------------------*/
#define INCSIZE	500
static char * addtostringbuf(char * buf, size_t * bufsz, const char * addstr)
{
	if(!buf || !(*bufsz))
	{
		buf=malloc(INCSIZE);
		*bufsz=INCSIZE;
		buf[0]=0;
	}
	if(strlen(buf)+strlen(addstr)+1>(*bufsz))
	{
		buf=realloc(buf, (*bufsz)+INCSIZE);
		*bufsz+=INCSIZE;
	}
	strncat(buf,addstr,(*bufsz) - strlen(buf) - 1);
	return buf;
}
/*---------------------------------------------------------------------------*/
static char * addNodeToRouteMap(char * routemap, size_t * routemapsz, char * prevlevelnodes, char ** currlevelnodes, size_t * currlevelnodessz, char * node)
{
	char * pnode, * pnodeend;
	char pathbuff[100];
	if((*currlevelnodes) && (*currlevelnodessz))
		*currlevelnodes=addtostringbuf(*currlevelnodes, currlevelnodessz, ";");
	*currlevelnodes=addtostringbuf(*currlevelnodes, currlevelnodessz, node);
	pnode=prevlevelnodes;
	pnodeend=pnode;
	while(pnodeend)
	{
		pnodeend=strchr(pnode,';');
		if(pnodeend)
			*pnodeend=0;
		snprintf(pathbuff,100,"\t%s -> %s;\n",pnode,node);
		routemap=addtostringbuf(routemap, routemapsz, pathbuff);
		if(pnodeend)
		{
			*pnodeend=';';
			pnode=pnodeend+1;
		}
	}
	return routemap;
}
/*---------------------------------------------------------------------------*/
static void UpdateLatency(const struct trace_packet_info_s 	*tp, double * minlat, double * maxlat, int * latinitialized)
{
    double currlat=timediff_ms(tp->sent, tp->recv);
	if(!(*latinitialized))
	{
		*minlat=currlat;
		*maxlat=currlat;
		*latinitialized=1;
	}
	else
	{
		if(*minlat > currlat)
			*minlat=currlat;
		if(*maxlat < currlat)
			*maxlat=currlat;
	}
}
/*---------------------------------------------------------------------------*/
static void PrintPacketInfoForGraphViz(lft_session_params * sess, const struct trace_packet_info_s 	*tp)
{
    if(tp->recv.tv_sec)
	{
        if (tp->last_hop.s_addr != tp->hopaddr.s_addr)
		{
            if (sess->do_aslookup)
			{
				if (tp->asnumber)
					printf(" [%d]", tp->asnumber);
				/* suppress [AS?] in GraphViz output */
            }
            if (sess->do_netlookup)
			{
				if((uintptr_t)tp->netname && strlen(tp->netname)>0
				   && strcmp(tp->netname,"NULL")!=0)
					printf(" [%s]", tp->netname);
				/* suppress unresolved net name in GraphViz output */
            }
            if (tp->icmp_type < -2 || tp->icmp_type > 17)
				printf (" [icmp code %d]", tp->icmp_type);
            else 
			{
                if (tp->icmp_type >= 0)
					printf (" [%s]", icmp_messages[tp->icmp_type + 1]);
			}
			
			printf(" ");
            print_host (sess, tp->hopaddr);
            if (tp->icmp_type == -1 && (sess->protocol<2 || sess->protocol>3))
				printf(":%d",sess->dport);
        }
    }
}
/*---------------------------------------------------------------------------*/
void GraphVizOutput(lft_session_params * sess)
{
	int reply,noreply,neglstart,neglend,anomtype,hopno,maxhop,icmpcode,holecount=0;
	char * rankstring=NULL;
	size_t rankstringsz=0;
    struct trace_packet_info_s 	*tp;
	char netnamecopy[512];
	char * prevlevelnodes=NULL;
	size_t prevlevelnodessz=0;
	char * currlevelnodes=NULL;
	size_t currlevelnodessz=0;
	char * routemap=NULL;
	size_t routemapsz;
	char nodenamebuff[200];
	double minlat, maxlat;
	char latencybuf[100];
	int latinitialized;
	int asseam_hopno=-1;
	struct in_addr asseam_hopaddr;
	int netseam_hopno=-1;
	struct in_addr netseam_hopaddr;
	struct in_addr classbmask;
	struct in_addr masked_target;
	int prevasn=-1;
	struct in_addr prevasn_hopaddr;
	int prevasn_hopno;
	int lastishole;
	int netreached=0;
	int isseam;
	char cpath[1024];
	int lastpos;
		
	if(sess->is_graphviz_subquery)
		return;
	
	if(sess->graphviz_icon_path)
		strncpy(cpath,sess->graphviz_icon_path,1022);
	else
	{
		if(!getcwd(cpath,1022))
		{
			cpath[0]='.';
			cpath[1]=DIRECTORY_SPLITTER;
			cpath[2]=0;
		}
	}
	lastpos=strlen(cpath)-1;
	if(cpath[lastpos]!=DIRECTORY_SPLITTER)
	{
		cpath[lastpos+1]=DIRECTORY_SPLITTER;
		cpath[lastpos+2]=0;
	}
	
	inet_aton("255.255.0.0", &classbmask);
	masked_target.s_addr=sess->remote_address.s_addr & classbmask.s_addr;

	printf("digraph %s {\n",GVGRAPHNAME);
	printf("\trankdir=TB;\n\tnode [fontname=%s,fontsize=%s];\n",GVFONTNAME,GVFONTSIZE);
	printf("\tSRC[%s, label=<",GVHOPSTYLE_SOURCE);
	prevlevelnodes=addtostringbuf(prevlevelnodes, &prevlevelnodessz, "SRC");
	rankstring=addtostringbuf(rankstring, &rankstringsz, "Source");
	printf("%s%s%s%s",GVNTBEG,cpath,GVNIMG_SOURCE,GVNTMID);
	print_host (sess, sess->local_address);
	if(sess->protocol==2 || sess->protocol==3)
		printf("%s>];\n",GVNTEND);
	else
	{
		if (sess->random_source)
			printf (":%d (pseudo-random)%s>];\n", sess->sport, GVNTEND);
		else 
			printf (":%d%s>];\n", sess->sport, GVNTEND);
	}
    if (sess->num_hops)
        maxhop = sess->num_hops;
    else
        maxhop = sess->hop_info_length - 1;
    noreply = 0;
    reply = 0;
	neglstart=-1;
	neglend=-1;
	/* Find SEAMs at first*/
	for(hopno = sess->ttl_min; hopno < sess->hop_info_length; hopno++)
	{
		icmpcode=-100;
		if(sess->hop_info[hopno].all_rcvd)
		{
			lastishole=0;
            SLIST_FOREACH(tp, &(sess->hop_info[hopno].packets), next_by_hop)
			{
                if(tp->recv.tv_sec)
				{
					if(hopno<=maxhop)
						icmpcode=tp->icmp_type;
					if(tp->last_hop.s_addr != tp->hopaddr.s_addr)
					{
						if((tp->hopaddr.s_addr & classbmask.s_addr) == masked_target.s_addr)
							netreached=1;
						else
						{
							netseam_hopno=hopno;
							netseam_hopaddr=tp->hopaddr;
						}
						if(sess->do_aslookup || sess->do_netlookup)
						{
							if(prevasn==-1)
							{
								if(tp->asnumber)
								{
									prevasn=tp->asnumber;
									prevasn_hopno=hopno;
									prevasn_hopaddr=tp->hopaddr;
								}
							}
							else
							{
								if(tp->asnumber)
								{
									if(tp->asnumber!=prevasn)
									{
										asseam_hopno=prevasn_hopno;
										asseam_hopaddr=prevasn_hopaddr;
									}
									prevasn=tp->asnumber;
									prevasn_hopno=hopno;
									prevasn_hopaddr=tp->hopaddr;
								}
							}
						}
					}
				}
			}
		}
		else
			lastishole=1;
		if(icmpcode==-1)
			break;
	}
	if(!netreached)
		netseam_hopno=-1;
	if(lastishole)
		asseam_hopno=-1;
	for(hopno = sess->ttl_min; hopno < sess->hop_info_length; hopno++)
	{
		icmpcode=-100;
		latinitialized=0;
		anomtype=0;
        if((sess->hop_info[hopno].state == HS_SEND_FIN) && (sess->hop_info[hopno+1].state == HS_SEND_SYN) && (sess->hop_info[hopno+1].ts_last_recv.tv_sec))
			anomtype=1;
        if((sess->hop_info[hopno].state != HS_SEND_SYN_ACK) && (sess->hop_info[hopno+1].state == HS_SEND_SYN_ACK) && (hopno == (sess->num_hops - 1)))
			anomtype=2;
        if((sess->hop_info[hopno].flags & HF_ENDPOINT) && (noreply >= ((maxhop - sess->ttl_min)/2)) && sess->num_hops > 3)
			anomtype=3;
        if(sess->hop_info[hopno].all_rcvd == 0)
		{
			reply=0;
			if(neglstart==-1)
				neglstart=hopno;
			neglend=hopno;
		}
		else
		{
			if(neglstart!=-1)
			{
				holecount++;
				printf("\tHOLE%d[%s, label=<",holecount,GVHOPSTYLE_HOLE);
				printf("%s%s%s%s",GVNTBEG,cpath,GVNIMG_HOLE,GVNTMID);
				snprintf(nodenamebuff,200,"HOLE%d",holecount);
				printf("Potentially Cloaked Device%s>];\n",GVNTEND);
				routemap=addNodeToRouteMap(routemap, &routemapsz, prevlevelnodes, &currlevelnodes, &currlevelnodessz, nodenamebuff);
				free(prevlevelnodes);
				prevlevelnodes=currlevelnodes;
				prevlevelnodessz=currlevelnodessz;
				currlevelnodes=NULL;
				currlevelnodessz=0;
				if(neglstart == neglend)
					snprintf(nodenamebuff,200,"\"No reply received from TTL %d\"", neglstart+1);
				else
					snprintf(nodenamebuff,200,"\"No reply received from TTLs %d through %d\"", neglstart+1, neglend+1);
				rankstring=addtostringbuf(rankstring, &rankstringsz, " -> ");
				rankstring=addtostringbuf(rankstring, &rankstringsz, nodenamebuff);
				neglstart=-1;
				neglend=-1;
			}
			int cnt=0;
            SLIST_FOREACH(tp, &(sess->hop_info[hopno].packets), next_by_hop)
            {
                if(tp->recv.tv_sec)
                {
					const char * img=GVNIMG_REGULAR;
					cnt++;
					if(hopno<=maxhop)
						icmpcode=tp->icmp_type;
                    reply = 1;
					strncpy(netnamecopy, tp->netname, 511);
					if(tp->last_hop.s_addr != tp->hopaddr.s_addr)
					{
						if(icmpcode==-1)
						{
							printf("\tTRG[");
							snprintf(nodenamebuff,200,"TRG");

							if(sess->target_open > 0)
							{
								printf("%s, label=<%s%s", GVHOPSTYLE_TARGET_OPEN,GVNTBEG,cpath);
								img=GVNIMG_TRGOPEN;
							}
							else
							{
								if(sess->target_filtered)
								{
									printf("%s, label=<%s%s", GVHOPSTYLE_TARGET_FILTERED,GVNTBEG,cpath);
									img=GVNIMG_TRGFILTERED;
								}
								else
								{
									printf("%s, label=<%s%s", GVHOPSTYLE_TARGET_CLOSED, GVNTBEG, cpath);
									img=GVNIMG_TRGCLOSED;
								}
							}
						}
						else
						{
							printf("\tHOP%d_%d[",hopno+1,cnt);
							snprintf(nodenamebuff,200,"HOP%d_%d",hopno+1,cnt);
							switch (anomtype)
							{
								case 1:
									printf("%s, label=<%s%s", GVHOPSTYLE_ANOMALY1, GVNTBEG, cpath);
									img=GVNIMG_ANOMALY1;
									break;
								case 2:
									printf("%s, label=<%s%s", GVHOPSTYLE_ANOMALY2, GVNTBEG, cpath);
									img=GVNIMG_ANOMALY2;
									break;
								case 3:
									printf("%s, label=<%s%s", GVHOPSTYLE_ANOMALY3, GVNTBEG, cpath);
									img=GVNIMG_ANOMALY3;
									break;
								default:
									printf("%s, label=<%s%s", GVHOPSTYLE_BASE, GVNTBEG, cpath);
									img=GVNIMG_REGULAR;
									break;
							}
						}
						if(img==GVNIMG_REGULAR && ((hopno==asseam_hopno && tp->hopaddr.s_addr==asseam_hopaddr.s_addr) || (hopno==netseam_hopno && tp->hopaddr.s_addr==netseam_hopaddr.s_addr)))
							img=GVNIMG_SEAM;
						routemap=addNodeToRouteMap(routemap, &routemapsz, prevlevelnodes, &currlevelnodes, &currlevelnodessz, nodenamebuff);
						UpdateLatency(tp,&minlat,&maxlat,&latinitialized);
						printf("%s%s",img,GVNTMID);
						PrintPacketInfoForGraphViz(sess,tp);
						isseam=0;
						if(hopno==asseam_hopno && tp->hopaddr.s_addr==asseam_hopaddr.s_addr)
						{
							printf("</td></tr><tr><td>(AS-Method Seam");
							isseam=1;
						}
						if(hopno==netseam_hopno && tp->hopaddr.s_addr==netseam_hopaddr.s_addr)
						{
							printf("</td></tr><tr><td>(Network-Method Seam");
							isseam=1;
						}
						if(isseam)
						{
							if(sess->check_seam)
							{
								printf(": ");
								char hostname[100];
								lft_session_params * subsess=LFTSessionOpen();
								strncpy(hostname, inet_ntoa(tp->hopaddr),100);
								subsess->senddevsel = 1;
								subsess->senddev = sess->pcap_send_dev;
								subsess->userdevsel = 1;
								subsess->userdev = sess->pcap_dev;
								subsess->auto_ports=0;
								subsess->dport=179;
								subsess->seq_start=30;
								subsess->retry_min=1;
								subsess->retry_max=1;
								subsess->resolve_names=0;
								subsess->ahead_limit=1;
								subsess->break_on_icmp = 0;
								subsess->is_graphviz_subquery=1;
								subsess->hostname=hostname;
								subsess->hostname_lsrr_size = 0;
								LFTExecute(subsess);
								if(subsess->target_open > 0)
									printf("Vulnerable/Handshake");
								else
								{
									if(subsess->target_filtered)
										printf("Protected/Filtered");
									else
										printf("Vulnerable/Shun");
								}
								LFTSessionClose(subsess);
							}
							printf(")");
						}
						printf("%s>];\n",GVNTEND);
					}
					else
						UpdateLatency(tp,&minlat,&maxlat,&latinitialized);
                }
            }
			if(minlat==maxlat)
				snprintf(latencybuf,100,": %.1fms",minlat);
			else
				snprintf(latencybuf,100,": %.1f - %.1fms",minlat,maxlat);
			if(icmpcode==-1)
			{
				rankstring=addtostringbuf(rankstring, &rankstringsz, " -> \"Destination");
				if (sess->protocol==0 || sess->protocol==4)
				{
					rankstring=addtostringbuf(rankstring, &rankstringsz, " [target ");
					if (sess->target_open > 0)
						rankstring=addtostringbuf(rankstring, &rankstringsz, "open]");
					else
					{
						if(sess->target_filtered > 0)
							rankstring=addtostringbuf(rankstring, &rankstringsz, "filtered]");
						else
							rankstring=addtostringbuf(rankstring, &rankstringsz, "closed]");
					}
				}
				rankstring=addtostringbuf(rankstring, &rankstringsz, latencybuf);
			}
			else
			{
				snprintf(nodenamebuff,200,"\"%d", hopno+1);
				rankstring=addtostringbuf(rankstring, &rankstringsz, " -> ");
				rankstring=addtostringbuf(rankstring, &rankstringsz, nodenamebuff);
				rankstring=addtostringbuf(rankstring, &rankstringsz, latencybuf);
			}
			switch (anomtype)
			{
				case 1:
					rankstring=addtostringbuf(rankstring, &rankstringsz, "\\n");
					rankstring=addtostringbuf(rankstring, &rankstringsz, GV_ANOMALY1_TEXT);
					break;
				case 2:
					rankstring=addtostringbuf(rankstring, &rankstringsz, "\\n");
					rankstring=addtostringbuf(rankstring, &rankstringsz, GV_ANOMALY2_TEXT);
					break;
				case 3:
					rankstring=addtostringbuf(rankstring, &rankstringsz, "\\n");
					rankstring=addtostringbuf(rankstring, &rankstringsz, GV_ANOMALY3_TEXT);
					break;
				default:
					break;
			}
			rankstring=addtostringbuf(rankstring, &rankstringsz, "\"");

			free(prevlevelnodes);
			prevlevelnodes=currlevelnodes;
			prevlevelnodessz=currlevelnodessz;
			currlevelnodes=NULL;
			currlevelnodessz=0;
		}
		if(icmpcode==-1)
			break;
	}
	if(neglstart!=-1)
	{
		holecount++;
		printf("\tHOLE%d[%s, label=<",holecount,GVHOPSTYLE_HOLE);
		printf("%s%s%s%s",GVNTBEG,cpath,GVNIMG_HOLE,GVNTMID);
		snprintf(nodenamebuff,200,"HOLE%d",holecount);
		printf("No Response%s>];\n",GVNTEND);
		routemap=addNodeToRouteMap(routemap, &routemapsz, prevlevelnodes, &currlevelnodes, &currlevelnodessz, nodenamebuff);
		free(prevlevelnodes);
		prevlevelnodes=currlevelnodes;
		prevlevelnodessz=currlevelnodessz;
		currlevelnodes=NULL;
		currlevelnodessz=0;
		snprintf(nodenamebuff,200,"\"No reply received after TTL %d\"", neglstart+1);
		rankstring=addtostringbuf(rankstring, &rankstringsz, " -> ");
		rankstring=addtostringbuf(rankstring, &rankstringsz, nodenamebuff);
		neglstart=-1;
		neglend=-1;
	}
	free(prevlevelnodes);
	printf("\tranksep=equally;\n\t{\n\t\tnode [shape=plaintext];\n\t\t%s;\n\t}\n",rankstring);
	free(rankstring);
	printf("%s",routemap);
	free(routemap);
	printf("}\n");
}
/*---------------------------------------------------------------------------*/
/*
 * ASCIIOutput() -- horizontal hop diagram for -o
 *
 * Layout: each hop occupies a column of width W_i = max of all per-hop
 * string widths.  Every line pads every column to exactly W_i chars and
 * every separator slot to exactly 4 chars (" -> " on the hostname row,
 * "    " on all other rows).  Columns are grouped into rows that fit
 * within the terminal width (TIOCGWINSZ, $COLUMNS, or 80 fallback).
 *
 * Per-column strings built:
 *   hdr  : "[ HOP N ]" (or "[ SOURCE ]" / "[ TARGET ]")
 *   host : result of format_host() — hostname (ip) or just ip
 *   sub  : ip line when host has a hostname prefix (may be empty)
 *   asn  : "[AS NNNNN]" or "[AS?]" or "" (when -A used)
 *   rtt  : "N.Nms" or "" (holes have no rtt)
 *   ann  : annotation: "*SEAM*", "*FW?*", "[open]", "[closed]", etc.
 *
 * Terminal capability detection (runs once, result stored in locals):
 *   use_unicode : box-drawing chars ┌─ HOP N ─┐ vs [- HOP N -]
 *   use_color   : ANSI color codes
 */

/* helper: write 'n' copies of char 'c' to stdout */
static void ascii_repeat(char c, int n)
{
    int i;
    for (i = 0; i < n; i++) putchar(c);
}

/* helper: display column count for a UTF-8 string.
 * UTF-8 continuation bytes (0x80-0xBF) are not counted; every other byte
 * (ASCII or a multi-byte leading byte) counts as one display column.
 * Correct for all BMP characters that occupy a single terminal cell,
 * which covers every glyph emitted by ASCIIOutput(). */
static int utf8_display_width(const char *s)
{
    int w = 0;
    while (*s) {
        unsigned char b = (unsigned char)*s++;
        if ((b & 0xC0) != 0x80)   /* not a continuation byte */
            w++;
    }
    return w;
}

/* Truncate string s in-place so its display width is ≤ max_w columns.
 * Backs up over UTF-8 continuation bytes to avoid splitting a code point. */
static void ascii_utf8_truncate_to(char *s, int max_w)
{
    int i = (int)strlen(s);
    while (i > 0 && utf8_display_width(s) > max_w) {
        i--;
        while (i > 0 && ((unsigned char)s[i] & 0xC0) == 0x80) i--;
        s[i] = '\0';
    }
}

/* helper: center string 's' within field of width w (display columns),
 * padding with spaces.  Uses utf8_display_width() so multi-byte glyphs
 * are measured in columns, not bytes. */
static void ascii_center(const char *s, int w)
{
    int sl    = utf8_display_width(s);
    int left  = (w - sl) / 2;
    int right = w - sl - left;
    ascii_repeat(' ', left);
    fputs(s, stdout);
    ascii_repeat(' ', right);
}

/* helper: build the host string into buf (like print_host but into a buffer) */
static void format_host_buf(lft_session_params *sess, struct in_addr addr,
                             char *hostbuf, int hostbufsz,
                             char *ipbuf,   int ipbufsz)
{
    char hbuf[NI_MAXHOST];
    const char *ip = inet_ntoa(addr);
    int resolved = 0;

    hostbuf[0] = '\0';
    ipbuf[0]   = '\0';

#ifdef HAVE_CARES
    if (sess->ares_chan && !sess->async_dns_disabled) {
        int i;
        for (i = 0; i < sess->dns_cache_count; i++) {
            if (sess->dns_cache[i].addr.s_addr == addr.s_addr &&
                sess->dns_cache[i].name[0]) {
                snprintf(hostbuf, hostbufsz, "%s", sess->dns_cache[i].name);
                if (!sess->hostnames_only)
                    snprintf(ipbuf, ipbufsz, "%s", ip);
                resolved = 1;
                break;
            }
        }
        if (!resolved)
            snprintf(hostbuf, hostbufsz, "%s", ip);
        return;
    }
#endif
    if (!sess->resolve_names) {
        snprintf(hostbuf, hostbufsz, "%s", ip);
        return;
    }
    /* synchronous reverse DNS */
    {
        struct sockaddr_in sa;
        int rc;
        memset(&sa, 0, sizeof(sa));
        sa.sin_family = AF_INET;
        sa.sin_addr   = addr;
#ifndef WIN32
        lft_revdns_timed_out = 0;
        signal(SIGALRM, lft_sigalrm);
        alarm(LFT_REVDNS_TIMEOUT);
        rc = getnameinfo((struct sockaddr *)&sa, sizeof(sa),
                         hbuf, sizeof(hbuf), NULL, 0, NI_NAMEREQD);
        alarm(0);
        signal(SIGALRM, SIG_DFL);
        if (lft_revdns_timed_out) rc = EAI_AGAIN;
#else
        rc = lft_getnameinfo_timed((struct sockaddr *)&sa, sizeof(sa),
                                   hbuf, sizeof(hbuf), NI_NAMEREQD);
#endif
        if (rc == 0) {
            snprintf(hostbuf, hostbufsz, "%s", hbuf);
            if (!sess->hostnames_only)
                snprintf(ipbuf, ipbufsz, "%s", ip);
        } else {
            snprintf(hostbuf, hostbufsz, "%s", ip);
        }
    }
}

/* ANSI color codes */
#define AC_RESET    "\033[0m"
#define AC_BOLD     "\033[1m"
#define AC_DIM      "\033[2m"
#define AC_CYAN     "\033[36m"
#define AC_YELLOW   "\033[33m"
#define AC_GREEN    "\033[32m"
#define AC_BGREEN   "\033[92m"
#define AC_RED      "\033[31m"
#define AC_BRED     "\033[91m"
#define AC_ORANGE   "\033[38;5;208m"   /* xterm-256 bright orange */
#define AC_LABEL    "\033[38;5;147m"   /* xterm-256 light steel blue — labels */
#define AC_WHITE    "\033[97m"
#define AC_BBLUE    "\033[1;34m"
#define AC_BLUE     "\033[34m"    /* dark blue — outline/frame chars */

/* RTT chart constants */
#define CHART_HEIGHT    8       /* character rows in the chart body */
#define CHART_SEP       "    "  /* 4-space separator between columns */
#define JITTER_WARN_MS  25.0    /* jitter alert threshold (ms) */

/* Unicode box-drawing strings */
#define UC_TL   "\xe2\x94\x8c"   /* ┌ */
#define UC_TR   "\xe2\x94\x90"   /* ┐ */
#define UC_BL   "\xe2\x94\x94"   /* └ */
#define UC_BR   "\xe2\x94\x98"   /* ┘ */
#define UC_H    "\xe2\x94\x80"   /* ─ */
#define UC_LS   "["               /* ASCII fallback left  */
#define UC_RS   "]"               /* ASCII fallback right */

/* Maximum hops we'll handle in one pass */
#define ASCII_MAX_HOPS  256
#define ASCII_MAX_COL_W  17   /* max column width; fields wider than this are truncated */

/* Per-hop column data */
struct ascii_col {
    char hdr[64];       /* "[ HOP N ]" */
    char host[256];     /* hostname or ip */
    char sub[128];      /* "(ip)" when host has name; else "" */
    char asn[32];       /* "[AS 12345]" or "" */
    char net[64];       /* "[net-name]" or "" (when -A used) */
    char org[64];       /* "[org-name]" or "" (when -A used) */
    char rtt[32];       /* "12.3ms" or "10/12/15/2ms" (stats) or "" */
    char ann[64];       /* "*SEAM*", "*FW?*", "[open]", "⚡ jitter Nms ⚡" etc. */
    char mtu[32];       /* "[MTU: 1492]" or "[MTU Limit]" when -K active */
    hop_rtt_stats_t stats;     /* per-hop RTT stats (when rtt_stats active; n==0 if none) */
    char jitter_ann[32];       /* "⚡ΔNms" for chart label row (alert column only) */
    int  jitter_warn;          /* 1 = this col has max jitter > JITTER_WARN_MS */
    int  is_hole;       /* 1 if no reply */
    int  is_target;
    int  is_source;
    int  anomtype;      /* 0=normal,1=fw stateful,2=fw flag,3=bsd bug */
    int  w;             /* column width = max of all string lengths */
};

/* Render the ┌─ HOP N ─┐ header row for a slice of columns.
 * Shared between the chart context (renders above the chart) and the
 * hop-diagram context (renders above hostname/RTT/annotation rows). */
static void
render_header_row(const struct ascii_col *cols, int start, int end,
                  int use_color, int use_unicode, int flip_corners)
{
    int i;
    for (i = start; i <= end; i++) {
        const struct ascii_col *c = &cols[i];
        int hlen = (int)strlen(c->hdr);

        if (use_unicode) {
            /* build "┌─ HOP N ─┐" (or └─ … ─┘ when flip_corners).
             * Frame chars (└┘┌┐─) in blue, label text in cyan. */
            int inner = c->w - 2;
            int dashes_l, dashes_r, label_len;
            char label[64];
            if (hlen >= 4) {
                snprintf(label, sizeof(label), "%.*s",
                         hlen - 4, c->hdr + 2); /* e.g. "HOP 1" */
                label_len = (int)strlen(label);
            } else {
                snprintf(label, sizeof(label), "%s", c->hdr);
                label_len = hlen;
            }
            dashes_l = (inner - label_len - 2) / 2;
            dashes_r = inner - label_len - 2 - dashes_l;
            if (dashes_l < 0) dashes_l = 0;
            if (dashes_r < 0) dashes_r = 0;
            if (use_color) fputs(AC_BLUE, stdout);
            fputs(flip_corners ? UC_BL : UC_TL, stdout);
            { int d; for (d=0;d<dashes_l;d++) fputs(UC_H, stdout); }
            if (use_color) fputs(AC_LABEL, stdout);
            fprintf(stdout, " %s ", label);
            if (use_color) fputs(AC_BLUE, stdout);
            { int d; for (d=0;d<dashes_r;d++) fputs(UC_H, stdout); }
            fputs(flip_corners ? UC_BR : UC_TR, stdout);
        } else {
            if (use_color) fputs(AC_LABEL, stdout);
            /* ASCII fallback */
            int inner = c->w - 2;
            int label_len;
            char label[64];
            if (hlen >= 4) {
                snprintf(label, sizeof(label), "%.*s", hlen - 4, c->hdr + 2);
                label_len = (int)strlen(label);
            } else {
                snprintf(label, sizeof(label), "%s", c->hdr);
                label_len = hlen;
            }
            {
                int dashes_l = (inner - label_len - 2) / 2;
                int dashes_r = inner - label_len - 2 - dashes_l;
                if (dashes_l < 0) dashes_l = 0;
                if (dashes_r < 0) dashes_r = 0;
                putchar(flip_corners ? '{' : '[');
                ascii_repeat('-', dashes_l);
                fprintf(stdout, " %s ", label);
                ascii_repeat('-', dashes_r);
                putchar(flip_corners ? '}' : ']');
            }
        }

        if (use_color) fputs(AC_RESET, stdout);

        if (i < end) fputs("    ", stdout);
    }
    putchar('\n');
}

/* Render the RTT candlestick chart above one screen-slice of hop columns.
 * g_min / g_max are the global RTT bounds across the entire trace (for uniform scale).
 * Rows: header | jitter-alert label | max-RTT label | chart body (CHART_HEIGHT rows) | x-axis label. */
static void
ascii_chart_render(const struct ascii_col *cols, int start, int end,
                   double g_min, double g_max,
                   int use_color, int use_unicode,
                   const char *info_str)
{
    int i, r;
    double range = (g_max > g_min + 0.1) ? (g_max - g_min) : 1.0;

    /* --- Y-axis geometry ---
     * yval_w: display width of the widest RTT label string e.g. "75ms" = 4.
     * Each chart row is prefixed with  "NNms ┤ "  (labeled, even rows) or
     *                                  "     │ "  (unlabeled, odd rows).
     * Non-chart rows (header, labels) get the same width as blank spaces.
     * Total prefix display width = yval_w + 3. */
    char _ymax_buf[16];
    snprintf(_ymax_buf, sizeof(_ymax_buf), "%dms", (int)ceil(g_max > 0 ? g_max : 1.0));
    int yval_w = (int)strlen(_ymax_buf);

    /* --- header row (repeated above chart for spatial context) --- */
    if (use_color) fputs(AC_DIM, stdout);
    printf("%*s   ", yval_w, "");
    if (use_color) fputs(AC_RESET, stdout);
    render_header_row(cols, start, end, use_color, use_unicode, 0);

    /* --- max RTT label row --- */
    {
        int any = 0;
        for (i = start; i <= end; i++)
            if (cols[i].stats.n > 0 || cols[i].is_hole) { any = 1; break; }
        if (any) {
            if (use_color) fputs(AC_DIM, stdout);
            printf("%*s   ", yval_w, "");
            if (use_color) fputs(AC_RESET, stdout);
            for (i = start; i <= end; i++) {
                const struct ascii_col *c = &cols[i];
                if (c->stats.n > 0) {
                    char lbl[24];
                    snprintf(lbl, sizeof(lbl), "%dms max", (int)round(c->stats.rtt_max));
                    if (use_color)
                        fputs(c->jitter_warn ? AC_YELLOW : AC_GREEN, stdout);
                    ascii_center(lbl, c->w);
                    if (use_color) fputs(AC_RESET, stdout);
                } else if (c->is_hole) {
                    /* Cloaked column — ░ padded with one space on each side */
                    if (use_color) fputs(AC_DIM, stdout);
                    if (c->w >= 2) {
                        int d;
                        putchar(' ');
                        for (d = 0; d < c->w - 2; d++)
                            fputs(use_unicode ? "\xe2\x96\x91" : ":", stdout);
                        putchar(' ');
                    } else {
                        int d; for (d = 0; d < c->w; d++)
                            fputs(use_unicode ? "\xe2\x96\x91" : ":", stdout);
                    }
                    if (use_color) fputs(AC_RESET, stdout);
                } else {
                    int d; for (d = 0; d < c->w; d++) putchar(' ');
                }
                if (i < end) fputs(CHART_SEP, stdout);
            }
            putchar('\n');
        }
    }

    /* --- precompute per-column row indices --- */
    /* Row 0 = top (highest RTT = g_max), Row CHART_HEIGHT-1 = bottom (g_min) */
    int col_max_row[ASCII_MAX_HOPS];
    int col_avg_row[ASCII_MAX_HOPS];
    int col_min_row[ASCII_MAX_HOPS];
    for (i = start; i <= end; i++) {
        const struct ascii_col *c = &cols[i];
        if (c->stats.n > 0) {
            col_max_row[i] = (int)round((g_max - c->stats.rtt_max) / range * (CHART_HEIGHT - 1));
            col_avg_row[i] = (int)round((g_max - c->stats.rtt_avg) / range * (CHART_HEIGHT - 1));
            col_min_row[i] = (int)round((g_max - c->stats.rtt_min) / range * (CHART_HEIGHT - 1));
            /* clamp */
            if (col_max_row[i] < 0) col_max_row[i] = 0;
            if (col_max_row[i] >= CHART_HEIGHT) col_max_row[i] = CHART_HEIGHT - 1;
            if (col_avg_row[i] < 0) col_avg_row[i] = 0;
            if (col_avg_row[i] >= CHART_HEIGHT) col_avg_row[i] = CHART_HEIGHT - 1;
            if (col_min_row[i] < 0) col_min_row[i] = 0;
            if (col_min_row[i] >= CHART_HEIGHT) col_min_row[i] = CHART_HEIGHT - 1;
            /* ensure max_row <= avg_row <= min_row */
            if (col_avg_row[i] < col_max_row[i]) col_avg_row[i] = col_max_row[i];
            if (col_avg_row[i] > col_min_row[i]) col_avg_row[i] = col_min_row[i];
            /* force minimum 1-row visual spread when rtt_min != rtt_max */
            if (c->stats.rtt_min != c->stats.rtt_max && col_max_row[i] == col_min_row[i]) {
                if (col_min_row[i] < CHART_HEIGHT - 1)
                    col_min_row[i]++;
                else if (col_max_row[i] > 0)
                    col_max_row[i]--;
                /* re-clamp avg */
                if (col_avg_row[i] < col_max_row[i]) col_avg_row[i] = col_max_row[i];
                if (col_avg_row[i] > col_min_row[i]) col_avg_row[i] = col_min_row[i];
            }
        } else {
            col_max_row[i] = col_avg_row[i] = col_min_row[i] = 0;
        }
    }

    /* --- chart body rows --- */
    for (r = 0; r < CHART_HEIGHT; r++) {
        /* Y-axis prefix: labeled tick at even rows, bare pipe at odd rows */
        if (use_color) fputs(AC_DIM, stdout);
        if (r % 2 == 0) {
            double row_rtt = g_max - (double)r / (CHART_HEIGHT - 1) * range;
            printf("%*dms ", yval_w - 2, (int)round(row_rtt));
            fputs(use_unicode ? "\xe2\x94\xa4 " : "| ", stdout);  /* ┤ */
        } else {
            printf("%*s ", yval_w, "");
            fputs(use_unicode ? "\xe2\x94\x82 " : "| ", stdout);  /* │ */
        }
        if (use_color) fputs(AC_RESET, stdout);

        for (i = start; i <= end; i++) {
            const struct ascii_col *c = &cols[i];
            int cw = c->w;

            if (c->stats.n == 0) {
                if (c->is_hole) {
                    /* Cloaked column — ░ across full chart height, padded
                     * with one space on each side so the column doesn't
                     * butt up against its neighbours. */
                    if (use_color) fputs(AC_DIM, stdout);
                    if (cw >= 2) {
                        int d;
                        putchar(' ');
                        for (d = 0; d < cw - 2; d++)
                            fputs(use_unicode ? "\xe2\x96\x91" : ":", stdout);
                        putchar(' ');
                    } else {
                        int d; for (d = 0; d < cw; d++)
                            fputs(use_unicode ? "\xe2\x96\x91" : ":", stdout);
                    }
                    if (use_color) fputs(AC_RESET, stdout);
                } else {
                    int d; for (d = 0; d < cw; d++) putchar(' ');
                }
            } else {
                int mr       = col_max_row[i];
                int ar       = col_avg_row[i];
                int mir      = col_min_row[i];
                int collapsed = (mr == mir);  /* rtt_min == rtt_max exactly */
                int in_shaft  = (r >= mr && r <= mir);
                int is_max    = (r == mr);
                int is_min    = (r == mir);
                int is_avg    = (r == ar);
                int d, mid;

                /* Color: avg block takes priority; then whisker caps white;
                 * shaft/wick is dim.  Applied before drawing, reset after. */
                if (use_color && in_shaft) {
                    if (is_avg)
                        fputs(c->jitter_warn ? AC_YELLOW : AC_GREEN, stdout);
                    else if (is_max || is_min)
                        fputs(AC_WHITE, stdout);
                    else
                        fputs(AC_DIM, stdout);
                }

                /* Bars are 1-col thinner on each side (bw = cw-2) so they
                 * sit inside the ┌─ HOP N ─┐ brackets above/below. */
                int bw  = (cw >= 2) ? (cw - 2) : cw;
                int pad = (cw >= 2) ? 1 : 0;

                if (!in_shaft) {
                    /* blank above max whisker or below min whisker */
                    for (d = 0; d < cw; d++) putchar(' ');
                } else if (collapsed || is_avg) {
                    /* avg body (or sole row when rtt_min==rtt_max): █ */
                    for (d = 0; d < pad; d++) putchar(' ');
                    for (d = 0; d < bw; d++)
                        fputs(use_unicode ? "\xe2\x96\x88" : "#", stdout);  /* █ */
                    for (d = 0; d < pad; d++) putchar(' ');
                } else if (is_max) {
                    /* max whisker cap: ──────┬────── */
                    for (d = 0; d < pad; d++) putchar(' ');
                    mid = (bw - 1) / 2;
                    for (d = 0; d < bw; d++)
                        fputs(d == mid
                              ? (use_unicode ? "\xe2\x94\xac" : "+")    /* ┬ */
                              : (use_unicode ? "\xe2\x94\x80" : "-"),   /* ─ */
                              stdout);
                    for (d = 0; d < pad; d++) putchar(' ');
                } else if (is_min) {
                    /* min whisker cap: ──────┴────── */
                    for (d = 0; d < pad; d++) putchar(' ');
                    mid = (bw - 1) / 2;
                    for (d = 0; d < bw; d++)
                        fputs(d == mid
                              ? (use_unicode ? "\xe2\x94\xb4" : "+")    /* ┴ */
                              : (use_unicode ? "\xe2\x94\x80" : "-"),   /* ─ */
                              stdout);
                    for (d = 0; d < pad; d++) putchar(' ');
                } else {
                    /* shaft / wick between avg and whisker: single │ centered */
                    ascii_center(use_unicode ? "\xe2\x94\x82" : "|", cw);  /* │ */
                }

                if (use_color) fputs(AC_RESET, stdout);
            }

            /* inter-column separator */
            if (i < end)
                fputs(CHART_SEP, stdout);
        }
        putchar('\n');
    }

    /* --- min RTT label row (mirrors the max label row above the chart) --- */
    {
        int any = 0;
        for (i = start; i <= end; i++)
            if (cols[i].stats.n > 0 || cols[i].is_hole) { any = 1; break; }
        if (any) {
            if (use_color) fputs(AC_DIM, stdout);
            printf("%*s   ", yval_w, "");
            if (use_color) fputs(AC_RESET, stdout);
            for (i = start; i <= end; i++) {
                const struct ascii_col *c = &cols[i];
                if (c->stats.n > 0) {
                    char lbl[24];
                    snprintf(lbl, sizeof(lbl), "%dms min", (int)round(c->stats.rtt_min));
                    if (use_color)
                        fputs(c->jitter_warn ? AC_YELLOW : AC_GREEN, stdout);
                    ascii_center(lbl, c->w);
                    if (use_color) fputs(AC_RESET, stdout);
                } else if (c->is_hole) {
                    /* Cloaked column — ░ padded one space each side */
                    if (use_color) fputs(AC_DIM, stdout);
                    if (c->w >= 2) {
                        int d;
                        putchar(' ');
                        for (d = 0; d < c->w - 2; d++)
                            fputs(use_unicode ? "\xe2\x96\x91" : ":", stdout);
                        putchar(' ');
                    } else {
                        int d; for (d = 0; d < c->w; d++)
                            fputs(use_unicode ? "\xe2\x96\x91" : ":", stdout);
                    }
                    if (use_color) fputs(AC_RESET, stdout);
                } else {
                    int d; for (d = 0; d < c->w; d++) putchar(' ');
                }
                if (i < end) fputs(CHART_SEP, stdout);
            }
            putchar('\n');
        }
    }

    /* --- X-axis: └─────┤ RTT Nmin–Nmax ms, N hops to <target> ├───── ---
     * Suppressed entirely when info_str is NULL or empty. */
    {
        int any = 0;
        for (i = start; i <= end; i++)
            if (cols[i].stats.n > 0) { any = 1; break; }
        if (any && info_str && info_str[0]) {
            int total_w = 0;
            for (i = start; i <= end; i++) {
                total_w += cols[i].w;
                if (i < end) total_w += 4;
            }
            /* Build the bracketed label:
             *   Unicode: "┤ End-to-End RTT 3–81ms, 14 hops distance to  1.2.3.4 ├"
             *   ASCII:   "| End-to-End RTT 3-81ms, 14 hops distance to  1.2.3.4 |"
             * info_str is pre-formatted by ASCIIOutput() from the target column. */
            char range_lbl[256];
            if (use_unicode)
                snprintf(range_lbl, sizeof(range_lbl),
                         "\xe2\x94\xa4 %s \xe2\x94\x9c",   /* ┤ … ├ */
                         info_str ? info_str : "");
            else
                snprintf(range_lbl, sizeof(range_lbl),
                         "| %s |",
                         info_str ? info_str : "");
            /* Display width (not byte length) for correct dash math */
            int label_disp = (int)utf8_display_width(range_lbl);
            /* Horizontal run = total_w minus the 2 display cols "└─" already emitted */
            int dash_total = total_w - 2 - label_disp;
            int dash_l = dash_total / 2;
            int dash_r = dash_total - dash_l;
            if (dash_l < 0) dash_l = 0;
            if (dash_r < 0) dash_r = 0;

            /* Frame chars (└─┤├) in blue; label text inside ┤…├ in cyan. */
            printf("%*s ", yval_w, "");   /* Y-axis blank margin */
            if (use_unicode) {
                int d;
                if (use_color) fputs(AC_BLUE, stdout);
                fputs("\xe2\x94\x94\xe2\x94\x80", stdout);   /* └─ */
                for (d = 0; d < dash_l; d++) fputs("\xe2\x94\x80", stdout);
                /* range_lbl = "┤ info ├" — outline text in OS_SENT */
                fputs("\xe2\x94\xa4", stdout);                /* ┤ */
                if (use_color) fputs(OS_SENT, stdout);
                fputs(" ", stdout);
                fputs(info_str ? info_str : "", stdout);
                fputs(" ", stdout);
                if (use_color) fputs(AC_BLUE, stdout);
                fputs("\xe2\x94\x9c", stdout);                /* ├ */
                for (d = 0; d < dash_r; d++) fputs("\xe2\x94\x80", stdout);
                if (use_color) fputs(AC_RESET, stdout);
            } else {
                if (use_color) fputs(AC_BLUE, stdout);
                fputs("+-", stdout);
                ascii_repeat('-', dash_l);
                fputs("|", stdout);
                if (use_color) fputs(OS_SENT, stdout);
                fputs(" ", stdout);
                fputs(info_str ? info_str : "", stdout);
                fputs(" ", stdout);
                if (use_color) fputs(AC_BLUE, stdout);
                fputs("|", stdout);
                ascii_repeat('-', dash_r);
                if (use_color) fputs(AC_RESET, stdout);
            }
            putchar('\n');
            (void)range_lbl;  /* kept for width math earlier, not emitted raw */
        }
    }
}

void ASCIIOutput(lft_session_params *sess)
{
#ifndef WIN32
    lft_o_spinner_stop();   /* erase spinner line before printing output */
#endif
    struct ascii_col cols[ASCII_MAX_HOPS];
    int ncols = 0;
    int hopno, maxhop;
    int use_color   = 0;
    int use_unicode = 0;
    int term_width  = 80;
    int reply, noreply, neglstart, neglend;
    int prevasn = -1;
    struct in_addr classbmask, masked_target, prevasn_hopaddr;
    int prevasn_hopno = 0;
    int netreached = 0;
    int asseam_hopno = -1, netseam_hopno = -1;
    struct in_addr asseam_hopaddr, netseam_hopaddr;
    int lastishole = 0;
    struct trace_packet_info_s *tp;

    /* --- terminal capability detection --- */
#if !defined(WIN32) && !defined(_WIN32)
    {
        const char *term      = getenv("TERM");
        const char *colorterm = getenv("COLORTERM");
        if (isatty(fileno(stdout)) && term &&
            strncmp(term, "dumb",    4) != 0 &&
            strncmp(term, "unknown", 7) != 0) {
            use_unicode = 1;
            if (!getenv("NO_COLOR") &&
                (colorterm ||
                 strncmp(term, "xterm",  5) == 0 ||
                 strncmp(term, "screen", 6) == 0 ||
                 strncmp(term, "tmux",   4) == 0 ||
                 strncmp(term, "rxvt",   4) == 0))
                use_color = 1;
        }
        /* terminal width */
        {
            const char *cols_env = getenv("COLUMNS");
            if (cols_env && atoi(cols_env) > 0)
                term_width = atoi(cols_env);
#ifdef TIOCGWINSZ
            else {
                struct winsize ws;
                if (ioctl(fileno(stdout), TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0)
                    term_width = ws.ws_col;
            }
#endif
        }
    }
#endif

    /* --- determine SEAM positions (same logic as GraphVizOutput) --- */
    inet_aton("255.255.0.0", &classbmask);
    masked_target.s_addr = sess->remote_address.s_addr & classbmask.s_addr;

    if (sess->num_hops)
        maxhop = sess->num_hops;
    else
        maxhop = sess->hop_info_length - 1;

    reply = noreply = 0;
    neglstart = neglend = -1;

    for (hopno = sess->ttl_min; hopno < sess->hop_info_length; hopno++) {
        int icmpcode = -100;
        if (sess->hop_info[hopno].all_rcvd) {
            lastishole = 0;
            SLIST_FOREACH(tp, &(sess->hop_info[hopno].packets), next_by_hop) {
                if (tp->recv.tv_sec) {
                    if (hopno <= maxhop) icmpcode = tp->icmp_type;
                    if (tp->last_hop.s_addr != tp->hopaddr.s_addr) {
                        if ((tp->hopaddr.s_addr & classbmask.s_addr) == masked_target.s_addr)
                            netreached = 1;
                        else {
                            netseam_hopno  = hopno;
                            netseam_hopaddr = tp->hopaddr;
                        }
                        if (sess->do_aslookup || sess->do_netlookup) {
                            if (prevasn == -1) {
                                if (tp->asnumber) {
                                    prevasn         = tp->asnumber;
                                    prevasn_hopno   = hopno;
                                    prevasn_hopaddr = tp->hopaddr;
                                }
                            } else if (tp->asnumber && tp->asnumber != prevasn) {
                                asseam_hopno  = prevasn_hopno;
                                asseam_hopaddr = prevasn_hopaddr;
                                prevasn         = tp->asnumber;
                                prevasn_hopno   = hopno;
                                prevasn_hopaddr = tp->hopaddr;
                            } else if (tp->asnumber && tp->asnumber == prevasn) {
                                /* same AS — keep prevasn_hopno pointing at the
                                 * most recent hop in this AS so the seam is
                                 * attributed to the last hop before the boundary,
                                 * not the first (matches GraphViz behaviour) */
                                prevasn_hopno   = hopno;
                                prevasn_hopaddr = tp->hopaddr;
                            }
                        }
                    }
                }
            }
        } else {
            lastishole = 1;
        }
        if (icmpcode == -1) break;
    }
    if (!netreached)   netseam_hopno = -1;
    if (lastishole)    asseam_hopno  = -1;
    (void)asseam_hopaddr;
    (void)netseam_hopaddr;

    /* --- build SOURCE column --- */
    {
        struct ascii_col *c = &cols[ncols++];
        char hostbuf[256], ipbuf[128];
        memset(c, 0, sizeof(*c));
        c->is_source = 1;
        snprintf(c->hdr, sizeof(c->hdr), "[ SOURCE ]");
        format_host_buf(sess, sess->local_address, hostbuf, sizeof(hostbuf), ipbuf, sizeof(ipbuf));
        /* -o default: numeric only; -h overrides to show hostname */
        if (!sess->hostnames_only) {
            snprintf(hostbuf, sizeof(hostbuf), "%s", inet_ntoa(sess->local_address));
            ipbuf[0] = '\0';
        }
        snprintf(c->host, sizeof(c->host), "%s", hostbuf);
        snprintf(c->sub,  sizeof(c->sub),  "%s", ipbuf);
        if (sess->protocol < 2 || sess->protocol > 3) {
            size_t hlen = strlen(c->host);
            snprintf(c->host + hlen, sizeof(c->host) - hlen, ":%d", sess->sport);
        }
        /* width (use display columns, not bytes, for correct Unicode accounting) */
        c->w = utf8_display_width(c->hdr);
        if (utf8_display_width(c->host) > c->w) c->w = utf8_display_width(c->host);
        if (utf8_display_width(c->sub)  > c->w) c->w = utf8_display_width(c->sub);
        if (utf8_display_width(c->rtt)  > c->w) c->w = utf8_display_width(c->rtt);
    }

    /* --- build per-hop columns --- */
    neglstart = neglend = -1;
    reply = noreply = 0;
    int found_target = 0;

    for (hopno = sess->ttl_min; hopno < sess->hop_info_length; hopno++) {
        int anomtype = 0;
        int icmpcode = -100;
        struct ascii_col *c;

        if (ncols >= ASCII_MAX_HOPS) break;

        if ((sess->hop_info[hopno].state == HS_SEND_FIN) &&
            (sess->hop_info[hopno+1].state == HS_SEND_SYN) &&
            (sess->hop_info[hopno+1].ts_last_recv.tv_sec))
            anomtype = 1;
        if ((sess->hop_info[hopno].state != HS_SEND_SYN_ACK) &&
            (sess->hop_info[hopno+1].state == HS_SEND_SYN_ACK) &&
            (hopno == (sess->num_hops - 1)))
            anomtype = 2;
        if ((sess->hop_info[hopno].flags & HF_ENDPOINT) &&
            (noreply >= ((maxhop - sess->ttl_min) / 2)) &&
            sess->num_hops > 3)
            anomtype = 3;

        if (sess->hop_info[hopno].all_rcvd == 0) {
            reply = 0;
            if (neglstart == -1) neglstart = hopno;
            neglend = hopno;
            continue; /* collect hole range, emit as one column below */
        }

        /* emit pending hole column before this reply hop */
        if (neglstart != -1) {
            c = &cols[ncols++];
            memset(c, 0, sizeof(*c));
            c->is_hole = 1;
            if (neglstart == neglend)
                snprintf(c->hdr,  sizeof(c->hdr),  "[ HOP %d ]", neglstart + 1);
            else
                snprintf(c->hdr,  sizeof(c->hdr),  "[ HOP %d-%d ]", neglstart + 1, neglend + 1);
            if (use_unicode)
                snprintf(c->host, sizeof(c->host), "cloaked");
            else
                snprintf(c->host, sizeof(c->host), "?cloaked?");
            /* TTL shown in the RTT row (dim color) — avoids a separate sub line */
            snprintf(c->rtt, sizeof(c->rtt),
                     neglstart == neglend ? "TTL %d" : "TTLs %d-%d",
                     neglstart + 1, neglend + 1);
            c->w = utf8_display_width(c->hdr);
            if (utf8_display_width(c->host) > c->w) c->w = utf8_display_width(c->host);
            if (utf8_display_width(c->rtt)  > c->w) c->w = utf8_display_width(c->rtt);
            neglstart = neglend = -1;
            if (ncols >= ASCII_MAX_HOPS) break;
        }

        /* reply hop */
        SLIST_FOREACH(tp, &(sess->hop_info[hopno].packets), next_by_hop) {
            if (!tp->recv.tv_sec) continue;
            if (hopno <= maxhop) icmpcode = tp->icmp_type;
            if (tp->last_hop.s_addr == tp->hopaddr.s_addr) continue;

            c = &cols[ncols++];
            memset(c, 0, sizeof(*c));
            c->anomtype = anomtype;

            /* is this the target? */
            if (icmpcode == -1) {
                c->is_target = 1;
                found_target = 1;
                snprintf(c->hdr, sizeof(c->hdr), "[ TARGET ]");
                if (sess->target_open > 0)
                    snprintf(c->ann, sizeof(c->ann), "[open]");
                else if (sess->target_filtered)
                    snprintf(c->ann, sizeof(c->ann), "[filtered]");
                else if (sess->protocol == 2 || sess->protocol == 3)
                    snprintf(c->ann, sizeof(c->ann), "[replied]");
                else
                    snprintf(c->ann, sizeof(c->ann), "[closed]");
            } else {
                snprintf(c->hdr, sizeof(c->hdr), "[ HOP %d ]", hopno + 1);
                /* annotations — Unicode glyphs when terminal supports them */
                if (anomtype == 1) {
                    if (use_unicode)
                        /* ⇶ = rightwards arrows: packets flowing through inspector */
                        snprintf(c->ann, sizeof(c->ann), "\xe2\x87\xb6 Stateful FW");
                    else
                        snprintf(c->ann, sizeof(c->ann), "[Stateful FW]");
                } else if (anomtype == 2) {
                    if (use_unicode)
                        /* ⚑ = black flag: flag-based filter */
                        snprintf(c->ann, sizeof(c->ann), "\xe2\x9a\x91 Flag FW");
                    else
                        snprintf(c->ann, sizeof(c->ann), "[Flag FW]");
                } else if (anomtype == 3) {
                    if (use_unicode)
                        /* ⇄ = right-over-left arrows: half-open / reversed state anomaly */
                        snprintf(c->ann, sizeof(c->ann), "\xe2\x87\x84 BSD Bug");
                    else
                        snprintf(c->ann, sizeof(c->ann), "[BSD Bug]");
                } else if (hopno == asseam_hopno) {
                    if (use_unicode)
                        /* ╳ = diagonal cross: boundary / crossing point */
                        snprintf(c->ann, sizeof(c->ann), "\xe2\x95\xb3 ASN Seam");
                    else
                        snprintf(c->ann, sizeof(c->ann), "x-AS-Seam-x");
                } else if (hopno == netseam_hopno) {
                    if (use_unicode)
                        snprintf(c->ann, sizeof(c->ann), "\xe2\x95\xb3 NET Seam");
                    else
                        snprintf(c->ann, sizeof(c->ann), "x-Net-Seam-x");
                }
            }

            /* hostname / address */
            {
                char hostbuf[256], ipbuf[128];
                format_host_buf(sess, tp->hopaddr, hostbuf, sizeof(hostbuf), ipbuf, sizeof(ipbuf));
                /* -o default: numeric only; -h overrides to show hostname */
                if (!sess->hostnames_only) {
                    snprintf(hostbuf, sizeof(hostbuf), "%s", inet_ntoa(tp->hopaddr));
                    ipbuf[0] = '\0';
                }
                snprintf(c->host, sizeof(c->host), "%s", hostbuf);
                snprintf(c->sub,  sizeof(c->sub),  "%s", ipbuf);
            }
            /* append :dport to target address */
            if (c->is_target && (sess->protocol < 2 || sess->protocol > 3)) {
                size_t hlen = strlen(c->host);
                snprintf(c->host + hlen, sizeof(c->host) - hlen, ":%d", sess->dport);
            }

            /* ASN — suppress unresolved in -o output */
            if (sess->do_aslookup && tp->asnumber)
                snprintf(c->asn, sizeof(c->asn), "AS%d", tp->asnumber);

            /* Net name (short route name) and org name — suppress blank/NULL.
             * Data is populated from pwhois bulk when do_netlookup or do_aslookup
             * is set; check the data itself so -N alone still shows netname. */
            if (tp->netname[0] && strcmp(tp->netname, "NULL") != 0) {
                snprintf(c->net, sizeof(c->net), "%s", tp->netname);
                ascii_utf8_truncate_to(c->net, ASCII_MAX_COL_W);
            }
            if (tp->orgname[0] && strcmp(tp->orgname, "NULL") != 0) {
                snprintf(c->org, sizeof(c->org), "%s", tp->orgname);
                ascii_utf8_truncate_to(c->org, ASCII_MAX_COL_W);
            }

            /* IANA fallback: private / special-purpose addresses are never in
             * pWhoIs, so substitute RFC labels when the fields are still blank.
             *   asn  → RFC tag (e.g. "RFC6598"), only when -A requested
             *   net  → descriptive name (e.g. "Shared Address Space"),
             *          only when -N requested (suppressed by default)
             *   org  → "IANA", always — gives the org row something to show
             *          without -N and fills in cleanly alongside netname with -N */
            if (sess->do_aslookup || sess->do_netlookup) {
                char _irfc[16], _iname[64];
                if (lft_iana_v4_lookup(tp->hopaddr,
                                       _irfc, sizeof(_irfc),
                                       _iname, sizeof(_iname))) {
                    if (!c->asn[0] && sess->do_aslookup)
                        snprintf(c->asn, sizeof(c->asn), "%s", _irfc);
                    if (!c->net[0] && sess->do_netlookup) {
                        snprintf(c->net, sizeof(c->net), "%s", _iname);
                        ascii_utf8_truncate_to(c->net, ASCII_MAX_COL_W);
                    }
                    if (!c->org[0])
                        snprintf(c->org, sizeof(c->org), "IANA");
                }
            }

            /* RTT */
            if (sess->rtt_stats) {
                c->stats = compute_hop_rtt_stats(sess, hopno);
                if (c->stats.n > 0)
                    snprintf(c->rtt, sizeof(c->rtt), "%d/%d/%d/%dms",
                             (int)round(c->stats.rtt_min),
                             (int)round(c->stats.rtt_avg),
                             (int)round(c->stats.rtt_max),
                             (int)round(c->stats.rtt_stddev));
            } else if (tp->recv.tv_sec) {
                snprintf(c->rtt, sizeof(c->rtt), "%.1fms",
                         timediff_ms(tp->sent, tp->recv));
            }

            /* PMTUD annotation — only show discovered MTU for replied hops.
             * Black holes (no-reply) are not in this column; they appear in
             * the neglected-hop hole columns and the post-trace summary. */
            if (sess->pmtud && sess->hop_info[hopno].path_mtu > 0)
                snprintf(c->mtu, sizeof(c->mtu), "[MTU: %u]",
                         (unsigned)sess->hop_info[hopno].path_mtu);

            /* column width = widest of all strings (display columns, not bytes).
             * host/sub can be long; cap them at ASCII_MAX_COL_W so wide hostnames
             * don't blow out the chart layout.  asn/net/org are already capped. */
            c->w = utf8_display_width(c->hdr);
            {
                int hw = utf8_display_width(c->host);
                if (hw > ASCII_MAX_COL_W) hw = ASCII_MAX_COL_W;
                if (hw > c->w) c->w = hw;
            }
            if (utf8_display_width(c->sub)  > c->w) c->w = utf8_display_width(c->sub);
            if (utf8_display_width(c->asn)  > c->w) c->w = utf8_display_width(c->asn);
            if (utf8_display_width(c->net)  > c->w) c->w = utf8_display_width(c->net);
            if (utf8_display_width(c->org)  > c->w) c->w = utf8_display_width(c->org);
            if (utf8_display_width(c->rtt)  > c->w) c->w = utf8_display_width(c->rtt);
            if (utf8_display_width(c->ann)  > c->w) c->w = utf8_display_width(c->ann);
            if (utf8_display_width(c->mtu)  > c->w) c->w = utf8_display_width(c->mtu);

            reply = 1;
            noreply = 0;
            if (found_target) break;
            if (ncols >= ASCII_MAX_HOPS) break;
        }
        if (found_target) break;
    }

    /* trailing hole (path ended without target reply) */
    if (neglstart != -1 && ncols < ASCII_MAX_HOPS) {
        struct ascii_col *c = &cols[ncols++];
        memset(c, 0, sizeof(*c));
        c->is_hole = 1;
        if (neglstart == neglend)
            snprintf(c->hdr, sizeof(c->hdr), "[ HOP %d ]", neglstart + 1);
        else
            snprintf(c->hdr, sizeof(c->hdr), "[ HOP %d-%d ]", neglstart + 1, neglend + 1);
        if (use_unicode)
            snprintf(c->host, sizeof(c->host), "cloaked");
        else
            snprintf(c->host, sizeof(c->host), "?cloaked?");
        /* TTL shown in the RTT row (dim color) — avoids a separate sub line */
        snprintf(c->rtt, sizeof(c->rtt),
                 neglstart == neglend ? "TTL %d" : "TTLs %d-%d",
                 neglstart + 1, neglend + 1);
        c->w = utf8_display_width(c->hdr);
        if (utf8_display_width(c->host) > c->w) c->w = utf8_display_width(c->host);
        if (utf8_display_width(c->rtt)  > c->w) c->w = utf8_display_width(c->rtt);
    }

    /* --- RTT stats: global scale + jitter alert detection --- */
    double g_rtt_min = 0.0, g_rtt_max = 0.0;
    if (sess->rtt_stats && !sess->no_ascii_chart) {
        int g_found = 0;
        int gi;
        double max_jitter = 0.0;
        int jitter_col = -1;

        for (gi = 0; gi < ncols; gi++) {
            if (cols[gi].stats.n > 0) {
                if (!g_found || cols[gi].stats.rtt_min < g_rtt_min)
                    g_rtt_min = cols[gi].stats.rtt_min;
                if (!g_found || cols[gi].stats.rtt_max > g_rtt_max)
                    g_rtt_max = cols[gi].stats.rtt_max;
                g_found = 1;
                {
                    double jitter = cols[gi].stats.rtt_max - cols[gi].stats.rtt_min;
                    if (jitter > max_jitter) {
                        max_jitter = jitter;
                        jitter_col = gi;
                    }
                }
            }
        }
        if (jitter_col >= 0 && max_jitter > JITTER_WARN_MS) {
            int jms = (int)round(max_jitter);
            cols[jitter_col].jitter_warn = 1;
            snprintf(cols[jitter_col].jitter_ann,
                     sizeof(cols[jitter_col].jitter_ann),
                     use_unicode ? "\xe2\x9a\xa1 %dms \xce\x94" : "! %dms D", jms);
            /* diagram annotation row (only if no existing anomaly annotation) */
            if (cols[jitter_col].ann[0] == '\0')
                snprintf(cols[jitter_col].ann,
                         sizeof(cols[jitter_col].ann),
                         use_unicode
                         ? "\xe2\x9a\xa1 jitter %dms"
                         : "! jitter %dms", jms);
            /* update column width for new strings */
            if (utf8_display_width(cols[jitter_col].jitter_ann) > cols[jitter_col].w)
                cols[jitter_col].w = utf8_display_width(cols[jitter_col].jitter_ann);
            if (utf8_display_width(cols[jitter_col].ann) > cols[jitter_col].w)
                cols[jitter_col].w = utf8_display_width(cols[jitter_col].ann);
        }
    }

    /* --- render rows --- */
    /* A row is a slice of cols[] that fits within term_width.
     * Arrow " -> " is 4 chars; we account for that between columns.
     * When the RTT chart is active its Y-axis prefix eats y_prefix_w columns,
     * so diagram rows get the same indentation to keep everything aligned. */

    /* Y-axis prefix width (display cols): same formula as inside ascii_chart_render */
    int y_prefix_w = 0;
    char _xa_info[256] = "";   /* x-axis inner label, pre-formatted */

    /* Build target URL and info string — always used for the top header.
     * When rtt_stats is active we add the E2E RTT range; otherwise just
     * show the hop count and target so the header is always informative. */
    {
        int n_hops = ncols - 1;   /* columns shown minus source */
        const char *tgt_name = inet_ntoa(sess->remote_address);

        /* Build "proto://IP[:port]" target URL */
        char tgt_url[80];
        switch (sess->protocol) {
            case 1:  /* UDP */
                snprintf(tgt_url, sizeof(tgt_url), "udp://%s:%d", tgt_name, sess->dport);
                break;
            case 2:  /* ICMP */
            case 3:  /* ICMP RFC 1393 */
                snprintf(tgt_url, sizeof(tgt_url), "icmp://%s", tgt_name);
                break;
            default: /* 0=TCP, 4=TCP basic */
                snprintf(tgt_url, sizeof(tgt_url), "tcp://%s:%d", tgt_name, sess->dport);
                break;
        }

        if (sess->rtt_stats && !sess->no_ascii_chart) {
            /* Y-axis prefix width (only needed when chart is rendered) */
            char _yb[16];
            snprintf(_yb, sizeof(_yb), "%dms",
                     (int)ceil(g_rtt_max > 0 ? g_rtt_max : 1.0));
            y_prefix_w = (int)strlen(_yb) + 3;   /* label + " ┤ " (3 display cols) */

            /* Find target column for end-to-end RTT range */
            double tgt_rtt_min = 0.0, tgt_rtt_max = 0.0;
            int found_tgt = 0;
            {
                int gi;
                for (gi = 0; gi < ncols; gi++) {
                    if (cols[gi].is_target && cols[gi].stats.n > 0) {
                        tgt_rtt_min = cols[gi].stats.rtt_min;
                        tgt_rtt_max = cols[gi].stats.rtt_max;
                        found_tgt = 1;
                        break;
                    }
                }
            }

            if (found_tgt) {
                /* Match --watch's format exactly: en-dash between min/max,
                 * "hops to" (no "distance"), URL-style target. */
                snprintf(_xa_info, sizeof(_xa_info),
                         "End-to-End RTT %d\xe2\x80\x93%dms, %d hops to %s",
                         (int)round(tgt_rtt_min), (int)round(tgt_rtt_max),
                         n_hops, tgt_url);
            } else {
                /* Match --watch's no-RTT fallback: "target  N hops" */
                snprintf(_xa_info, sizeof(_xa_info),
                         "%s  %d hops", tgt_url, n_hops);
            }
        } else {
            /* No per-hop RTT stats — same fallback as --watch */
            snprintf(_xa_info, sizeof(_xa_info),
                     "%s  %d hops", tgt_url, n_hops);
        }
    }
    int effective_width = term_width - y_prefix_w;
    if (effective_width < 20) effective_width = 20;

    /* ---- Top header: ┌──┤ LFT_LABEL ├[dl]┤ RTT_INFO ├[dr]┐
     * Starts at col 0; spans full term_width.
     * LFT label is left-aligned (short fixed indent); RTT info is centered.
     * Always printed in -o mode; RTT range only present when -j is active. */
    {
        const char *lft_label = "Layer Four Traceroute (LFT)";
        if (use_unicode) {
            /* lft_end     = ┌──┤ space label space ├ = lft_dw + 7
             * rtt_section = ┤ space info space ├     = xa_dw + 4
             * Center RTT on full term_width:
             *   dash_l = (term_width - rtt_section) / 2 - lft_end */
            int lft_dw  = (int)utf8_display_width(lft_label);
            int xa_dw   = _xa_info[0] ? (int)utf8_display_width(_xa_info) : 0;
            int lft_end = lft_dw + 7;
            int rtt_sec = xa_dw + 4;
            int total_dashes = term_width - lft_end - rtt_sec - 1;  /* -1 for ┐ */
            if (total_dashes < 0) total_dashes = 0;
            int dash_l = (term_width - rtt_sec) / 2 - lft_end;
            if (dash_l < 0) dash_l = 0;
            if (dash_l > total_dashes) dash_l = total_dashes;
            int dash_r = total_dashes - dash_l;
            int d;
            /* Frame chars in dark blue, outline text labels in medium slate
             * blue (xterm 75) — matches --watch's colour convention. */
            if (use_color) fputs(AC_BLUE, stdout);
            fputs("\xe2\x94\x8c\xe2\x94\x80\xe2\x94\x80", stdout);  /* ┌── */
            fputs("\xe2\x94\xa4", stdout);                            /* ┤  */
            if (use_color) fputs(OS_SENT, stdout);
            fputs(" ", stdout); fputs(lft_label, stdout); fputs(" ", stdout);
            if (use_color) fputs(AC_BLUE, stdout);
            fputs("\xe2\x94\x9c", stdout);                            /* ├  */
            for (d = 0; d < dash_l; d++) fputs("\xe2\x94\x80", stdout);
            if (_xa_info[0]) {
                fputs("\xe2\x94\xa4", stdout);                        /* ┤  */
                if (use_color) fputs(OS_SENT, stdout);
                fputs(" ", stdout); fputs(_xa_info, stdout); fputs(" ", stdout);
                if (use_color) fputs(AC_BLUE, stdout);
                fputs("\xe2\x94\x9c", stdout);                        /* ├  */
            }
            for (d = 0; d < dash_r; d++) fputs("\xe2\x94\x80", stdout);
            fputs("\xe2\x94\x90", stdout);                            /* ┐  */
            if (use_color) fputs(AC_RESET, stdout);
            putchar('\n');
        } else {
            /* ASCII fallback — same centering logic */
            int lft_dw  = (int)strlen(lft_label);
            int xa_dw   = _xa_info[0] ? (int)strlen(_xa_info) : 0;
            int lft_end = lft_dw + 7;
            int rtt_sec = xa_dw + 4;
            int total_dashes = term_width - lft_end - rtt_sec - 1;
            if (total_dashes < 0) total_dashes = 0;
            int dash_l = (term_width - rtt_sec) / 2 - lft_end;
            if (dash_l < 0) dash_l = 0;
            if (dash_l > total_dashes) dash_l = total_dashes;
            int dash_r = total_dashes - dash_l;
            int d;
            fputs("+--", stdout);
            fputs("|", stdout);
            fputs(" ", stdout); fputs(lft_label, stdout); fputs(" ", stdout);
            fputs("|", stdout);
            for (d = 0; d < dash_l; d++) putchar('-');
            if (_xa_info[0]) {
                fputs("|", stdout);
                fputs(" ", stdout); fputs(_xa_info, stdout); fputs(" ", stdout);
                fputs("|", stdout);
            }
            for (d = 0; d < dash_r; d++) putchar('-');
            fputs("+", stdout);
            putchar('\n');
        }
    }

    {
        int start = 0;
        while (start < ncols) {
            int end = start;
            int used = cols[start].w;
            /* greedily add columns while they fit within the narrowed width */
            while (end + 1 < ncols) {
                int needed = used + 4 + cols[end + 1].w;
                if (needed > effective_width) break;
                used = needed;
                end++;
            }

            /* ---- RTT chart (above header, only when rtt_stats active) ---- */
            if (sess->rtt_stats && !sess->no_ascii_chart) {
                putchar('\n');
                ascii_chart_render(cols, start, end,
                                   g_rtt_min, g_rtt_max,
                                   use_color, use_unicode,
                                   NULL);   /* x-axis line suppressed; info in top header */
            }

            /* ---- row header line (hdr strings)
             * └┘ when chart hangs above (rtt_stats), ┌┐ when no chart above.
             * Skip the leading \n when a chart was just drawn — the chart's
             * min-RTT row already ended with \n, so an extra \n here was
             * producing a redundant blank line between the chart and the
             * first hop-label row. */
            if (!(sess->rtt_stats && !sess->no_ascii_chart))
                putchar('\n');
            if (y_prefix_w > 0) printf("%*s", y_prefix_w, "");
            render_header_row(cols, start, end, use_color, use_unicode,
                              (sess->rtt_stats && !sess->no_ascii_chart) ? 1 : 0);

            /* ---- hostname line (host strings, sep = " -> ") ---- */
            {
                int i;
                if (y_prefix_w > 0) printf("%*s", y_prefix_w, "");
                for (i = start; i <= end; i++) {
                    struct ascii_col *c = &cols[i];
                    const char *col_color = AC_RESET;
                    if (use_color) {
                        if (c->is_hole)                col_color = AC_DIM;
                        else if (c->is_target)         col_color = AC_BOLD;
                        else if (c->anomtype > 0)      col_color = AC_RED;
                        else if (c->is_source)         col_color = AC_BOLD;
                    }
                    if (use_color) fputs(col_color, stdout);
                    ascii_center(c->host, c->w);
                    if (use_color) fputs(AC_RESET, stdout);
                    if (i < end)
                        fputs(" -> ", stdout);
                }
            }
            putchar('\n');

            /* ---- sub line (ip in parens, or TTL N for holes) ---- */
            {
                int i, any = 0;
                for (i = start; i <= end; i++)
                    if (cols[i].sub[0]) { any = 1; break; }
                if (any) {
                    if (y_prefix_w > 0) printf("%*s", y_prefix_w, "");
                    for (i = start; i <= end; i++) {
                        struct ascii_col *c = &cols[i];
                        if (use_color && c->is_hole) fputs(AC_DIM, stdout);
                        ascii_center(c->sub, c->w);
                        if (use_color && c->is_hole) fputs(AC_RESET, stdout);
                        if (i < end) fputs("    ", stdout);
                    }
                    putchar('\n');
                }
            }

            /* ---- RTT line ---- */
            {
                int i, any = 0;
                for (i = start; i <= end; i++)
                    if (cols[i].rtt[0]) { any = 1; break; }
                if (any) {
                    if (y_prefix_w > 0) printf("%*s", y_prefix_w, "");
                    for (i = start; i <= end; i++) {
                        struct ascii_col *c = &cols[i];
                        if (use_color && c->rtt[0])
                            fputs(c->is_hole ? AC_DIM : AC_GREEN, stdout);
                        ascii_center(c->rtt, c->w);
                        if (use_color && c->rtt[0]) fputs(AC_RESET, stdout);
                        if (i < end) fputs("    ", stdout);
                    }
                    putchar('\n');
                }
            }

            /* ---- ASN line (when -A used) ---- */
            {
                int i, any = 0;
                for (i = start; i <= end; i++)
                    if (cols[i].asn[0]) { any = 1; break; }
                if (any) {
                    if (y_prefix_w > 0) printf("%*s", y_prefix_w, "");
                    for (i = start; i <= end; i++) {
                        struct ascii_col *c = &cols[i];
                        if (use_color && c->asn[0])
                            fputs(c->is_hole ? AC_DIM : AC_YELLOW, stdout);
                        ascii_center(c->asn, c->w);
                        if (use_color && c->asn[0]) fputs(AC_RESET, stdout);
                        if (i < end) fputs("    ", stdout);
                    }
                    putchar('\n');
                }
            }

            /* ---- net-name line (when -A used) ---- */
            {
                int i, any = 0;
                for (i = start; i <= end; i++)
                    if (cols[i].net[0]) { any = 1; break; }
                if (any) {
                    if (y_prefix_w > 0) printf("%*s", y_prefix_w, "");
                    for (i = start; i <= end; i++) {
                        struct ascii_col *c = &cols[i];
                        if (use_color && c->net[0]) fputs(AC_YELLOW, stdout);
                        ascii_center(c->net, c->w);
                        if (use_color && c->net[0]) fputs(AC_RESET, stdout);
                        if (i < end) fputs("    ", stdout);
                    }
                    putchar('\n');
                }
            }

            /* ---- org-name line (when -A used, pwhois only) ---- */
            {
                int i, any = 0;
                for (i = start; i <= end; i++)
                    if (cols[i].org[0]) { any = 1; break; }
                if (any) {
                    if (y_prefix_w > 0) printf("%*s", y_prefix_w, "");
                    for (i = start; i <= end; i++) {
                        struct ascii_col *c = &cols[i];
                        if (use_color && c->org[0]) fputs(AC_YELLOW, stdout);
                        ascii_center(c->org, c->w);
                        if (use_color && c->org[0]) fputs(AC_RESET, stdout);
                        if (i < end) fputs("    ", stdout);
                    }
                    putchar('\n');
                }
            }

            /* ---- annotation line (*SEAM*, *FW?*, [OPEN], etc.) ---- */
            {
                int i, any = 0;
                for (i = start; i <= end; i++)
                    if (cols[i].ann[0]) { any = 1; break; }
                if (any) {
                    if (y_prefix_w > 0) printf("%*s", y_prefix_w, "");
                    for (i = start; i <= end; i++) {
                        struct ascii_col *c = &cols[i];
                        const char *ac = NULL;
                        if (use_color && c->ann[0]) {
                            if (c->is_target) {
                                if      (sess->target_open > 0)                          ac = AC_BGREEN;
                                else if (sess->target_filtered)                           ac = AC_YELLOW;
                                else if (sess->protocol == 2 || sess->protocol == 3)     ac = AC_BGREEN;
                                else                                                      ac = AC_BRED;
                            } else if (c->anomtype > 0)            ac = AC_RED;
                            else                                   ac = AC_ORANGE;
                        }
                        if (ac) fputs(ac, stdout);
                        ascii_center(c->ann, c->w);
                        if (ac) fputs(AC_RESET, stdout);
                        if (i < end) fputs("    ", stdout);
                    }
                    putchar('\n');
                }
            }

            /* ---- MTU line (when -K active and any hop has MTU info) ---- */
            if (sess->pmtud) {
                int i, any = 0;
                for (i = start; i <= end; i++)
                    if (cols[i].mtu[0]) { any = 1; break; }
                if (any) {
                    if (y_prefix_w > 0) printf("%*s", y_prefix_w, "");
                    for (i = start; i <= end; i++) {
                        struct ascii_col *c = &cols[i];
                        if (use_color && c->mtu[0]) fputs(AC_YELLOW, stdout);
                        ascii_center(c->mtu, c->w);
                        if (use_color && c->mtu[0]) fputs(AC_RESET, stdout);
                        if (i < end) fputs("    ", stdout);
                    }
                    putchar('\n');
                }
            }

            start = end + 1;
        }
    }
    putchar('\n');
}
/*---------------------------------------------------------------------------*/
