lft-4.01/000755 000765 000024 00000000000 15250425210 012143 5ustar00vicstaff000000 000000 lft-4.01/lft_xml.c000644 000765 000024 00000002455 15231735015 013770 0ustar00vicstaff000000 000000 /* lft_xml.c — XML attribute/body escaping. See lft_xml.h. Single forward scan; each source char maps to itself or a 4-6 byte entity. Before writing, we confirm the whole mapping fits in the remaining buffer (leaving room for the NUL); if not, we stop rather than emit a partial entity. Introduced for LFT 4.0. */ #include "lft_xml.h" #include const char *lft_xml_escape(const char *in, char *out, size_t outsz) { size_t pos = 0; if (!out || outsz == 0) return out; if (!in) { out[0] = '\0'; return out; } for (; *in; in++) { const char *ent; size_t elen; switch (*in) { case '&': ent = "&"; elen = 5; break; case '<': ent = "<"; elen = 4; break; case '>': ent = ">"; elen = 4; break; case '"': ent = """; elen = 6; break; case '\'': ent = "'"; elen = 6; break; default: ent = 0; elen = 1; break; } /* Need elen bytes plus the trailing NUL. */ if (pos + elen + 1 > outsz) break; if (ent) { memcpy(out + pos, ent, elen); pos += elen; } else { out[pos++] = *in; } } out[pos] = '\0'; return out; } lft-4.01/lft_pathwatch.h000644 000765 000024 00000005411 15227413256 015160 0ustar00vicstaff000000 000000 /* lft_pathwatch.h — J1 path-change alert heuristic for watch mode (-W) Pure state machine: no ncurses, no session, no network dependencies. Classifies per-TTL hop observations across watch cycles into path-change verdicts with a learning gate, 2-cycle hysteresis, and multipath (ECMP) tolerance. See Output/LFT/j1-path-change-heuristic-design.md. This file is part of LFT. */ #ifndef LFT_PATHWATCH_H #define LFT_PATHWATCH_H #define PW_KNOWN_MAX 8 /* remembered IPs per TTL (ECMP set) */ #define PW_IPLEN 64 /* matches watch-mode host[64] strings */ #define PW_ECMP_LOG_MAX 3 /* ECMP-shift log lines per hop before suppress */ typedef struct { char known_ips[PW_KNOWN_MAX][PW_IPLEN]; /* IPs ever seen at this TTL */ int n_known; char pending_ip[PW_IPLEN]; /* candidate change awaiting hysteresis */ int pending_cycles; /* observed sightings of pending_ip */ int ecmp_logged; /* ECMP-shift lines emitted for this hop */ } watch_pathwatch_t; typedef enum { PW_IGNORE = 0, /* cloaked/empty on either side — no action */ PW_STABLE, /* cur == confirmed, nothing pending */ PW_DISCOVERY, /* pre-arm or new-slot observation, learned quietly */ PW_PENDING, /* new IP, hysteresis started/continuing */ PW_FLAP, /* pending reverted to confirmed — quiet log */ PW_ECMP_SHIFT, /* known multipath member — quiet log (rate-capped) */ PW_CONFIRMED /* change persisted 2 observed sightings — ALERT */ } pw_verdict_t; /* Classify one hop observation. Mutates pw and confirmed (in-out buffer of * PW_IPLEN). flap_was (out, PW_IPLEN, may be NULL) receives the reverted * pending IP on PW_FLAP. On PW_CONFIRMED the known set is reset to {cur} * so a later reversion to the old path alerts (route restore) instead of * reading as an ECMP shift. Pending-match is tested before set membership * — that ordering is load-bearing (see design doc). */ pw_verdict_t pw_observe(watch_pathwatch_t *pw, char *confirmed, const char *cur, int armed, char *flap_was); /* Add an IP to the known set without classification (alt_hosts feeding, * discovery). Deduplicating; silently drops when the set is full. */ void pw_note_ip(watch_pathwatch_t *pw, const char *ip); /* Arming latch: returns 1 once stable_cycles >= 3 or cycle >= 8, and never * returns 0 after having returned 1 (pass the prior value back as armed). */ int pw_update_armed(int armed, int stable_cycles, int cycle); /* Clear all per-hop heuristic state (watch reset key). */ void pw_reset(watch_pathwatch_t *pw); #endif /* LFT_PATHWATCH_H */ lft-4.01/lft_lib.c000644 000765 000024 00001624073 15247705774 013765 0ustar00vicstaff000000 000000 /* * 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_addr.h" /* lft_resolve_af — AF-aware target resolution (4.0) */ #include "lft_xml.h" /* lft_xml_escape — well-formed XML attribute output */ #include "lft_icons_svg.h" /* LFT_SVG_DEFS — flatline glyphs for SVGOutput */ #include /* JSONOutput: force LC_NUMERIC "C" while formatting floats */ #ifdef HAVE_IPV6 #include "lft_probe6.h" /* lft_cksum6, lft_build_tcp_options callers (4.0) */ #include #include #endif #include "lft_btcptrace.h" #include "lft_icmptrace.h" #ifndef WIN32 #include #include #include #include #include #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" /* DejaVu Sans ships with GraphViz/fontconfig, so it resolves on every render * host (Helvetica is often absent on Linux and silently substituted). */ #define GVFONTNAME "DejaVu Sans" /* Monospace face for numeric addresses in HTML-like labels — tabular columns. */ #define GVFONTNAME_MONO "DejaVu Sans Mono" static const char GVNTBEG[]="
"; static const char GVNTEND[]="
"; 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"; } } #ifdef HAVE_IPV6 /* ICMPv6 human name from LFT's internal itype (-2 = Time Exceeded; otherwise a * Destination Unreachable code per RFC 4443). Companion to the v4 * lft_icmp_verbose_name for the verbose RCVD line on v6 traces. */ static const char * lft_icmp6_verbose_name(int t) { switch (t) { case -2: return "Time Exceeded"; case 0: return "No route to destination"; case 1: return "Administratively prohibited"; case 2: return "Beyond scope of source address"; case 3: return "Address unreachable"; case 4: return "Port unreachable"; case 5: return "Source address failed ingress/egress policy"; case 6: return "Reject route to destination"; default: return "Destination Unreachable"; } } #endif /* 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"; } /* AF-aware normalization for the verbose RCVD line. v4 keeps the on-wire ICMP * type numbers (TE=11, Unreach=3) and names; v6 reports the ICMPv6 types * (TE=3, Dest-Unreach=1), RFC 4443 names, and the v6 hop address (the event's * v4 ipaddr is 0 on a v6 trace). */ #ifdef HAVE_IPV6 static int lft_rcvd_icmp_type(const lft_session_params *s, int it) { return (s->af == AF_INET6) ? ((it == -2) ? 3 : 1) : ((it == -2) ? 11 : 3); } static const char *lft_rcvd_icmp_name(const lft_session_params *s, int it) { return (s->af == AF_INET6) ? lft_icmp6_verbose_name(it) : lft_icmp_verbose_name(it); } static const char *lft_rcvd_src(const lft_session_params *s, const EvtRecvPacketParam *e) { static char b[INET6_ADDRSTRLEN]; if (s->af == AF_INET6 && e->tp) { /* XML (machine-readable) always gets the fully-expanded v6 form. */ lft_ntop_fmt(&e->tp->hopaddr6, b, sizeof b, s->addr_expand || getOutputStyle() == 1); return b; } return inet_ntoa(e->ipaddr); } #else #define lft_rcvd_icmp_type(s, it) ((it) == -2 ? 11 : 3) #define lft_rcvd_icmp_name(s, it) lft_icmp_verbose_name(it) #define lft_rcvd_src(s, e) inet_ntoa((e)->ipaddr) #endif 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); void SVGOutput(lft_session_params * sess); void MermaidOutput(lft_session_params * sess); void JSONOutput(lft_session_params * sess); void GeoJSONOutput(lft_session_params * sess); void KMLOutput(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 " 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; } /* Best-effort terminal write for spinner / cursor-control output. The * return value is intentionally ignored (cosmetic output; nothing to do if * the terminal write is short), but we consume it so glibc's * warn_unused_result on write() under _FORTIFY_SOURCE stays quiet. * Async-signal-safe (only calls write()). */ static void lft_bewrite(int fd, const void *buf, size_t n) { if (write(fd, buf, n) < 0) { /* ignore: cosmetic terminal output */ } } 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; } lft_bewrite(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); lft_bewrite(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.). */ lft_bewrite(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 */ lft_bewrite(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("%s",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("%s",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("%s",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("%s",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("%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(""); } break; case WRN_UDP_PORT_TOO_HIGH: if(global_output_style==1) printf("Starting UDP port %d is too high. Will start with %d instead.", 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("Packet length %d is too high. Will use %d instead.", 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("Packet length %d is too low. Will use %d instead.", 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("%s",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("%s",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("%s",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("Net interface names are limited to %d characters. (e.g., \"eth0\" or \"ppp0\" or \"10.10.10.10\")",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("%s",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("Unknown host: %s",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("socket: %s",code,strerror(errno)); #else printf("Socket trouble; unable to determine interface",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("UDP connect(); unable to determine interface: %s",code,strerror(errno)); else perror("LFT: UDP connect(); unable to determine interface"); break; case WRN_GETIFFORREMOTE_SOCKNAME: if(global_output_style==1) printf("getsockname: %s",code,strerror(errno)); else perror("getsockname"); break; case ERR_RAW_SOCKET: if(global_output_style==1) printf("raw socket: %s",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("bind: %s",code,strerror(errno)); else perror ("LFT: bind"); sess->exit_state=-4; break; case WRN_WSAIOCTL: if(global_output_style==1) printf("WSAIoctl: %s",code,strerror(errno)); else perror("LFT: WSAIoctl"); break; case ERR_IP_HDRINCL: if(global_output_style==1) printf("IP_HDRINCL: %s",code,strerror(errno)); else perror ("LFT: IP_HDRINCL"); sess->exit_state=-5; break; case ERR_NOT_ENOUGH_MEM: if(global_output_style==1) printf("malloc(): %s",code,strerror(errno)); else perror("malloc"); sess->exit_state=-6; break; case ERR_RAW_TCP_DISABLED: if(global_output_style==1) { printf("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("\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("Bad state %#x for hop %d", 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("", 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(""); } 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("select: %s",code,strerror(errno)); else perror("select"); sess->exit_state=-8; break; case ERR_WIN_RECV: if(global_output_style==1) printf("read: %s",code,strerror(errno)); else perror("read"); sess->exit_state=-9; break; case ERR_WIN_WSASTARTUP: if(global_output_style==1) printf("WSAStartup: %s",code,strerror(errno)); else perror("WSAStartup"); sess->exit_state=-10; break; case ERR_PCAP_ERROR: if(global_output_style==1) printf("%s",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("Failed to discover an appropriate interface",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("Unable to locate a local interface with IP address %s",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("The network device \"%s\" isn\'t available to LFT. Try another or fix:\nERROR: %s",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("BIOCIMMEDIATE: %s", 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("Error opening socket", 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("Error setting nonblocking mode (IOCTL)", 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("Error on socket select call", 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("Error trying to read socket error", 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("Error on socket", 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("Timeout on socket", 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("Error setting nonblocking mode (FCNTL GET)", 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("Error setting nonblocking mode (FCNTL SET)", 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("Error trying socket connect", code); break; case ERR_PCAP_NONBLOCK_ERROR: if(global_output_style==1) printf("%s",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("Failed to activate capture on device \"%s\": %s",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 activating capture on device \"%s\": %s",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; #ifdef HAVE_IPV6 struct in6_addr addr6; #endif } 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; for (i = 0; i < sess->dns_cache_count; i++) { #ifdef HAVE_IPV6 int _match = (sess->af == AF_INET6) ? (memcmp(&sess->dns_cache[i].addr6, &ud->addr6, sizeof(struct in6_addr)) == 0) : (sess->dns_cache[i].addr.s_addr == ud->addr.s_addr); if (_match) { #else if (sess->dns_cache[i].addr.s_addr == ud->addr.s_addr) { #endif if (status == ARES_SUCCESS && host && host->h_name) { 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'; } sess->dns_cache[i].pending = 0; /* allow retry on a later cycle */ 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; int slot = -1; 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) { if (sess->dns_cache[i].name[0]) return; /* resolved — nothing to do */ if (sess->dns_cache[i].pending) return; /* query already in flight this cycle */ /* Unresolved, not in flight: in watch mode the cache persists * across cycles, so a PTR lookup that timed out on an earlier * cycle would otherwise stay blank forever. Retry up to 3 * attempts, then accept that the IP has no PTR record. */ if (sess->dns_cache[i].attempts >= 3) return; slot = i; break; } if (slot < 0) { if (sess->dns_cache_count >= LFT_DNS_CACHE_SIZE) return; /* cache full — shouldn't happen within ttl_limit */ slot = sess->dns_cache_count++; sess->dns_cache[slot].addr = addr; sess->dns_cache[slot].name[0] = '\0'; sess->dns_cache[slot].attempts = 0; } sess->dns_cache[slot].attempts++; sess->dns_cache[slot].pending = 1; 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); } #ifdef HAVE_IPV6 /* v6 companion to lft_ares_fire: async PTR query for a v6 hop address. */ static void lft_ares_fire6(lft_session_params *sess, const struct in6_addr *addr6) { lft_ares_ud_t *ud; int i, slot = -1; if (!sess->ares_chan || sess->async_dns_disabled || !sess->resolve_names) return; for (i = 0; i < sess->dns_cache_count; i++) if (memcmp(&sess->dns_cache[i].addr6, addr6, sizeof(struct in6_addr)) == 0) { if (sess->dns_cache[i].name[0]) return; /* resolved */ if (sess->dns_cache[i].pending) return; /* in flight */ if (sess->dns_cache[i].attempts >= 3) return; slot = i; break; } if (slot < 0) { if (sess->dns_cache_count >= LFT_DNS_CACHE_SIZE) return; slot = sess->dns_cache_count++; sess->dns_cache[slot].addr6 = *addr6; sess->dns_cache[slot].addr.s_addr = 0; sess->dns_cache[slot].name[0] = '\0'; sess->dns_cache[slot].attempts = 0; } sess->dns_cache[slot].attempts++; sess->dns_cache[slot].pending = 1; ud = malloc(sizeof(lft_ares_ud_t)); if (!ud) return; ud->sess = sess; ud->addr6 = *addr6; sess->ares_pending++; ares_gethostbyaddr(sess->ares_chan, addr6, sizeof(*addr6), AF_INET6, lft_ares_callback, ud); } #endif /* HAVE_IPV6 */ /* 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); } #ifdef HAVE_ARES_PROCESS_FDS /* ======== c-ares 1.34+ event-model path (ares_process_fds) ================ */ /* 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); } #else /* !HAVE_ARES_PROCESS_FDS */ /* ======== classic c-ares poll path (ares_fds + ares_process) ============= * For c-ares older than 1.34 (the event API is absent) -- this is the version * shipped by most distributions (Debian/Ubuntu, RHEL). Instead of a socket- * state callback and an fd table, we ask c-ares for its current fd set on each * pass via ares_fds() and hand the ready fds back with ares_process(). Same * observable behaviour (async PTR resolution overlapping the trace, drained * after it); the sock_state_cb and fd table are simply unused here. * * ares_fds()/ares_process() are the correct API for this (older) c-ares, but a * newer c-ares header (e.g. a 1.34 header shadowing an older linked library) * marks them deprecated. This branch only compiles when the event API is * unavailable, so the deprecation is expected — silence it here. */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" /* Non-blocking poll: harvest any c-ares replies that have arrived. */ void lft_ares_poll(lft_session_params *sess) { fd_set rfd, wfd; struct timeval tv = {0, 0}; int nfds; if (!sess->ares_chan || sess->ares_pending == 0) return; FD_ZERO(&rfd); FD_ZERO(&wfd); nfds = ares_fds(sess->ares_chan, &rfd, &wfd); if (nfds == 0) return; select(nfds, &rfd, &wfd, NULL, &tv); /* non-blocking: tv = {0, 0} */ ares_process(sess->ares_chan, &rfd, &wfd); } /* Post-trace blocking wait: drain remaining queries up to a hard deadline * (see the event-model version above for the ceiling rationale). */ void lft_ares_wait(lft_session_params *sess) { struct timeval deadline, now, remaining, ares_tv, *atvp, wait_start; int 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; gettimeofday(&now, NULL); timersub(&deadline, &now, &remaining); if (remaining.tv_sec < 0) break; /* deadline passed — display remaining hops numerically */ FD_ZERO(&rfd); FD_ZERO(&wfd); nfds = ares_fds(sess->ares_chan, &rfd, &wfd); if (nfds == 0) { ares_process(sess->ares_chan, &rfd, &wfd); /* drive timeouts */ break; } atvp = ares_timeout(sess->ares_chan, &remaining, &ares_tv); select(nfds, &rfd, &wfd, NULL, atvp); ares_process(sess->ares_chan, &rfd, &wfd); } gettimeofday(&now, NULL); sess->ares_wait_ms = timediff_ms(wait_start, now); } #pragma GCC diagnostic pop #endif /* HAVE_ARES_PROCESS_FDS */ #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 */ /* Emit a numeric address in the plain / GraphViz label branch. GraphViz * (style 2) wraps it in a monospace so address columns align in the * HTML-like node labels; plain text (style 0) is unchanged. */ static void emit_addr (const char *pre, const char *ip, const char *post) { if (global_output_style == 2) printf ("%s%s%s", pre, GVFONTNAME_MONO, ip, post); else printf ("%s%s%s", pre, ip, post); } 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) { char _esc[NI_MAXHOST * 2]; printf (" host=\"%s\"", lft_xml_escape(name, _esc, sizeof _esc)); } else printf ("%s", name); if (!sess->hostnames_only) { if (global_output_style == 1) printf (" ip=\"%s\"", inet_ntoa (addr)); else emit_addr (" (", inet_ntoa (addr), ")"); } } else { if (global_output_style == 1) printf (" ip=\"%s\"", inet_ntoa (addr)); else emit_addr ("", 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 emit_addr ("", 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) { char _esc[NI_MAXHOST * 2]; printf (" host=\"%s\"", lft_xml_escape(hbuf, _esc, sizeof _esc)); } else printf ("%s", hbuf); if (!sess->hostnames_only) { if (global_output_style == 1) printf (" ip=\"%s\"", inet_ntoa (addr)); else emit_addr (" (", inet_ntoa (addr), ")"); } } else { if (global_output_style == 1) printf (" ip=\"%s\"", inet_ntoa (addr)); else emit_addr ("", inet_ntoa (addr), ""); } } /*---------------------------------------------------------------------------*/ #ifdef HAVE_IPV6 /* v6 companion to print_host. Numeric form (RFC 5952, or expanded with * --expand-addr) plus synchronous PTR when name resolution is enabled. * The c-ares async cache is v4-keyed, so v6 always resolves synchronously * here (same alarm-guarded getnameinfo the v4 non-cares fallback uses). */ static void print_host6 (lft_session_params *sess, const lft_addr_t *a) { char ipbuf[INET6_ADDRSTRLEN]; char hbuf[NI_MAXHOST]; struct sockaddr_in6 sa; int rc = -1; /* XML is machine-readable: always emit the fully-expanded (fixed-width) v6 * form there, regardless of the --expand-addr display toggle. */ lft_ntop_fmt(a, ipbuf, sizeof ipbuf, sess->addr_expand || global_output_style == 1); #ifdef HAVE_CARES /* When c-ares is active, PTR names are pre-populated in the cache during * the trace (async); no blocking call at display time. Mirrors print_host. */ if (sess->ares_chan && !sess->async_dns_disabled) { const char *name = NULL; int i; for (i = 0; i < sess->dns_cache_count; i++) { if (memcmp(&sess->dns_cache[i].addr6, &a->a.v6, sizeof(struct in6_addr)) == 0) { if (sess->dns_cache[i].name[0]) name = sess->dns_cache[i].name; break; } } if (name) { if (global_output_style == 1) { char _esc[NI_MAXHOST * 2]; printf (" host=\"%s\"", lft_xml_escape(name, _esc, sizeof _esc)); } else printf ("%s", name); if (!sess->hostnames_only) { if (global_output_style == 1) printf (" ip=\"%s\"", ipbuf); else emit_addr (" (", ipbuf, ")"); } } else { if (global_output_style == 1) printf (" ip=\"%s\"", ipbuf); else emit_addr ("", ipbuf, ""); } return; } #endif if (sess->resolve_names) { memset(&sa, 0, sizeof(sa)); sa.sin6_family = AF_INET6; sa.sin6_addr = a->a.v6; #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) { char _esc[NI_MAXHOST * 2]; printf (" host=\"%s\"", lft_xml_escape(hbuf, _esc, sizeof _esc)); } else printf ("%s", hbuf); if (!sess->hostnames_only) { if (global_output_style == 1) printf (" ip=\"%s\"", ipbuf); else emit_addr (" (", ipbuf, ")"); } } else { if (global_output_style == 1) printf (" ip=\"%s\"", ipbuf); else emit_addr ("", ipbuf, ""); } } /* AF-aware renderers: v6 when af==AF_INET6, else the v4 print_host. */ static void print_hop_host (lft_session_params *sess, const struct trace_packet_info_s *tp) { if (sess->af == AF_INET6) print_host6(sess, &tp->hopaddr6); else print_host(sess, tp->hopaddr); } static void print_local_host (lft_session_params *sess) { if (sess->af == AF_INET6) { lft_addr_t a; lft_addr_from_ss(&sess->local_ss, &a); print_host6(sess, &a); } else print_host(sess, sess->local_address); } static void print_remote_host (lft_session_params *sess) { if (sess->af == AF_INET6) { lft_addr_t a; lft_addr_from_ss(&sess->remote_ss, &a); print_host6(sess, &a); } else print_host(sess, sess->remote_address); } #else #define print_hop_host(s, tp) print_host((s), (tp)->hopaddr) #define print_local_host(s) print_host((s), (s)->local_address) #define print_remote_host(s) print_host((s), (s)->remote_address) #endif /* AF-aware "did the responding hop address change" test used to distinguish * a new hop from a repeated probe/response. v6 compares the lft_addr_t pair; * v4 compares in_addr. Unused v6 args vanish from the non-HAVE_IPV6 expansion. */ #ifdef HAVE_IPV6 #define HOP_CHANGED(sess, p4, c4, p6, c6) \ ((sess)->af == AF_INET6 ? !lft_addr_eq((p6), (c6)) : (p4).s_addr != (c4).s_addr) /* Seam trigger: this hop is the recorded seam hop (AF-aware address match). */ #define SEAM_HIT(sess, hopno, seamno, tp, s4, s6) \ ((hopno) == (seamno) && ((sess)->af == AF_INET6 \ ? lft_addr_eq(&(tp)->hopaddr6, &(s6)) : (tp)->hopaddr.s_addr == (s4).s_addr)) #else #define HOP_CHANGED(sess, p4, c4, p6, c6) ((p4).s_addr != (c4).s_addr) #define SEAM_HIT(sess, hopno, seamno, tp, s4, s6) \ ((hopno) == (seamno) && (tp)->hopaddr.s_addr == (s4).s_addr) #endif /* AF-aware IANA special-purpose lookup for a hop. */ static int lft_iana_lookup_af(const lft_session_params *sess, const struct trace_packet_info_s *tp, char *rfc_out, size_t rfc_sz, char *name_out, size_t name_sz) { #ifdef HAVE_IPV6 if (sess->af == AF_INET6) return lft_iana_v6_lookup(&tp->hopaddr6.a.v6, rfc_out, rfc_sz, name_out, name_sz); #endif return lft_iana_v4_lookup(tp->hopaddr, rfc_out, rfc_sz, name_out, name_sz); } /*---------------------------------------------------------------------------*/ double timediff_ms (struct timeval prior, struct timeval latter) { return (latter.tv_usec - prior.tv_usec) / 1000. + (latter.tv_sec - prior.tv_sec) * 1000.; } /* RTT-stat spread marker: "±" on UTF-8 terminals, "~" elsewhere. The stat * display is "min/avg/max ±stddev ms"; the marker severs the spread from the * sample triple so it can't be misread as a fourth sample. */ static const char *lft_pm(void) { static const char *pm; if (!pm) { const char *l = getenv("LC_ALL"); if (!l || !*l) l = getenv("LC_CTYPE"); if (!l || !*l) l = getenv("LANG"); pm = "~"; if (l) { for (; *l; l++) { if ((l[0]=='u'||l[0]=='U') && (l[1]=='t'||l[1]=='T') && (l[2]=='f'||l[2]=='F')) { pm = "\xc2\xb1"; break; } } } } return pm; } /* 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; } /* Per-member RTT stats: like compute_hop_rtt_stats but only the packets whose * responder address matches match_tp (one ECMP member of the hop). */ static hop_rtt_stats_t compute_member_rtt_stats(lft_session_params *sess, int hopno, const struct trace_packet_info_s *match_tp) { hop_rtt_stats_t s; struct trace_packet_info_s *tp; double sum = 0.0, sum2 = 0.0, rtt_min = 0.0, rtt_max = 0.0; int n = 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) { double rtt; if (!tp->recv.tv_sec) continue; #ifdef HAVE_IPV6 if (sess->af == AF_INET6) { if (!lft_addr_eq(&tp->hopaddr6, &match_tp->hopaddr6)) continue; } else #endif if (tp->hopaddr.s_addr != match_tp->hopaddr.s_addr) continue; 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; } /*---------------------------------------------------------------------------*/ /* Count distinct responders (ECMP members) at a hop for the multi-member text * layout. ECMP replies interleave, so dedup by address. */ static int lft_hop_member_count(lft_session_params *sess, int hopno) { struct trace_packet_info_s *tp; struct in_addr seen[LFT_ECMP_SEEN_MAX]; int n = 0, i, dup; #ifdef HAVE_IPV6 lft_addr_t seen6[LFT_ECMP_SEEN_MAX]; #endif SLIST_FOREACH(tp, &(sess->hop_info[hopno].packets), next_by_hop) { if (!tp->recv.tv_sec) continue; dup = 0; #ifdef HAVE_IPV6 if (sess->af == AF_INET6) { for (i = 0; i < n; i++) if (lft_addr_eq(&seen6[i], &tp->hopaddr6)) { dup = 1; break; } if (!dup && n < LFT_ECMP_SEEN_MAX) seen6[n] = tp->hopaddr6; } else #endif { for (i = 0; i < n; i++) if (seen[i].s_addr == tp->hopaddr.s_addr) { dup = 1; break; } if (!dup && n < LFT_ECMP_SEEN_MAX) seen[n] = tp->hopaddr; } if (!dup && n < LFT_ECMP_SEEN_MAX) n++; } return n; } /* Record/query a member address already emitted at the current hop (dedup for * the interleaved ECMP replies). Returns 1 if already shown. */ static int lft_ecmp_member_seen(lft_session_params *sess, const struct trace_packet_info_s *tp) { int i; #ifdef HAVE_IPV6 if (sess->af == AF_INET6) { for (i = 0; i < sess->_ecmp_seen_n; i++) if (lft_addr_eq(&sess->_ecmp_seen6[i], &tp->hopaddr6)) return 1; if (sess->_ecmp_seen_n < LFT_ECMP_SEEN_MAX) sess->_ecmp_seen6[sess->_ecmp_seen_n++] = tp->hopaddr6; return 0; } #endif for (i = 0; i < sess->_ecmp_seen_n; i++) if (sess->_ecmp_seen[i].s_addr == tp->hopaddr.s_addr) return 1; if (sess->_ecmp_seen_n < LFT_ECMP_SEEN_MAX) sess->_ecmp_seen[sess->_ecmp_seen_n++] = tp->hopaddr; return 0; } 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 && !HOP_CHANGED(sess, ehip->last_hop, ehip->tp->hopaddr, &ehip->tp->last_hop6, &ehip->tp->hopaddr6)) return; if(ehip->tp->recv.tv_sec) { if (HOP_CHANGED(sess, ehip->last_hop, ehip->tp->hopaddr, &ehip->tp->last_hop6, &ehip->tp->hopaddr6)) { /* ECMP multi-member layout: dedup the interleaved replies, then emit * a per-member newline + TTL-or-bar prefix (style-0 report only). */ if (global_output_style == 0 && sess->_ecmp_n > 1) { if (lft_ecmp_member_seen(sess, ehip->tp)) return; /* this member already shown at the current hop */ if (sess->_ecmp_idx > 0) putchar('\n'); if (sess->_ecmp_idx == sess->_ecmp_center) printf("%2d ", sess->_rtt_stat_hopno + 1); else /* bar in the TTL column ties the members to one hop */ fputs(strcmp(lft_pm(), "~") ? " \xe2\x94\x82 " : " | ", stdout); sess->_ecmp_idx++; } /* Suppress repeated ASN/netname for a router already annotated at an * earlier hop (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++) { #ifdef HAVE_IPV6 if (sess->af == AF_INET6) { if (lft_addr_eq(&sess->shown_asn_ips6[_j], &ehip->tp->hopaddr6)) { _hop_seen = 1; break; } continue; } #endif 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_lookup_af(sess, ehip->tp, _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) { char _esc[512]; printf(" net=\"%s\"", lft_xml_escape(ehip->netname, _esc, sizeof _esc)); } else { /* Check IANA first (same reason as ASN block above) */ char _irfc[16], _iname[64]; if (lft_iana_lookup_af(sess, ehip->tp, _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) { #ifdef HAVE_IPV6 if (sess->af == AF_INET6) sess->shown_asn_ips6[sess->n_shown_asn_ips] = ehip->tp->hopaddr6; else #endif sess->shown_asn_ips[sess->n_shown_asn_ips] = ehip->tp->hopaddr; sess->n_shown_asn_ips++; } } /* !_hop_seen */ /* ICMP message label. For a router already seen at a prior hop, * type-3 codes (port/host/net unreachable) mean it is filtering the * probe rather than being the destination — label it [filtered]. * For a new router, show the specific ICMP code. */ if (ehip->tp->icmp_type < -2 || ehip->tp->icmp_type > 15) { 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 */ printf(global_output_style == 1 ? "replied" : " 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_hop_host (sess, ehip->tp); 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 (single-member hop * stats print at EVT_RPT_PACKET_LIST_END); multi-member hops print * per-member stats below so -m, -j, and --ecmp render identically. */ if (sess->rtt_stats && global_output_style == 0 && sess->_ecmp_n > 1 && ind == ' ') { hop_rtt_stats_t ms = compute_member_rtt_stats(sess, sess->_rtt_stat_hopno, ehip->tp); if (ms.n > 1) printf (" %d/%d/%d %s%dms", (int)round(ms.rtt_min), (int)round(ms.rtt_avg), (int)round(ms.rtt_max), lft_pm(), (int)round(ms.rtt_stddev)); else printf (" %.1fms", timediff_ms(ehip->tp->sent, ehip->tp->recv)); } else 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 { /* Text output: one RTT figure per member. On the member's * first line (ind==' ') print min/avg/max/stddev (integer ms, * as in watch/-j) when it has >1 sample, else the single RTT. * The '/'-appended duplicates fold into that summary. */ if (ind == ' ') { hop_rtt_stats_t ms = compute_member_rtt_stats(sess, sess->_rtt_stat_hopno, ehip->tp); if (ms.n > 1) printf (" %d/%d/%d %s%dms", (int)round(ms.rtt_min), (int)round(ms.rtt_avg), (int)round(ms.rtt_max), lft_pm(), (int)round(ms.rtt_stddev)); else printf (" %.1fms", 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) { lft_bewrite(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) { lft_bewrite(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 */ lft_bewrite(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; } lft_bewrite(STDOUT_FILENO, buf, (size_t)n); } sigprocmask(SIG_UNBLOCK, &blk, &old); } #endif /* !WIN32 */ /*---------------------------------------------------------------------------*/ /* Text-mode RCVD TCP display: the raw header is seen in EVT_RCVD_TCP but the * matched probe (SRC / hop / RTT) is only known in EVT_RECV_PACKET, so the * header fields are stashed here and the line prints once, in canonical * order, when the match outcome is known. Anomaly/skip/duplicate handlers * flush the stash so unmatched segments stay visible. */ static struct { int valid; unsigned char flags; unsigned int seq, ack; unsigned short sport, dport; } g_rcvd_tcp_stash; static void print_tcp_flag_list(unsigned char f) { int flcnt = 0; printf("("); if (f & TH_SYN) printf("%sSYN", flcnt++ ? " " : ""); if (f & TH_ACK) printf("%sACK", flcnt++ ? " " : ""); if (f & TH_FIN) printf("%sFIN", flcnt++ ? " " : ""); if (f & TH_RST) printf("%sRST", flcnt++ ? " " : ""); printf(")"); } /* Flush an unmatched stashed RCVD TCP header (no SRC/RTT known); leaves the * line open so the caller's notice completes it. */ static void flush_rcvd_tcp_stash(void) { if (!g_rcvd_tcp_stash.valid) return; printf("RCVD TCP PORTS=%u->%u FLAGS=%#x ", g_rcvd_tcp_stash.sport, g_rcvd_tcp_stash.dport, g_rcvd_tcp_stash.flags); print_tcp_flag_list(g_rcvd_tcp_stash.flags); printf(" ACK=%u SEQ=%u ", g_rcvd_tcp_stash.ack, g_rcvd_tcp_stash.seq); g_rcvd_tcp_stash.valid = 0; } 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("\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_local_host (sess); 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(" sport=\"%u\" dport=\"%u\" seq=\"%u\" xflags=\"%#x\" flags=\"", spparam->sport, spparam->dport, 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("\""); /* -B: TCP option block on SYN/SYN-ACK probes (parity with -VV text) */ if (sess->noisy >= 2 && sess->tcp_options && sess->protocol == 0 && (spparam->flags & TH_SYN)) printf(" options=\"MSS=1460;SACK;TS=%u/0;WS=7\"", (unsigned)sess->now.tv_sec); printf(" />\n"); } else if(sess->protocol==1) { printf(" sport=\"%u\" dport=\"%u\" ipid=\"%u\" />\n", spparam->sport, spparam->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 PORTS=%u->%u FLAGS=%#x ", spparam->nhop+1, spparam->sport, spparam->dport, spparam->flags); print_tcp_flag_list(spparam->flags); /* -B: show the client TCP option block on SYN/SYN-ACK probes */ if (sess->noisy >= 2 && sess->tcp_options && sess->protocol == 0 && (spparam->flags & TH_SYN)) printf(" OPTS ( MSS 1460, SACK, TS %u/0, WS 7 )", (unsigned)sess->now.tv_sec); printf(" SEQ=%u", spparam->tseq); if (sess->pmtud && sess->af != AF_INET6) { #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 if (sess->pmtud) { printf(" [PMTUD%s]\n", sess->pmtud_verifying ? " verify" : ""); } else { printf("\n"); } } else if(sess->protocol==1) { if (sess->pmtud && sess->af != AF_INET6) { #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 PORTS=%u->%u IPID=%u LEN=%d [PMTUD%s]\n", spparam->nhop+1, spparam->sport, spparam->dport, (spparam->tseq & 0xFFFF) ? (unsigned)(spparam->tseq & 0xFFFF) : 0xFFFFu, pkt_len, sess->pmtud_verifying ? " verify" : ""); } else { printf("SENT UDP TTL=%d PORTS=%u->%u IPID=%u\n", spparam->nhop+1, spparam->sport, spparam->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 SEQ=%u\n", spparam->nhop+1, (unsigned)spparam->tseq); } } 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\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_remote_host (sess); 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 if (sess->_ecmp_n > 1) printf("\n"); /* multi-member: per-member stats already shown */ else printf(" %d/%d/%d %s%dms\n", (int)round(rs.rtt_min), (int)round(rs.rtt_avg), (int)round(rs.rtt_max), lft_pm(), (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 ("\n"); /* per-member "ms" is emitted with each RTT */ } } break; case EVT_RPT_HOP_INFO_START: sess->_rtt_stat_hopno = *((const int *)params); /* track for rtt_stats */ sess->_ecmp_n = 0; sess->_ecmp_idx = 0; sess->_ecmp_seen_n = 0; if(global_output_style<2) { if(global_output_style) printf (" index=\"%2d\"", (*((const int *)params)) + 1); else { /* Multi-member (ECMP) hops print one responder per line: the TTL * on the first member, a bar in the TTL column on the rest; the * per-member prefix is emitted in the packet handler. Single-member * hops stay inline as before. Applies in -j stats mode too, so -m, * -j, and --ecmp render multi-member hops identically. */ sess->_ecmp_n = lft_hop_member_count(sess, sess->_rtt_stat_hopno); sess->_ecmp_center = 0; if (sess->_ecmp_n <= 1) 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); if(global_output_style==5) SVGOutput(sess); if(global_output_style==6) MermaidOutput(sess); if(global_output_style==7) JSONOutput(sess); if(global_output_style==8) GeoJSONOutput(sess); if(global_output_style==9) KMLOutput(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 { flush_rcvd_tcp_stash(); 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 { flush_rcvd_tcp_stash(); 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 { flush_rcvd_tcp_stash(); 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 { flush_rcvd_tcp_stash(); 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 { flush_rcvd_tcp_stash(); 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; { double _rtt = timediff_ms(erpparam->tp->sent, sess->now); 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 = lft_rcvd_icmp_type(sess, erpparam->tp->icmp_type); int _rc = (erpparam->tp->icmp_type == -2) ? 0 : erpparam->tp->icmp_type; printf (" icmptype=\"%d\" icmpcode=\"%d\" icmpname=\"%s\"" " src=\"%s\" pttl=\"%d\" rtt=\"%.1f\" pseq=\"%u\" />\n", _rt, _rc, lft_rcvd_icmp_name(sess, erpparam->tp->icmp_type), lft_rcvd_src(sess, erpparam), erpparam->tp->hopno+1, _rtt, erpparam->seq); } else { printf (" src=\"%s\" pttl=\"%d\" rtt=\"%.1f\" pseq=\"%u\" />\n", lft_rcvd_src(sess, erpparam), erpparam->tp->hopno+1, _rtt, erpparam->seq); } } else { /* Routing (canonical field order: descriptors, SRC, PTTL, * RTT, then flags/seqs last): * 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/RTT/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) compose the whole * line from the header stashed by 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 = lft_rcvd_icmp_type(sess, erpparam->tp->icmp_type); int _rc = (erpparam->tp->icmp_type == -2) ? 0 : erpparam->tp->icmp_type; g_rcvd_tcp_stash.valid = 0; /* not a direct TCP reply */ printf ("RCVD ICMP TYPE=%d CODE=%d NAME='%s' SRC=%s PTTL=%d RTT=%.1fms PSEQ=%u\n", _rt, _rc, lft_rcvd_icmp_name(sess, erpparam->tp->icmp_type), lft_rcvd_src(sess, erpparam), erpparam->tp->hopno+1, _rtt, erpparam->seq); } else if (!_is_icmp_engine && g_rcvd_tcp_stash.valid) { printf ("RCVD TCP PORTS=%u->%u SRC=%s PTTL=%d RTT=%.1fms FLAGS=%#x ", g_rcvd_tcp_stash.sport, g_rcvd_tcp_stash.dport, lft_rcvd_src(sess, erpparam), erpparam->tp->hopno+1, _rtt, g_rcvd_tcp_stash.flags); print_tcp_flag_list(g_rcvd_tcp_stash.flags); printf (" ACK=%u SEQ=%u PSEQ=%u\n", g_rcvd_tcp_stash.ack, g_rcvd_tcp_stash.seq, erpparam->seq); g_rcvd_tcp_stash.valid = 0; } else { /* ICMP-engine tail, or TCP anomaly continuation */ printf ("SRC=%s PTTL=%d RTT=%.1fms PSEQ=%u\n", lft_rcvd_src(sess, erpparam), erpparam->tp->hopno+1, _rtt, 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 PORTS=%d->%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 PORTS=%d->%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 { /* Stash; EVT_RECV_PACKET (or an anomaly/skip handler) * prints the composed line in canonical order. */ g_rcvd_tcp_stash.valid = 1; g_rcvd_tcp_stash.flags = tcp->th_flags; g_rcvd_tcp_stash.seq = ntohl (tcp->th_seq); g_rcvd_tcp_stash.ack = ntohl (tcp->th_ack); g_rcvd_tcp_stash.sport = ntohs (tcp->th_sport); g_rcvd_tcp_stash.dport = ntohs (tcp->th_dport); } } 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\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_remote_host (sess); printf(" dport=\"%d\" />\n",sess->dport); } else if(sess->noisy) { printf("TCP open checking started for "); print_remote_host (sess); printf(" port %d.\n",sess->dport); } } break; case EVT_OCHECK_OPEN: if(global_output_style<2) { if(global_output_style) { print_remote_host (sess); printf(" dport=\"%d\" />\n",sess->dport); } else if(sess->noisy) { printf("TCP port %d is open on ",sess->dport); print_remote_host (sess); 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->af = AF_INET; /* IPv4 by default; -6 or a v6 target flips it */ sess->force_af = AF_UNSPEC; /* no -4/-6 override until requested */ 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; /* -z off by default (default = per-hop sport) */ sess->user_sport = 0; /* -s not given 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->ecmp = 0; /* --ecmp ECMP member exposure off by default */ sess->ecmp_probes = 0; /* per-hop ECMP sampling count (set by --ecmp) */ 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->show_seams=0; /* seams hidden by default; --show-seams or -y enables */ sess->color_mode=0; /* -o colour auto-detect by default; --color/--no-color override */ sess->json_pretty=0; /* --json is compact; --json-pretty indents */ snprintf(sess->geo_color,sizeof sess->geo_color,"17b890"); /* --geo-color default: teal */ sess->geo_width=3; /* --geo-width default */ sess->geo_arc=1.0; /* --geo-arc default: 3-D arches in KML */ sess->src_geo_ok=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; /* Do NOT tighten scatter_ms here. A tight clamp raises the per-source * send rate of narrowed cycles enough to trip ICMP rate-limit policers * on backbone routers, which shows as phantom far-hop "loss"; the engine * already adapts scatter_ms to observed RTTs during the trace. The small * per-cycle time saved is noise at watch intervals. */ } 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) { /* -s is authoritative: fixed port for every hop, overriding both the * per-hop default and -z (whichever order the flags were given). */ sess->sport = port; sess->user_sport = 1; 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; } } /*---------------------------------------------------------------------------*/ /* -B sequence realism. The default TCP engine issues probe ISNs as * seq_start + probe_index, so a burst of SYNs to one target carries adjacent, * monotonic sequence numbers differing by one. A stateful middlebox can read * that as retransmissions of a single flow rather than independent client * connections and rate-limit or drop them. This scatters the ISNs across the * 32-bit space so they look like unrelated client connections. * * The map is an invertible 32-bit finalizer (odd-constant multiplies and * high-bit xorshifts are each bijective mod 2^32), so it is a bijection: * distinct inputs always yield distinct outputs. Reply correlation is * therefore preserved without change \(em LFT stores the emitted value in * pinfo->seq and matches replies by exact comparison against the sequence * echoed in the ICMP quote (or target ACK-1), so any unique per-probe value * works. Applied only to the full 32-bit TCP sequence of the default engine; * the 16-bit-truncated ICMP id/seq and UDP IP-ID probe tokens are never * routed through here, so their per-probe uniqueness is untouched. */ static unsigned int lft_scramble_isn (unsigned int x) { x ^= x >> 16; x *= 0x7feb352du; x ^= x >> 15; x *= 0x846ca68bu; x ^= x >> 16; return x; } /*---------------------------------------------------------------------------*/ #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); } /*---------------------------------------------------------------------------*/ /* Options-aware TCP checksum. opts/optlen are the TCP option bytes that sit * between the 20-byte header and any payload (optlen == 0 / opts == NULL for * the classic no-options segment). The TCP length in the pseudo-header and the * summed region both include the options. */ u_int32_t tcp_cksum_opt (struct ip *ip, struct tcphdr *tcp, const char *opts, int optlen, const char * payload, int payload_len) { u_int32_t sum = 0; register int count; u_short *sp; int seglen = (int)sizeof(struct tcphdr) + optlen + payload_len; u_short *temptcp = (u_short *)malloc(seglen + 4); u_int32_t tempip; memset(temptcp, 0, seglen + 4); memcpy(temptcp, (char *)tcp, sizeof(struct tcphdr)); if (optlen > 0 && opts) memcpy((u_char *)temptcp + sizeof(struct tcphdr), opts, optlen); memcpy((u_char *)temptcp + sizeof(struct tcphdr) + optlen, payload, payload_len); sp = temptcp; for(count = (seglen + seglen % 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 (seglen); while (sum > 0xffff) sum = (sum & 0xffff) + (sum >> 16); sum = ~sum & 0xffff; return (sum); } u_int32_t tcp_cksum (struct ip *ip, struct tcphdr *tcp, const char * payload, int payload_len) { return tcp_cksum_opt(ip, tcp, NULL, 0, payload, payload_len); } /* Build the -B/--tcp-options realistic client SYN option block. Emitted only * for SYN / SYN-ACK probes of the default (protocol 0) TCP engine; other * protocols, non-SYN probes, and the feature-off case return 0. The layout is * a common Linux client SYN (MSS, SACK-permitted, Timestamps, NOP, Window * scale), 20 bytes, 4-byte aligned so th_off stays integral. */ int lft_build_tcp_options (lft_session_params *sess, unsigned char flags, unsigned char *buf, int bufsz) { unsigned char *p = buf; unsigned int tsval; if (!sess->tcp_options) return 0; /* feature off */ if (sess->protocol != 0) return 0; /* default/adaptive TCP engine only */ if (!(flags & TH_SYN)) return 0; /* SYN and SYN-ACK probes only */ if (bufsz < LFT_TCP_OPT_LEN) return 0; /* safety */ *p++ = 2; *p++ = 4; *p++ = 0x05; *p++ = 0xb4; /* MSS = 1460 */ *p++ = 4; *p++ = 2; /* SACK permitted */ *p++ = 8; *p++ = 10; /* Timestamps */ tsval = (unsigned int)sess->now.tv_sec; /* monotonic-ish TSval */ *p++ = (tsval >> 24) & 0xff; *p++ = (tsval >> 16) & 0xff; *p++ = (tsval >> 8) & 0xff; *p++ = tsval & 0xff; *p++ = 0; *p++ = 0; *p++ = 0; *p++ = 0; /* TSecr = 0 on SYN */ *p++ = 1; /* NOP (align) */ *p++ = 3; *p++ = 3; *p++ = 7; /* Window scale = 7 */ return (int)(p - buf); /* == LFT_TCP_OPT_LEN (20) */ } /*---------------------------------------------------------------------------*/ 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; /* Run once. */ 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; #ifdef HAVE_IPV6 /* IPv6 egress source: UDP-connect to the target, read back the source the * kernel would use into sess->local_ss, return the interface name. * Mirrors the v4 logic below. */ if (sess->af == AF_INET6) { int sd6; struct sockaddr_in6 s6; socklen_t sl6 = sizeof s6; lft_addr_t la; memset(&s6, 0, sizeof s6); s6.sin6_family = AF_INET6; s6.sin6_addr = ((struct sockaddr_in6 *)(void *)&sess->remote_ss)->sin6_addr; s6.sin6_port = htons(p1); if ((sd6 = socket(AF_INET6, SOCK_DGRAM, 0)) == -1) { LFTErrHandler(sess, WRN_GETIFFORREMOTE_SOCKET, NULL); return NULL; } if (connect(sd6, (const struct sockaddr *)(const void *)&s6, sizeof s6) == -1) { LFTErrHandler(sess, WRN_GETIFFORREMOTE_CONNECT, NULL); close(sd6); return NULL; } if (getsockname(sd6, (struct sockaddr *)(void *)&s6, &sl6) == -1) { LFTErrHandler(sess, WRN_GETIFFORREMOTE_SOCKNAME, NULL); close(sd6); return NULL; } close(sd6); memcpy(&sess->local_ss, &s6, sizeof s6); lft_addr_from_ss(&sess->local_ss, &la); return lft_getifname_af(&la); } #endif 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; } } /* v4 target resolution; skipped for v6 (af/remote_ss set in LFTExecute; * get_address is AF_INET-only). */ #ifdef HAVE_IPV6 if (sess->af != AF_INET6) #endif 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 (sess->tcp_options && sess->protocol == 0) hdr_size += LFT_TCP_OPT_LEN; /* -B SYN options ride in the header */ 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 */ } #ifdef HAVE_IPV6 /* * ICMPv6 Packet Too Big (Type 2) handler. Steps the path MTU down and * resizes the probe payload. The v6 minimum link MTU is 1280 (RFC 8200), * so the path MTU is never taken below that. */ static void lft_pmtud_handle_ptb6(lft_session_params *sess, uint32_t mtu, unsigned int seq) { struct trace_packet_info_s *tp = NULL; uint16_t new_mtu; int nhop, hdr_size; 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; if (mtu >= 1280 && mtu < sess->pmtud_current_mtu) new_mtu = (uint16_t)mtu; else new_mtu = lft_pmtud_next_plateau(sess->pmtud_current_mtu); if (new_mtu >= sess->pmtud_current_mtu || new_mtu < 1280) return; /* no real reduction, or below v6 floor */ sess->hop_info[nhop].path_mtu = new_mtu; sess->pmtud_current_mtu = new_mtu; sess->pmtud_ptb_mtu = new_mtu; hdr_size = (int)(sizeof(struct ip6_hdr) + sizeof(struct tcphdr)); if (sess->tcp_options && sess->protocol == 0) hdr_size += LFT_TCP_OPT_LEN; if ((int)new_mtu > hdr_size) sess->trace_packet.payload_len = (int)new_mtu - hdr_size; } #endif /* HAVE_IPV6 */ /* * 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 #ifdef HAVE_IPV6 /* IPv6: reopen the send socket in the v6 domain. On Linux, IPV6_HDRINCL * lets us supply the entire packet (mirroring v4 IP_HDRINCL). The v4 * socket setup above is left untouched; for a v6 trace we simply swap the * freshly-created v4 raw socket for a v6 one. */ if (sess->af == AF_INET6) { /* IPv6 send socket: raw + IPV6_HDRINCL, the v6 analogue of the v4 raw + * IP_HDRINCL path above. Swaps the v4 socket just created. All L4 * types (TCP/UDP/ICMPv6) are hand-built and sent via IPPROTO_RAW, so the * full packet (v6 header + L4 + checksum) is ours. */ int on6 = 1; if (sess->send_sock > 0) close(sess->send_sock); sess->send_sock = socket(PF_INET6, SOCK_RAW, IPPROTO_RAW); if (sess->send_sock < 0) { LFTErrHandler(sess, ERR_RAW_SOCKET, NULL); return; } #ifdef HAVE_IPV6_HDRINCL if (setsockopt(sess->send_sock, IPPROTO_IPV6, IPV6_HDRINCL, (char *)&on6, sizeof(on6)) < 0) { LFTErrHandler(sess, ERR_IP_HDRINCL, NULL); return; } #else (void)on6; /* BSD/macOS sendmsg+IPV6_PKTINFO path deferred */ #endif } #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;ptridxicmp_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 { #ifdef HAVE_IPV6 if (sess->af == AF_INET6) sess->userlen -= (sizeof (struct ip6_hdr) + sizeof(struct tcphdr)); else #endif if (sess->protocol==1) sess->userlen -= (sizeof (struct ip) + sizeof(struct udphdr)); else sess->userlen -= (sizeof (struct ip) + sizeof(struct tcphdr)); } /* -B SYN options consume header room; shrink the payload so the * total on-wire length still matches the requested/MTU size. */ if (sess->tcp_options && sess->protocol == 0 && sess->userlen > LFT_TCP_OPT_LEN) sess->userlen -= LFT_TCP_OPT_LEN; 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); /* Per-hop source port (default): each hop gets a distinct 5-tuple so a * stateful firewall can't rate-limit the trace as one flow. Suppressed by -s * and -z. In watch mode it mixes with the per-cycle rotated base, so each hop * is distinct within a cycle (edge traversal) yet fresh between cycles. * Pseudo-random mix of the session base and hop index, folded into * 49152-65535; stable across a hop's retries. */ static unsigned short lft_perhop_sport (lft_session_params *sess, short nhop) { unsigned int h; if (sess->random_source || sess->user_sport || nhop < 0) return (unsigned short)sess->sport; h = (unsigned int)sess->sport ^ 0x9e3779b9u; h ^= (unsigned int)(nhop + 1) * 2654435761u; /* Vary the port per probe within a hop: * --ecmp: on every probe, so successive probes hash onto different * equal-cost paths and expose load-balanced members. * default: on retries only -- a middlebox that answered the first flow * suppresses same-5-tuple retries as duplicates, so each retry gets a * fresh flow (recovers otherwise-neglected hops); the first probe keeps * the hop-stable port, so single-probe behavior is unchanged. * Adaptive mode (-E) is EXCLUDED from the retry variation: it deliberately * reuses one stable per-hop 5-tuple and varies the TCP flags (SYN->FIN-> * SYN-ACK) to probe stateful firewalls; a fresh port per retry would split * that flag sequence across flows and defeat the firewall heuristic. * (--ecmp still wins if both are set, since the user asked for it.) * Reply correlation is by sequence number, not port, so all are safe. */ if (sess->ecmp || (sess->hop_info[nhop].num_sent > 0 && !sess->adaptive)) h ^= (unsigned int)(sess->hop_info[nhop].num_sent + 1) * 0x85ebca6bu; h ^= h >> 15; h *= 2246822519u; h ^= h >> 13; return (unsigned short)(49152 + (h & 0x3fff)); } #ifdef HAVE_IPV6 /* IPv6 TCP probe: hand-build the v6 header + TCP (+ -B options), checksum the * L4 with lft_cksum6, send via the raw IPV6_HDRINCL socket. v6 analogue of the * TCP path in send_packet; hop limit nhop+1; bookkeeping matches send_packet. */ static unsigned int lft_send_probe6 (lft_session_params *sess, short nhop, unsigned short ttl, unsigned int seq, unsigned char flags) { struct trace_packet_info_s *pinfo = NULL; unsigned char *buf; unsigned char tcpopt[40]; struct ip6_hdr *ip6; struct tcphdr *tcp; lft_addr_t src, dst; struct sockaddr_in6 dest; int optlen = 0, l4len, blen, plen6 = 0; int is_udp = (sess->protocol == 1); int is_icmp6 = (sess->protocol == 2 || sess->protocol == 3); unsigned char nxt = IPPROTO_TCP; unsigned int tseq; unsigned short tttl; /* Optional probe payload (set by open_sockets for -l / PMTUD sizing). */ if (sess->trace_packet.payload && sess->trace_packet.payload_len > 0) plen6 = sess->trace_packet.payload_len; if (!(pinfo = (struct trace_packet_info_s *)malloc(sizeof *pinfo))) { LFTErrHandler(sess, ERR_NOT_ENOUGH_MEM, NULL); return 0; } memset(pinfo, 0, sizeof *pinfo); blen = (int)sizeof(struct ip6_hdr) + (int)sizeof(struct tcphdr) + (int)sizeof tcpopt + plen6; if (!(buf = (unsigned char *)malloc((size_t)blen))) { LFTErrHandler(sess, ERR_NOT_ENOUGH_MEM, NULL); free(pinfo); return 0; } tseq = seq ? seq : new_seq(sess); /* -B sequence realism (see send_packet): scatter the default v6 TCP engine's * adjacent ISNs. Bijective, so exact reply-matching is preserved. v6 * carries the full 32-bit tseq for every protocol, but this stays scoped to * non-adaptive protocol-0 TCP to mirror -B's semantics exactly. */ if (!seq && sess->tcp_options && !sess->adaptive && sess->protocol == 0) tseq = lft_scramble_isn(tseq); tttl = (nhop == -1) ? ttl : (unsigned short)(nhop + 1); sess->ts_last_sent = sess->now; /* SENT display parity with the v4 engine. */ lft_o_probe_sent(); /* spinner: count in-flight probe */ if (sess->noisy > 1) { EvtSentPacketParam espparam; espparam.flags = flags; espparam.nhop = nhop; espparam.tseq = tseq; espparam.tttl = tttl; espparam.dport = is_icmp6 ? 0 : (unsigned short)sess->dport; espparam.sport = is_icmp6 ? 0 : lft_perhop_sport(sess, nhop); LFTEvtHandler(sess, EVT_SENT_PACKET, &espparam); if (sess->exit_state < 0) { free(buf); free(pinfo); return 0; } } lft_addr_from_ss(&sess->local_ss, &src); lft_addr_from_ss(&sess->remote_ss, &dst); memset(buf, 0, (size_t)blen); /* Header-include: we supply the whole v6 packet (v6 header + TCP), as the * v4 path does with IP_HDRINCL. */ ip6 = (struct ip6_hdr *)(void *)buf; if (is_icmp6) { /* ICMPv6 Echo Request; the 32-bit probe seq rides in id(hi16)+seq(lo16), * echoed by the target and quoted in TE/unreach errors for matching. */ struct icmp6_hdr *ic6 = (struct icmp6_hdr *)(void *)(buf + sizeof(struct ip6_hdr)); if (plen6 > 0) memcpy(buf + sizeof(struct ip6_hdr) + sizeof(struct icmp6_hdr), sess->trace_packet.payload, (size_t)plen6); ic6->icmp6_type = ICMP6_ECHO_REQUEST; ic6->icmp6_code = 0; ic6->icmp6_id = htons((unsigned short)(tseq >> 16)); ic6->icmp6_seq = htons((unsigned short)(tseq & 0xffff)); l4len = (int)sizeof(struct icmp6_hdr) + plen6; ic6->icmp6_cksum = 0; ic6->icmp6_cksum = htons(lft_cksum6(&src, &dst, IPPROTO_ICMPV6, ic6, (uint16_t)l4len)); nxt = IPPROTO_ICMPV6; } else if (is_udp) { /* UDP with fixed ports (ECMP-stable, as v4). v6 has no IP ID, so the * 32-bit probe seq is carried in the first 4 payload bytes and recovered * from the ICMPv6 error's inner UDP quote. */ struct udphdr *udp = (struct udphdr *)(void *)(buf + sizeof(struct ip6_hdr)); unsigned char *pl = buf + sizeof(struct ip6_hdr) + sizeof(struct udphdr); int payload = (plen6 < 4) ? 4 : plen6; uint32_t nseq = htonl(tseq); if (plen6 > 0) memcpy(pl, sess->trace_packet.payload, (size_t)plen6); memcpy(pl, &nseq, sizeof nseq); /* seq token in first 4 payload bytes */ /* Per-hop source port: distinct 5-tuple per hop so a stateful v6 edge * can't rate-limit the trace as one flow. Matching is by the payload * seq token, not ports, so varying the source port is safe. */ udp->uh_sport = htons(lft_perhop_sport(sess, nhop)); udp->uh_dport = htons((unsigned short)sess->dport); udp->uh_ulen = htons((uint16_t)(sizeof(struct udphdr) + payload)); l4len = (int)sizeof(struct udphdr) + payload; udp->uh_sum = 0; udp->uh_sum = htons(lft_cksum6(&src, &dst, IPPROTO_UDP, udp, (uint16_t)l4len)); nxt = IPPROTO_UDP; } else { tcp = (struct tcphdr *)(void *)(buf + sizeof(struct ip6_hdr)); tcp->th_sport = htons(lft_perhop_sport(sess, nhop)); tcp->th_dport = htons((unsigned short)sess->dport); tcp->th_seq = htonl(tseq); tcp->th_ack = 0; tcp->th_off = sizeof(struct tcphdr) / 4; tcp->th_flags = flags; tcp->th_win = htons((unsigned short)sess->win_len); tcp->th_urp = 0; optlen = lft_build_tcp_options(sess, flags, tcpopt, sizeof tcpopt); if (optlen > 0) { memcpy(buf + sizeof(struct ip6_hdr) + sizeof(struct tcphdr), tcpopt, optlen); tcp->th_off = (sizeof(struct tcphdr) + optlen) / 4; } if (plen6 > 0) memcpy(buf + sizeof(struct ip6_hdr) + sizeof(struct tcphdr) + optlen, sess->trace_packet.payload, (size_t)plen6); l4len = (int)sizeof(struct tcphdr) + optlen + plen6; tcp->th_sum = 0; tcp->th_sum = htons(lft_cksum6(&src, &dst, IPPROTO_TCP, tcp, (uint16_t)l4len)); nxt = IPPROTO_TCP; } ip6->ip6_flow = 0; ip6->ip6_vfc = 0x60; ip6->ip6_plen = htons((uint16_t)l4len); ip6->ip6_nxt = nxt; ip6->ip6_hlim = (uint8_t)tttl; ip6->ip6_src = src.a.v6; ip6->ip6_dst = dst.a.v6; blen = (int)sizeof(struct ip6_hdr) + l4len; memset(&dest, 0, sizeof dest); dest.sin6_family = AF_INET6; dest.sin6_addr = dst.a.v6; if (sendto(sess->send_sock, buf, (size_t)blen, 0, (const struct sockaddr *)(const void *)&dest, sizeof dest) < 0) { LFTErrHandler(sess, ERR_RAW_TCP_DISABLED, NULL); free(buf); free(pinfo); return 0; } free(buf); pinfo->hopno = nhop; 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; } #endif /* HAVE_IPV6 */ static unsigned int send_packet (lft_session_params * sess, short nhop, unsigned short ttl, unsigned int seq, unsigned char flags) { #ifdef HAVE_IPV6 if (sess->af == AF_INET6) return lft_send_probe6(sess, nhop, ttl, seq, flags); #endif { 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; unsigned char tcpopt[40]; /* -B/--tcp-options SYN option block */ int optlen = 0; /* its length (0 = none emitted) */ 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; /* -B sequence realism: scatter the default TCP engine's adjacent ISNs so * the SYN burst does not look like retransmissions of one flow. Bijective, * so exact reply-matching on the echoed sequence is preserved. Restricted * to freshly generated (seq == 0), non-adaptive, protocol-0 TCP probes; * adaptive already randomizes per probe, and UDP (which reuses tseq's low * 16 bits as the IP ID) is protocol 1, so it is never affected. */ if (seq == 0 && sess->tcp_options && !sess->adaptive && sess->protocol == 0) tseq = lft_scramble_isn(tseq); 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; /* Per-probe ports for the SENT display: UDP keeps a fixed 5-tuple; TCP * source port comes from the same per-hop generator used to build the * header below. */ espparam.dport = (unsigned short)sess->dport; espparam.sport = (sess->protocol == 1) ? (unsigned short)sess->sport : lft_perhop_sport(sess, nhop); 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; } } /* -B/--tcp-options: build the realistic SYN option block now, before the * IP checksum, so the IP total length accounts for the extra bytes. * Returns 0 unless this is a SYN/SYN-ACK probe of the default TCP engine * with the feature enabled, so UDP and non-SYN probes are unaffected. */ optlen = lft_build_tcp_options(sess, flags, tcpopt, sizeof(tcpopt)); { /* Derive the IP total length from what is actually sent. The template * ip_len can already include the option block after a PMTUD resize * (-K sets it to the MTU), so adding optlen again made ip_len exceed the * buffer and BSD raw sockets rejected the probe with EINVAL. */ u_short tot = (u_short)(sizeof(struct ip) + packet->lsrr.ipl_len + (sess->protocol == 1 ? sizeof(struct udphdr) : sizeof(struct tcphdr)) + optlen + packet->payload_len); #if defined(SCREWED_IP_LEN) packet->ip_hdr.ip_len = tot; #else packet->ip_hdr.ip_len = htons(tot); #endif } 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 (lft_perhop_sport(sess, nhop)); } packet->tcp_hdr.th_flags = flags; /* -B/--tcp-options: widen the data offset to cover the option bytes. * The seq stays at TCP bytes 4-8 (ahead of the options), so both the * ack-based endpoint match and the ICMP-quoted-seq intermediate match * are unaffected. */ if (optlen > 0) packet->tcp_hdr.th_off = (sizeof (struct tcphdr) + optlen) / 4; #if defined(SOLARIS_LENGTH_IN_CHECKSUM) packet->tcp_hdr.th_sum = htons (sizeof (struct tcphdr) + optlen) + packet->payload_len; #else packet->tcp_hdr.th_sum = 0; packet->tcp_hdr.th_sum = tcp_cksum_opt(&packet->ip_hdr, &packet->tcp_hdr, (const char *)tcpopt, optlen, 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); if (optlen > 0) { memcpy(bptr, tcpopt, optlen); bptr += optlen; } memcpy(bptr, packet->payload, packet->payload_len); blen = sizeof(struct ip) + packet->lsrr.ipl_len + sizeof(struct tcphdr) + optlen + 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; } /* end v4 body wrapped for the HAVE_IPV6 dispatch above */ } /*---------------------------------------------------------------------------*/ 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++) { #ifdef HAVE_IPV6 if (sess->af == AF_INET6) { if (lft_addr_eq(&sess->shown_asn_ips6[k], &tp->hopaddr6)) { found = 1; break; } continue; } #endif 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; } #ifdef HAVE_IPV6 /* v6 companion: first received v6 hop address at hopno (zeroed if none). */ static lft_addr_t hop_first_rcvd_ip6 (const lft_session_params *sess, int hopno) { const struct trace_packet_info_s *tp; lft_addr_t z; memset(&z, 0, sizeof z); SLIST_FOREACH(tp, &(sess->hop_info[hopno].packets), next_by_hop) { if (tp->recv.tv_sec || tp->recv.tv_usec) return tp->hopaddr6; } return z; } #endif /* 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 for TTL[s] N [through M] */ void print_filtered_run (int start_ttl, int end_ttl, const char *ipstr, 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", ipstr, name, start_ttl); else printf("** [filtered] %s replied %s for TTLs %d through %d\n", ipstr, name, start_ttl, end_ttl); } /*---------------------------------------------------------------------------*/ /* Geo outputs (--geojson/--kml): run the extended pWhoIs bulk lookup for the * per-hop ASN list plus every distinct responder and the source, copy asn/net/ * org back into ipaslist for the normal report path, and push geolocation onto * each responder packet and the session. Shared by the TCP/UDP and ICMP * engines. Returns 0 on success, -1 when the lookup failed or no memory. */ int lft_geo_bulk_lookup(lft_session_params *sess, struct ip_list_array *ipaslist, int maxhop) { struct ext_ip_list_array *xl = calloc(1, sizeof *xl); struct trace_packet_info_s *tp; int gi, ghop, nsrc, nh = ipaslist->numItems; if (!xl) return -1; for (gi = 0; gi < nh; gi++) xl->ipaddr[gi] = ipaslist->ipaddr[gi]; xl->numItems = nh; /* the ASN list holds one responder per hop; geo wants every distinct * responder (ECMP members), appended after them */ for (ghop = sess->ttl_min; ghop <= maxhop; ghop++) { SLIST_FOREACH(tp, &(sess->hop_info[ghop].packets), next_by_hop) { int k, dup = 0; if (!tp->recv.tv_sec) continue; for (k = 0; k < xl->numItems; k++) if (xl->ipaddr[k].s_addr == tp->hopaddr.s_addr) { dup = 1; break; } if (!dup && xl->numItems < 1023) xl->ipaddr[xl->numItems++] = tp->hopaddr; } } nsrc = xl->numItems; xl->ipaddr[nsrc] = sess->local_address; xl->numItems = nsrc + 1; /* + source, last */ strncpy(xl->application, ipaslist->application, sizeof xl->application - 1); if (w_lookup_all_pwhois_bulk_ext(sess->wsess, xl) != 0) { free(xl); return -1; } for (gi = 0; gi < nh; gi++) { ipaslist->asn[gi] = xl->asn[gi]; strncpy(ipaslist->netName[gi], xl->netName[gi], sizeof ipaslist->netName[gi] - 1); strncpy(ipaslist->orgName[gi], xl->orgName[gi], sizeof ipaslist->orgName[gi] - 1); } /* apply by address, not list position */ for (ghop = sess->ttl_min; ghop <= maxhop; ghop++) { SLIST_FOREACH(tp, &(sess->hop_info[ghop].packets), next_by_hop) { if (!tp->recv.tv_sec) continue; for (gi = 0; gi < nsrc; gi++) { if (tp->hopaddr.s_addr != xl->ipaddr[gi].s_addr) continue; if (xl->latitude[gi] != 0.0 || xl->longitude[gi] != 0.0) { tp->geo_ok = 1; tp->geo_lat = xl->latitude[gi]; tp->geo_lon = xl->longitude[gi]; strncpy(tp->geo_city, xl->city[gi], sizeof tp->geo_city - 1); strncpy(tp->geo_region, xl->state[gi], sizeof tp->geo_region - 1); strncpy(tp->geo_country, xl->country[gi], sizeof tp->geo_country - 1); strncpy(tp->geo_cc, xl->country_code[gi], sizeof tp->geo_cc - 1); } break; } } } if (xl->latitude[nsrc] != 0.0 || xl->longitude[nsrc] != 0.0) { sess->src_geo_ok = 1; sess->src_geo_lat = xl->latitude[nsrc]; sess->src_geo_lon = xl->longitude[nsrc]; strncpy(sess->src_geo_city, xl->city[nsrc], sizeof sess->src_geo_city - 1); strncpy(sess->src_geo_region, xl->state[nsrc], sizeof sess->src_geo_region - 1); strncpy(sess->src_geo_country, xl->country[nsrc], sizeof sess->src_geo_country - 1); strncpy(sess->src_geo_cc, xl->country_code[nsrc], sizeof sess->src_geo_cc - 1); } free(xl); return 0; } 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;*/ /* appname + ' ' + version + NUL. (The old expression mis-parsed due to * operator precedence and under-allocated by one byte, overflowing on the * final strcat below -- caught by _FORTIFY_SOURCE on modern glibc.) */ char *myApp = (char *)malloc(strlen(appname) + 1 + strlen(version) + 1); 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; #ifdef HAVE_IPV6 lft_addr_t asseam_hopaddr6, prevasn_hopaddr6; /* v6 seam companions (AS-method) */ #endif 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 #ifdef HAVE_IPV6 && sess->af != AF_INET6 /* v6 resolves per-hop in the report walk */ #endif ) { /* 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 is malloc'd to exactly strlen(appname)+1+strlen(version)+1, * so this cannot truncate; the compiler can't correlate the * size expression with the format inputs, hence the guard. */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-truncation" snprintf(myApp, strlen(appname) + 1 + strlen(version) + 1, "%s %s", appname, version); #pragma GCC diagnostic pop strncpy((*ipaslist).application,myApp,511); } if (outputStyleIsGeoJSON() || outputStyleIsKML()) { if (lft_geo_bulk_lookup(sess, ipaslist, maxhop) != 0) if (sess->noisy) LFTErrHandler(sess, WRN_NS_LOOKUP_FAILED, NULL); } else 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; #ifdef HAVE_IPV6 lft_addr_t last_hop6; memset(&last_hop6, 0, sizeof last_hop6); #endif 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(HOP_CHANGED(sess, last_hop, tp->hopaddr, &last_hop6, &tp->hopaddr6)) { /* Network-method seam is IPv4 class-B arithmetic (v4 only). */ #ifdef HAVE_IPV6 if (sess->af != AF_INET6) #endif { 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) { #ifdef HAVE_IPV6 if (sess->af == AF_INET6) { /* Per-IP AS lookup, only when seam detection needs it * (the main report walk sets tp->asnumber for display). */ if (sess->show_seams) { char _v6s[INET6_ADDRSTRLEN]; lft_ntop(&tp->hopaddr6, _v6s, sizeof _v6s); if (sess->use_cymru) tp->asnumber = w_lookup_as_cymru(sess->wsess, _v6s); else if (sess->use_ris) tp->asnumber = w_lookup_as_riswhois(sess->wsess, _v6s); else if (sess->use_radb) tp->asnumber = w_lookup_as(sess->wsess, _v6s); else tp->asnumber = w_lookup_as_pwhois(sess->wsess, _v6s); } } else #endif 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; #ifdef HAVE_IPV6 prevasn_hopaddr6=tp->hopaddr6; #endif } } else { if(tp->asnumber) { if(tp->asnumber!=prevasn) { asseam_hopno=prevasn_hopno; asseam_hopaddr=prevasn_hopaddr; #ifdef HAVE_IPV6 asseam_hopaddr6=prevasn_hopaddr6; #endif } prevasn=tp->asnumber; prevasn_hopno=hopno; prevasn_hopaddr=tp->hopaddr; #ifdef HAVE_IPV6 prevasn_hopaddr6=tp->hopaddr6; #endif } } } last_hop=tp->hopaddr; #ifdef HAVE_IPV6 last_hop6=tp->hopaddr6; #endif } } } 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; #ifdef HAVE_IPV6 lft_addr_t filt_run_ip6; memset(&filt_run_ip6, 0, sizeof filt_run_ip6); #endif for (hopno = sess->ttl_min; hopno <= maxhop; hopno++) { struct in_addr last_hop; #ifdef HAVE_IPV6 lft_addr_t last_hop6; #endif /* 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) { { char _frbuf[INET6_ADDRSTRLEN]; const char *_frip; #ifdef HAVE_IPV6 if (sess->af == AF_INET6) _frip = lft_ntop_fmt(&filt_run_ip6, _frbuf, sizeof _frbuf, sess->addr_expand); else #endif _frip = inet_ntoa(filt_run_ip); print_filtered_run(filt_run_start, filt_run_end, _frip, 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); #ifdef HAVE_IPV6 filt_run_ip6 = hop_first_rcvd_ip6(sess, hopno); #endif 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; #ifdef HAVE_IPV6 memset(&last_hop6, 0, sizeof(last_hop6)); #endif 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 (HOP_CHANGED(sess, last_hop, tp->hopaddr, &last_hop6, &tp->hopaddr6)) { ehip.asnumber = 0; /* init/clear the ASN */ #ifdef HAVE_IPV6 if (sess->af == AF_INET6) { /* v6 uses per-IP string lookups (the bulk path is v4-only). */ char _v6s[INET6_ADDRSTRLEN]; lft_ntop(&tp->hopaddr6, _v6s, sizeof _v6s); if (sess->do_aslookup) { if (sess->use_cymru) ehip.asnumber = w_lookup_as_cymru(sess->wsess, _v6s); else if (sess->use_ris) ehip.asnumber = w_lookup_as_riswhois(sess->wsess, _v6s); else if (sess->use_radb) ehip.asnumber = w_lookup_as(sess->wsess, _v6s); else ehip.asnumber = w_lookup_as_pwhois(sess->wsess, _v6s); } tp->asnumber = ehip.asnumber; ehip.netname = NULL; if (sess->do_netlookup) ehip.netname = w_lookup_netname(sess->wsess, _v6s); if (ehip.netname) strncpy(tp->netname, ehip.netname, 511); else tp->netname[0] = 0; tp->orgname[0] = 0; if ((sess->do_aslookup || sess->do_netlookup) && !sess->use_cymru && !sess->use_radb) { char *_org = w_lookup_orgname(sess->wsess, _v6s); if (_org) strncpy(tp->orgname, _org, sizeof(tp->orgname) - 1); } } else #endif { 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; #ifdef HAVE_IPV6 tp->last_hop6 = last_hop6; last_hop6 = tp->hopaddr6; #endif } 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->show_seams && SEAM_HIT(sess, hopno, asseam_hopno, tp, asseam_hopaddr, asseam_hopaddr6)) { isseam=1; ehip.is_asseam=1; } if(sess->show_seams && 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(); { #ifdef HAVE_IPV6 if (sess->af == AF_INET6) { lft_ntop(&tp->hopaddr6, hostname, sizeof hostname); subsess->force_af = AF_INET6; } else #endif 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) { char _frbuf[INET6_ADDRSTRLEN]; const char *_frip; #ifdef HAVE_IPV6 if (sess->af == AF_INET6) _frip = lft_ntop_fmt(&filt_run_ip6, _frbuf, sizeof _frbuf, sess->addr_expand); else #endif _frip = inet_ntoa(filt_run_ip); print_filtered_run(filt_run_start, filt_run_end, _frip, 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 */ /* The ICMP blackhole-verify sender (icmp_pmtud_send_verify_hop) and its * receive path (pcap_icmp_process_packet) are IPv4-only: they build a v4 * ICMP datagram and rely on the ICMP engine's static callback handlers. * A v6 trace runs the generic engine (process_packet6) and never sets * those handlers, so invoking the verify path would call through a NULL * pointer. v6 ICMP PMTUD blackhole-verification is not implemented, so * skip it — the core v6 trace and TCP/UDP v6 PMTUD verify are unaffected. */ if ((sess->protocol == 2 || sess->protocol == 3) && sess->af == AF_INET6) return; 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 (sess->tcp_options && sess->protocol == 0) hdr_size += LFT_TCP_OPT_LEN; /* -B SYN options ride in the header */ 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; } if (nhop > LFT_GIVEUP_NEARHOP_NHOP) no_reply++; /* exempt near hops (see lft_lib.h) */ } } } 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++) { /* --ecmp: keep sampling flows from a hop that HAS replied, to map * its ECMP members. Scoped to replied hops and gated by * ecmp_probes (not retry_max), so a silent hop's neglected marking * is unchanged — it is never forced through ecmp_probes probes. */ if (sess->ecmp && sess->hop_info[nhop].ts_last_recv.tv_sec && sess->hop_info[nhop].num_sent < sess->ecmp_probes) { send_hop(sess, nhop); return 0; } 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) && (ipaddr.s_addr == sess->remote_address.s_addr)) 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) && (ipaddr.s_addr == sess->remote_address.s_addr)) 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); vsnprintf(buf, sizeof buf, templ, ap); va_end (ap); LFTEvtHandler(sess, EVT_DBG_LOG_MESSAGE, buf); } /*---------------------------------------------------------------------------*/ #ifdef HAVE_IPV6 /* Match a v6 reply to its probe and record the hop. Mirrors recv_packet: * exact-seq match (plus seq+1 for a target ACK), dedup, hop_info/endpoint, * completion. Hop address is stored in tp->hopaddr6. */ static void recv_packet6 (lft_session_params *sess, unsigned int seq, const lft_addr_t *hop, int icmp_type, const struct pcap_pkthdr *hdr) { double ms; struct trace_packet_info_s *tp = NULL; #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; #endif SLIST_FOREACH(tp, &(sess->trace_packets), next) if (tp->seq == seq) break; if (tp == NULL && icmp_type == -1) SLIST_FOREACH(tp, &(sess->trace_packets), next) if (tp->seq == seq + 1) break; /* target ACKed seq+1 */ if (!tp) { if (!sess->nostatus) LFTEvtHandler(sess, EVT_PROGRESS_SKIP_PACKET, NULL); return; } if (tp->recv.tv_sec) { if (!sess->nostatus) LFTEvtHandler(sess, EVT_PROGRESS_DUPLICATE, NULL); return; } tp->icmp_type = icmp_type; tp->hopaddr6 = *hop; /* set before EVT_RECV_PACKET so the verbose line has the v6 SRC */ #ifdef HAVE_CARES lft_ares_fire6(sess, &hop->a.v6); /* queue async PTR for this v6 hop */ #endif if (sess->noisy > 1) { EvtRecvPacketParam erpp; erpp.ipaddr.s_addr = 0; /* v6 hop carried in tp->hopaddr6 */ 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; 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); if (icmp_type == -1) sess->hop_info[tp->hopno].flags |= HF_ENDPOINT; } 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) { 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; } } ms = timediff_ms(tp->sent, tp->recv); sess->scatter_ms = (sess->scatter_ms * (sess->ahead_limit - 1) + ms) / sess->ahead_limit; } /* Classify a captured v6 frame (TCP mode): ICMPv6 time-exceeded/unreach quote * -> inner TCP seq + router source; a TCP SYN-ACK/RST from the target -> its * source + ack-1. The L2 header is already skipped. */ static void process_packet6 (lft_session_params *sess, const u_char *l3, size_t l3len, const struct pcap_pkthdr *hdr) { const struct ip6_hdr *ip6 = (const void *)l3; const struct icmp6_hdr *ic6; const struct ip6_hdr *inner; const struct tcphdr *tcp; const u_char *pkt_end = l3 + l3len; /* end of captured L3 bytes */ lft_addr_t hop; unsigned int seq; int itype; if (l3len < sizeof(struct ip6_hdr)) /* truncated outer header */ return; hop.af = AF_INET6; hop.a.v6 = ip6->ip6_src; if (ip6->ip6_nxt == IPPROTO_ICMPV6) { lft_icmp6_kind k; const u_char *iq; int tgt; ic6 = (const void *)(l3 + sizeof(struct ip6_hdr)); if ((const u_char *)ic6 + sizeof(struct icmp6_hdr) > pkt_end) return; k = lft_icmp6_classify(ic6->icmp6_type, ic6->icmp6_code); tgt = (memcmp(&ip6->ip6_src, &((struct sockaddr_in6 *)(void *)&sess->remote_ss)->sin6_addr, sizeof(struct in6_addr)) == 0); if (k == LFT_ICMP6_TOO_BIG) { /* Packet Too Big: step the path MTU down (v6 floor 1280). */ inner = (const void *)((const u_char *)ic6 + sizeof(struct icmp6_hdr)); if ((const u_char *)inner + sizeof(struct ip6_hdr) > pkt_end) return; if (sess->pmtud && inner->ip6_nxt == IPPROTO_TCP) { tcp = (const void *)((const u_char *)inner + sizeof(struct ip6_hdr)); /* Only th_seq (offset 4-7) is read below. */ if ((const u_char *)tcp + 8 > pkt_end) return; lft_pmtud_handle_ptb6(sess, ntohl(ic6->icmp6_mtu), ntohl(tcp->th_seq)); } return; } /* Echo Reply from the target: our ICMPv6 probe reached it. */ if (k == LFT_ICMP6_ECHO_REPLY) { if (!tgt) return; seq = ((unsigned int)ntohs(ic6->icmp6_id) << 16) | ntohs(ic6->icmp6_seq); sess->target_open++; recv_packet6(sess, seq, &hop, -1, hdr); return; } if (k != LFT_ICMP6_TIME_EXCEEDED && k != LFT_ICMP6_UNREACH) return; inner = (const void *)((const u_char *)ic6 + sizeof(struct icmp6_hdr)); if ((const u_char *)inner + sizeof(struct ip6_hdr) > pkt_end) return; if (memcmp(&inner->ip6_dst, &((struct sockaddr_in6 *)(void *)&sess->remote_ss)->sin6_addr, sizeof(struct in6_addr)) != 0) return; /* quote isn't for our target */ iq = (const u_char *)inner + sizeof(struct ip6_hdr); /* inner L4 */ if (inner->ip6_nxt == IPPROTO_TCP) { /* Only the sequence (offset 4-7) is read; bound to that rather * than the full 20-byte tcphdr so a short quote isn't dropped. */ if (iq + 8 > pkt_end) return; seq = ntohl(((const struct tcphdr *)(const void *)iq)->th_seq); } else if (inner->ip6_nxt == IPPROTO_UDP) { uint32_t nseq; if (iq + sizeof(struct udphdr) + sizeof nseq > pkt_end) return; memcpy(&nseq, iq + sizeof(struct udphdr), sizeof nseq); /* seq token */ seq = ntohl(nseq); } else if (inner->ip6_nxt == IPPROTO_ICMPV6) { const struct icmp6_hdr *iic = (const void *)iq; if (iq + sizeof(struct icmp6_hdr) > pkt_end) return; seq = ((unsigned int)ntohs(iic->icmp6_id) << 16) | ntohs(iic->icmp6_seq); } else { return; /* not a quote of our probe */ } /* A port-unreachable from the target ends a UDP trace (target reached). */ if (k == LFT_ICMP6_UNREACH && ic6->icmp6_code == ICMP6_DST_UNREACH_NOPORT && tgt) { sess->target_open++; recv_packet6(sess, seq, &hop, -1, hdr); return; } itype = (k == LFT_ICMP6_TIME_EXCEEDED) ? -2 : (int)ic6->icmp6_code; recv_packet6(sess, seq, &hop, itype, hdr); return; } if (ip6->ip6_nxt == IPPROTO_TCP) { tcp = (const void *)(l3 + sizeof(struct ip6_hdr)); if ((const u_char *)tcp + sizeof(struct tcphdr) > pkt_end) return; if (!(tcp->th_flags & TH_RST) && !(tcp->th_flags & TH_ACK) && !(tcp->th_flags & TH_SYN)) return; if (memcmp(&ip6->ip6_src, &((struct sockaddr_in6 *)(void *)&sess->remote_ss)->sin6_addr, sizeof(struct in6_addr)) != 0) return; /* not from our target */ if (ntohs(tcp->th_sport) == sess->dport) { if ((tcp->th_flags & TH_ACK) && (tcp->th_flags & TH_SYN) && !(tcp->th_flags & TH_RST)) sess->target_open++; if ((tcp->th_flags & TH_ACK) && !(tcp->th_flags & TH_SYN) && (tcp->th_flags & TH_RST)) sess->target_open = 0; } /* Display parity with v4: stash the header so EVT_RECV_PACKET can * compose the full RCVD TCP line. */ if (sess->noisy > 1) { LFTEvtHandler(sess, EVT_RCVD_TCP, tcp); if (sess->exit_state < 0) return; } seq = ntohl(tcp->th_ack) - 1; recv_packet6(sess, seq, &hop, -1, hdr); return; } } #endif /* HAVE_IPV6 */ 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; const u_char *pkt_end = packet + hdr->caplen; /* end of captured bytes */ 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; /* Adjust the L2 offset per-packet (802.1q VLAN / dot1q). Kept local so one * injected VLAN frame can't shift parsing for the rest of the trace. */ { size_t l2 = sess->skip_header_len; eptr = (const struct ether_header *) packet; if ((l2 == sizeof (struct ether_header)) && (ntohs (eptr->ether_type) == ETHERTYPE_VLAN)) l2 += 4; if (hdr->caplen <= l2) return; /* truncated before L3 */ #ifdef HAVE_IPV6 if (sess->af == AF_INET6) { size_t l3len = (size_t)(hdr->caplen - l2); if (lft_l3_dispatch(packet + l2, l3len) == LFT_L3_V6) process_packet6(sess, packet + l2, l3len, hdr); return; } #endif packet += l2; } /* v4: drop non-IPv4 frames rather than misparse (mirror the TCP/ICMP * engines), then bounds-check the outer IP header. */ if ((packet[0] >> 4) != 4) return; if (packet + sizeof(struct ip) > pkt_end) return; ip = (const void *)packet; packet += 4 * ip->ip_hl; switch (ip->ip_p) { case IPPROTO_ICMP: orig_ip = ip; icmp = (const void *)packet; /* ICMP header must be fully captured before reading icmp_type. */ if ((const u_char *)icmp + ICMP_MINLEN > pkt_end) return; if (icmp->icmp_type != ICMP_UNREACH && icmp->icmp_type != ICMP_TIMXCEED) { return; } ip = &icmp->icmp_ip; /* The quoted inner IP header is attacker-controlled; bound it * (and its ip_hl-derived length) before dereferencing. */ if ((const u_char *)ip + sizeof(struct ip) > pkt_end) return; 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) { if (packet + sizeof(struct udphdr) > pkt_end) return; /* truncated inner UDP header */ 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 { /* RFC 792 guarantees only the inner IP header plus the first 8 * bytes of the quoted transport header. We read the ports * (offsets 0-3) and the sequence (4-7), all within those 8 * bytes; requiring the full 20-byte tcphdr would drop * time-exceeded replies from routers that quote only the RFC * minimum (common inside some carrier networks). */ if (packet + 8 > pkt_end) return; /* inner TCP quote truncated before seq */ 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 */ if (packet + sizeof(struct tcphdr) > pkt_end) return; /* truncated TCP header */ 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; } /*---------------------------------------------------------------------------*/ #ifdef HAVE_IPV6 /* Record a v6 source address in the session's local_ss. */ static void lft_set_local_ss6(lft_session_params *sess, const struct in6_addr *a6) { struct sockaddr_in6 *s6 = (struct sockaddr_in6 *)&sess->local_ss; memset(s6, 0, sizeof(*s6)); s6->sin6_family = AF_INET6; s6->sin6_addr = *a6; } #endif /*---------------------------------------------------------------------------*/ /* 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_IPV6 /* Resolve address family up front, before egress/socket/send. -6 or a * bare v6 literal selects v6; anything else (incl. dual-stack hostnames) * stays v4. Guard on HAVE_IPV6 only, never HAVE_CARES. */ if (sess->hostname) { lft_addr_t _lit; int _wantv6 = (sess->force_af == AF_INET6) || (sess->force_af == AF_UNSPEC && lft_addr_parse(sess->hostname, &_lit) && _lit.af == AF_INET6); if (_wantv6) { struct sockaddr_storage _rss; int _raf; if (lft_resolve_af(sess->hostname, AF_INET6, &_rss, &_raf)) { sess->af = _raf; memcpy(&sess->remote_ss, &_rss, sizeof _rss); } } } /* Basic TCP (-b) uses a separate engine that has no IPv6 send path. */ if (sess->af == AF_INET6 && sess->protocol == 4) { if (global_output_style == 1) printf("Basic TCP tracing is not implemented in IPv6, please use another tracing mode."); else fprintf(stderr, "LFT: Basic TCP tracing is not implemented in IPv6, please use another tracing mode.\n"); sess->exit_state = -1; return; } #endif #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 */ amask = ARES_OPT_TIMEOUTMS | ARES_OPT_TRIES; #ifdef HAVE_ARES_PROCESS_FDS /* Event-model path tracks fds via the socket-state callback; the * classic ares_fds() path (older c-ares) needs neither. */ 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_SOCK_STATE_CB; #endif 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; #ifdef HAVE_IPV6 lft_addr_t udev6; if (sess->af == AF_INET6 && lft_addr_parse(sess->userdev, &udev6) && udev6.af == AF_INET6) { /* -D : find the interface owning it, use it as source. */ sess->pcap_dev = lft_getifname_af(&udev6); if (sess->pcap_dev == 0) { LFTErrHandler(sess, ERR_UNKNOWN_INTERFACE, NULL); if(sess->wsess != NULL) { free(sess->wsess); sess->wsess = NULL; } return; } sess->pcap_send_dev = sess->pcap_dev; lft_set_local_ss6(sess, &udev6.a.v6); } else #endif if (inet_aton(sess->userdev, &addr)) { /* specified by ip address -- look up the device with that IP. */ sess->pcap_dev = lft_getifname(addr); if (sess->pcap_dev == 0) { /* lft_getifname returns NULL on * failure; error only then (the * test was inverted, aborting a * valid -D ). */ 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->pcap_send_dev = sess->pcap_dev; /* was left unset for the * -D case */ sess->local_address.s_addr = lft_getifaddr(sess->pcap_dev); } else { /* specified by device name (braces were missing, so the * send-dev and address assignments ran unconditionally). */ sess->pcap_dev = sess->userdev; sess->pcap_send_dev = sess->userdev; sess->local_address.s_addr = lft_getifaddr(sess->pcap_dev); #ifdef HAVE_IPV6 if (sess->af == AF_INET6) { /* -D : take the interface's v6 source address. */ lft_addr_t ifa6; if (lft_getifaddr_af(sess->pcap_dev, AF_INET6, &ifa6)) lft_set_local_ss6(sess, &ifa6.a.v6); } #endif } }; /* if user wants a different/spoof interface, facilitate */ if (sess->senddevsel > 0) { struct in_addr addr; #ifdef HAVE_IPV6 lft_addr_t sdev6; if (sess->af == AF_INET6 && lft_addr_parse(sess->senddev, &sdev6) && sdev6.af == AF_INET6) { /* -f : use it as the send source address. */ sess->pcap_send_dev = lft_getifforremote(sess, sess->hostname); if (sess->pcap_send_dev == 0) { LFTErrHandler(sess, ERR_UNKNOWN_SEND_INTERFACE, NULL); return; } lft_set_local_ss6(sess, &sdev6.a.v6); } else #endif 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); #ifdef HAVE_IPV6 if (sess->af == AF_INET6) { lft_addr_t ifa6; if (lft_getifaddr_af(sess->pcap_dev, AF_INET6, &ifa6)) lft_set_local_ss6(sess, &ifa6.a.v6); } #endif } }; #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 /* Drop root unconditionally. Sub-sessions (seam verification, GraphViz * sub-queries) are spawned by finish() only after the parent has already * dropped, so setuid(getuid()) is a harmless no-op for them -- and a no-op * when invoked directly as root (getuid()==0), where open_sockets() above * has already acquired the raw socket while still privileged. Making the * drop unconditional removes a gated privileged path with no benefit. If * the drop fails we must NOT continue with elevated privileges. */ if (setuid (getuid ()) != 0) { perror("LFT: failed to drop privileges (setuid)"); exit(EXIT_FAILURE); } /* Post-condition: a setuid-root invocation must no longer be euid 0. */ if (getuid () != 0 && geteuid () == 0) { fprintf(stderr, "LFT: privilege drop did not take effect; aborting\n"); exit(EXIT_FAILURE); } #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(). */ #ifdef HAVE_IPV6 if (sess->af == AF_INET6) { struct sockaddr_in6 *_ls = (struct sockaddr_in6 *)(void *)&sess->local_ss; struct sockaddr_in6 *_rs = (struct sockaddr_in6 *)(void *)&sess->remote_ss; lft_ares_fire6(sess, &_ls->sin6_addr); lft_ares_fire6(sess, &_rs->sin6_addr); } else #endif { 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; } /* IPv6 uses the default loop for every method (TCP/UDP/ICMPv6 are all * hand-built by lft_send_probe6 and classified by process_packet6); the * v4-only icmp/btcp engines are never entered for v6. (-b is rejected * earlier.) */ if(sess->protocol<2 #ifdef HAVE_IPV6 || sess->af == AF_INET6 #endif ) /*UDP or TCP (or any IPv6 method)*/ { #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;idebugmapidx;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) { /* styles: 0 ordinary, 1 xml, 2 graphviz, 3 ascii, 4 watch, 5 svg, 6 mermaid, * 7 json, 8 geojson, 9 kml. Watch (4) requires ncurses; the others do not. */ if(nstyle<0 || nstyle>9) nstyle=0; #ifndef HAVE_NCURSES if(nstyle==4) nstyle=0; #endif 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 (HOP_CHANGED(sess, tp->last_hop, tp->hopaddr, &tp->last_hop6, &tp->hopaddr6)) { 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 > 15) printf (" [icmp code %d]", tp->icmp_type); else { if (tp->icmp_type >= 0) printf (" [%s]", icmp_messages[tp->icmp_type + 1]); } printf(" "); print_hop_host (sess, tp); 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; #ifdef HAVE_IPV6 lft_addr_t asseam_hopaddr6, prevasn_hopaddr6; /* v6 seam companions (AS-method) */ #endif 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_local_host (sess); 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(HOP_CHANGED(sess, tp->last_hop, tp->hopaddr, &tp->last_hop6, &tp->hopaddr6)) { 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; #ifdef HAVE_IPV6 prevasn_hopaddr6=tp->hopaddr6; #endif } } else { if(tp->asnumber) { if(tp->asnumber!=prevasn) { asseam_hopno=prevasn_hopno; asseam_hopaddr=prevasn_hopaddr; #ifdef HAVE_IPV6 asseam_hopaddr6=prevasn_hopaddr6; #endif } prevasn=tp->asnumber; prevasn_hopno=hopno; prevasn_hopaddr=tp->hopaddr; #ifdef HAVE_IPV6 prevasn_hopaddr6=tp->hopaddr6; #endif } } } } } } } 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(HOP_CHANGED(sess, tp->last_hop, tp->hopaddr, &tp->last_hop6, &tp->hopaddr6)) { 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 && sess->show_seams && ((SEAM_HIT(sess, hopno, asseam_hopno, tp, asseam_hopaddr, asseam_hopaddr6)) || (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(sess->show_seams && SEAM_HIT(sess, hopno, asseam_hopno, tp, asseam_hopaddr, asseam_hopaddr6)) { printf("(AS-Method Seam"); isseam=1; } if(sess->show_seams && hopno==netseam_hopno && tp->hopaddr.s_addr==netseam_hopaddr.s_addr) { printf("(Network-Method Seam"); isseam=1; } if(isseam) { if(sess->check_seam) { printf(": "); char hostname[100]; lft_session_params * subsess=LFTSessionOpen(); { #ifdef HAVE_IPV6 if (sess->af == AF_INET6) { lft_ntop(&tp->hopaddr6, hostname, sizeof hostname); subsess->force_af = AF_INET6; } else #endif 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", (int)hostbufsz - 1, 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;dw - 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 ├───── --- * 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; } /* --color forces colour on even when not a detected terminal; * --no-color forces it off. (sess->color_mode: 0 auto,1 on,2 off) */ if (sess->color_mode == 1) use_color = 1; else if (sess->color_mode == 2) use_color = 0; /* 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; /* v4-only seam detection (class-B netreached + ASN seam). */ if (sess->af != AF_INET6 && 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 ]"); #ifdef HAVE_IPV6 if (sess->af == AF_INET6) { lft_addr_t _la; lft_addr_from_ss(&sess->local_ss, &_la); lft_ntop_fmt(&_la, hostbuf, sizeof(hostbuf), sess->addr_expand); ipbuf[0] = '\0'; } else #endif { 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 (!HOP_CHANGED(sess, tp->last_hop, tp->hopaddr, &tp->last_hop6, &tp->hopaddr6)) 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 (sess->show_seams && 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 (sess->show_seams && 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]; #ifdef HAVE_IPV6 if (sess->af == AF_INET6) { lft_ntop_fmt(&tp->hopaddr6, hostbuf, sizeof(hostbuf), sess->addr_expand); ipbuf[0] = '\0'; } else #endif { 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", (int)sizeof(c->net) - 1, 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", (int)sizeof(c->org) - 1, 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_lookup_af(sess, tp, _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 %s%dms", (int)round(c->stats.rtt_min), (int)round(c->stats.rtt_avg), (int)round(c->stats.rtt_max), lft_pm(), (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 */ char _tgtbuf[INET6_ADDRSTRLEN]; const char *tgt_name; #ifdef HAVE_IPV6 if (sess->af == AF_INET6) { lft_addr_t _ta; lft_addr_from_ss(&sess->remote_ss, &_ta); lft_ntop_fmt(&_ta, _tgtbuf, sizeof _tgtbuf, sess->addr_expand); tgt_name = _tgtbuf; } else #endif 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'); } /*---------------------------------------------------------------------------*/ /*=========================================================================== * SVG and Mermaid trace emitters (output styles 4 and 5) * * Both consume a linear "diagram model" built by lft_build_diag(), which walks * the hop table once, mirroring the classification GraphVizOutput/ASCIIOutput * use: source, routers, AS/network seams, stateful/flag firewalls, cloaked * black-holes, and the open/closed/filtered target. This keeps the renderers * free of probe-engine detail. *=========================================================================*/ enum diag_type { DT_SOURCE=0, DT_ROUTER, DT_SEAM, DT_FW_SMART, DT_FW_STUPID, DT_HOLE, DT_TARGET_OPEN, DT_TARGET_CLOSED, DT_TARGET_FILTERED }; #define DIAG_MEMBERS_MAX 12 struct diag_member { char host[160]; char ip[128]; char asn[40]; char rtt[48]; char special_use[48]; /* IANA special-purpose token, e.g. private_use */ char net[80]; /* network name (whois) for this responder */ int geo_ok; /* pWhoIs geolocation present */ double geo_lat, geo_lon; char geo_city[50], geo_region[50], geo_country[50], geo_cc[4]; int asnum; /* numeric ASN, 0 = unknown */ int nrtt; /* RTT samples for this responder */ double rtt_min, rtt_max, rtt_sum, rtt_sumsq; /* for avg / stddev */ }; struct diag_node { int type; int hopno; /* 1-based TTL for the header; 0 for source */ int hopno_end; /* holes: last TTL of the silent run */ char host[256]; /* hostname or numeric address (primary/first member) */ char ip[128]; /* numeric address when host is a name; else "" */ char asn[40]; char net[80]; char rtt[48]; char note[80]; /* seam kind / firewall kind / hole TTL range / target state */ int n_members; /* distinct responders at this level; >1 = ECMP diamond */ struct diag_member member[DIAG_MEMBERS_MAX]; /* all members (incl. the first) */ }; #define DIAG_MAX 256 /* machine-readable styles (--json, --geojson, --kml): RFC 5952 v6 text and * boundaries classified regardless of --show-seams */ static int diag_data_mode(void) { return outputStyleIsJSON() || outputStyleIsGeoJSON() || outputStyleIsKML(); } /* device glyph symbol id (in LFT_SVG_DEFS) + Mermaid class, per node type */ static const char *diag_svg_sym(int t) { switch (t) { case DT_SOURCE: return "d-source"; case DT_SEAM: return "d-seam"; case DT_FW_SMART: return "d-fwsmart"; case DT_FW_STUPID: return "d-fwstupid"; case DT_HOLE: return "d-hole"; case DT_TARGET_OPEN: return "d-topen"; case DT_TARGET_CLOSED: return "d-tclosed"; case DT_TARGET_FILTERED: return "d-tfiltered"; default: return "d-router"; } } static const char *diag_class(int t) { switch (t) { case DT_SOURCE: return "source"; case DT_SEAM: return "seam"; case DT_FW_SMART: return "fwsmart"; case DT_FW_STUPID: return "fwstupid"; case DT_HOLE: return "hole"; case DT_TARGET_OPEN: return "topen"; case DT_TARGET_CLOSED: return "tclosed"; case DT_TARGET_FILTERED: return "tfiltered"; default: return "router"; } } /* accent colour for the SVG card rail; NULL = no rail (source/router/hole) */ static const char *diag_accent(int t) { switch (t) { case DT_SEAM: return "#8b6cd9"; case DT_FW_SMART: return "#4f8fd6"; case DT_FW_STUPID: return "#d9743a"; case DT_TARGET_OPEN: return "#17b890"; case DT_TARGET_CLOSED: return "#e6a13c"; case DT_TARGET_FILTERED: return "#e5484d"; default: return NULL; } } /* Wrap a hostname to at most maxchars per line (breaking just after a '.' where * possible, otherwise a hard split), into up to maxlines lines; returns the * line count. Keeps long FQDNs from overrunning the fixed-width SVG card. */ static int diag_wrap(const char *s, int maxchars, char out[][160], int maxlines) { int nl = 0, i; size_t len = s ? strlen(s) : 0, pos = 0; if (maxchars < 4) maxchars = 4; if (maxchars > 159) maxchars = 159; if (!len) { out[0][0] = '\0'; return 1; } while (pos < len && nl < maxlines) { size_t remain = len - pos; if ((int)remain <= maxchars) { memcpy(out[nl], s + pos, remain); out[nl][remain] = '\0'; nl++; pos = len; break; } int brk = 0; for (i = maxchars; i > maxchars/3; i--) if (s[pos+i] == '.') { brk = i + 1; break; } /* break just after a dot */ if (!brk) brk = maxchars; /* no dot: hard split */ memcpy(out[nl], s + pos, brk); out[nl][brk] = '\0'; nl++; pos += brk; } if (pos < len && nl > 0) { /* ran out of lines: ellipsize */ int l = (int)strlen(out[nl-1]); if (l >= 3) out[nl-1][l-1] = out[nl-1][l-2] = out[nl-1][l-3] = '.'; } return nl ? nl : 1; } /* fold one RTT sample into a member's running min/max/sum/sumsq */ static void diag_rtt_add(struct diag_member *mm, double ms) { if (mm->nrtt == 0 || ms < mm->rtt_min) mm->rtt_min = ms; if (mm->nrtt == 0 || ms > mm->rtt_max) mm->rtt_max = ms; mm->rtt_sum += ms; mm->rtt_sumsq += ms * ms; mm->nrtt++; } /* lower-case snake_case token from a registry display name ("Private-Use" -> * "private_use"); stable across registry wording/punctuation changes */ static void diag_token(const char *in, char *out, size_t outsz) { size_t o = 0; int last_us = 1; for (; *in && o + 1 < outsz; in++) { unsigned char c = (unsigned char)*in; int alnum = (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); if (alnum) { out[o++] = (char)((c >= 'A' && c <= 'Z') ? c + 32 : c); last_us = 0; } else if (!last_us) { out[o++] = '_'; last_us = 1; } } while (o > 0 && out[o-1] == '_') o--; out[o] = '\0'; } static int lft_build_diag(lft_session_params *sess, struct diag_node *nd) { int n=0, hopno, maxhop, icmpcode; struct trace_packet_info_s *tp; int neglstart=-1, neglend=-1, noreply=0, found_target=0; int asseam_hopno=-1, netseam_hopno=-1, netreached=0, lastishole=0; int prevasn=-1, prevasn_hopno=0; struct in_addr asseam_hopaddr, netseam_hopaddr, prevasn_hopaddr, classbmask, masked_target; #ifdef HAVE_IPV6 lft_addr_t asseam_hopaddr6, prevasn_hopaddr6; memset(&asseam_hopaddr6,0,sizeof asseam_hopaddr6); memset(&prevasn_hopaddr6,0,sizeof prevasn_hopaddr6); #endif memset(&asseam_hopaddr,0,sizeof asseam_hopaddr); memset(&netseam_hopaddr,0,sizeof netseam_hopaddr); memset(&prevasn_hopaddr,0,sizeof prevasn_hopaddr); if (sess->num_hops) maxhop = sess->num_hops; else maxhop = sess->hop_info_length - 1; inet_aton("255.255.0.0", &classbmask); masked_target.s_addr = sess->remote_address.s_addr & classbmask.s_addr; /* seam prescan (AS-method + network-method), matching GraphViz */ 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) continue; if (hopno <= maxhop) icmpcode = tp->icmp_type; if (HOP_CHANGED(sess, tp->last_hop, tp->hopaddr, &tp->last_hop6, &tp->hopaddr6)) { 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; #ifdef HAVE_IPV6 prevasn_hopaddr6=tp->hopaddr6; #endif } } else if (tp->asnumber) { if (tp->asnumber != prevasn) { asseam_hopno=prevasn_hopno; asseam_hopaddr=prevasn_hopaddr; #ifdef HAVE_IPV6 asseam_hopaddr6=prevasn_hopaddr6; #endif } prevasn=tp->asnumber; prevasn_hopno=hopno; prevasn_hopaddr=tp->hopaddr; #ifdef HAVE_IPV6 prevasn_hopaddr6=tp->hopaddr6; #endif } } } } } else lastishole = 1; if (icmpcode == -1) break; } if (!netreached) netseam_hopno = -1; if (lastishole) asseam_hopno = -1; /* source node */ { struct diag_node *c=&nd[n++]; char hostbuf[256], ipbuf[128]; memset(c,0,sizeof(*c)); c->type=DT_SOURCE; c->hopno=0; #ifdef HAVE_IPV6 if (sess->af==AF_INET6) { lft_addr_t la; lft_addr_from_ss(&sess->local_ss,&la); lft_ntop_fmt(&la,hostbuf,sizeof hostbuf,diag_data_mode()?0:sess->addr_expand); ipbuf[0]='\0'; /* data modes: RFC 5952 */ } else #endif { snprintf(hostbuf,sizeof hostbuf,"%s",inet_ntoa(sess->local_address)); ipbuf[0]='\0'; } snprintf(c->host,sizeof c->host,"%s",hostbuf); snprintf(c->ip,sizeof c->ip,"%s",ipbuf); if (sess->protocol<2 || sess->protocol>3) { size_t hl=strlen(c->host); snprintf(c->host+hl,sizeof c->host-hl,":%d",sess->sport); } } /* per-hop nodes */ for (hopno=sess->ttl_min; hopnohop_info_length && nhop_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) { if (neglstart==-1) neglstart=hopno; neglend=hopno; noreply++; continue; } if (neglstart!=-1) { struct diag_node *c=&nd[n++]; memset(c,0,sizeof(*c)); c->type=DT_HOLE; c->hopno=neglstart+1; c->hopno_end=neglend+1; snprintf(c->host,sizeof c->host,"cloaked"); if (neglstart==neglend) snprintf(c->note,sizeof c->note,"TTL %d",neglstart+1); else snprintf(c->note,sizeof c->note,"TTLs %d-%d",neglstart+1,neglend+1); neglstart=neglend=-1; if (n>=DIAG_MAX) break; } { struct diag_node *c = &nd[n]; struct trace_packet_info_s *first_tp = NULL; struct in_addr mem_addr[DIAG_MEMBERS_MAX]; #ifdef HAVE_IPV6 lft_addr_t mem_addr6[DIAG_MEMBERS_MAX]; #endif int m = 0, i, seen; memset(c,0,sizeof(*c)); c->hopno=hopno+1; icmpcode=-100; /* collect this hop's DISTINCT responders (ECMP members), deduped by * address; member[0] is the primary, member[1..] the diamond. */ SLIST_FOREACH(tp,&(sess->hop_info[hopno].packets),next_by_hop) { struct diag_member *mm; char hostbuf[256], ipbuf[128]; if (!tp->recv.tv_sec) continue; if (hopno<=maxhop) icmpcode=tp->icmp_type; seen=0; for (i=0;iaf==AF_INET6) { if (lft_addr_eq(&mem_addr6[i],&tp->hopaddr6)){seen=1;break;} continue; } #endif if (mem_addr[i].s_addr==tp->hopaddr.s_addr){seen=1;break;} } if (seen) { /* repeat probe to a known responder: fold in its RTT */ diag_rtt_add(&c->member[i], timediff_ms(tp->sent,tp->recv)); continue; } if (m>=DIAG_MEMBERS_MAX) continue; #ifdef HAVE_IPV6 if (sess->af==AF_INET6) mem_addr6[m]=tp->hopaddr6; else #endif mem_addr[m]=tp->hopaddr; mm=&c->member[m]; #ifdef HAVE_IPV6 if (sess->af==AF_INET6) { lft_ntop_fmt(&tp->hopaddr6,hostbuf,sizeof hostbuf,diag_data_mode()?0:sess->addr_expand); ipbuf[0]='\0'; } else #endif { format_host_buf(sess,tp->hopaddr,hostbuf,sizeof hostbuf,ipbuf,sizeof ipbuf); } /* member host field is smaller than hostbuf; bound the copy to * the field so it is a deliberate (not accidental) truncation. */ snprintf(mm->host,sizeof mm->host,"%.*s",(int)sizeof(mm->host) - 1,hostbuf); snprintf(mm->ip,sizeof mm->ip,"%s",ipbuf); if (sess->do_aslookup && tp->asnumber) snprintf(mm->asn,sizeof mm->asn,"AS%d",tp->asnumber); if (!mm->asn[0] && (sess->do_aslookup||sess->do_netlookup)) { char irfc[16], iname[64]; if (sess->do_aslookup && lft_iana_lookup_af(sess,tp,irfc,sizeof irfc,iname,sizeof iname)) { snprintf(mm->asn,sizeof mm->asn,"%s",irfc); diag_token(iname, mm->special_use, sizeof mm->special_use); } } if (tp->recv.tv_sec) snprintf(mm->rtt,sizeof mm->rtt,"%.1fms",timediff_ms(tp->sent,tp->recv)); mm->asnum = tp->asnumber; if (tp->netname[0] && strcmp(tp->netname,"NULL")!=0) snprintf(mm->net,sizeof mm->net,"%.*s",(int)sizeof mm->net-1,tp->netname); mm->geo_ok = tp->geo_ok; mm->geo_lat = tp->geo_lat; mm->geo_lon = tp->geo_lon; snprintf(mm->geo_city,sizeof mm->geo_city,"%s",tp->geo_city); snprintf(mm->geo_region,sizeof mm->geo_region,"%s",tp->geo_region); snprintf(mm->geo_country,sizeof mm->geo_country,"%s",tp->geo_country); snprintf(mm->geo_cc,sizeof mm->geo_cc,"%s",tp->geo_cc); diag_rtt_add(mm, timediff_ms(tp->sent,tp->recv)); if (m==0) first_tp=tp; m++; } if (m==0) continue; /* all_rcvd but nothing usable */ c->n_members=m; n++; tp=first_tp; if (icmpcode==-1) { if (sess->target_open>0) { c->type=DT_TARGET_OPEN; snprintf(c->note,sizeof c->note,"open"); } else if (sess->target_filtered){ c->type=DT_TARGET_FILTERED; snprintf(c->note,sizeof c->note,"filtered"); } else if (sess->protocol==2||sess->protocol==3){ c->type=DT_TARGET_CLOSED; snprintf(c->note,sizeof c->note,"replied"); } else { c->type=DT_TARGET_CLOSED; snprintf(c->note,sizeof c->note,"closed"); } } else if (anomtype==1) { c->type=DT_FW_SMART; snprintf(c->note,sizeof c->note,"Stateful FW"); } else if (anomtype==2) { c->type=DT_FW_STUPID; snprintf(c->note,sizeof c->note,"Flag FW"); } else if (anomtype==3) { c->type=DT_FW_STUPID; snprintf(c->note,sizeof c->note,"BSD Bug"); } /* seams are a display toggle for the diagrams (--show-seams) but plain * data for --json, which always reports the boundary */ else if ((sess->show_seams || diag_data_mode()) && SEAM_HIT(sess,hopno,asseam_hopno,tp,asseam_hopaddr,asseam_hopaddr6)) { c->type=DT_SEAM; snprintf(c->note,sizeof c->note,"ASN Seam"); } else if ((sess->show_seams || diag_data_mode()) && hopno==netseam_hopno && tp->hopaddr.s_addr==netseam_hopaddr.s_addr) { c->type=DT_SEAM; snprintf(c->note,sizeof c->note,"Network Seam"); } else c->type=DT_ROUTER; /* primary fields = first member */ snprintf(c->host,sizeof c->host,"%s",c->member[0].host); snprintf(c->ip,sizeof c->ip,"%s",c->member[0].ip); snprintf(c->asn,sizeof c->asn,"%s",c->member[0].asn); snprintf(c->rtt,sizeof c->rtt,"%s",c->member[0].rtt); if (first_tp->netname[0] && strcmp(first_tp->netname,"NULL")!=0) snprintf(c->net,sizeof c->net,"%.*s",(int)sizeof c->net-1,first_tp->netname); if ((c->type==DT_TARGET_OPEN||c->type==DT_TARGET_CLOSED||c->type==DT_TARGET_FILTERED) && (sess->protocol<2||sess->protocol>3)) { size_t hl=strlen(c->host); snprintf(c->host+hl,sizeof c->host-hl,":%d",sess->dport); hl=strlen(c->member[0].host); snprintf(c->member[0].host+hl,sizeof c->member[0].host-hl,":%d",sess->dport); } noreply=0; if (icmpcode==-1) found_target=1; if (n>=DIAG_MAX) break; } if (found_target) break; } if (neglstart!=-1 && ntype=DT_HOLE; c->hopno=neglstart+1; c->hopno_end=neglend+1; snprintf(c->host,sizeof c->host,"cloaked"); if (neglstart==neglend) snprintf(c->note,sizeof c->note,"after TTL %d",neglstart+1); else snprintf(c->note,sizeof c->note,"TTLs %d-%d",neglstart+1,neglend+1); } return n; } /* escape a string for XML/SVG text content or a Mermaid quoted label */ static void diag_xesc(const char *s) { for (; *s; s++) { switch (*s) { case '&': fputs("&", stdout); break; case '<': fputs("<", stdout); break; case '>': fputs(">", stdout); break; case '"': fputs(""", stdout); break; default: putchar((unsigned char)*s); } } } /*---------------------------------------------------------------------------*/ /* x-centres of a level's columns (1 for a normal hop, N for an ECMP fork). */ static int diag_svg_cols(const struct diag_node *c, int cx, int memw, int memgap, int *out) { if (c->n_members > 1) { int k=c->n_members, gw=k*memw+(k-1)*memgap, sx=cx-gw/2, j; for (j=0;j0?n:1)); int *ht = (int *)malloc(sizeof(int) * (n>0?n:1)); int cy_top=20, contentW, totalW, totalH, cx; if (!y || !ht) { free(y); free(ht); return; } for (i=0; imaxfork) maxfork=nodes[i].n_members; contentW = CARDW; if (maxfork>1) { int gw=maxfork*MEMW+(maxfork-1)*MEMGAP; if (gw>contentW) contentW=gw; } totalW = contentW + 2*PADX; cx = totalW/2; for (i=0; in_members>1) { int ml=1; for (k=0;kn_members;k++) { char hl_[3][160]; int l = diag_wrap(c->member[k].host, 22, hl_, 3); if (c->member[k].ip[0]) l++; if (c->member[k].asn[0]) l++; if (c->member[k].rtt[0]) l++; if (l>ml) ml=l; } ht[i] = FHDR + MEMTOP + ml*LH + BOT; } else { char hl_[3][160]; int lines = diag_wrap(c->host, 32, hl_, 3); if (c->ip[0]) lines++; if (c->asn[0]) lines++; if (c->net[0]) lines++; if (c->rtt[0]) lines++; if (c->note[0]) lines++; ht[i] = ICON_TOP + ICON + TXT_GAP + lines*LH + BOT; } y[i]=cy_top; cy_top += ht[i] + GAPY; } totalH = cy_top + 6; printf("\n"); printf("\n", totalW, totalH, totalW, totalH); printf("\n", totalW, totalH); printf("%s", LFT_SVG_DEFS); printf("" "\n"); /* edges: each exit column of level i to each entry column of level i+1 */ for (i=0; i1)?y[i+1]+FHDR:y[i+1]; const char *col=(na>1||nb>1)?"#8b6cd9":"#9aa4b2"; int a,b; for (a=0;a\n", ca[a], ey, cb[b], fy-2, col); } for (i=0; in_members>1) { int gw=c->n_members*MEMW+(c->n_members-1)*MEMGAP, sx=cx-gw/2; int ml=1; for (k=0;kn_members;k++) { int l=1; if (c->member[k].ip[0]) l++; if (c->member[k].asn[0]) l++; if (c->member[k].rtt[0]) l++; if (l>ml) ml=l; } printf("" "\xe2\x97\x87 ECMP %d-way\n", cx, y[i]+16, c->n_members); for (k=0;kn_members;k++) { struct diag_member *mm=&c->member[k]; int mx=sx+k*(MEMW+MEMGAP), mcx=mx+MEMW/2, mty=y[i]+FHDR, mch=MEMTOP+ml*LH+BOT, tvy; printf("\n", mx, mty, MEMW, mch); printf("\n", mx, mty, mch); tvy=mty+MEMTOP+13; { char hl_[3][160]; int hn=diag_wrap(mm->host,22,hl_,3), hi; for(hi=0;hi", mcx, tvy); diag_xesc(hl_[hi]); printf("\n"); tvy+=LH; } } if (mm->ip[0]) { printf("", mcx, tvy); diag_xesc(mm->ip); printf("\n"); tvy+=LH; } if (mm->asn[0]) { printf("", mcx, tvy); diag_xesc(mm->asn); printf("\n"); tvy+=LH; } if (mm->rtt[0]) { printf("", mcx, tvy); diag_xesc(mm->rtt); printf("\n"); tvy+=LH; } } } else { const char *accent=diag_accent(c->type); int ty=y[i], tvy; printf("\n", cx-CARDW/2, ty, CARDW, ht[i]); if (accent) printf("\n", cx-CARDW/2, ty, ht[i], accent); printf("\n", diag_svg_sym(c->type), cx-ICON/2, ty+ICON_TOP, ICON, ICON); tvy = ty + ICON_TOP + ICON + TXT_GAP; { char hl_[3][160]; int hn=diag_wrap(c->host,32,hl_,3), hi; for(hi=0;hi", cx, tvy); diag_xesc(hl_[hi]); printf("\n"); tvy+=LH; } } if (c->ip[0]) { printf("", cx, tvy); diag_xesc(c->ip); printf("\n"); tvy+=LH; } if (c->asn[0]) { printf("", cx, tvy); diag_xesc(c->asn); printf("\n"); tvy+=LH; } if (c->net[0]) { printf("", cx, tvy); diag_xesc(c->net); printf("\n"); tvy+=LH; } if (c->rtt[0]) { printf("", cx, tvy); diag_xesc(c->rtt); printf("\n"); tvy+=LH; } if (c->note[0]) { printf("", cx, tvy, accent?accent:"#5b6472"); diag_xesc(c->note); printf("\n"); } } } printf("\n"); free(y); free(ht); } /*---------------------------------------------------------------------------*/ /* MermaidOutput -- styled Mermaid flowchart definition of the trace. */ void MermaidOutput(lft_session_params *sess) { static struct diag_node nodes[DIAG_MAX]; int n = lft_build_diag(sess, nodes); int i; printf("flowchart TB\n"); /* nodes: a fork level (n_members>1) becomes one node per ECMP member */ for (i=0; in_members > 1) { int k; for (k=0; kn_members; k++) { struct diag_member *mm=&c->member[k]; printf(" n%dm%d[\"", i, k); diag_xesc(mm->host); if (mm->ip[0]) { printf("
"); diag_xesc(mm->ip); } if (mm->asn[0]) { printf("
"); diag_xesc(mm->asn); } if (mm->rtt[0]) { printf("
"); diag_xesc(mm->rtt); } printf("\"]:::ecmp\n"); } } else { const char *ob, *cb; switch (c->type) { case DT_SOURCE: case DT_TARGET_OPEN: case DT_TARGET_CLOSED: case DT_TARGET_FILTERED: ob="([\""; cb="\"])"; break; /* stadium */ case DT_SEAM: ob="{{\""; cb="\"}}"; break; /* hexagon */ case DT_FW_SMART: case DT_FW_STUPID:ob="[[\""; cb="\"]]"; break; /* framed */ default: ob="[\""; cb="\"]"; break; /* rect */ } printf(" n%d%s", i, ob); diag_xesc(c->host); if (c->ip[0]) { printf("
"); diag_xesc(c->ip); } if (c->asn[0]) { printf("
"); diag_xesc(c->asn); } if (c->net[0]) { printf("
"); diag_xesc(c->net); } if (c->rtt[0]) { printf("
"); diag_xesc(c->rtt); } if (c->note[0]){ printf("
"); diag_xesc(c->note); printf(""); } printf("%s:::%s\n", cb, diag_class(c->type)); } } /* edges: connect each exit of level i to each entry of level i+1. A fork * (either side multi-member) uses thick violet links; the first link into a * fork carries a "diamond ECMP N-way" label. */ { int edge=0, a, b, ec[1024], nec=0; for (i=0; in_members>1)?ci->n_members:1; int nb=(cj->n_members>1)?cj->n_members:1; int fork=(na>1||nb>1); for (a=0;an_members>1) snprintf(sid,sizeof sid,"n%dm%d",i,a); else snprintf(sid,sizeof sid,"n%d",i); if (cj->n_members>1) snprintf(did,sizeof did,"n%dm%d",i+1,b); else snprintf(did,sizeof did,"n%d",i+1); if (cj->n_members>1 && a==0 && b==0) printf(" %s ==>|\"\xe2\x97\x87 ECMP %d-way\"| %s\n", sid, cj->n_members, did); else printf(" %s %s %s\n", sid, fork?"==>":"-->", did); if (fork && nec<1024) ec[nec++]=edge; edge++; } } if (nec>0) { printf(" linkStyle "); for (i=0;i1 element = ECMP, * first-seen order; empty for a silent TTL * - always-present keys, null for unknown; floats "C"-locale, 3 decimals, * never NaN/Inf; strings RFC 8259-escaped, invalid UTF-8 -> U+FFFD; * IPv6 in RFC 5952 form regardless of --expand-addr * Built from the shared diagram model so it agrees hop-for-hop with the * text / SVG / Mermaid renderings. *=========================================================================*/ /* emit a JSON string literal: RFC 8259 escapes; invalid UTF-8 -> U+FFFD */ static void json_str(const char *s) { const unsigned char *p = (const unsigned char *)(s ? s : ""); putchar('"'); while (*p) { unsigned char c = *p; int need, k, ok = 1; if (c < 0x80) { switch (c) { case '"': fputs("\\\"", stdout); break; case '\\': fputs("\\\\", stdout); break; case '\n': fputs("\\n", stdout); break; case '\r': fputs("\\r", stdout); break; case '\t': fputs("\\t", stdout); break; default: if (c < 0x20) printf("\\u%04x", c); else putchar(c); } p++; continue; } if ((c & 0xE0) == 0xC0 && c >= 0xC2) need = 1; else if ((c & 0xF0) == 0xE0) need = 2; else if ((c & 0xF8) == 0xF0 && c <= 0xF4) need = 3; else { fputs("\\ufffd", stdout); p++; continue; } for (k = 1; k <= need; k++) if ((p[k] & 0xC0) != 0x80) { ok = 0; break; } if (!ok) { fputs("\\ufffd", stdout); p++; continue; } fwrite(p, 1, (size_t)need + 1, stdout); p += need + 1; } putchar('"'); } /* finite float with 3 decimals, else null (JSON has no NaN / Inf) */ static void json_num(double v) { if (isfinite(v)) printf("%.3f", v); else fputs("null", stdout); } /* tiny JSON writer: tracks depth and whether a separator is due; lays out * newlines / indentation only when pretty-printing */ struct jw { int pretty, depth, first[16]; }; static void jw_nl(struct jw *w) { int i; if (!w->pretty) return; putchar('\n'); for (i = 0; i < w->depth; i++) fputs(" ", stdout); } static void jw_sep(struct jw *w) /* before each member / element */ { if (!w->first[w->depth]) putchar(','); w->first[w->depth] = 0; jw_nl(w); } static void jw_open(struct jw *w, char c) { putchar(c); if (w->depth < 15) w->depth++; w->first[w->depth] = 1; } static void jw_close(struct jw *w, char c) { int was_empty = w->first[w->depth]; w->depth--; if (!was_empty) jw_nl(w); putchar(c); } static void jw_key(struct jw *w, const char *k) { jw_sep(w); json_str(k); putchar(':'); if (w->pretty) putchar(' '); } static void jw_null(void) { fputs("null", stdout); } static void jw_bool(int b) { fputs(b ? "true" : "false", stdout); } /* role of the hop; a seam is a boundary property, not a role (see below) */ static const char *json_kind(const struct diag_node *c) { switch (c->type) { case DT_FW_SMART: case DT_FW_STUPID: return "firewall"; case DT_TARGET_OPEN: case DT_TARGET_CLOSED: case DT_TARGET_FILTERED: return "target"; case DT_HOLE: return "silent"; default: return "router"; /* DT_ROUTER and DT_SEAM */ } } static const char *json_boundary(const struct diag_node *c) /* NULL = none */ { if (c->type != DT_SEAM) return NULL; return strstr(c->note, "Network") ? "network" : "asn"; } static const char *json_firewall(const struct diag_node *c) /* NULL = none */ { if (c->type == DT_FW_SMART) return "stateful"; if (c->type == DT_FW_STUPID) return strstr(c->note, "BSD") ? "bsd_bug" : "flag"; return NULL; } /* the diagram model appends ":" to endpoint hosts; strip it back off */ static void json_strip_port(char *s, int port) { char suf[16]; size_t sl, l; snprintf(suf, sizeof suf, ":%d", port); sl = strlen(suf); l = strlen(s); if (l > sl && strcmp(s + l - sl, suf) == 0) s[l - sl] = '\0'; } /* local or remote endpoint address as text (v6 always RFC 5952 shorthand) */ static void json_endpoint(lft_session_params *sess, int remote, char *buf, size_t sz) { #ifdef HAVE_IPV6 if (sess->af == AF_INET6) { lft_addr_t a; lft_addr_from_ss(remote ? &sess->remote_ss : &sess->local_ss, &a); lft_ntop_fmt(&a, buf, sz, 0); return; } #endif snprintf(buf, sz, "%s", inet_ntoa(remote ? sess->remote_address : sess->local_address)); } /* one responder object: identical key set every time */ static void json_responder(struct jw *w, const struct diag_member *mm, int strip, int port) { char host[160]; const char *addr, *hn; int n = mm->nrtt; double avg = n ? mm->rtt_sum / n : 0.0; snprintf(host, sizeof host, "%s", mm->host); if (strip) json_strip_port(host, port); addr = mm->ip[0] ? mm->ip : host; /* ip set => host is a name */ hn = mm->ip[0] ? host : NULL; jw_open(w, '{'); jw_key(w, "addr"); json_str(addr); jw_key(w, "hostname"); if (hn) json_str(hn); else jw_null(); jw_key(w, "asn"); if (mm->asnum) printf("%d", mm->asnum); else jw_null(); jw_key(w, "netname"); if (mm->net[0]) json_str(mm->net); else jw_null(); jw_key(w, "special_use"); if (mm->special_use[0]) json_str(mm->special_use); else jw_null(); jw_key(w, "rtt_ms"); jw_open(w, '{'); jw_key(w, "count"); printf("%d", n); jw_key(w, "min"); if (n) json_num(mm->rtt_min); else jw_null(); jw_key(w, "avg"); if (n) json_num(avg); else jw_null(); jw_key(w, "max"); if (n) json_num(mm->rtt_max); else jw_null(); jw_key(w, "stddev"); /* population; null when < 2 samples */ if (n > 1) { double var = mm->rtt_sumsq / n - avg * avg; if (var < 0) var = 0; json_num(sqrt(var)); } else jw_null(); jw_close(w, '}'); jw_close(w, '}'); } void JSONOutput(lft_session_params *sess) { static struct diag_node nodes[DIAG_MAX]; static const struct diag_node *at[DIAG_MAX + 8]; /* node per TTL; NULL = silent */ int n = lft_build_diag(sess, nodes); int i, ttl, ttl_lo, ttl_hi = 0, tgt_ttl = 0, found = 0, af6 = 0; int has_port = (sess->protocol < 2 || sess->protocol > 3); struct jw w; char buf[160], tbuf[48], *oldloc = NULL; const char *proto, *state, *cur; /* floats must be "C"-locale for valid JSON whatever the environment */ cur = setlocale(LC_NUMERIC, NULL); if (cur) oldloc = strdup(cur); setlocale(LC_NUMERIC, "C"); memset(&w, 0, sizeof w); w.pretty = sess->json_pretty; w.first[0] = 1; memset(at, 0, sizeof at); #ifdef HAVE_IPV6 af6 = (sess->af == AF_INET6); #endif switch (sess->protocol) { case 1: proto = "udp"; break; case 2: proto = "icmp"; break; case 3: proto = "icmp_rfc1393"; break; default: proto = "tcp"; break; /* 0 (SYN) and 4 (basic range) */ } /* index the model by TTL so the array is contiguous ttl_min..ttl_max */ ttl_lo = sess->ttl_min + 1; for (i = 0; i < n; i++) { const struct diag_node *c = &nodes[i]; if (c->type == DT_SOURCE) continue; if (c->type == DT_HOLE) { /* at[] stays NULL = silent */ if (c->hopno_end > ttl_hi) ttl_hi = c->hopno_end; continue; } if (c->hopno > 0 && c->hopno < DIAG_MAX + 8) { at[c->hopno] = c; if (c->hopno > ttl_hi) ttl_hi = c->hopno; } if (c->type == DT_TARGET_OPEN || c->type == DT_TARGET_CLOSED || c->type == DT_TARGET_FILTERED) { found = 1; tgt_ttl = c->hopno; } } if (ttl_hi >= DIAG_MAX + 8) ttl_hi = DIAG_MAX + 7; if (ttl_hi < ttl_lo) ttl_hi = ttl_lo - 1; /* nothing probed: empty hops */ if (!found) state = "unreached"; else if (sess->target_open > 0) state = "open"; else if (sess->target_filtered) state = "filtered"; else if (sess->protocol == 2 || sess->protocol == 3) state = "replied"; else state = "closed"; { size_t l = strftime(tbuf, sizeof tbuf, "%Y-%m-%dT%H:%M:%S", gmtime((time_t *)&sess->begin_time.tv_sec)); snprintf(tbuf + l, sizeof tbuf - l, ".%03ldZ", (long)(sess->begin_time.tv_usec / 1000)); } jw_open(&w, '{'); jw_key(&w, "schema"); json_str("lft-trace"); jw_key(&w, "schema_version"); printf("1"); jw_key(&w, "generator"); jw_open(&w, '{'); jw_key(&w, "name"); json_str("lft"); jw_key(&w, "version"); json_str(version); jw_close(&w, '}'); jw_key(&w, "run"); jw_open(&w, '{'); jw_key(&w, "started_at"); json_str(tbuf); jw_key(&w, "ip_version"); printf("%d", af6 ? 6 : 4); jw_key(&w, "protocol"); json_str(proto); jw_key(&w, "ttl_min"); printf("%d", ttl_lo); jw_key(&w, "ttl_max"); printf("%d", ttl_hi); { double tr = timediff_ms(sess->begin_time, sess->trace_done_time); double rs = timediff_ms(sess->trace_done_time, sess->now); double el = timediff_ms(sess->begin_time, sess->now); if (tr < 0) tr = 0; if (rs < 0) rs = 0; if (el < tr + rs) el = tr + rs; /* invariant: total >= tracing + resolving */ jw_key(&w, "timing"); jw_open(&w, '{'); jw_key(&w, "total_ms"); json_num(el); jw_key(&w, "tracing_ms"); json_num(tr); jw_key(&w, "resolving_ms"); json_num(rs); jw_close(&w, '}'); } jw_key(&w, "options"); jw_open(&w, '{'); jw_key(&w, "ecmp_discovery"); jw_bool(sess->ecmp); jw_key(&w, "adaptive_tcp"); jw_bool(sess->adaptive); jw_key(&w, "resolve_names"); jw_bool(sess->resolve_names); jw_key(&w, "asn_lookup"); jw_bool(sess->do_aslookup); jw_key(&w, "netname_lookup"); jw_bool(sess->do_netlookup); jw_close(&w, '}'); jw_close(&w, '}'); jw_key(&w, "source"); jw_open(&w, '{'); json_endpoint(sess, 0, buf, sizeof buf); jw_key(&w, "addr"); json_str(buf); jw_key(&w, "port"); if (has_port) printf("%d", sess->sport); else jw_null(); jw_close(&w, '}'); jw_key(&w, "target"); jw_open(&w, '{'); jw_key(&w, "input"); json_str(sess->hostname); json_endpoint(sess, 1, buf, sizeof buf); jw_key(&w, "addr"); json_str(buf); jw_key(&w, "port"); if (has_port) printf("%d", sess->dport); else jw_null(); jw_close(&w, '}'); jw_key(&w, "result"); jw_open(&w, '{'); jw_key(&w, "state"); json_str(state); jw_key(&w, "ttl"); if (found) printf("%d", tgt_ttl); else jw_null(); jw_close(&w, '}'); jw_key(&w, "hops"); jw_open(&w, '['); for (ttl = ttl_lo; ttl <= ttl_hi; ttl++) { const struct diag_node *c = at[ttl]; const char *b, *f; int is_tgt, m; jw_sep(&w); jw_open(&w, '{'); jw_key(&w, "ttl"); printf("%d", ttl); if (!c) { /* silent TTL */ jw_key(&w, "kind"); json_str("silent"); jw_key(&w, "boundary"); jw_null(); jw_key(&w, "firewall"); jw_null(); jw_key(&w, "responders"); jw_open(&w, '['); jw_close(&w, ']'); jw_close(&w, '}'); continue; } b = json_boundary(c); f = json_firewall(c); is_tgt = (c->type == DT_TARGET_OPEN || c->type == DT_TARGET_CLOSED || c->type == DT_TARGET_FILTERED); jw_key(&w, "kind"); json_str(json_kind(c)); jw_key(&w, "boundary"); if (b) json_str(b); else jw_null(); jw_key(&w, "firewall"); if (f) json_str(f); else jw_null(); jw_key(&w, "responders"); jw_open(&w, '['); for (m = 0; m < c->n_members; m++) { jw_sep(&w); json_responder(&w, &c->member[m], is_tgt && has_port, sess->dport); } jw_close(&w, ']'); jw_close(&w, '}'); } jw_close(&w, ']'); jw_close(&w, '}'); putchar('\n'); if (oldloc) { setlocale(LC_NUMERIC, oldloc); free(oldloc); } } /*========================================================================= * Geographic outputs — the path on a map. * --geojson (style 8): RFC 7946 FeatureCollection. One Point Feature per * located responder (source, every ECMP member, target) with the --json * responder fields (ttl/addr/hostname/asn/netname/special_use/boundary/ * firewall/RTT stats) plus city/country, a speed-of-light plausibility * verdict, simplestyle marker keys and title/description. One segment * Feature per consecutive pair of located hops — a geodesic * MultiLineString split at the antimeridian — carrying from/to TTL, * distance, RTT delta, AS change and the TTLs it spans. The "lft" * foreign member holds run metadata, the silent TTLs and the responders * that could not be located, so nothing measured is lost. * --kml (style 9): Google Earth document with Hops / Path / Unlocated * folders, a Schema + ExtendedData table per placemark, short on-globe * labels ("9 · Los Angeles · 102 ms"), a LookAt framing the route, and * the path as smooth great-circle arches (relativeToGround, peak scaled * to the hop distance; --geo-arc 0 flattens). --geo-color RRGGBB|rtt and * --geo-width style the path in both formats. * Geolocation is the pWhoIs bulk lookup (extended form), so the geo styles * imply the ASN lookup. A hop whose RTT is below the speed-of-light floor * for its distance from the source is kept but flagged implausible (the * location is usually the registrant's, or an anycast address). Coincident * hops get a small deterministic display offset; true coordinates are kept. * IPv6 hops currently have no geolocation (the bulk geo path is IPv4). *=========================================================================*/ #ifndef M_PI #define M_PI 3.14159265358979323846 #endif #define GEO_LIGHT_KM_PER_MS 100.0 /* ~200 km/ms in fibre, halved for the round trip */ #define GEO_STEP_M 150000.0/* geodesic sample spacing */ #define GEO_PTS_MAX (DIAG_MAX + 64) #define GEO_SAMPLES_MAX 50 static void geo_coord(double v) { if (isfinite(v)) printf("%.6f", v); else fputs("null", stdout); } /* great-circle interpolation between two lat/lon points (degrees), t in [0,1] */ static void geo_slerp(double la1, double lo1, double la2, double lo2, double t, double *la, double *lo) { double p1 = la1*M_PI/180, l1 = lo1*M_PI/180, p2 = la2*M_PI/180, l2 = lo2*M_PI/180; double x1 = cos(p1)*cos(l1), y1 = cos(p1)*sin(l1), z1 = sin(p1); double x2 = cos(p2)*cos(l2), y2 = cos(p2)*sin(l2), z2 = sin(p2); double d = x1*x2 + y1*y2 + z1*z2, om, a, b, x, y, z; if (d > 1) d = 1; if (d < -1) d = -1; om = acos(d); if (om < 1e-9) { a = 1 - t; b = t; } else { a = sin((1-t)*om)/sin(om); b = sin(t*om)/sin(om); } x = a*x1 + b*x2; y = a*y1 + b*y2; z = a*z1 + b*z2; *la = atan2(z, sqrt(x*x + y*y)) * 180/M_PI; *lo = atan2(y, x) * 180/M_PI; } static double geo_dist_m(double la1, double lo1, double la2, double lo2) { double p1 = la1*M_PI/180, l1 = lo1*M_PI/180, p2 = la2*M_PI/180, l2 = lo2*M_PI/180; double d = cos(p1)*cos(l1)*cos(p2)*cos(l2) + cos(p1)*sin(l1)*cos(p2)*sin(l2) + sin(p1)*sin(p2); if (d > 1) d = 1; if (d < -1) d = -1; return acos(d) * 6371000.0; } /* sample the geodesic between two points; returns the point count (>= 2) */ static int geo_sample(double la1, double lo1, double la2, double lo2, double *la, double *lo, double *dist_m) { double d = geo_dist_m(la1, lo1, la2, lo2); int n = (int)(d / GEO_STEP_M), k; if (n < 4) n = 4; if (n > GEO_SAMPLES_MAX - 1) n = GEO_SAMPLES_MAX - 1; for (k = 0; k <= n; k++) geo_slerp(la1, lo1, la2, lo2, (double)k / n, &la[k], &lo[k]); la[n] = la2; lo[n] = lo2; if (dist_m) *dist_m = d; return n + 1; } /* per-kind accent colours (CSS hex, no '#') — match the SVG palette */ static const char *geo_kind_color(const struct diag_node *c) { if (!c) return "4f8fd6"; /* source */ switch (c->type) { case DT_TARGET_OPEN: return "17b890"; case DT_TARGET_CLOSED: return "e6a13c"; case DT_TARGET_FILTERED: return "e5484d"; case DT_FW_SMART: case DT_FW_STUPID: return "d9743a"; case DT_SEAM: return "8b6cd9"; default: return "6b7f99"; } } static const char *geo_kml_style(const struct diag_node *c, int plausible) { if (!plausible) return "suspect"; if (!c) return "source"; switch (c->type) { case DT_TARGET_OPEN: return "topen"; case DT_TARGET_CLOSED: return "tclosed"; case DT_TARGET_FILTERED: return "tfiltered"; case DT_FW_SMART: case DT_FW_STUPID: return "firewall"; case DT_SEAM: return "boundary"; default: return "router"; } } /* simplestyle marker-symbol: Maki name, or a digit for a numbered pin */ static const char *geo_symbol(const struct diag_node *c, int ttl, int plausible, char *buf, size_t sz) { if (!plausible) return "circle-stroked"; if (!c) return "home"; switch (c->type) { case DT_TARGET_OPEN: case DT_TARGET_CLOSED: case DT_TARGET_FILTERED: return "star"; case DT_FW_SMART: case DT_FW_STUPID: return "roadblock"; default: break; } if (ttl >= 1 && ttl <= 9) { snprintf(buf, sz, "%d", ttl); return buf; } return "circle"; } /* CSS RRGGBB -> KML aabbggrr */ static void kml_abgr(const char *rrggbb, const char *alpha, char *out, size_t sz) { if (strlen(rrggbb) == 6) snprintf(out, sz, "%s%c%c%c%c%c%c", alpha, rrggbb[4], rrggbb[5], rrggbb[2], rrggbb[3], rrggbb[0], rrggbb[1]); else snprintf(out, sz, "%s90b817", alpha); } /* --geo-color rtt: 1..300 ms log scale through a green-yellow-red ramp */ static void geo_rtt_color(double ms, char *out, size_t sz) { static const unsigned char stop[5][3] = { {0x2b,0xb5,0x7e}, {0x9c,0xd6,0x3a}, {0xf2,0xc0,0x37}, {0xf0,0x7b,0x2f}, {0xd9,0x3b,0x3b} }; double t, f; int i, ch, v[3]; if (!(ms > 1)) ms = 1; t = log10(ms) / log10(300.0); if (t > 1) t = 1; f = t * 4; i = (int)f; if (i > 3) i = 3; f -= i; for (ch = 0; ch < 3; ch++) v[ch] = (int)(stop[i][ch] + (stop[i+1][ch] - stop[i][ch]) * f + 0.5); snprintf(out, sz, "%02x%02x%02x", v[0], v[1], v[2]); } /* stroke colour for the segment ending at 'to' (RRGGBB, no '#') */ static void geo_segment_color(lft_session_params *sess, const struct diag_member *to, char *out, size_t sz) { if (strcmp(sess->geo_color, "rtt") == 0) { if (to && to->nrtt) geo_rtt_color(to->rtt_sum / to->nrtt, out, sz); else snprintf(out, sz, "17b890"); } else snprintf(out, sz, "%s", sess->geo_color); } /* the located points of the trace in path order, plus what was left out */ struct geo_pt { double lat, lon; /* displayed (coincident points are nudged apart) */ double tlat, tlon; /* as geolocated */ const struct diag_node *node; /* NULL = source */ int member; /* ECMP member index; 0 lies on the path */ int is_source, ttl; int prev_ttl; /* previous located hop (0 = source) */ int skip_first, skip_last; /* silent TTLs since it (0 = none) */ int n_unloc; /* responding but unlocated hops since it */ double dist_src_m, floor_ms; int plausible, offset_k; }; struct geo_unloc { int ttl; char addr[128]; char reason[48]; }; struct geo_ctx { struct geo_pt pts[GEO_PTS_MAX]; int np; int path[GEO_PTS_MAX]; int npath; /* indices of pts on the path (member 0) */ struct geo_unloc unloc[DIAG_MAX]; int nunloc; char silent[256]; int n_hops; /* responding hop levels, located or not */ double path_m; double bbox[4]; /* W S E N */ }; static const char *geo_member_addr(const struct diag_member *mm, int strip, int port, char *buf, size_t sz) { snprintf(buf, sz, "%s", mm->ip[0] ? mm->ip : mm->host); if (strip && !mm->ip[0]) json_strip_port(buf, port); return buf; } static const char *geo_member_host(const struct diag_member *mm, int strip, int port, char *buf, size_t sz) { if (!mm->ip[0]) return NULL; snprintf(buf, sz, "%s", mm->host); if (strip) json_strip_port(buf, port); return buf; } static void geo_bbox_add(struct geo_ctx *g, double lat, double lon) { if (lon < g->bbox[0]) g->bbox[0] = lon; if (lat < g->bbox[1]) g->bbox[1] = lat; if (lon > g->bbox[2]) g->bbox[2] = lon; if (lat > g->bbox[3]) g->bbox[3] = lat; } static void geo_silent_add(struct geo_ctx *g, int a, int b) { size_t l = strlen(g->silent); if (a == b) snprintf(g->silent + l, sizeof g->silent - l, "%s%d", l ? "," : "", a); else snprintf(g->silent + l, sizeof g->silent - l, "%s%d-%d", l ? "," : "", a, b); } static void geo_collect(lft_session_params *sess, struct diag_node *nodes, int n, struct geo_ctx *g) { int i, m, prev_ttl = 0, skip_first = 0, skip_last = 0, n_unloc = 0; int has_port = (sess->protocol < 2 || sess->protocol > 3); memset(g, 0, sizeof *g); g->bbox[0] = g->bbox[1] = 1e9; g->bbox[2] = g->bbox[3] = -1e9; if (sess->src_geo_ok) { struct geo_pt *p = &g->pts[g->np++]; p->tlat = p->lat = sess->src_geo_lat; p->tlon = p->lon = sess->src_geo_lon; p->is_source = 1; p->plausible = 1; } for (i = 0; i < n; i++) { const struct diag_node *c = &nodes[i]; int is_tgt = (c->type == DT_TARGET_OPEN || c->type == DT_TARGET_CLOSED || c->type == DT_TARGET_FILTERED); int located = 0; if (c->type == DT_SOURCE) continue; if (c->type == DT_HOLE) { geo_silent_add(g, c->hopno, c->hopno_end); if (!skip_first) skip_first = c->hopno; skip_last = c->hopno_end; continue; } g->n_hops++; for (m = 0; m < c->n_members; m++) { const struct diag_member *mm = &c->member[m]; if (mm->geo_ok && g->np < GEO_PTS_MAX) { struct geo_pt *p = &g->pts[g->np++]; p->tlat = p->lat = mm->geo_lat; p->tlon = p->lon = mm->geo_lon; p->node = c; p->member = m; p->ttl = c->hopno; p->prev_ttl = prev_ttl; p->skip_first = skip_first; p->skip_last = skip_last; p->n_unloc = n_unloc; p->plausible = 1; if (m == 0) located = 1; } else if (!mm->geo_ok && g->nunloc < DIAG_MAX) { struct geo_unloc *u = &g->unloc[g->nunloc++]; u->ttl = c->hopno; geo_member_addr(mm, is_tgt && has_port, sess->dport, u->addr, sizeof u->addr); snprintf(u->reason, sizeof u->reason, "%s", mm->special_use[0] ? mm->special_use : "no_geo"); } } if (located) { prev_ttl = c->hopno; skip_first = skip_last = 0; n_unloc = 0; } else n_unloc++; } /* plausibility against the source, coincident-point nudge, path, bbox */ for (i = 0; i < g->np; i++) { struct geo_pt *p = &g->pts[i]; int j, k = 0; if (!p->is_source && sess->src_geo_ok) { const struct diag_member *mm = &p->node->member[p->member]; p->dist_src_m = geo_dist_m(sess->src_geo_lat, sess->src_geo_lon, p->tlat, p->tlon); p->floor_ms = p->dist_src_m / 1000.0 / GEO_LIGHT_KM_PER_MS; p->plausible = (mm->nrtt == 0) || (mm->rtt_min >= p->floor_ms); } for (j = 0; j < i; j++) if (fabs(g->pts[j].tlat - p->tlat) < 1e-4 && fabs(g->pts[j].tlon - p->tlon) < 1e-4) k++; if (k) { double r = 0.02 * k, a = k * 137.5 * M_PI / 180; p->lat += r * cos(a); p->lon += r * sin(a) / (cos(p->lat * M_PI / 180) + 1e-9); p->offset_k = k; } if (p->is_source || p->member == 0) g->path[g->npath++] = i; geo_bbox_add(g, p->lat, p->lon); } for (i = 0; i + 1 < g->npath; i++) { const struct geo_pt *a = &g->pts[g->path[i]], *b = &g->pts[g->path[i+1]]; double la[GEO_SAMPLES_MAX], lo[GEO_SAMPLES_MAX], d; int ns = geo_sample(a->lat, a->lon, b->lat, b->lon, la, lo, &d), k; g->path_m += d; for (k = 0; k < ns; k++) geo_bbox_add(g, la[k], lo[k]); } } static const char *geo_proto(lft_session_params *sess) { switch (sess->protocol) { case 1: return "udp"; case 2: return "icmp"; case 3: return "icmp_rfc1393"; default: return "tcp"; } } static const char *geo_target_state(const struct diag_node *c) { if (!c) return NULL; if (c->type == DT_TARGET_OPEN || c->type == DT_TARGET_CLOSED || c->type == DT_TARGET_FILTERED) return c->note; return NULL; } /* "Virginia Beach, Virginia, United States of America (US)" from the parts present */ static void geo_place(const char *city, const char *region, const char *country, const char *cc, char *buf, size_t sz) { size_t l = 0; buf[0] = '\0'; if (city[0]) { snprintf(buf + l, sz - l, "%s", city); l = strlen(buf); } if (region[0] && strcmp(region, city) != 0) { snprintf(buf + l, sz - l, "%s%s", l ? ", " : "", region); l = strlen(buf); } if (country[0]) { snprintf(buf + l, sz - l, "%s%s", l ? ", " : "", country); l = strlen(buf); } if (cc[0]) snprintf(buf + l, sz - l, "%s(%s)", l ? " " : "", cc); } static void geo_note(const struct geo_pt *p, const struct diag_member *mm, char *buf, size_t sz) { if (p->plausible) { buf[0] = '\0'; return; } snprintf(buf, sz, "RTT %.1f ms is below the %.1f ms speed-of-light floor for %.0f km from the source; " "the location is probably the registrant's or an anycast address", mm->rtt_min, p->floor_ms, p->dist_src_m / 1000.0); } static void geo_skipped(const struct geo_pt *p, char *buf, size_t sz) { if (!p->skip_first) { buf[0] = '\0'; return; } if (p->skip_first == p->skip_last) snprintf(buf, sz, "%d", p->skip_first); else snprintf(buf, sz, "%d-%d", p->skip_first, p->skip_last); } /* "TTL 9 · Los Angeles · 102 ms" style label */ static void geo_title(const struct geo_pt *p, const struct diag_member *mm, int kml, char *buf, size_t sz) { const char *city = p->is_source ? "" : mm->geo_city; if (p->is_source) { snprintf(buf, sz, "source%s%s", city[0] ? " · " : "", city); return; } snprintf(buf, sz, kml ? "%d" : "TTL %d", p->ttl); if (city[0]) { size_t l = strlen(buf); snprintf(buf + l, sz - l, " · %s", city); } if (mm->nrtt) { size_t l = strlen(buf); snprintf(buf + l, sz - l, " · %.0f ms", mm->rtt_sum / mm->nrtt); } if (geo_target_state(p->node)) { size_t l = strlen(buf); snprintf(buf + l, sz - l, " · target %s", p->node->note); } if (!p->plausible) { size_t l = strlen(buf); snprintf(buf + l, sz - l, " (?)"); } } /* geodesic between two path points as GeoJSON MultiLineString parts, split at ±180 */ static void geojson_geodesic(struct jw *w, const struct geo_pt *a, const struct geo_pt *b) { double la[GEO_SAMPLES_MAX], lo[GEO_SAMPLES_MAX]; int ns = geo_sample(a->lat, a->lon, b->lat, b->lon, la, lo, NULL), k; jw_open(w, '['); /* parts */ jw_sep(w); jw_open(w, '['); /* first part */ for (k = 0; k < ns; k++) { if (k && fabs(lo[k] - lo[k-1]) > 180) { /* crossed the antimeridian */ double e1 = lo[k-1] > 0 ? 180 : -180, f, lc; f = (e1 - lo[k-1]) / ((lo[k] + (lo[k-1] > 0 ? 360 : -360)) - lo[k-1]); lc = la[k-1] + (la[k] - la[k-1]) * f; jw_sep(w); jw_open(w, '['); jw_sep(w); geo_coord(e1); jw_sep(w); geo_coord(lc); jw_close(w, ']'); jw_close(w, ']'); jw_sep(w); jw_open(w, '['); jw_sep(w); jw_open(w, '['); jw_sep(w); geo_coord(-e1); jw_sep(w); geo_coord(lc); jw_close(w, ']'); } jw_sep(w); jw_open(w, '['); jw_sep(w); geo_coord(lo[k]); jw_sep(w); geo_coord(la[k]); jw_close(w, ']'); } jw_close(w, ']'); jw_close(w, ']'); } static void geojson_point(struct jw *w, lft_session_params *sess, const struct geo_ctx *g, int idx) { const struct geo_pt *p = &g->pts[idx]; const struct diag_node *c = p->node; const struct diag_member *mm = c ? &c->member[p->member] : NULL; int has_port = (sess->protocol < 2 || sess->protocol > 3); int is_tgt = c && geo_target_state(c) != NULL; char abuf[160], hbuf[160], buf[480], sym[8], place[160]; const char *addr, *hn, *s; if (p->is_source) { json_endpoint(sess, 0, abuf, sizeof abuf); addr = abuf; hn = NULL; } else { addr = geo_member_addr(mm, is_tgt && has_port, sess->dport, abuf, sizeof abuf); hn = geo_member_host(mm, is_tgt && has_port, sess->dport, hbuf, sizeof hbuf); } jw_sep(w); jw_open(w, '{'); jw_key(w, "type"); json_str("Feature"); if (p->is_source) snprintf(buf, sizeof buf, "source"); else if (p->member) snprintf(buf, sizeof buf, "hop-%d-%d", p->ttl, p->member + 1); else snprintf(buf, sizeof buf, "hop-%d", p->ttl); jw_key(w, "id"); json_str(buf); jw_key(w, "geometry"); jw_open(w, '{'); jw_key(w, "type"); json_str("Point"); jw_key(w, "coordinates"); jw_open(w, '['); jw_sep(w); geo_coord(p->lon); jw_sep(w); geo_coord(p->lat); jw_close(w, ']'); jw_close(w, '}'); jw_key(w, "properties"); jw_open(w, '{'); jw_key(w, "kind"); json_str(p->is_source ? "source" : json_kind(c)); jw_key(w, "ttl"); printf("%d", p->ttl); jw_key(w, "addr"); json_str(addr); jw_key(w, "hostname"); if (hn) json_str(hn); else jw_null(); jw_key(w, "asn"); if (mm && mm->asnum) printf("%d", mm->asnum); else jw_null(); jw_key(w, "netname"); if (mm && mm->net[0]) json_str(mm->net); else jw_null(); jw_key(w, "special_use"); if (mm && mm->special_use[0]) json_str(mm->special_use); else jw_null(); jw_key(w, "boundary"); s = c ? json_boundary(c) : NULL; if (s) json_str(s); else jw_null(); jw_key(w, "firewall"); s = c ? json_firewall(c) : NULL; if (s) json_str(s); else jw_null(); jw_key(w, "target_state"); s = geo_target_state(c); if (s) json_str(s); else jw_null(); jw_key(w, "port"); if (is_tgt && has_port) printf("%d", sess->dport); else jw_null(); { const char *ci = p->is_source ? sess->src_geo_city : mm->geo_city, *re = p->is_source ? sess->src_geo_region : mm->geo_region; const char *co = p->is_source ? sess->src_geo_country : mm->geo_country, *cc = p->is_source ? sess->src_geo_cc : mm->geo_cc; jw_key(w, "city"); if (ci[0]) json_str(ci); else jw_null(); jw_key(w, "region"); if (re[0]) json_str(re); else jw_null(); jw_key(w, "country"); if (co[0]) json_str(co); else jw_null(); jw_key(w, "country_code"); if (cc[0]) json_str(cc); else jw_null(); geo_place(ci, re, co, cc, place, sizeof place); jw_key(w, "place"); if (place[0]) json_str(place); else jw_null(); } jw_key(w, "rtt_count"); printf("%d", mm ? mm->nrtt : 0); jw_key(w, "rtt_min_ms"); if (mm && mm->nrtt) json_num(mm->rtt_min); else jw_null(); jw_key(w, "rtt_avg_ms"); if (mm && mm->nrtt) json_num(mm->rtt_sum / mm->nrtt); else jw_null(); jw_key(w, "rtt_max_ms"); if (mm && mm->nrtt) json_num(mm->rtt_max); else jw_null(); jw_key(w, "rtt_stddev_ms"); if (mm && mm->nrtt > 1) { double n = mm->nrtt, mean = mm->rtt_sum / n, var = mm->rtt_sumsq / n - mean * mean; json_num(var > 0 ? sqrt(var) : 0); } else jw_null(); jw_key(w, "dist_from_source_km"); if (!p->is_source && sess->src_geo_ok) json_num(p->dist_src_m / 1000.0); else jw_null(); jw_key(w, "light_floor_ms"); if (!p->is_source && sess->src_geo_ok) json_num(p->floor_ms); else jw_null(); jw_key(w, "geo_plausible"); jw_bool(p->plausible); geo_note(p, mm, buf, sizeof buf); jw_key(w, "geo_note"); if (buf[0]) json_str(buf); else jw_null(); jw_key(w, "prev_located_ttl"); if (p->is_source) jw_null(); else printf("%d", p->prev_ttl); geo_skipped(p, buf, sizeof buf); jw_key(w, "skipped_ttls"); if (buf[0]) json_str(buf); else jw_null(); jw_key(w, "unlocated_between"); printf("%d", p->n_unloc); jw_key(w, "ecmp_index"); if (c && c->n_members > 1) printf("%d", p->member + 1); else jw_null(); jw_key(w, "ecmp_members"); if (c && c->n_members > 1) printf("%d", c->n_members); else jw_null(); jw_key(w, "display_offset"); jw_bool(p->offset_k > 0); jw_key(w, "true_coordinates"); if (p->offset_k) { jw_open(w, '['); jw_sep(w); geo_coord(p->tlon); jw_sep(w); geo_coord(p->tlat); jw_close(w, ']'); } else jw_null(); geo_title(p, mm, 0, buf, sizeof buf); jw_key(w, "title"); json_str(buf); if (p->is_source) snprintf(buf, sizeof buf, "%s%s%s", addr, place[0] ? " · " : "", place); else { size_t l; snprintf(buf, sizeof buf, "%s%s%s%s", addr, hn ? " (" : "", hn ? hn : "", hn ? ")" : ""); l = strlen(buf); if (mm->asnum) { snprintf(buf + l, sizeof buf - l, " · AS%d%s%s", mm->asnum, mm->net[0] ? " " : "", mm->net); l = strlen(buf); } else if (mm->special_use[0]) { snprintf(buf + l, sizeof buf - l, " · %s", mm->special_use); l = strlen(buf); } if (mm->nrtt > 1) snprintf(buf + l, sizeof buf - l, " · %.1f/%.1f/%.1f ms", mm->rtt_min, mm->rtt_sum / mm->nrtt, mm->rtt_max); else if (mm->nrtt) snprintf(buf + l, sizeof buf - l, " · %.1f ms", mm->rtt_min); l = strlen(buf); if (place[0]) snprintf(buf + l, sizeof buf - l, " · %s", place); l = strlen(buf); if (!p->plausible) snprintf(buf + l, sizeof buf - l, " · location implausible for the RTT"); } jw_key(w, "description"); json_str(buf); snprintf(buf, sizeof buf, "#%s", p->plausible ? geo_kind_color(c) : "9aa4b1"); jw_key(w, "marker-color"); json_str(buf); jw_key(w, "marker-size"); json_str(is_tgt || p->is_source ? "medium" : "small"); jw_key(w, "marker-symbol"); json_str(geo_symbol(c, p->ttl, p->plausible, sym, sizeof sym)); jw_close(w, '}'); jw_close(w, '}'); } static void geojson_segment(struct jw *w, lft_session_params *sess, const struct geo_ctx *g, int ia, int ib) { const struct geo_pt *a = &g->pts[ia], *b = &g->pts[ib]; const struct diag_member *ma = a->node ? &a->node->member[a->member] : NULL; const struct diag_member *mb = &b->node->member[b->member]; int has_port = (sess->protocol < 2 || sess->protocol > 3); int suspect = !a->plausible || !b->plausible; double d = geo_dist_m(a->lat, a->lon, b->lat, b->lon); char abuf[160], bbuf[160], buf[320], col[8]; const char *fa, *fb; const char *ca = a->is_source ? sess->src_geo_city : ma->geo_city, *cb = mb->geo_city; if (a->is_source) { json_endpoint(sess, 0, abuf, sizeof abuf); fa = abuf; } else fa = geo_member_addr(ma, geo_target_state(a->node) && has_port, sess->dport, abuf, sizeof abuf); fb = geo_member_addr(mb, geo_target_state(b->node) && has_port, sess->dport, bbuf, sizeof bbuf); jw_sep(w); jw_open(w, '{'); jw_key(w, "type"); json_str("Feature"); snprintf(buf, sizeof buf, "seg-%d-%d", a->ttl, b->ttl); jw_key(w, "id"); json_str(buf); jw_key(w, "geometry"); jw_open(w, '{'); jw_key(w, "type"); json_str("MultiLineString"); jw_key(w, "coordinates"); geojson_geodesic(w, a, b); jw_close(w, '}'); jw_key(w, "properties"); jw_open(w, '{'); jw_key(w, "kind"); json_str("segment"); jw_key(w, "from_ttl"); printf("%d", a->ttl); jw_key(w, "to_ttl"); printf("%d", b->ttl); jw_key(w, "from_addr"); json_str(fa); jw_key(w, "to_addr"); json_str(fb); jw_key(w, "from_city"); if (ca[0]) json_str(ca); else jw_null(); jw_key(w, "to_city"); if (cb[0]) json_str(cb); else jw_null(); jw_key(w, "from_asn"); if (ma && ma->asnum) printf("%d", ma->asnum); else jw_null(); jw_key(w, "to_asn"); if (mb->asnum) printf("%d", mb->asnum); else jw_null(); jw_key(w, "as_change"); jw_bool(ma && ma->asnum && mb->asnum && ma->asnum != mb->asnum); geo_skipped(b, buf, sizeof buf); jw_key(w, "skipped_ttls"); if (buf[0]) json_str(buf); else jw_null(); jw_key(w, "unlocated_between"); printf("%d", b->n_unloc); jw_key(w, "distance_km"); json_num(d / 1000.0); jw_key(w, "rtt_from_ms"); if (ma && ma->nrtt) json_num(ma->rtt_sum / ma->nrtt); else jw_null(); jw_key(w, "rtt_to_ms"); if (mb->nrtt) json_num(mb->rtt_sum / mb->nrtt); else jw_null(); jw_key(w, "rtt_delta_ms"); if (ma && ma->nrtt && mb->nrtt) json_num(mb->rtt_sum / mb->nrtt - ma->rtt_sum / ma->nrtt); else jw_null(); jw_key(w, "light_floor_ms"); json_num(d / 1000.0 / GEO_LIGHT_KM_PER_MS); jw_key(w, "suspect"); jw_bool(suspect); jw_key(w, "ecmp_members"); if (b->node->n_members > 1) printf("%d", b->node->n_members); else jw_null(); { size_t l; snprintf(buf, sizeof buf, "%d → %d", a->ttl, b->ttl); l = strlen(buf); if (ca[0] || cb[0]) { snprintf(buf + l, sizeof buf - l, " %s → %s", ca[0] ? ca : "?", cb[0] ? cb : "?"); l = strlen(buf); } snprintf(buf + l, sizeof buf - l, " %.0f km", d / 1000.0); l = strlen(buf); if (ma && ma->nrtt && mb->nrtt) { snprintf(buf + l, sizeof buf - l, " Δ%+.1f ms", mb->rtt_sum / mb->nrtt - ma->rtt_sum / ma->nrtt); l = strlen(buf); } if (ma && ma->asnum && mb->asnum && ma->asnum != mb->asnum) snprintf(buf + l, sizeof buf - l, " AS%d → AS%d", ma->asnum, mb->asnum); } jw_key(w, "title"); json_str(buf); geo_segment_color(sess, mb, col, sizeof col); snprintf(buf, sizeof buf, "#%s", col); jw_key(w, "stroke"); json_str(buf); jw_key(w, "stroke-width"); printf("%d", sess->geo_width); jw_key(w, "stroke-opacity"); printf(suspect ? "0.35" : "0.9"); jw_close(w, '}'); jw_close(w, '}'); } void GeoJSONOutput(lft_session_params *sess) { static struct diag_node nodes[DIAG_MAX]; static struct geo_ctx g; int n = lft_build_diag(sess, nodes), i; struct jw w; char buf[160], *oldloc = NULL; const char *cur = setlocale(LC_NUMERIC, NULL); if (cur) oldloc = strdup(cur); setlocale(LC_NUMERIC, "C"); geo_collect(sess, nodes, n, &g); memset(&w, 0, sizeof w); w.pretty = 1; w.first[0] = 1; jw_open(&w, '{'); jw_key(&w, "type"); json_str("FeatureCollection"); snprintf(buf, sizeof buf, "lft trace to %s", sess->hostname); jw_key(&w, "title"); json_str(buf); jw_key(&w, "lft"); jw_open(&w, '{'); /* foreign member: run metadata */ jw_key(&w, "generator"); snprintf(buf, sizeof buf, "lft %s", version); json_str(buf); jw_key(&w, "target"); json_str(sess->hostname); json_endpoint(sess, 1, buf, sizeof buf); jw_key(&w, "target_addr"); json_str(buf); jw_key(&w, "protocol"); json_str(geo_proto(sess)); jw_key(&w, "hops_responding"); printf("%d", g.n_hops); jw_key(&w, "located_points"); printf("%d", g.np); jw_key(&w, "path_km"); if (g.npath >= 2) json_num(g.path_m / 1000.0); else jw_null(); jw_key(&w, "silent_ttls"); if (g.silent[0]) json_str(g.silent); else jw_null(); jw_key(&w, "unlocated"); jw_open(&w, '['); for (i = 0; i < g.nunloc; i++) { jw_sep(&w); jw_open(&w, '{'); jw_key(&w, "ttl"); printf("%d", g.unloc[i].ttl); jw_key(&w, "addr"); json_str(g.unloc[i].addr); jw_key(&w, "reason"); json_str(g.unloc[i].reason); jw_close(&w, '}'); } jw_close(&w, ']'); jw_key(&w, "plausibility"); json_str("geo_plausible is false when rtt_min is under the speed-of-light floor (100 km per ms, round trip) for the distance from the located source"); jw_close(&w, '}'); if (g.np) { jw_key(&w, "bbox"); jw_open(&w, '['); for (i = 0; i < 4; i++) { jw_sep(&w); geo_coord(g.bbox[i]); } jw_close(&w, ']'); } jw_key(&w, "features"); jw_open(&w, '['); for (i = 0; i < g.np; i++) geojson_point(&w, sess, &g, i); for (i = 0; i + 1 < g.npath; i++) geojson_segment(&w, sess, &g, g.path[i], g.path[i+1]); jw_close(&w, ']'); jw_close(&w, '}'); putchar('\n'); if (oldloc) { setlocale(LC_NUMERIC, oldloc); free(oldloc); } } /* ---- KML ---- */ static void kml_sd_str(const char *name, const char *v) { if (!v || !v[0]) return; printf("", name); diag_xesc(v); printf(""); } static void kml_sd_int(const char *name, int v) { printf("%d", name, v); } static void kml_sd_num(const char *name, double v) { if (isfinite(v)) printf("%.3f", name, v); } static void kml_sd_bool(const char *name, int v) { printf("%s", name, v ? "true" : "false"); } static void kml_point(lft_session_params *sess, const struct geo_ctx *g, int idx) { const struct geo_pt *p = &g->pts[idx]; const struct diag_node *c = p->node; const struct diag_member *mm = c ? &c->member[p->member] : NULL; int has_port = (sess->protocol < 2 || sess->protocol > 3); int is_tgt = c && geo_target_state(c) != NULL; char abuf[160], hbuf[160], buf[320], place[160]; const char *addr, *hn, *s; const char *ci = p->is_source ? sess->src_geo_city : mm->geo_city, *re = p->is_source ? sess->src_geo_region : mm->geo_region; const char *co = p->is_source ? sess->src_geo_country : mm->geo_country, *cc = p->is_source ? sess->src_geo_cc : mm->geo_cc; if (p->is_source) { json_endpoint(sess, 0, abuf, sizeof abuf); addr = abuf; hn = NULL; } else { addr = geo_member_addr(mm, is_tgt && has_port, sess->dport, abuf, sizeof abuf); hn = geo_member_host(mm, is_tgt && has_port, sess->dport, hbuf, sizeof hbuf); } geo_place(ci, re, co, cc, place, sizeof place); if (p->is_source) printf(" \n"); else if (p->member) printf(" \n", p->ttl, p->member + 1); else printf(" \n", p->ttl); geo_title(p, mm, 1, buf, sizeof buf); printf(" "); diag_xesc(buf); printf("\n"); printf(" "); if (hn) diag_xesc(hn); else diag_xesc(addr); if (mm && mm->asnum) printf(" · AS%d", mm->asnum); printf("\n"); printf(" "); /* HTML, entity-escaped */ printf("<b>"); diag_xesc(addr); printf("</b>"); if (hn) { printf("<br/>"); diag_xesc(hn); } if (mm && mm->asnum) { printf("<br/>AS%d", mm->asnum); if (mm->net[0]) { printf(" "); diag_xesc(mm->net); } } else if (mm && mm->special_use[0]) { printf("<br/>"); diag_xesc(mm->special_use); } if (mm && mm->nrtt > 1) printf("<br/>rtt %.1f / %.1f / %.1f ms (%d samples)", mm->rtt_min, mm->rtt_sum / mm->nrtt, mm->rtt_max, mm->nrtt); else if (mm && mm->nrtt) printf("<br/>rtt %.1f ms", mm->rtt_min); if (place[0]) { printf("<br/>"); diag_xesc(place); } if (!p->is_source) { if (sess->src_geo_ok) printf("<br/>%.0f km from the source (light floor %.1f ms)", p->dist_src_m / 1000.0, p->floor_ms); if (c->n_members > 1) printf("<br/>ECMP member %d of %d", p->member + 1, c->n_members); if (p->skip_first) { geo_skipped(p, buf, sizeof buf); printf("<br/>silent TTL %s before this hop", buf); } if (p->n_unloc) printf("<br/>%d unlocated hop%s before this one", p->n_unloc, p->n_unloc == 1 ? "" : "s"); if (p->offset_k) printf("<br/>marker nudged for display; true position %.4f,%.4f", p->tlat, p->tlon); geo_note(p, mm, buf, sizeof buf); if (buf[0]) { printf("<br/><i>"); diag_xesc(buf); printf("</i>"); } } printf("\n"); printf(" #%s\n", geo_kml_style(c, p->plausible)); printf(" "); kml_sd_int("ttl", p->ttl); kml_sd_str("addr", addr); kml_sd_str("hostname", hn); if (mm && mm->asnum) kml_sd_int("asn", mm->asnum); kml_sd_str("netname", mm ? mm->net : NULL); kml_sd_str("special_use", mm ? mm->special_use : NULL); kml_sd_str("kind", p->is_source ? "source" : json_kind(c)); s = c ? json_boundary(c) : NULL; kml_sd_str("boundary", s); s = c ? json_firewall(c) : NULL; kml_sd_str("firewall", s); kml_sd_str("target_state", geo_target_state(c)); kml_sd_str("city", ci); kml_sd_str("region", re); kml_sd_str("country", co); kml_sd_str("country_code", cc); kml_sd_str("place", place); kml_sd_int("rtt_count", mm ? mm->nrtt : 0); if (mm && mm->nrtt) { kml_sd_num("rtt_min_ms", mm->rtt_min); kml_sd_num("rtt_avg_ms", mm->rtt_sum / mm->nrtt); kml_sd_num("rtt_max_ms", mm->rtt_max); } if (mm && mm->nrtt > 1) { double n = mm->nrtt, mean = mm->rtt_sum / n, var = mm->rtt_sumsq / n - mean * mean; kml_sd_num("rtt_stddev_ms", var > 0 ? sqrt(var) : 0); } if (!p->is_source && sess->src_geo_ok) { kml_sd_num("dist_from_source_km", p->dist_src_m / 1000.0); kml_sd_num("light_floor_ms", p->floor_ms); } kml_sd_bool("geo_plausible", p->plausible); if (!p->is_source) kml_sd_int("prev_located_ttl", p->prev_ttl); geo_skipped(p, buf, sizeof buf); kml_sd_str("skipped_ttls", buf); kml_sd_int("unlocated_between", p->n_unloc); if (c && c->n_members > 1) { kml_sd_int("ecmp_index", p->member + 1); kml_sd_int("ecmp_members", c->n_members); } printf("\n"); printf(" %.6f,%.6f,0\n \n", p->lon, p->lat); } static void kml_segment(lft_session_params *sess, const struct geo_ctx *g, int ia, int ib) { const struct geo_pt *a = &g->pts[ia], *b = &g->pts[ib]; const struct diag_member *ma = a->node ? &a->node->member[a->member] : NULL; const struct diag_member *mb = &b->node->member[b->member]; int has_port = (sess->protocol < 2 || sess->protocol > 3); int suspect = !a->plausible || !b->plausible; double la[GEO_SAMPLES_MAX], lo[GEO_SAMPLES_MAX], d, peak; int ns = geo_sample(a->lat, a->lon, b->lat, b->lon, la, lo, &d), k; char abuf[160], bbuf[160], buf[320], col[8], abgr[12]; const char *fa, *fb; const char *ca = a->is_source ? sess->src_geo_city : ma->geo_city, *cb = mb->geo_city; if (a->is_source) { json_endpoint(sess, 0, abuf, sizeof abuf); fa = abuf; } else fa = geo_member_addr(ma, geo_target_state(a->node) && has_port, sess->dport, abuf, sizeof abuf); fb = geo_member_addr(mb, geo_target_state(b->node) && has_port, sess->dport, bbuf, sizeof bbuf); printf(" \n", a->ttl, b->ttl); snprintf(buf, sizeof buf, "%d → %d", a->ttl, b->ttl); if (ca[0] || cb[0]) { size_t l = strlen(buf); snprintf(buf + l, sizeof buf - l, " · %s → %s", ca[0] ? ca : "?", cb[0] ? cb : "?"); } { size_t l = strlen(buf); snprintf(buf + l, sizeof buf - l, " · %.0f km", d / 1000.0); } printf(" "); diag_xesc(buf); printf("\n"); printf(" "); printf("<b>TTL %d → %d</b><br/>", a->ttl, b->ttl); diag_xesc(fa); printf(" → "); diag_xesc(fb); printf("<br/>%.0f km great circle (light floor %.1f ms)", d / 1000.0, d / 1000.0 / GEO_LIGHT_KM_PER_MS); if (ma && ma->nrtt && mb->nrtt) printf("<br/>rtt %.1f → %.1f ms (Δ%+.1f)", ma->rtt_sum / ma->nrtt, mb->rtt_sum / mb->nrtt, mb->rtt_sum / mb->nrtt - ma->rtt_sum / ma->nrtt); else if (mb->nrtt) printf("<br/>rtt %.1f ms", mb->rtt_sum / mb->nrtt); if (ma && ma->asnum && mb->asnum && ma->asnum != mb->asnum) printf("<br/>AS%d → AS%d", ma->asnum, mb->asnum); else if (mb->asnum) printf("<br/>AS%d", mb->asnum); if (b->skip_first) { geo_skipped(b, buf, sizeof buf); printf("<br/>silent TTL %s along the way", buf); } if (b->n_unloc) printf("<br/>%d unlocated hop%s along the way", b->n_unloc, b->n_unloc == 1 ? "" : "s"); if (suspect) printf("<br/><i>an endpoint's location is implausible for its RTT</i>"); printf("\n"); geo_segment_color(sess, mb, col, sizeof col); kml_abgr(col, suspect ? "59" : "e6", abgr, sizeof abgr); printf(" \n", abgr, sess->geo_width); printf(" "); kml_sd_int("from_ttl", a->ttl); kml_sd_int("to_ttl", b->ttl); kml_sd_str("from_addr", fa); kml_sd_str("to_addr", fb); kml_sd_str("from_city", ca); kml_sd_str("to_city", cb); if (ma && ma->asnum) kml_sd_int("from_asn", ma->asnum); if (mb->asnum) kml_sd_int("to_asn", mb->asnum); kml_sd_bool("as_change", ma && ma->asnum && mb->asnum && ma->asnum != mb->asnum); geo_skipped(b, buf, sizeof buf); kml_sd_str("skipped_ttls", buf); kml_sd_int("unlocated_between", b->n_unloc); kml_sd_num("distance_km", d / 1000.0); if (ma && ma->nrtt) kml_sd_num("rtt_from_ms", ma->rtt_sum / ma->nrtt); if (mb->nrtt) kml_sd_num("rtt_to_ms", mb->rtt_sum / mb->nrtt); if (ma && ma->nrtt && mb->nrtt) kml_sd_num("rtt_delta_ms", mb->rtt_sum / mb->nrtt - ma->rtt_sum / ma->nrtt); kml_sd_num("light_floor_ms", d / 1000.0 / GEO_LIGHT_KM_PER_MS); kml_sd_bool("suspect", suspect); printf("\n"); printf(" \n %s\n %d\n \n", sess->geo_arc > 0 ? "relativeToGround" : "clampToGround", sess->geo_arc > 0 ? 0 : 1); peak = sess->geo_arc * 0.15 * d; /* arch height, scaled to the hop */ if (peak > 1.2e6) peak = 1.2e6; for (k = 0; k < ns; k++) { double t = (double)k / (ns - 1), alt = (sess->geo_arc > 0) ? peak * sin(M_PI * t) : 0; printf(" %.6f,%.6f,%.0f\n", lo[k], la[k], alt); } printf(" \n \n \n"); } void KMLOutput(lft_session_params *sess) { static struct diag_node nodes[DIAG_MAX]; static struct geo_ctx g; int n = lft_build_diag(sess, nodes), i; char abgr[12], buf[160], *oldloc = NULL; const char *cur = setlocale(LC_NUMERIC, NULL); static const struct { const char *id, *rgb, *icon; } styles[] = { { "source", "4f8fd6", "shapes/homegardenbusiness.png" }, { "router", "6b7f99", "shapes/placemark_circle.png" }, { "boundary", "8b6cd9", "shapes/placemark_circle.png" }, { "firewall", "d9743a", "shapes/caution.png" }, { "topen", "17b890", "shapes/star.png" }, { "tclosed", "e6a13c", "shapes/star.png" }, { "tfiltered", "e5484d", "shapes/star.png" }, { "suspect", "9aa4b1", "shapes/open-diamond.png" }, }; if (cur) oldloc = strdup(cur); setlocale(LC_NUMERIC, "C"); geo_collect(sess, nodes, n, &g); printf("\n"); printf("\n\n"); printf(" LFT trace to "); diag_xesc(sess->hostname); printf("\n 1\n"); printf(" %d located point%s", g.np, g.np == 1 ? "" : "s"); if (g.npath >= 2) printf(" · %.0f km", g.path_m / 1000.0); if (g.np && g.pts[g.np-1].node && g.pts[g.np-1].node->member[0].nrtt) printf(" · %.0f ms", g.pts[g.np-1].node->member[0].rtt_sum / g.pts[g.np-1].node->member[0].nrtt); printf("\n"); json_endpoint(sess, 1, buf, sizeof buf); printf(" %s trace to ", geo_proto(sess)); diag_xesc(buf); printf(" — %d responding hop%s, %d located", g.n_hops, g.n_hops == 1 ? "" : "s", g.np); if (g.silent[0]) printf("; silent TTL %s", g.silent); if (g.nunloc) printf("; %d unlocated (see folder)", g.nunloc); printf(". Hollow diamonds mark locations implausible for the measured RTT. Generated by lft %s\n", version); if (g.np) { /* frame the route */ double x = 0, y = 0, z = 0, cla, clo, maxd = 0, range; for (i = 0; i < g.np; i++) { double p = g.pts[i].lat*M_PI/180, l = g.pts[i].lon*M_PI/180; x += cos(p)*cos(l); y += cos(p)*sin(l); z += sin(p); } cla = atan2(z, sqrt(x*x + y*y)) * 180/M_PI; clo = atan2(y, x) * 180/M_PI; for (i = 0; i < g.np; i++) { double d = geo_dist_m(cla, clo, g.pts[i].lat, g.pts[i].lon); if (d > maxd) maxd = d; } range = 2.8 * maxd; if (range < 5e5) range = 5e5; if (range > 2.5e7) range = 2.5e7; printf(" %.6f%.6f0%.0f00\n", clo, cla, range); } printf(" " "" "" "" "" "" "" "" "" "" "\n"); printf(" " "" "" "" "" "\n"); for (i = 0; i < (int)(sizeof styles / sizeof styles[0]); i++) { kml_abgr(styles[i].rgb, "e6", abgr, sizeof abgr); printf(" \n", styles[i].id, abgr, strncmp(styles[i].id, "t", 1) == 0 || strcmp(styles[i].id, "source") == 0 ? "1.1" : "0.9", styles[i].icon); } printf(" \n Hops\n 1\n"); for (i = 0; i < g.np; i++) kml_point(sess, &g, i); printf(" \n"); if (g.npath >= 2) { printf(" \n Path\n"); for (i = 0; i + 1 < g.npath; i++) kml_segment(sess, &g, g.path[i], g.path[i+1]); printf(" \n"); } if (g.nunloc) { /* geometry-less placemarks: listed, not drawn */ printf(" \n Unlocated hops\n 0\n"); for (i = 0; i < g.nunloc; i++) { printf(" \n %d · ", g.unloc[i].ttl, i, g.unloc[i].ttl); diag_xesc(g.unloc[i].addr); printf("\n no geolocation ("); diag_xesc(g.unloc[i].reason); printf(")\n \n"); } printf(" \n"); } printf("\n\n"); if (oldloc) { setlocale(LC_NUMERIC, oldloc); free(oldloc); } } int outputStyleIsGeoJSON(void) { return global_output_style==8; } int outputStyleIsKML(void) { return global_output_style==9; } int outputStyleIsJSON(void) { return global_output_style==7; } int outputStyleIsSVG(void) { return global_output_style==5; } int outputStyleIsMermaid(void) { return global_output_style==6; } lft-4.01/lft_probe6.c000644 000765 000024 00000003751 15231735015 014365 0ustar00vicstaff000000 000000 /* lft_probe6.c — pure IPv6 probe helpers (LFT 4.0). See lft_probe6.h. No session/pcap/network deps; unit-tested by tests/probe6_test.c. The live send path is added at integration time (guarded by HAVE_IPV6). */ #include "lft_probe6.h" #include lft_l3_kind lft_l3_dispatch(const unsigned char *l3, size_t len) { if (!l3 || len == 0) return LFT_L3_DROP; switch (l3[0] >> 4) { case 4: return (len >= 20) ? LFT_L3_V4 : LFT_L3_DROP; case 6: return (len >= 40) ? LFT_L3_V6 : LFT_L3_DROP; default: return LFT_L3_DROP; } } lft_icmp6_kind lft_icmp6_classify(int type, int code) { (void)code; switch (type) { case 1: return LFT_ICMP6_UNREACH; /* Destination Unreachable */ case 2: return LFT_ICMP6_TOO_BIG; /* Packet Too Big */ case 3: return LFT_ICMP6_TIME_EXCEEDED; /* Time Exceeded */ case 129: return LFT_ICMP6_ECHO_REPLY; /* Echo Reply */ default: return LFT_ICMP6_OTHER; } } /* Ones-complement 16-bit accumulate, big-endian pairing, odd-length safe. * Callers pass buffers whose combined length preserves 2-byte alignment * across calls (the 40-byte pseudo-header is even, so the payload starts * aligned). */ static uint32_t csum_accum(const unsigned char *b, size_t n, uint32_t sum) { size_t i; for (i = 0; i + 1 < n; i += 2) sum += (uint32_t)((b[i] << 8) | b[i + 1]); if (n & 1) sum += (uint32_t)(b[n - 1] << 8); return sum; } uint16_t lft_cksum6(const lft_addr_t *src, const lft_addr_t *dst, uint8_t proto, const void *ulp, uint16_t ulplen) { unsigned char ph[40]; uint32_t sum = 0; if (lft_pack_pseudo_header(src, dst, proto, ulplen, ph) != 40) return 0; sum = csum_accum(ph, 40, sum); sum = csum_accum((const unsigned char *)ulp, ulplen, sum); while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16); return (uint16_t)(~sum & 0xffff); } lft-4.01/INSTALL000644 000765 000024 00000030341 15163651453 013212 0ustar00vicstaff000000 000000 ####################################################### ## Installation Instructions for Layer Four Traceroute ####################################################### Instructions are provided for different platforms below. ####################################################### On WINDOWS: ####################################################### Building this program on Windows is possible without UNIX-like compatibility environments. To compile the program, use the Microsoft Visual Studio Express edition which is free and may be downloaded from http://www.microsoft.com You must also download and install the Windows Platform SDK which is available from the same web site. The platform SDK provides the necessary Winsock header files. Please read the documentation related to fully installing the Windows Platform SDK for use with Visual Studio Express or your alternative compiler to ensure all necessary files are placed in the appropriate locations, specifically the header files. Using VS Express, you may build LFT by following these instructions: Open a Command Prompt window (cmd.exe), change directories to the "lft" folder you downloaded, and type the following commands (from inside the LFT folder): "%VS80COMNTOOLS%vsvars32.bat" nmake -f makefile.vc This will create lft.exe and whob.exe in the current folder. Of course, you may move the files wherever you choose. Depending on the Windows platform, you may or may not have TCP raw sockets functionality, causing TCP traces to fail. However, in this case ICMP and UDP traces should still work. You may also have to explicitly allow lft.exe to use the network by creating a rule in Windows (or your 3rd-party) Firewall. ####################################################### On UNIX-like operating systems: ####################################################### Optional Dependencies ===================== LFT requires libpcap (mandatory) and optionally uses c-ares for asynchronous DNS resolution. c-ares eliminates the post-trace serial hostname lookup delay (typically 180-260ms per trace) and is strongly recommended. It is auto-detected by ./configure; install it before running ./configure to enable it: Debian/Ubuntu: apt-get install libc-ares-dev RHEL/Fedora: dnf install c-ares-devel macOS Homebrew: brew install c-ares FreeBSD ports: dns/c-ares MacPorts: port install c-ares ./configure searches the standard system paths plus common third-party prefix directories (/opt/homebrew, /usr/local, /opt/local, /opt/pkg) automatically, so no manual LDFLAGS or CPPFLAGS are needed on most systems. Some special options to consider when running './configure' are: 1. Using '--enable-gtod' is useful on platforms where BPF timestamps are not precise. This forces LFT to call gettimeofday() on each packet instead of relying on the timestamp in the packet's pcap header. 2. Using '--enable-universal' will automatically compile universal binaries on macOS for arm64 (Apple Silicon) and x86_64 (Intel) architectures. 3. Using '--disable-async-dns' disables c-ares support at compile time even if the library is installed. 4. Using '--with-cares=PATH' specifies the installation prefix for c-ares when it is installed in a non-standard location (e.g. --with-cares=/opt/cares). 5. Using '--with-pcap=PATH' specifies the installation prefix for libpcap when it is installed in a non-standard location. Below are generic installation instructions. The `configure' shell script attempts to guess correct values for various system-dependent variables used during compilation. It uses those values to create a `Makefile' in each directory of the package. It may also create one or more `.h' files containing system-dependent definitions. Finally, it creates a shell script `config.status' that you can run in the future to recreate the current configuration, a file `config.cache' that saves the results of its tests to speed up reconfiguring, and a file `config.log' containing compiler output (useful mainly for debugging `configure'). If you need to do unusual things to compile the package, please try to figure out how `configure' could check whether to do them, and mail diffs or instructions to the address given in the `README' so they can be considered for the next release. If at some point `config.cache' contains results you don't want to keep, you may remove or edit it. The file `config/configure.ac' is used to create `configure' by a program called `autoconf'. You only need `config/configure.ac' if you want to change it or regenerate `configure' using a newer version of `autoconf'. To regenerate `configure' from the project root, run: autoconf config/configure.ac > configure The simplest way to compile this package is: 1. `cd' to the directory containing the package's source code and type `./configure' to configure the package for your system. If you're using `csh' on an old version of System V, you might need to type `sh ./configure' instead to prevent `csh' from trying to execute `configure' itself. Running `configure' takes awhile. While running, it prints some messages telling which features it is checking for. 2. Type `make' to compile the package. 3. Optionally, type `make check' to run any self-tests that come with the package. 4. Type `make install' (as root or with sudo) to install the programs and any data files and documentation. On Linux, the installer will use setcap(8) to grant lft the raw socket and packet capture capabilities it needs without installing it setuid root. On FreeBSD and other platforms, lft is installed setuid root. In both cases root access is required to run `make install'. On macOS, lft requires setuid root. This is because lft uses two separate privilege paths: libpcap (via /dev/bpf*) for capturing response packets, and a raw socket (SOCK_RAW) for sending probe packets. BPF device permissions (e.g., ChmodBPF / access_bpf group) only cover the capture side -- they do not grant the ability to create raw sockets, which on macOS requires root. Unlike Linux, macOS has no setcap equivalent to selectively grant raw socket capability to a binary. `make install' performs this step automatically when run as root. If you are installing manually: sudo chown root lft && sudo chmod u+s lft This is standard practice for network diagnostic tools on macOS -- ping and traceroute ship setuid root for the same reason. 5. You can remove the program binaries and object files from the source code directory by typing `make clean'. To also remove the files that `configure' created (so you can compile the package for a different kind of computer), type `make distclean'. There is also a `make maintainer-clean' target, but that is intended mainly for the package's developers. If you use it, you may have to get all sorts of other programs in order to regenerate files that came with the distribution. Compilers and Options ===================== Some systems require unusual options for compilation or linking that the `configure' script does not know about. You can give `configure' initial values for variables by setting them in the environment. Using a Bourne-compatible shell, you can do that on the command line like this: CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure Or on systems that have the `env' program, you can do it like this: env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure Compiling For Multiple Architectures ==================================== You can compile the package for more than one kind of computer at the same time, by placing the object files for each architecture in their own directory. To do this, you must use a version of `make' that supports the `VPATH' variable, such as GNU `make'. `cd' to the directory where you want the object files and executables to go and run the `configure' script. `configure' automatically checks for the source code in the directory that `configure' is in and in `..'. If you have to use a `make' that does not supports the `VPATH' variable, you have to compile the package for one architecture at a time in the source code directory. After you have installed the package for one architecture, use `make distclean' before reconfiguring for another architecture. Installation Names ================== By default, `make install' will install the package's files in `/usr/local/bin', `/usr/local/man', etc. You can specify an installation prefix other than `/usr/local' by giving `configure' the option `--prefix=PATH'. You can specify separate installation prefixes for architecture-specific files and architecture-independent files. If you give `configure' the option `--exec-prefix=PATH', the package will use PATH as the prefix for installing programs and libraries. Documentation and other data files will still use the regular prefix. In addition, if you use an unusual directory layout you can give options like `--bindir=PATH' to specify different values for particular kinds of files. Run `configure --help' for a list of the directories you can set and what kinds of files go in them. If the package supports it, you can cause programs to be installed with an extra prefix or suffix on their names by giving `configure' the option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. Optional Features ================= Some packages pay attention to `--enable-FEATURE' options to `configure', where FEATURE indicates an optional part of the package. They may also pay attention to `--with-PACKAGE' options, where PACKAGE is something like `gnu-as' or `x' (for the X Window System). The `README' should mention any `--enable-' and `--with-' options that the package recognizes. For packages that use the X Window System, `configure' can usually find the X include and library files automatically, but if it doesn't, you can use the `configure' options `--x-includes=DIR' and `--x-libraries=DIR' to specify their locations. Specifying the System Type ========================== There may be some features `configure' can not figure out automatically, but needs to determine by the type of host the package will run on. Usually `configure' can figure that out, but if it prints a message saying it can not guess the host type, give it the `--host=TYPE' option. TYPE can either be a short name for the system type, such as `sun4', or a canonical name with three fields: CPU-COMPANY-SYSTEM See the file `config.sub' for the possible values of each field. If `config.sub' isn't included in this package, then this package doesn't need to know the host type. If you are building compiler tools for cross-compiling, you can also use the `--target=TYPE' option to select the type of system they will produce code for and the `--build=TYPE' option to select the type of system on which you are compiling the package. Sharing Defaults ================ If you want to set default values for `configure' scripts to share, you can create a site shell script called `config.site' that gives default values for variables like `CC', `cache_file', and `prefix'. `configure' looks for `PREFIX/share/config.site' if it exists, then `PREFIX/etc/config.site' if it exists. Or, you can set the `CONFIG_SITE' environment variable to the location of the site script. A warning: not all `configure' scripts look for a site script. Operation Controls ================== `configure' recognizes the following options to control how it operates. `--cache-file=FILE' Use and save the results of the tests in FILE instead of `./config.cache'. Set FILE to `/dev/null' to disable caching, for debugging `configure'. `--help' Print a summary of the options to `configure', and exit. `--quiet' `--silent' `-q' Do not print messages saying which checks are being made. To suppress all normal output, redirect it to `/dev/null' (any error messages will still be shown). `--srcdir=DIR' Look for the package's source code in directory DIR. Usually `configure' can determine that directory automatically. `--version' Print the version of Autoconf used to generate the `configure' script, and exit. `configure' also accepts some other, not widely useful, options. lft-4.01/lft_addr.c000644 000765 000024 00000011633 15231735015 014100 0ustar00vicstaff000000 000000 /* lft_addr.c — address-family-agnostic address value type and helpers. Pure module (no pcap/session/network). All parse/format via inet_pton/inet_ntop; only the fully-expanded v6 form and the checksum pseudo-headers are hand-built. See lft_addr.h. Unit-tested by tests/addr_test.c. Introduced for LFT 4.0 IPv6. */ #include "lft_addr.h" #include #include #include #include int lft_addr_from_ss(const struct sockaddr_storage *ss, lft_addr_t *out) { if (!ss || !out) return 0; if (ss->ss_family == AF_INET) { const struct sockaddr_in *s = (const struct sockaddr_in *)(const void *)ss; out->af = AF_INET; out->a.v4 = s->sin_addr; return 1; } if (ss->ss_family == AF_INET6) { const struct sockaddr_in6 *s = (const struct sockaddr_in6 *)(const void *)ss; out->af = AF_INET6; out->a.v6 = s->sin6_addr; return 1; } return 0; } int lft_addr_parse(const char *s, lft_addr_t *out) { struct in6_addr v6; struct in_addr v4; if (!s || !*s || !out) return 0; if (strchr(s, '/')) /* not a bare address (CIDR) */ return 0; if (strchr(s, ':')) { /* candidate v6 */ if (inet_pton(AF_INET6, s, &v6) != 1) return 0; if (IN6_IS_ADDR_V4MAPPED(&v6)) { out->af = AF_INET; memcpy(&out->a.v4, &v6.s6_addr[12], 4); return 1; } out->af = AF_INET6; out->a.v6 = v6; return 1; } if (inet_pton(AF_INET, s, &v4) == 1) { out->af = AF_INET; out->a.v4 = v4; return 1; } return 0; } const char *lft_ntop(const lft_addr_t *a, char *buf, size_t sz) { return lft_ntop_fmt(a, buf, sz, 0); } const char *lft_ntop_fmt(const lft_addr_t *a, char *buf, size_t sz, int expand) { if (!buf || sz == 0) return buf; buf[0] = '\0'; if (!a) return buf; if (a->af == AF_INET) { inet_ntop(AF_INET, &a->a.v4, buf, (socklen_t)sz); return buf; } if (a->af == AF_INET6) { if (!expand) { inet_ntop(AF_INET6, &a->a.v6, buf, (socklen_t)sz); } else { const unsigned char *b = a->a.v6.s6_addr; snprintf(buf, sz, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:" "%02x%02x:%02x%02x:%02x%02x:%02x%02x", b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]); } return buf; } return buf; } int lft_addr_eq(const lft_addr_t *x, const lft_addr_t *y) { if (!x || !y || x->af != y->af) return 0; if (x->af == AF_INET) return memcmp(&x->a.v4, &y->a.v4, sizeof(struct in_addr)) == 0; if (x->af == AF_INET6) return memcmp(&x->a.v6, &y->a.v6, sizeof(struct in6_addr)) == 0; return 0; } int lft_addr_is_v6(const lft_addr_t *a) { return a && a->af == AF_INET6; } size_t lft_pack_pseudo_header(const lft_addr_t *src, const lft_addr_t *dst, uint8_t proto, uint16_t plen, void *out) { unsigned char *p = (unsigned char *)out; if (!src || !dst || !out || src->af != dst->af) return 0; if (src->af == AF_INET) { uint16_t nlen = htons(plen); memcpy(p, &src->a.v4, 4); memcpy(p + 4, &dst->a.v4, 4); p[8] = 0; p[9] = proto; memcpy(p + 10, &nlen, 2); return 12; } if (src->af == AF_INET6) { uint32_t nlen = htonl((uint32_t)plen); memcpy(p, &src->a.v6, 16); memcpy(p + 16, &dst->a.v6, 16); memcpy(p + 32, &nlen, 4); p[36] = 0; p[37] = 0; p[38] = 0; p[39] = proto; return 40; } return 0; } int lft_resolve_af(const char *host, int force_af, struct sockaddr_storage *out, int *out_af) { struct addrinfo hints, *res, *ai; lft_addr_t probe; int rc; if (!host || !*host || !out || !out_af) return 0; memset(&hints, 0, sizeof hints); hints.ai_family = (force_af == AF_INET || force_af == AF_INET6) ? force_af : AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; /* we only want the address */ /* Numeric literal fast path: skip DNS and AI_ADDRCONFIG gating so a v6 * literal resolves even on a host with no global v6 configured. */ hints.ai_flags = lft_addr_parse(host, &probe) ? AI_NUMERICHOST : AI_ADDRCONFIG; if (getaddrinfo(host, NULL, &hints, &res) != 0) return 0; rc = 0; for (ai = res; ai; ai = ai->ai_next) { if (ai->ai_family == AF_INET || ai->ai_family == AF_INET6) { memcpy(out, ai->ai_addr, ai->ai_addrlen); *out_af = ai->ai_family; rc = 1; break; } } freeaddrinfo(res); return rc; } lft-4.01/whois.c000644 000765 000024 00000441120 15250413042 013442 0ustar00vicstaff000000 000000 /* * Handle communication with whois servers. * * This file is part of the Prefix WhoIs Project. * See http://pwhois.org * * The full text of our legal notices is contained in the file called * COPYING, included with this Distribution. * * This software includes: * - simplified access to regular expressions * - tokenizer * - lookup functions for AS, NETNAME, ORGNAME * - works with the following sources: * ARIN, RIPE, APNIC, RADB, CYMRU, PWHOIS, RISWHOIS * - will do recursive lookups * - convenient framework for further whois digging * * To compile the standalone client: * cc -o whob whois.c -DSTANDALONE * * * Portions (c) Victor Oppleman * Portions (c) 2011 Markus Gothe * Portions (c) 2002 Ugen Antsilevitch (ugen@xonix.com) * */ #include "lft_lib.h" #define PORT_WHOIS 43 #if defined(WIN32) || defined(_WIN32) #define read(a, b, c) recv(a, b, c, 0) #define write(a, b, c) send(a, b, c, 0) #define close(s) closesocket(s) #define snprintf _snprintf #endif #include "whois.h" #ifndef WIN32 #include #include #include #include #include #endif #if defined(BSD_IP_STACK) #define pcap_snprintf snprintf #endif /*#define ASSERT(x) if (!(x)) { fprintf(stderr, "Assertion ("#x") failed\n"); exit(EXIT_FAILURE); }*/ /* OPTIONS and variable initialization */ static char pwhois_server[] = "whois.pwhois.org"; static char myaddress_server[] = "myaddress.today"; static char radb_server[] = "whois.ra.net"; static char cymru_server[] = "whois.cymru.com"; static char arin_server[] = "whois.arin.net"; static char apnic_server[] = "whois.apnic.net"; static char ripe_server[] = "whois.ripe.net"; static char ripe_ris_server[] = "riswhois.ripe.net"; #ifdef STANDALONE const char *version = "4.01"; /* set version string for library and client */ static const char *version_date = "(09/2026)"; /* date of this version */ const char *appname = "WhoB"; /* set application name */ static int go_interactive = 0; /* We'll wait on STDIN unless args suggest otherwise */ static int use_cymru = 0; /* Don't use Cymru by default */ static int use_riswhois = 0; /* Don't use RIPE NCC RIS by default */ static int display_orgname = 1; /* Display orgname by default */ static int display_aspath = 0; /* Don't display AS-PATH by default */ static int display_netname = 0; /* Don't display netname by default */ static int display_radb_as = 0; /* Don't display RADB Origin-AS by default */ static int show_routes_byasn = 0; /* Don't show all advertisements by default */ static int show_routes_bytransitasn = 0; /* Don't show all routes that transit ASN by default */ static int show_networks_byasn = 0; /* Don't show all networks by default */ static int show_contacts_byasn = 0; /* Don't show all contact info by default */ static int show_routes_byprefix = 0; /* Don't show all routes by prefix by default */ static int show_server_status = 0; /* Don't show pwhois server status by default */ static int show_cache_date = 0; /* Don't show pwhois cache date by default */ static int read_from_file = 0; /* Don't read input from file by default */ static int riswhoisfromfile = 0; /* Don't use riswhois by default for readfromfile */ static int cymrufromfile = 0; /* Don't use Cymru by default for readfromfile */ static int use_gigo = 1; /* Use GIGO feature by default */ static int use_stdin = 0; /* Don't use STDIN for bulk file input by default */ static const unsigned int max_hostname_input = 200; /* Maximum length of input from user */ static const int max_lines = 2500; /* Maximum lines to read from bulk file per query */ static const int line_size = 256; /* Maximum line length */ /* --- pWhoIs output colorization (STANDALONE / whob only) ------------------ * Colour is applied to stdout only, and only when stdout is an interactive * terminal (never a file or pipe). --no-color forces it off, --color forces * it on. The palette is "Beacon+": the classic ANSI Beacon scheme with the * field-label colour from Cartographer and the geo colour from Teal Ledger. * A dark and a light variant are chosen automatically from the terminal's * background (OSC 11 query, then COLORFGBG, then a dark default). */ static int w_color_mode = 0; /* 0 = auto, 1 = --color (force on), 2 = --no-color */ static int w_use_color = 0; /* resolved once at startup by w_detect_color() */ static int w_truecolor = 0; /* 1 = emit 24-bit SGR, 0 = 256-colour fallback */ static int w_bg_light = 0; /* 0 = dark background variant, 1 = light */ static char w_highlight_asn[24] = ""; /* queried ASN to bold within AS-Path (-a/-A) */ /* field groups, used both to pick a colour and to index the palette */ enum { WF_LABEL = 0, WF_ASN, WF_PREFIX, WF_ORG, WF_GEO, WF_DATE, WF_NGROUPS, WF_NONE = -1 }; /* one palette entry: exact RGB (truecolor) plus nearest xterm-256 index */ typedef struct { unsigned char r, g, b; unsigned char x256; } wc_color; /* Cartographer palette. [0] = dark-background variant, [1] = light. */ static const wc_color w_palette[2][WF_NGROUPS] = { /* DARK */ { { 0x7a, 0xa2, 0xc8, 110 }, /* label — steel blue */ { 0xe0, 0xa4, 0x58, 179 }, /* asn — amber */ { 0x6c, 0xc4, 0xa1, 79 }, /* prefix — emerald */ { 0xe8, 0xe6, 0xdf, 254 }, /* org — warm off-white*/ { 0xe0, 0x86, 0x5a, 173 }, /* geo — terracotta */ { 0x7c, 0x84, 0x71, 101 }, /* date — slate */ }, /* LIGHT */ { { 0x35, 0x68, 0x8f, 24 }, /* label — steel blue */ { 0xa8, 0x66, 0x12, 130 }, /* asn — dark amber */ { 0x1f, 0x7a, 0x5a, 29 }, /* prefix — deep emerald */ { 0x17, 0x17, 0x16, 233 }, /* org — near black */ { 0xbf, 0x4a, 0x2a, 166 }, /* geo — burnt sienna */ { 0x8a, 0x8a, 0x80, 245 }, /* date — warm grey */ }, }; /* Table-only date shades so Create / Modify / Originated columns read apart. * [bg][0=Create,1=Modify,2=Originated]. Modify mirrors the base date colour. */ static const wc_color w_date_shade[2][3] = { /* DARK */ { { 0x6c, 0x8a, 0x86, 66 }, /* Create — teal-slate */ { 0x7c, 0x84, 0x71, 101 }, /* Modify — slate */ { 0xa0, 0x8a, 0x5f, 137 } }, /* Originated — tan-slate */ /* LIGHT */ { { 0x3f, 0x74, 0x6e, 23 }, /* Create — teal */ { 0x6a, 0x6a, 0x60, 241 }, /* Modify — grey */ { 0x8a, 0x6a, 0x3a, 94 } }, /* Originated — tan */ }; #define WC_RESET "\033[0m" #define WC_BOLD "\033[1m" #define WC_DIM "\033[2m" #define OPT_W_COLOR 256 /* --color (long option, force colour on) */ #define OPT_W_NO_COLOR 257 /* --no-color (long option, force colour off) */ #define OPT_W_ANNOTATE 258 /* --annotate[=FILE]: enrich IPs in text via pWhoIs */ #define OPT_W_FIELDS 259 /* -F/--fields LIST: which pWhoIs fields the annotation shows */ static void w_print_pwhois_colorized(const char *reply); /* defined below */ static void w_print_status_colorized(const char *reply); /* defined below */ static char hostname[256]; #endif /* END of OPTIONS and variable initialization */ #if defined(WIN32) || defined(_WIN32) char * index(char *s, char c) { char *t; if (!s) return NULL; for (t = s; *t; t++) if (*t == c) { return t; } /* Return terminating \0 if specifically requested */ if (c == '\0') return t; return NULL; } #endif #if defined(WIN32) || defined(_WIN32) int inet_aton(const char *cp, struct in_addr *pin) { if (!pin) return -1; pin->s_addr = inet_addr(cp); return (pin->s_addr != -1) ? 1 : 0; } #endif typedef struct token_s { char *ptr; } token_t; static token_t * tokens(char *buf, const char *sep) { char *c, *c1; int size, cur; token_t *rt; if (!buf || !sep) return NULL; size = 1; for (c = buf; *c ; c++) if (index(sep, *c)) { size++; while (*c && index(sep, *c)) c++; } size++; /* for the NULL */ if (!(rt = (token_t *)malloc(size * sizeof(token_t)))) return NULL; memset(rt, 0, size * sizeof(token_t)); rt[0].ptr = buf; cur = 0; for (c = buf; *c ; c++) { if (index(sep, *c)) { c1 = c; while (*c && index(sep, *c)) c++; if (*c) rt[++cur].ptr = c; *c1 = '\0'; } } rt[++cur].ptr = NULL; return rt; } typedef struct ip_blk_s { unsigned int start; unsigned int end; } ip_blk_t; static ip_blk_t * w_blk2range(char *s_start, char *s_end) { struct in_addr in; unsigned int s, e; ip_blk_t *r; if (!s_start || !s_end) return NULL; if (!inet_aton(s_start, &in)) return NULL; s = ntohl(in.s_addr); if (!inet_aton(s_end, &in)) return NULL; e = ntohl(in.s_addr); if (!(r = malloc(sizeof(ip_blk_t)))) return NULL; r->start = s; r->end = e; return r; } static ip_blk_t * w_mask2range(char *addr, char *mask) { struct in_addr in; unsigned int s, m; ip_blk_t *r; if (!addr || !mask) return NULL; m = (unsigned int)strtoul(mask, (char **)NULL, 10); if (m > 32) return NULL; if (!inet_aton(addr, &in)) return NULL; s = ntohl(in.s_addr); if (!(r = malloc(sizeof(ip_blk_t)))) return NULL; r->start = s &~ (((unsigned)0xffffffff) >> m); r->end = s | (((unsigned)0xffffffff) >> m); return r; } static int rm_spaces(char* str) { /* Remove spaces (isspace()) from anywhere within a string ONLY operates on a null-terminated (\0) string! */ int j = -1; unsigned int i; if (!str) return 0; for (i=0; i<=strlen(str); i++) if (!(isspace((unsigned char)*(str+i)))) *(str+(++j)) = *(str+i); else continue; return 1; } static char *match_prefix(const char *prefix, const char *target) { /* Target will be something like "origin: AS22773" and prefix will be "origin:" and * we return a pointer to "AS22773" */ while (*prefix) { if (tolower((unsigned char)*prefix) != tolower((unsigned char)*target)) return NULL; prefix++; target++; } while (isspace((unsigned char)*target)) target++; /* strip out the leading AS from the number */ if (strncmp(target,"AS",2) == 0) target += 2; return strdup(target); } static ip_blk_t *match_iprange(char *target) { /* matches something like "1.2.3.4-5.6.7.8" */ char *pos, *dash, *beforedash; /* ip_blk_t *out; */ while (isspace((unsigned char)*target)) target++; pos = target; while (*pos && !isspace((unsigned char)*pos)) pos++; beforedash = strdup(target); beforedash[pos-target] = 0; dash = strchr(target, '-'); if (!dash) return NULL; dash++; while (isspace((unsigned char)*dash)) dash++; return w_blk2range(beforedash, dash); } static ip_blk_t *match_ipprefix(char *target) { /* matches something like 1.2.3.0/24 */ char *slash, *pos; char *beforeslash; /* ip_blk_t *out; */ while (isspace((unsigned char)*target)) target++; pos = target; while (*pos && !isspace((unsigned char)*pos) && *pos != '/') pos++; beforeslash = strdup(target); beforeslash[pos - target] = 0; slash = strchr(target, '/'); if (!slash) return NULL; slash++; return w_mask2range(beforeslash, slash); } static char *match_inparens(char *target) { /* matches something like " (HELLO)" and returns "HELLO" */ char *end, *res; target = strchr(target, '('); if (!target) return NULL; target++; end = strchr(target, ')'); if (!end) return NULL; res = strdup(target); res[end - target] = 0; return res; } static char *match_afterparens(char *target) { /* matches something like " (HELLO) xxx" and returns a pointer to "xxx" */ target = strchr(target, '('); if (!target) return NULL; target = strchr(target, ')'); if (!target) return NULL; target++; while(*target && isspace((unsigned char)*target)) target++; if (*target) return strdup(target); else return NULL; } static int stricontains(const char *haystack, const char *needle) { /* Search for a substring without a string library */ int i, j, match; i = 0, j = 0; while (haystack[i] != '\0') { while (tolower((unsigned char)haystack[i]) != tolower((unsigned char)needle[0]) && haystack[i] != '\0') i++; if (haystack[i] == '\0') return (-1); match = i; while (tolower((unsigned char)haystack[i]) == tolower((unsigned char)needle[j]) && haystack[i] != '\0' && needle[j] != '\0') { i++; j++; } if (needle[j] == '\0') return (match); if (haystack[i] == '\0') return (-1); i = match + 1; j = 0; } return 0; } #ifdef HAVE_CARES #ifdef HAVE_ARES_PROCESS_FDS /* fd-state tracking for w_prefetch()'s private ares_channel (event-model API) */ #define W_ARES_MAX_FDS 4 struct w_ares_state { struct { ares_socket_t fd; unsigned int events; /* mask of ares_fd_eventflag_t */ } fd_table[W_ARES_MAX_FDS]; int nfds; }; static void w_ares_sock_state_cb(void *data, ares_socket_t fd, int readable, int writable) { struct w_ares_state *st = (struct w_ares_state *)data; int i; for (i = 0; i < st->nfds; i++) { if (st->fd_table[i].fd == fd) { if (!readable && !writable) { st->fd_table[i] = st->fd_table[--st->nfds]; } else { st->fd_table[i].events = (readable ? (unsigned)ARES_FD_EVENT_READ : 0u) | (writable ? (unsigned)ARES_FD_EVENT_WRITE : 0u); } return; } } if (!readable && !writable) return; if (st->nfds >= W_ARES_MAX_FDS) return; st->fd_table[st->nfds].fd = fd; st->fd_table[st->nfds].events = (readable ? (unsigned)ARES_FD_EVENT_READ : 0u) | (writable ? (unsigned)ARES_FD_EVENT_WRITE : 0u); st->nfds++; } #endif /* HAVE_ARES_PROCESS_FDS */ /* Callback for w_prefetch() forward (A-record) lookups. */ static void w_ares_callback(void *arg, int status, int timeouts, struct ares_addrinfo *ai) { struct w_dns_entry *entry = (struct w_dns_entry *)arg; (void)timeouts; if (status == ARES_SUCCESS && ai && ai->nodes) { struct sockaddr_in *sin = (struct sockaddr_in *)ai->nodes->ai_addr; memcpy(&entry->addr, &sin->sin_addr, sizeof(struct in_addr)); } if (ai) ares_freeaddrinfo(ai); entry->resolved = 1; /* mark done whether succeeded or failed */ } /* Resolve a single whois server hostname on first use. * Finds the matching cache slot, fires one c-ares A-record lookup via * ares_getaddrinfo(), and drains via ares_process_fds() until resolved or * the 1-second deadline expires. No-op if already resolved or the server * is not in the known-server list. */ static void w_prefetch(whois_session_params *wsess, const char *serv) { int i; struct w_dns_entry *entry = NULL; ares_channel wchan; struct ares_options opts; #ifdef HAVE_ARES_PROCESS_FDS struct w_ares_state st; #endif struct ares_addrinfo_hints hints; if (!wsess || !serv) return; /* find the cache slot for this server */ for (i = 0; i < wsess->w_dns_cache_count; i++) { if (wsess->w_dns_cache[i].host && strcmp(wsess->w_dns_cache[i].host, serv) == 0) { entry = &wsess->w_dns_cache[i]; break; } } if (!entry) return; /* not a known whois server; getaddrinfo handles it */ if (entry->resolved) return; /* already resolved on a previous call */ /* fire a single lookup and drain with a 1-second hard deadline */ memset(&opts, 0, sizeof(opts)); opts.timeout = 500; opts.tries = 2; #ifdef HAVE_ARES_PROCESS_FDS memset(&st, 0, sizeof(st)); opts.sock_state_cb = w_ares_sock_state_cb; opts.sock_state_cb_data = &st; if (ares_init_options(&wchan, &opts, ARES_OPT_TIMEOUTMS | ARES_OPT_TRIES | ARES_OPT_SOCK_STATE_CB) != ARES_SUCCESS) return; #else if (ares_init_options(&wchan, &opts, ARES_OPT_TIMEOUTMS | ARES_OPT_TRIES) != ARES_SUCCESS) return; #endif memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; ares_getaddrinfo(wchan, serv, NULL, &hints, w_ares_callback, entry); { struct timeval deadline, now; gettimeofday(&deadline, NULL); deadline.tv_sec += 1; for (;;) { struct timeval tv, remaining; int nfds = 0; fd_set rfd, wfd; #ifdef HAVE_ARES_PROCESS_FDS ares_fd_events_t events[W_ARES_MAX_FDS]; size_t nevents = 0; int j; #endif if (entry->resolved) break; gettimeofday(&now, NULL); remaining.tv_sec = deadline.tv_sec - now.tv_sec; remaining.tv_usec = deadline.tv_usec - now.tv_usec; if (remaining.tv_usec < 0) { remaining.tv_sec--; remaining.tv_usec += 1000000; } if (remaining.tv_sec < 0) break; tv.tv_sec = 0; tv.tv_usec = (remaining.tv_sec == 0 && remaining.tv_usec < 10000) ? remaining.tv_usec : 10000; FD_ZERO(&rfd); FD_ZERO(&wfd); #ifdef HAVE_ARES_PROCESS_FDS if (st.nfds == 0) { /* No fds yet — drive timeout processing only, then stop */ ares_process_fds(wchan, NULL, 0, 0); break; } for (j = 0; j < st.nfds; j++) { if (st.fd_table[j].events & ARES_FD_EVENT_READ) FD_SET(st.fd_table[j].fd, &rfd); if (st.fd_table[j].events & ARES_FD_EVENT_WRITE) FD_SET(st.fd_table[j].fd, &wfd); if ((int)st.fd_table[j].fd + 1 > nfds) nfds = (int)st.fd_table[j].fd + 1; } if (nfds > 0) select(nfds, &rfd, &wfd, NULL, &tv); for (j = 0; j < st.nfds; j++) { unsigned int ev = ARES_FD_EVENT_NONE; if (FD_ISSET(st.fd_table[j].fd, &rfd)) ev |= ARES_FD_EVENT_READ; if (FD_ISSET(st.fd_table[j].fd, &wfd)) ev |= ARES_FD_EVENT_WRITE; if (ev != ARES_FD_EVENT_NONE) { events[nevents].fd = st.fd_table[j].fd; events[nevents].events = ev; nevents++; } } ares_process_fds(wchan, events, nevents, 0); #else /* classic ares_fds()/ares_process() poll API (c-ares < 1.34) */ /* Intentional for older c-ares; a newer shadowing header marks * these deprecated — expected in this branch, so silence it. */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" nfds = ares_fds(wchan, &rfd, &wfd); if (nfds == 0) { ares_process(wchan, &rfd, &wfd); break; } select(nfds, &rfd, &wfd, NULL, &tv); ares_process(wchan, &rfd, &wfd); #pragma GCC diagnostic pop #endif } } ares_destroy(wchan); } #endif /* HAVE_CARES */ /*---------------------------------------------------------------------------*/ /* Timed getaddrinfo() wrapper for whois server hostname resolution. * On Windows: spawns a thread and waits up to 1 s; kills if it doesn't finish. * On POSIX: arms SIGALRM for 1 s, restores on return. * Keeps the call-sites in w_ask() clean and platform-independent. */ #if defined(WIN32) || defined(_WIN32) struct w_gai_args { const char *serv; struct addrinfo hints; /* copy — thread must not touch caller's stack */ struct addrinfo **res; int rc; }; static DWORD WINAPI w_gai_thread(LPVOID arg) { struct w_gai_args *a = (struct w_gai_args *)arg; a->rc = getaddrinfo(a->serv, NULL, &a->hints, a->res); return 0; } static int w_getaddrinfo_timed(const char *serv, const struct addrinfo *hints, struct addrinfo **res) { struct w_gai_args a; HANDLE th; DWORD tid; a.serv = serv; a.hints = *hints; /* copy the hints struct into our local */ a.res = res; a.rc = EAI_AGAIN; th = CreateThread(NULL, 0, w_gai_thread, &a, 0, &tid); if (!th) return EAI_AGAIN; if (WaitForSingleObject(th, 1000) == WAIT_TIMEOUT) TerminateThread(th, 0); CloseHandle(th); return a.rc; } #else /* POSIX */ static volatile sig_atomic_t w_dns_timed_out; static void w_sigalrm(int sig) { (void)sig; w_dns_timed_out = 1; } static int w_getaddrinfo_timed(const char *serv, const struct addrinfo *hints, struct addrinfo **res) { int rc; w_dns_timed_out = 0; signal(SIGALRM, w_sigalrm); alarm(1); rc = getaddrinfo(serv, NULL, hints, res); alarm(0); signal(SIGALRM, SIG_DFL); if (w_dns_timed_out && rc != 0) rc = EAI_AGAIN; return rc; } #endif /* WIN32 */ /*---------------------------------------------------------------------------*/ whois_session_params *w_init(void) { /* int e; */ whois_session_params *wsess = (whois_session_params *)malloc(sizeof(whois_session_params)); wsess->w_noisy = 0; /* Don't show debug msgs by default */ memset(&(wsess->pw_serv), 0, sizeof(wsess->pw_serv)); wsess->consolidated_asn[0] = wsess->consolidated_asp[0] = wsess->consolidated_route[0] = wsess->consolidated_orgname[0] = wsess->consolidated_netname[0] ='?'; wsess->consolidated_asn[1] = wsess->consolidated_asp[1] = wsess->consolidated_route[1] = wsess->consolidated_orgname[1] = wsess->consolidated_netname[1] =0; memset(&wsess->tbuf, 0, sizeof(wsess->tbuf)); wsess->logprintfCookie = 0; #ifdef HAVE_CARES { /* Register all known whois server hostnames in the DNS cache so * w_prefetch() can find the right slot when w_ask() is first called * for each server. No lookups are fired here; resolution happens * lazily on first use via w_prefetch(). */ static const char *servers[] = { pwhois_server, myaddress_server, radb_server, cymru_server, arin_server, apnic_server, ripe_server, ripe_ris_server }; int ns = (int)(sizeof(servers) / sizeof(servers[0])); int i; memset(wsess->w_dns_cache, 0, sizeof(wsess->w_dns_cache)); wsess->w_dns_cache_count = 0; for (i = 0; i < ns && i < 8; i++) { wsess->w_dns_cache[i].host = servers[i]; wsess->w_dns_cache[i].resolved = 0; memset(&wsess->w_dns_cache[i].addr, 0, sizeof(wsess->w_dns_cache[i].addr)); } wsess->w_dns_cache_count = (ns < 8) ? ns : 8; } #endif /* HAVE_CARES */ return wsess; } whois_session_params * w_reinit(whois_session_params * wsess) { /* int e; */ wsess->w_noisy = 0; /* Don't show debug msgs by default */ memset(&(wsess->pw_serv), 0, sizeof(wsess->pw_serv)); wsess->consolidated_asn[0] = wsess->consolidated_asp[0] = wsess->consolidated_route[0] = wsess->consolidated_orgname[0] = wsess->consolidated_netname[0] = '?'; wsess->consolidated_asn[1] = wsess->consolidated_asp[1] = wsess->consolidated_route[1] = wsess->consolidated_orgname[1] = wsess->consolidated_netname[1] = 0; memset(&wsess->tbuf, 0, sizeof(wsess->tbuf)); wsess->logprintfCookie = 0; return wsess; } __inline__ void w_close(whois_session_params * wsess) { free(wsess); } static char * w_ask(whois_session_params *wsess, const char *serv, const char *q, const char *port) { int s; struct sockaddr_in sin4; struct addrinfo ai_hints, *ai_res; char *br; int q_s, br_s, cur, n, myport; char buf[128], *sendbuf; #ifdef USE_WHOIS_TIMEOUT #if defined(WIN32) || defined(_WIN32) int whreadtimeout = 20000; #else struct timeval whreadtimeout; whreadtimeout.tv_sec = 20; whreadtimeout.tv_usec = 0; #endif #endif if (!serv || !q) return NULL; memset(&sin4, 0, sizeof(sin4)); sin4.sin_family = AF_INET; #ifdef HAVE_CARES { /* Resolve this server on first use (no-op if already cached). */ w_prefetch(wsess, serv); /* Check DNS cache to avoid a blocking getaddrinfo() call; * falls through to getaddrinfo() for unknown/custom servers. */ int _i, _found = 0; for (_i = 0; wsess && _i < wsess->w_dns_cache_count; _i++) { if (wsess->w_dns_cache[_i].host && strcmp(wsess->w_dns_cache[_i].host, serv) == 0 && wsess->w_dns_cache[_i].resolved && wsess->w_dns_cache[_i].addr.s_addr != 0) { memcpy(&sin4.sin_addr, &wsess->w_dns_cache[_i].addr, sizeof(sin4.sin_addr)); _found = 1; break; } } if (!_found) { memset(&ai_hints, 0, sizeof(ai_hints)); ai_hints.ai_family = AF_INET; ai_hints.ai_socktype = SOCK_STREAM; if (w_getaddrinfo_timed(serv, &ai_hints, &ai_res) != 0) return NULL; memcpy(&sin4.sin_addr, &((struct sockaddr_in *)ai_res->ai_addr)->sin_addr, sizeof(sin4.sin_addr)); freeaddrinfo(ai_res); } } #else memset(&ai_hints, 0, sizeof(ai_hints)); ai_hints.ai_family = AF_INET; ai_hints.ai_socktype = SOCK_STREAM; if (w_getaddrinfo_timed(serv, &ai_hints, &ai_res) != 0) return NULL; memcpy(&sin4.sin_addr, &((struct sockaddr_in *)ai_res->ai_addr)->sin_addr, sizeof(sin4.sin_addr)); freeaddrinfo(ai_res); #endif /* HAVE_CARES */ if (port) { if (!(myport = (int)strtol(port, (char **)NULL, 10))) return NULL; sin4.sin_port = htons(myport); } else { sin4.sin_port = htons(PORT_WHOIS); } if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0) return NULL; #ifdef USE_WHOIS_TIMEOUT setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &whreadtimeout, sizeof(whreadtimeout)); #endif if (connect(s, (const struct sockaddr *)(const void *)&sin4, sizeof (sin4)) < 0) return NULL; br_s = 512; if (!(br = (char *)malloc(br_s))) return NULL; q_s = strlen(q); sendbuf = (char *)malloc(q_s+2); if(q[q_s-1]=='\r' || q[q_s-1]=='\n') { strncpy(sendbuf, q, q_s+1); } else { strncpy(sendbuf, q, q_s+1); sendbuf[q_s]='\n'; q_s++; sendbuf[q_s]=0; } if (write(s, sendbuf, q_s) != q_s) { /* || write(s, "\r\n", 2) != 2)*/ free(sendbuf); free(br); close(s); return NULL; } cur = 0; while ((n = read(s, buf, sizeof(buf))) > 0) { if ((cur + n) >= br_s) { char *tmp; br_s = br_s * 2; if (!(tmp = realloc(br, br_s))) { free(br); free(sendbuf); close(s); return NULL; } br = tmp; } strncpy((char *)&br[cur], buf, n); cur += n; } br[cur] = 0; close(s); free(sendbuf); return br; } int w_lookup_all_pwhois(whois_session_params * wsess, char *addr) { token_t *ls; char *serv, *reply; const char *format; int i; if (!addr) return -1; if (strlen(wsess->pw_serv) > 0) serv = wsess->pw_serv; else serv = pwhois_server; reply = w_ask(wsess, serv, addr, NULL); if (!reply) { if (wsess->w_noisy) { if(wsess->logprintfCookie) lft_printf(wsess->logprintfCookie,"No reply from %s.\n",serv); else fprintf(stderr,"No reply from %s.\n",serv); } return -1; } ls = tokens(reply, "\n"); for (i = 0; ls[i].ptr; i++) { char *value = NULL; if ((value = match_prefix("origin-as:", ls[i].ptr))) { if (strncmp(wsess->consolidated_asn,"?",1) == 0) strncpy(wsess->consolidated_asn,value,255); } else if ((value = match_prefix("prefix:", ls[i].ptr))) { if (strncmp(wsess->consolidated_route,"?",1) == 0) strncpy(wsess->consolidated_route,value,255); } else if ((value = match_prefix("as-path:", ls[i].ptr))) { if (strncmp(wsess->consolidated_asp,"?",1) == 0) strncpy(wsess->consolidated_asp,value,255); } else if ((value = match_prefix("org-name:", ls[i].ptr))) { if (strncmp(wsess->consolidated_orgname,"?",1) == 0) strncpy(wsess->consolidated_orgname,value,255); } else if ((value = match_prefix("net-name:", ls[i].ptr))) { if (strncmp(wsess->consolidated_netname,"?",1) == 0) strncpy(wsess->consolidated_netname,value,255); } else if ((value = match_prefix("cache-date:", ls[i].ptr))) { if ((wsess->tval = atol(value)) != 0) { format = "%d-%b-%y %H:%M:%S %Z"; (void)strftime(wsess->tbuf, sizeof(wsess->tbuf), format, localtime(&wsess->tval)); } } if(value) free(value); } free(ls); free(reply); return 0; } int w_lookup_all_riswhois(whois_session_params * wsess, char *addr) { token_t *ls=NULL; char *serv=NULL, *reply=NULL; const char *risopts = "-1 -M "; /* 1 object/prefix, Most specific */ char *risquery = malloc((strlen(risopts)* sizeof(char)) + (strlen(addr) * sizeof(char)) + 1); unsigned int i; if (!addr) { free(risquery); return -1; } /* prepare the text-string-based query */ risquery[0]=0; strcat(risquery,risopts); strcat(risquery,addr); reply = w_ask(wsess, ripe_ris_server, risquery, NULL); if (!reply) { if (wsess->w_noisy) { if(wsess->logprintfCookie) lft_printf(wsess->logprintfCookie,"No reply from %s.\n",ripe_ris_server); else fprintf(stderr,"No reply from %s.\n",ripe_ris_server); } free(risquery); return -1; } ls = tokens(reply, "\n"); for (i = 0; ls[i].ptr; i++) { char *value = NULL; if ((value = match_prefix("origin:", ls[i].ptr))) { if (strncmp(wsess->consolidated_asn,"?",1) == 0) strncpy(wsess->consolidated_asn,value,255); } else if ((value = match_prefix("route:", ls[i].ptr))) { if (strncmp(wsess->consolidated_route,"?",1) == 0) strncpy(wsess->consolidated_route,value,255); } else if ((value = match_prefix("descr:", ls[i].ptr))) { if (strncmp(wsess->consolidated_orgname,"?",1) == 0) strncpy(wsess->consolidated_orgname,value,255); if (strncmp(wsess->consolidated_netname,"?",1) == 0) strncpy(wsess->consolidated_netname,value,255); } if(value) free(value); } free(ls); free(reply); free(risquery); return 0; } int w_lookup_as_pwhois(whois_session_params * wsess, char *addr) { token_t *ls; char *reply = NULL, *value = NULL; unsigned int i; int ans = 0; if (!addr) return 0; reply = w_ask(wsess, pwhois_server, addr, NULL); if (!reply) { if (wsess->w_noisy) { if(wsess->logprintfCookie) lft_printf(wsess->logprintfCookie,"No reply from %s.\n",pwhois_server); else fprintf(stderr,"No reply from %s.\n",pwhois_server); } return 0; } ls = tokens(reply, "\n"); for (i = 0; ls[i].ptr; i++) { if ((value = match_prefix("origin-as:", ls[i].ptr))) break; } free(ls); free(reply); if (!value) return 0; rm_spaces(value); for (i = 0; i < strlen(value); i++) { if (!isdigit((unsigned char)value[i])) { free(value); return 0; } } ans = strtol(value, (char **)NULL, 10); free(value); return ans; } int w_lookup_as_riswhois(whois_session_params * wsess, char *addr) { token_t *ls; char *reply = NULL, *value = NULL; const char *risopts = "-1 -M "; /* 1 object/prefix, Most specific */ char *risquery = malloc((strlen(risopts) * sizeof(char)) + (strlen(addr) * sizeof(char)) + 1); unsigned int i; int ans = 0; if (!addr) { free(risquery); return 0; } /* prepare the text-string-based query */ risquery[0]=0; strcat(risquery,risopts); strcat(risquery,addr); reply = w_ask(wsess, ripe_ris_server, risquery, NULL); if (!reply) { if (wsess->w_noisy) { if(wsess->logprintfCookie) lft_printf(wsess->logprintfCookie,"No reply from %s.\n",ripe_ris_server); else fprintf(stderr,"No reply from %s.\n",ripe_ris_server); } free(risquery); return 0; } ls = tokens(reply, "\n"); for (i = 0; ls[i].ptr; i++) { if ((value = match_prefix("origin:", ls[i].ptr))) break; } free(ls); free(reply); free(risquery); if (!value) return 0; rm_spaces(value); for (i = 0; i < strlen(value); i++) { if (!isdigit((unsigned char)value[i])) { free(value); return 0; } } ans = atoi(value); free(value); return ans; } int w_lookup_all_riswhois_bulk(whois_session_params * wsess, struct ip_list_array *iplist) { token_t *responses=0; char *reply=0; const char *bulk_begin = "-k -1 -M\n"; /* Keepalive, 1 object/prefix, Most specific */ const char *bulk_end = "-k"; char *bulk_ip_query = malloc((strlen(bulk_begin) * sizeof(char)) + ((strlen(bulk_end)+1) * sizeof(char)) + (16 * (*iplist).numItems)); int i = 0; unsigned int j = 0; int k = 0; int entity_id = 0; unsigned int until = 0; char *value = NULL; bulk_ip_query[0]=0; if (!iplist) { free(bulk_ip_query); return -1; } /* clean up the response data set in case the caller doesn't (and we return error) */ for (i = 0; i < (*iplist).numItems; i++) { (*iplist).asn[(i)] = 0; memset((*iplist).netName[i],0,sizeof((*iplist).netName[i])); memset((*iplist).orgName[i],0,sizeof((*iplist).orgName[i])); } /* prepare the text-string-based query */ strcat(bulk_ip_query,bulk_begin); for (i = 0; i < ((*iplist).numItems); i++) { strcat(bulk_ip_query,inet_ntoa((*iplist).ipaddr[i])); strcat(bulk_ip_query,"\n"); } strcat(bulk_ip_query,bulk_end); reply = w_ask(wsess, ripe_ris_server, bulk_ip_query, NULL); if (!reply) { if (wsess->w_noisy) { if(wsess->logprintfCookie) lft_printf(wsess->logprintfCookie,"No reply from %s.\n",ripe_ris_server); else fprintf(stderr,"No reply from %s.\n",ripe_ris_server); } /* clean up the response data set in case the caller doesn't */ for (i = 0; i < (*iplist).numItems; i++) (*iplist).asn[(i)] = 0; free(bulk_ip_query); return -1; } responses = tokens(reply, "\n"); for (i = 0; responses[i].ptr; i++) { value = NULL; if ((value = match_prefix("origin:", responses[i].ptr)) != NULL) { if (k > 0) { entity_id++; k = 0; } rm_spaces(value); /* strip out any spaces from the ASN */ for (j = 0; j < strlen(value); j++) { if (!isdigit((unsigned char)value[j])) { if (wsess->w_noisy) { if(wsess->logprintfCookie) lft_printf(wsess->logprintfCookie,"Parse error at \'%c\': non-numeric value at position %d of %s).\n",value[j],j,value); else printf("Parse error at \'%c\': non-numeric value at position %d of %s).\n",value[j],j,value); } break; } } if(strtol(value, (char **) NULL, 10)) { (*iplist).asn[(entity_id)] = strtol(value, (char **)NULL, 10); k++; } else if (wsess->w_noisy > 2) { if(wsess->logprintfCookie) lft_printf(wsess->logprintfCookie,"Skipping additional object for same query.\n"); else fprintf(stderr,"Skipping additional object for same query.\n"); } } else if ((value = match_prefix("descr:", responses[i].ptr))) { strncpy((*iplist).orgName[entity_id],value,sizeof((*iplist).orgName[entity_id])-1); (*iplist).orgName[entity_id][sizeof((*iplist).orgName[entity_id])-1] = '\0'; /* yes, this is duplicated. riswhois adds a netname attribute here, so we reuse 'descr' */ for (until = 0; until < strlen(value); until++) { if (isspace((unsigned char)value[until])) break; } /* clamp the server-controlled descr length to the field width and * NUL-terminate (netName rows are fixed-size). */ if (until > sizeof((*iplist).netName[entity_id]) - 1) until = sizeof((*iplist).netName[entity_id]) - 1; strncpy((*iplist).netName[entity_id],value,(until)); (*iplist).netName[entity_id][until] = '\0'; k++; } else if ((value = match_prefix("% ", responses[i].ptr)) != NULL) { if (i > 5 && k < 1) { /* Weed out up to 5 leading lines from RIPE NCC RIS */ if (wsess->w_noisy > 2) { if(wsess->logprintfCookie) lft_printf(wsess->logprintfCookie,"%% MATCHED on '%s'\n",responses[i].ptr); else printf("%% MATCHED on '%s'\n",responses[i].ptr); } /* (*iplist).asn[(entity_id)] = 0; */ k++; } /* else printf("'%s'\n",responses[i].ptr); */ } /* else printf("'%s'\n",responses[i].ptr); */ if(value) free(value); if ((entity_id) >= (*iplist).numItems) break; } free(responses); free(reply); free(bulk_ip_query); return 0; } int w_lookup_as(whois_session_params * wsess, char *addr) { token_t *ls; ip_blk_t *a = NULL, *b = NULL; /* char *sa, *sb; */ char *reply, *value = NULL; unsigned int i; int ans = 0; int use_this = 1; if (!addr) return 0; reply = w_ask(wsess, radb_server, addr, NULL); if (!reply) { if (wsess->w_noisy) { if(wsess->logprintfCookie) lft_printf(wsess->logprintfCookie,"No reply from %s.\n",radb_server); else fprintf(stderr,"No reply from %s.\n",radb_server); } return 0; } ls = tokens(reply, "\n"); for (i = 0; ls[i].ptr; i++) { value = NULL; if ((value = match_prefix("local-as:", ls[i].ptr)) != NULL) { break; } else if ((value = match_prefix("route:", ls[i].ptr)) != NULL || (value = match_prefix("route6:", ls[i].ptr)) != NULL) { /* route6 prefixes yield a NULL range from the v4 mask helper; * the size-refinement below is simply skipped for them */ a = match_ipprefix(value); if (b && a) { if (((b->end - b->start) > (a->end - a->start))) { use_this = 1; free(b); b = a; a = NULL; } else { use_this = 0; free(a); a = NULL; } } else { use_this = 1; if (a) { if (b) free(b); b = a; } a = NULL; } } else if (use_this && (value = match_prefix("origin:", ls[i].ptr))) { break; } if(value != NULL) free(value); } free(ls); free(reply); if(b != NULL) free(b); if (!value) return 0; rm_spaces(value); for (i = 0; i < strlen(value); i++) { if (!isdigit((unsigned char)value[i])) { return 0; } } ans = strtol(value, (char **)NULL, 10); free(value); return ans; } int w_lookup_as_cymru(whois_session_params * wsess, char *addr) { /* * Look up the ASN at the prefix-based Cymru whois server */ token_t *ls; char *reply; unsigned int i; char value[6]; memset(&value, 0, sizeof(value)); if (!addr) return 0; reply = w_ask(wsess, cymru_server, addr, NULL); if (!reply) { if (wsess->w_noisy) { if(wsess->logprintfCookie) lft_printf(wsess->logprintfCookie,"No reply from %s.\n",cymru_server); else fprintf(stderr,"No reply from %s.\n",cymru_server); } return 0; } ls = tokens(reply, "\n"); /* Set i to 1 to skip the first/header line of reply from cymru */ strncpy(value,ls[1].ptr,5); rm_spaces(value); /* strip out any spaces from the ASN */ for (i = 0; i < strlen(value); i++) { if (!isdigit((unsigned char)value[i])) { return 0; } } free(ls); free(reply); return (strtol(value, (char **)NULL, 10)); } int w_lookup_as_cymru_bulk(whois_session_params * wsess, struct ip_list_array *iplist) { token_t *responses; char *reply; const char *bulk_begin = "begin\n"; const char *bulk_end = "end\n"; char *bulk_ip_query = malloc((strlen(bulk_begin) * sizeof(char)) + (strlen(bulk_end)* sizeof(char)) + (16 * (*iplist).numItems)); int i; unsigned int j; char value[6]; memset(&value, 0, sizeof(value)); bulk_ip_query[0]=0; if (!iplist) { free(bulk_ip_query); return -1; } /* clean up the response data set in case the caller doesn't (and we return error) */ for (i = 0; i < (*iplist).numItems; i++) (*iplist).asn[(i)] = 0; /* prepare the text-string-based query */ strcat(bulk_ip_query,bulk_begin); for (i = 0; i < ((*iplist).numItems); i++) { strcat(bulk_ip_query,inet_ntoa((*iplist).ipaddr[i])); strcat(bulk_ip_query,"\n"); } strcat(bulk_ip_query,bulk_end); reply = w_ask(wsess, cymru_server, bulk_ip_query, NULL); if (!reply) { if (wsess->w_noisy) { if(wsess->logprintfCookie) lft_printf(wsess->logprintfCookie,"No reply from %s.\n",cymru_server); else fprintf(stderr,"No reply from %s.\n",cymru_server); } free(bulk_ip_query); return -1; } responses = tokens(reply, "\n"); /* Set i to 1 to skip the first/header line of reply from cymru */ for (i = 1; responses[i].ptr; i++) { strncpy(value,responses[i].ptr,5); rm_spaces(value); /* strip out any spaces from the ASN */ for (j = 0; j < strlen(value); j++) { if (!isdigit((unsigned char)value[j])) { if (wsess->w_noisy) { if(wsess->logprintfCookie) lft_printf(wsess->logprintfCookie,"Parse error at \'%c\': non-numeric value at position %d of %s).\n",value[j],j,value); else fprintf(stderr,"Parse error at \'%c\': non-numeric value at position %d of %s).\n",value[j],j,value); } break; } } if(strtol(value, (char **)NULL, 10)) { (*iplist).asn[(i-1)] = strtol(value, (char **)NULL, 10); } else { (*iplist).asn[(i-1)] = 0; } if ((i+1) > (*iplist).numItems) break; } free(responses); free(reply); free(bulk_ip_query); return 0; } #ifdef STANDALONE static int w_display_rvbyasn_pwhois(whois_session_params * wsess, char *asn) { char *reply; const char *query_begin = "routeview source-as="; char *whob_query = NULL; char *serv; if (!asn) return -1; if (strlen(wsess->pw_serv) > 0) serv = wsess->pw_serv; else serv = pwhois_server; whob_query = malloc( (strlen(appname) + strlen(version) + strlen(query_begin) + strlen(asn)) * sizeof(char) + 10); whob_query[0]=0; /* prepare the text-string-based query */ strcat(whob_query,"app=\""); strcat(whob_query,appname); strcat(whob_query," "); strcat(whob_query,version); strcat(whob_query,"\" "); strcat(whob_query,query_begin); strcat(whob_query,asn); strcat(whob_query,"\n"); reply = w_ask(wsess, serv, whob_query, NULL); if (!reply) { if (wsess->w_noisy) fprintf(stderr,"No reply from %s.\n",serv); return -1; } snprintf(w_highlight_asn, sizeof(w_highlight_asn), "%s", asn); w_print_pwhois_colorized(reply); w_highlight_asn[0] = '\0'; free(reply); free(whob_query); return 0; } static int w_display_rvbytransitasn_pwhois(whois_session_params * wsess, char *asn) { char *reply; const char *query_begin = "routeview transit-as="; char *whob_query = NULL; char *serv; if (!asn) return -1; if (strlen(wsess->pw_serv) > 0) serv = wsess->pw_serv; else serv = pwhois_server; whob_query = malloc( (strlen(appname) + strlen(version) + strlen(query_begin) + strlen(asn)) * sizeof(char) + 10); whob_query[0]=0; /* prepare the text-string-based query */ strcat(whob_query,"app=\""); strcat(whob_query,appname); strcat(whob_query," "); strcat(whob_query,version); strcat(whob_query,"\" "); strcat(whob_query,query_begin); strcat(whob_query,asn); strcat(whob_query,"\n"); reply = w_ask(wsess, serv, whob_query, NULL); if (!reply) { if (wsess->w_noisy) fprintf(stderr,"No reply from %s.\n",serv); return -1; } snprintf(w_highlight_asn, sizeof(w_highlight_asn), "%s", asn); w_print_pwhois_colorized(reply); w_highlight_asn[0] = '\0'; free(reply); free(whob_query); return 0; } static int w_display_contactsbyasn_pwhois(whois_session_params * wsess, char *asn) { char *reply; const char *query_begin = "registry source-as="; char *whob_query = NULL; char *serv; if (!asn) return -1; if (strlen(wsess->pw_serv) > 0) serv = wsess->pw_serv; else serv = pwhois_server; whob_query = malloc( ((strlen(appname) + strlen(version) + strlen(query_begin) + strlen(asn)) * sizeof(char)) + 10); whob_query[0]=0; /* prepare the text-string-based query */ strcat(whob_query,"app=\""); strcat(whob_query,appname); strcat(whob_query," "); strcat(whob_query,version); strcat(whob_query,"\" "); strcat(whob_query,query_begin); strcat(whob_query,asn); strcat(whob_query,"\n"); reply = w_ask(wsess, serv, whob_query, NULL); if (!reply) { if (wsess->w_noisy) fprintf(stderr,"No reply from %s.\n",serv); return -1; } w_print_pwhois_colorized(reply); free(reply); free(whob_query); return 0; } static int w_display_networksbyasn_pwhois(whois_session_params * wsess, char *asn) { char *reply; const char *query_begin = "netblock source-as="; char *whob_query = NULL; char *serv; if (!asn) return -1; if (strlen(wsess->pw_serv) > 0) serv = wsess->pw_serv; else serv = pwhois_server; whob_query =(char *)malloc(((strlen(appname) + strlen(version) + strlen(query_begin) + strlen(asn)) * sizeof(char)) + 10); whob_query[0]=0; /* prepare the text-string-based query */ strcat(whob_query,"app=\""); strcat(whob_query,appname); strcat(whob_query," "); strcat(whob_query,version); strcat(whob_query,"\" "); strcat(whob_query,query_begin); strcat(whob_query,asn); strcat(whob_query,"\n"); reply = w_ask(wsess, serv, whob_query, NULL); if (!reply) { if (wsess->w_noisy) fprintf(stderr,"No reply from %s.\n",serv); return -1; } w_print_pwhois_colorized(reply); free(reply); free(whob_query); return 0; } static int w_display_rvbyprefix_pwhois(whois_session_params * wsess, char *prefix) { char *reply; const char *query_begin = "routeview prefix="; char *whob_query = NULL; char *serv; if (!prefix) return -1; if (strlen(wsess->pw_serv) > 0) serv = wsess->pw_serv; else serv = pwhois_server; whob_query = malloc(((strlen(appname) * sizeof(char))+10) + (strlen(version) * sizeof(char)) + (strlen(query_begin) * sizeof(char)) + (strlen(prefix))* sizeof(char)); whob_query[0]=0; /* prepare the text-string-based query */ strcat(whob_query,"app=\""); strcat(whob_query,appname); strcat(whob_query," "); strcat(whob_query,version); strcat(whob_query,"\" "); strcat(whob_query,query_begin); strcat(whob_query,prefix); strcat(whob_query,"\n"); reply = w_ask(wsess, serv, whob_query, NULL); if (!reply) { if (wsess->w_noisy) fprintf(stderr,"No reply from %s.\n",serv); return -1; } w_print_pwhois_colorized(reply); free(reply); free(whob_query); return 0; } static int w_display_bulkfromfile_pwhois(whois_session_params * wsess, char *filespec) { const char *query_begin = "begin\n"; const char *query_end = "end\n"; const char *appname_extras = "BULK_FILE"; const char *format_instructions = "type=cymru\n"; char *reply; char *serv; FILE *bulkFile; int num_lines = 0, i = 0; char *lines = malloc(line_size * max_lines); char *this_line = malloc(line_size); size_t whob_query_len = ((strlen(appname) * sizeof(char) +10) + (strlen(appname_extras) * sizeof(char)) + (strlen(version) * sizeof(char))) + (strlen(query_begin) * sizeof(char)) + (strlen(format_instructions) * sizeof(char)) + (strlen(query_end) * sizeof(char)) + (line_size * max_lines); char *whob_query = (char *)malloc(whob_query_len); reply = NULL; *whob_query = '\0'; if (!filespec) { fprintf(stderr,"You must specify a file to use (or '-' for stdin) for bulk query input.\n"); free(lines); free(this_line); free(whob_query); exit(EXIT_FAILURE); } if (!strncmp(filespec,"-",1) || use_stdin == 1) { bulkFile = stdin; } else bulkFile = fopen(filespec, "r"); if (!bulkFile) { fprintf(stderr,"%s: Unable to open \'%s\' for reading.\n",appname,filespec); free(lines); free(this_line); free(whob_query); exit(EXIT_FAILURE); } if (strlen(wsess->pw_serv) > 0) serv = wsess->pw_serv; else serv = pwhois_server; while (!feof(bulkFile)) { if (num_lines >= max_lines && wsess->w_noisy >= 2) fprintf(stderr,"Processing next batch of %d beginning at line %d.\n",max_lines,(num_lines+1)); memset(lines, 0, line_size * max_lines); memset(whob_query, 0, whob_query_len); for (i = 0; i < max_lines; i++) { if (fgets(this_line,line_size - 1,bulkFile)) { if (strncmp(this_line, "#", 1) && strncmp(this_line, ";", 1)) { strcat(lines,this_line); num_lines++; if (wsess->w_noisy >= 4) fprintf(stderr,"Line %d: %s",num_lines,this_line); } } else if (feof(bulkFile)) { if (wsess->w_noisy >= 2) fprintf(stderr,"End of file reached after reading %d lines.\n",num_lines); break; } else if (ferror(bulkFile)) { if (wsess->w_noisy >= 1) fprintf(stderr,"Error in stream on line %d.\n",num_lines+1); break; } } /* prepare the text-string-based query */ strcat(whob_query, query_begin); strcat(whob_query, "app=\""); strcat(whob_query, appname); strcat(whob_query, " "); strcat(whob_query, version); strcat(whob_query, " "); strcat(whob_query, appname_extras); strcat(whob_query, "\"\n"); if (use_cymru) strcat(whob_query, format_instructions); strcat(whob_query, lines); strcat(whob_query, query_end); reply = w_ask(wsess, serv, whob_query, NULL); if (!reply) { if (wsess->w_noisy) fprintf(stderr,"No reply from %s.\n",serv); free(this_line); free(lines); free(whob_query); free(reply); return -1; } w_print_pwhois_colorized(reply); } free(this_line); free(lines); free(whob_query); free(reply); fclose(bulkFile); return 0; } static int w_display_bulkfromfile_riswhois(whois_session_params * wsess, char *filespec) { const char *query_begin = "-k -1 -M\n"; /* Keepalive, 1 object/prefix, Most specific */ const char *query_end = "-k"; char *reply; char *serv; FILE *bulkFile; int num_lines = 0, i = 0; char *lines = (char *)malloc(line_size * max_lines); char *this_line = (char *)malloc(line_size); size_t whob_query_len = 10 + (strlen(query_begin) * sizeof(char)) + (strlen(query_end) * sizeof(char)) + (line_size * max_lines); char *whob_query = (char *)malloc(whob_query_len); reply = NULL; *whob_query = '\0'; if (!filespec) { fprintf(stderr,"You must specify a file to use (or '-' for stdin) for bulk query input.\n"); free(lines); free(this_line); free(whob_query); exit(EXIT_FAILURE); } if (!strncmp(filespec,"-",1) || use_stdin == 1) { bulkFile = stdin; } else bulkFile = fopen(filespec, "r"); if (!bulkFile) { fprintf(stderr,"%s: Unable to open \'%s\' for reading.\n",appname,filespec); free(lines); free(this_line); free(whob_query); exit(EXIT_FAILURE); } if (strlen(wsess->pw_serv) > 0) serv = wsess->pw_serv; else serv = ripe_ris_server; while (!feof(bulkFile)) { if (num_lines >= max_lines && wsess->w_noisy >= 2) fprintf(stderr,"Processing next batch of %d beginning at line %d.\n",max_lines,(num_lines+1)); memset(lines, 0, line_size * max_lines); memset(whob_query, 0, whob_query_len); for (i = 0; i < max_lines; i++) { if (fgets(this_line,line_size - 1,bulkFile)) { if (strncmp(this_line, "#", 1) && strncmp(this_line, ";", 1)) { strcat(lines,this_line); num_lines++; if (wsess->w_noisy >= 4) fprintf(stderr,"Line %d: %s",num_lines,this_line); } } else if(feof(bulkFile)) { if (wsess->w_noisy >= 2) fprintf(stderr,"End of file reached after reading %d lines.\n",num_lines); break; } else if (ferror(bulkFile)) { if (wsess->w_noisy >= 1) fprintf(stderr,"Error in stream on line %d.\n",num_lines+1); break; } } /* prepare the text-string-based query */ strcat(whob_query, query_begin); strcat(whob_query, lines); strcat(whob_query, query_end); reply = w_ask(wsess, serv, whob_query, NULL); if (!reply) { if (wsess->w_noisy) fprintf(stderr,"No reply from %s.\n",serv); free(this_line); free(lines); free(whob_query); free(reply); return -1; } printf("%s",reply); } free(this_line); free(lines); free(whob_query); free(reply); fclose(bulkFile); return 0; } static int w_display_bulkfromfile_cymru(whois_session_params * wsess, char *filespec) { const char *query_begin = "begin\n"; const char *query_end = "end"; char *reply; char *serv; FILE *bulkFile; int num_lines = 0, i = 0; char *lines = (char *)malloc(line_size * max_lines); char *this_line = (char *)malloc(line_size); size_t whob_query_len = 10 + (strlen(query_begin) * sizeof(char)) + (strlen(query_end) * sizeof(char)) + (line_size * max_lines); char *whob_query = (char *)malloc(whob_query_len); reply = NULL; *whob_query = '\0'; if (!filespec) { fprintf(stderr,"You must specify a file to use (or '-' for stdin) for bulk query input.\n"); free(lines); free(this_line); free(whob_query); exit(EXIT_FAILURE); } if (!strncmp(filespec,"-",1) || use_stdin == 1) { bulkFile = stdin; } else bulkFile = fopen(filespec, "r"); if (!bulkFile) { fprintf(stderr,"%s: Unable to open \'%s\' for reading.\n",appname,filespec); free(lines); free(this_line); free(whob_query); exit(EXIT_FAILURE); } if (strlen(wsess->pw_serv) > 0) serv = wsess->pw_serv; else serv = cymru_server; while (!feof(bulkFile)) { if (num_lines >= max_lines && wsess->w_noisy >= 2) fprintf(stderr,"Processing next batch of %d beginning at line %d.\n",max_lines,(num_lines+1)); memset(lines, 0, line_size * max_lines); memset(whob_query, 0, whob_query_len); for (i = 0; i < max_lines; i++) { if (fgets(this_line, line_size - 1, bulkFile)) { if (strncmp(this_line, "#", 1) && strncmp(this_line, ";", 1)) { strcat(lines, this_line); num_lines++; if (wsess->w_noisy >= 4) fprintf(stderr,"Line %d: %s",num_lines,this_line); } } else if(feof(bulkFile)) { if (wsess->w_noisy >= 2) fprintf(stderr,"End of file reached after reading %d lines.\n",num_lines); break; } else if(ferror(bulkFile)) { if (wsess->w_noisy >= 1) fprintf(stderr,"Error in stream on line %d.\n",num_lines+1); break; } } /* prepare the text-string-based query */ strcat(whob_query, query_begin); strcat(whob_query, lines); strcat(whob_query, query_end); reply = w_ask(wsess, serv, whob_query, NULL); if (!reply) { if(wsess->w_noisy) fprintf(stderr,"No reply from %s.\n",serv); free(this_line); free(lines); free(whob_query); free(reply); return -1; } printf("%s",reply); } free(this_line); free(lines); free(whob_query); free(reply); fclose(bulkFile); return 0; } static int w_display_pwhois_version(whois_session_params * wsess) { char *reply; char *serv; if (strlen(wsess->pw_serv) > 0) serv = wsess->pw_serv; else serv = pwhois_server; if (wsess->w_noisy) fprintf(stderr,"Querying '%s' for version/status.\n",serv); reply = w_ask(wsess, serv, "version", NULL); if (!reply) { if(wsess->w_noisy) fprintf(stderr,"No reply from %s.\n",serv); return -1; } w_print_status_colorized(reply); free(reply); return 0; } static int w_display_myaddress_http(whois_session_params * wsess) { char *reply; char *serv; if (strlen(wsess->pw_serv) > 0) serv = wsess->pw_serv; else serv = myaddress_server; if (wsess->w_noisy) fprintf(stderr,"Querying '%s' for my public address.\n",serv); reply = w_ask(wsess, serv, "Accept:*/*\nHost: myaddress.today\nUser-Agent: WhoB\nGET /\n\n", "80"); if (!reply) { if(wsess->w_noisy) fprintf(stderr,"No reply from %s.\n",serv); return -1; } printf("%s",reply); free(reply); return 0; } static int w_display_myaddress(whois_session_params * wsess) { char *reply; char *serv; if (strlen(wsess->pw_serv) > 0) serv = wsess->pw_serv; else serv = pwhois_server; if (wsess->w_noisy) fprintf(stderr,"Querying '%s' for my public address.\n",serv); reply = w_ask(wsess, serv, "whoami", NULL); if (!reply) { if(wsess->w_noisy) fprintf(stderr,"No reply from %s.\n",serv); return -1; } printf("%s",reply); free(reply); return 0; } /* Query the terminal's background colour via OSC 11 and decide light vs dark. * Returns 1 (light), 0 (dark), or -1 (unknown / not a terminal / no answer). * The round-trip runs ONLY when both stdout and stdin are real terminals, so * a redirect or pipe on either side never triggers it. Terminal raw-mode is * saved and restored; a ~150 ms poll bounds the wait so an unresponsive * terminal (e.g. macOS Terminal.app) can't stall whob. */ static int w_query_bg_is_light(void) { #if defined(WIN32) || defined(_WIN32) return -1; #else struct termios saved, raw; char buf[64]; int n = 0, got = 0, light = -1; unsigned long r = 0, g = 0, b = 0, mx = 0; /* Only probe an interactive terminal on BOTH ends. */ if (!isatty(STDOUT_FILENO) || !isatty(STDIN_FILENO)) return -1; if (tcgetattr(STDIN_FILENO, &saved) != 0) return -1; raw = saved; raw.c_lflag &= ~(ICANON | ECHO); raw.c_cc[VMIN] = 0; raw.c_cc[VTIME] = 0; if (tcsetattr(STDIN_FILENO, TCSANOW, &raw) != 0) return -1; /* Ask: OSC 11 ; ? BEL — reply is ESC ] 11 ; rgb:RRRR/GGGG/BBBB (ST|BEL) */ if (write(STDOUT_FILENO, "\033]11;?\007", 7) == 7) { struct pollfd pfd; pfd.fd = STDIN_FILENO; pfd.events = POLLIN; /* Drain the reply, allowing a short gap between bursts. */ while (n < (int)sizeof(buf) - 1 && poll(&pfd, 1, 150) > 0) { ssize_t k = read(STDIN_FILENO, buf + n, sizeof(buf) - 1 - n); if (k <= 0) break; n += (int)k; if (memchr(buf, '\007', n) || (n >= 2 && buf[n-2] == '\033' && buf[n-1] == '\\')) break; /* terminator seen */ } } tcsetattr(STDIN_FILENO, TCSANOW, &saved); buf[n] = '\0'; /* Parse "rgb:RRRR/GGGG/BBBB" (1-4 hex digits per channel). */ { char *p = strstr(buf, "rgb:"); if (p) { char cs[3][8]; if (sscanf(p + 4, "%7[0-9a-fA-F]/%7[0-9a-fA-F]/%7[0-9a-fA-F]", cs[0], cs[1], cs[2]) == 3) { int L0 = (int)strlen(cs[0]); r = strtoul(cs[0], NULL, 16); g = strtoul(cs[1], NULL, 16); b = strtoul(cs[2], NULL, 16); mx = (1UL << (4 * L0)) - 1; /* full-scale for that width */ got = (mx > 0); } } } if (got) { /* Perceived luminance on 0..1; > 0.5 => light background. */ double lum = (0.2126 * r + 0.7152 * g + 0.0722 * b) / (double)mx; light = (lum > 0.5) ? 1 : 0; } return light; #endif } /* COLORFGBG fallback ("fg;bg", e.g. "15;0"): bg 0-6 or 8 => dark, else light. */ static int w_colorfgbg_is_light(void) { const char *e = getenv("COLORFGBG"); const char *bg; int v; if (!e) return -1; bg = strrchr(e, ';'); bg = bg ? bg + 1 : e; if (bg[0] < '0' || bg[0] > '9') /* "default" etc. */ return -1; v = atoi(bg); if (v == 8) return 0; return (v >= 7) ? 1 : 0; } /* Resolve colour state once, after option parsing and before any output. * Mirrors lft's -o detection (isatty + TERM + NO_COLOR); --color / --no-color * override. Also resolves truecolor capability and the light/dark variant. */ static void w_detect_color(void) { w_use_color = 0; w_truecolor = 0; w_bg_light = 0; if (w_color_mode == 2) /* --no-color: hard off */ return; #if !defined(WIN32) && !defined(_WIN32) { const char *term = getenv("TERM"); const char *colorterm = getenv("COLORTERM"); if (w_color_mode == 1) { /* --color: force on */ w_use_color = 1; } else if (isatty(fileno(stdout)) && term && strncmp(term, "dumb", 4) != 0 && strncmp(term, "unknown", 7) != 0 && !getenv("NO_COLOR") && (colorterm || strncmp(term, "xterm", 5) == 0 || strncmp(term, "screen", 6) == 0 || strncmp(term, "tmux", 4) == 0 || strncmp(term, "rxvt", 4) == 0)) { w_use_color = 1; } if (w_use_color) { int bg; if (colorterm && (strstr(colorterm, "truecolor") || strstr(colorterm, "24bit"))) w_truecolor = 1; /* Background probe is self-gated to TTY-on-both-ends. */ bg = w_query_bg_is_light(); if (bg < 0) bg = w_colorfgbg_is_light(); w_bg_light = (bg == 1) ? 1 : 0; /* default: dark */ } } #else if (w_color_mode == 1) w_use_color = 1; #endif } /* Colour is allowed on the verbose/diagnostic stream (stderr) only when colour * is enabled AND stderr is itself a terminal, so redirected logs stay clean. */ static int w_ecolor(void) { #if defined(WIN32) || defined(_WIN32) return 0; #else return w_use_color && isatty(fileno(stderr)); #endif } /* SGR sequence for a field group, honouring truecolor vs 256-colour and the * resolved light/dark variant. Per-group static buffers so several calls can * coexist in one printf. */ static const char *w_field_seq(int field) { static char buf[WF_NGROUPS][24]; const wc_color *c; if (field < 0 || field >= WF_NGROUPS) return ""; c = &w_palette[w_bg_light][field]; if (w_truecolor) snprintf(buf[field], sizeof(buf[field]), "\033[38;2;%u;%u;%um", c->r, c->g, c->b); else snprintf(buf[field], sizeof(buf[field]), "\033[38;5;%um", c->x256); return buf[field]; } /* SGR for one of the three table date shades (0=Create,1=Modify,2=Originated). */ static const char *w_date_seq(int shade) { static char buf[3][24]; const wc_color *c; if (shade < 0 || shade > 2) return ""; c = &w_date_shade[w_bg_light][shade]; if (w_truecolor) snprintf(buf[shade], sizeof(buf[shade]), "\033[38;2;%u;%u;%um", c->r, c->g, c->b); else snprintf(buf[shade], sizeof(buf[shade]), "\033[38;5;%um", c->x256); return buf[shade]; } /* Case-insensitive "does s end with suf?" */ static int w_ends(const char *s, const char *suf) { size_t ls = strlen(s), lf = strlen(suf); return ls >= lf && strcasecmp(s + ls - lf, suf) == 0; } /* Map a pWhoIs field label to a colour group. Pattern-based (suffix/exact) * rather than an exhaustive table, so it covers the compact v4 record, the * larger v6 / type=all record (Geo-*, Abuse-0-*, Admin-0-*, NOC-0-*, Tech-0-* * contact blocks, Route-Prefix, Create-/Modify-Date, …) and future fields * uniformly. Order matters: more specific tests come first. */ static int w_pwhois_field(const char *s) { if (w_ends(s, "-Date") || strcasecmp(s, "Route-Originated-TS") == 0) return WF_DATE; if (w_ends(s, "Latitude") || w_ends(s, "Longitude")) return WF_GEO; if (w_ends(s, "-City") || w_ends(s, "-Country") || w_ends(s, "-State") || w_ends(s, "-Postal-Code") || w_ends(s, "-Street-1") || w_ends(s, "-Street-2") || w_ends(s, "-Region") || w_ends(s, "-CC") || strcasecmp(s, "City") == 0 || strcasecmp(s, "Region") == 0 || strcasecmp(s, "Country") == 0 || strcasecmp(s, "Country-Code") == 0 || strcasecmp(s, "State") == 0 || strcasecmp(s, "Postal-Code") == 0 || strcasecmp(s, "Street-1") == 0 || strcasecmp(s, "Street-2") == 0) return WF_GEO; if (w_ends(s, "-Prefix") || strcasecmp(s, "IP") == 0 || strcasecmp(s, "Prefix") == 0 || strcasecmp(s, "Net-Range") == 0 || strcasecmp(s, "Next-Hop") == 0 || strcasecmp(s, "Router-ID") == 0) return WF_PREFIX; if (strcasecmp(s, "Origin-AS") == 0 || strcasecmp(s, "Transit-AS") == 0 || strcasecmp(s, "AS-Path") == 0 || strcasecmp(s, "ASN") == 0) return WF_ASN; if (w_ends(s, "-Name") || w_ends(s, "-Handle") || w_ends(s, "-Org-ID") || strcasecmp(s, "Org-ID") == 0 || strcasecmp(s, "Org-Name") == 0 || strcasecmp(s, "Net-Name") == 0) return WF_ORG; return WF_NONE; } /* True if the value is purely a count/number (digits, optional grouping * commas/spaces) — used to give unlabelled numeric values (e.g. the server * statistics counts) the ASN/number colour. */ static int w_value_numeric(const char *s, size_t len) { size_t i; int digits = 0; if (!len) return 0; for (i = 0; i < len; i++) { if (s[i] >= '0' && s[i] <= '9') digits = 1; else if (s[i] != ',' && s[i] != ' ') return 0; } return digits; } /* Per-column render kinds for the -a / -A route tables. */ enum { CK_NONE = 0, CK_PREFIX, CK_ASN, CK_ASPATH, CK_ORG_HI, CK_ORG_LO, CK_GEO, CK_DATE_C, CK_DATE_M, CK_DATE_O }; #define W_MAXCOL 24 /* Map a (trimmed) table column header name to a render kind. */ static int w_col_kind(const char *name) { static const struct { const char *n; int k; } map[] = { { "Prefix", CK_PREFIX }, { "Next-Hop", CK_PREFIX }, { "Origin-AS", CK_ASN }, { "AS-Path", CK_ASPATH }, { "Org-Name", CK_ORG_HI }, /* more specific -> higher-viz */ { "AS-Org-Name", CK_ORG_LO }, { "CC", CK_GEO }, { "Country-Code", CK_GEO }, { "Create-Date", CK_DATE_C }, { "Modify-Date", CK_DATE_M }, { "Originated-Date", CK_DATE_O }, { "Route-Originated-Date",CK_DATE_O }, { NULL, CK_NONE } }; int i; for (i = 0; map[i].n; i++) if (strcasecmp(name, map[i].n) == 0) return map[i].k; return CK_NONE; } /* Copy [p,end) with surrounding spaces trimmed into a bounded buffer. */ static void w_trim_copy(const char *p, const char *end, char *out, size_t outsz) { size_t n; while (p < end && *p == ' ') p++; while (end > p && end[-1] == ' ') end--; n = (size_t)(end - p); if (n >= outsz) n = outsz - 1; memcpy(out, p, n); out[n] = '\0'; } /* Emit the leading BGP status flag of a data row's first column: the best-path * '>' takes the prefix colour, '*' is dimmed, everything else prints as-is. */ static void w_emit_flag(const char *s, size_t len) { size_t i; for (i = 0; i < len; i++) { if (s[i] == '>') printf("%s>%s", w_field_seq(WF_PREFIX), WC_RESET); else if (s[i] == '*') printf("%s*%s", WC_DIM, WC_RESET); else putchar(s[i]); } } /* Emit an AS-Path cell in the ASN colour, bolding whole-token occurrences of * the queried ASN so it can be traced through every path. */ static void w_emit_aspath(const char *s, size_t len) { const char *seq = w_field_seq(WF_ASN); size_t hllen = strlen(w_highlight_asn); size_t i = 0; printf("%s", seq); while (i < len) { size_t j; if (s[i] == ' ' || s[i] == '\t') { putchar(s[i]); i++; continue; } j = i; while (j < len && s[j] != ' ' && s[j] != '\t') j++; if (hllen && (j - i) == hllen && strncmp(s + i, w_highlight_asn, hllen) == 0) printf("%s%.*s\033[22m", WC_BOLD, (int)(j - i), s + i); /* bold, keep colour */ else printf("%.*s", (int)(j - i), s + i); i = j; } printf("%s", WC_RESET); } /* Emit one table data cell (not the flag/prefix column 0) by its render kind. */ static void w_emit_cell(const char *s, size_t len, int kind) { switch (kind) { case CK_PREFIX: printf("%s%.*s%s", w_field_seq(WF_PREFIX), (int)len, s, WC_RESET); break; case CK_ASN: printf("%s%.*s%s", w_field_seq(WF_ASN), (int)len, s, WC_RESET); break; case CK_GEO: printf("%s%.*s%s", w_field_seq(WF_GEO), (int)len, s, WC_RESET); break; case CK_ORG_HI: printf("%s%s%.*s%s", WC_BOLD, w_field_seq(WF_ORG), (int)len, s, WC_RESET); break; case CK_ORG_LO: printf("%s%s%.*s%s", WC_DIM, w_field_seq(WF_ORG), (int)len, s, WC_RESET); break; case CK_DATE_C: printf("%s%.*s%s", w_date_seq(0), (int)len, s, WC_RESET); break; case CK_DATE_M: printf("%s%.*s%s", w_date_seq(1), (int)len, s, WC_RESET); break; case CK_DATE_O: printf("%s%.*s%s", w_date_seq(2), (int)len, s, WC_RESET); break; case CK_ASPATH: w_emit_aspath(s, len); break; default: printf("%.*s", (int)len, s); break; } } /* Split a '|'-delimited line into cells and invoke cb(field, end, col). * Separators are emitted dimmed. Preserves every space so column alignment * (which the server already produced) is untouched — only zero-width SGR * codes are inserted. */ static void w_render_row(const char *line, size_t len, void (*cb)(const char *, const char *, int)) { const char *p = line, *end = line + len; int col = 0; while (p <= end) { const char *bar = (const char *)memchr(p, '|', (size_t)(end - p)); const char *fend = bar ? bar : end; cb(p, fend, col); if (!bar) break; printf("%s|%s", WC_DIM, WC_RESET); p = bar + 1; col++; } } /* Header/data column kinds shared between the header row and the rows below it. * A reply carries one header then its rows, so file-scope state is sufficient. */ static int w_col_kinds[W_MAXCOL]; static int w_col_ncols = 0; static void w_header_cb(const char *p, const char *fend, int col) { char name[40]; w_trim_copy(p, fend, name, sizeof(name)); if (col < W_MAXCOL) { w_col_kinds[col] = w_col_kind(name); w_col_ncols = col + 1; } printf("%s%s%.*s%s", WC_BOLD, w_field_seq(WF_LABEL), (int)(fend - p), p, WC_RESET); } static void w_data_cb(const char *p, const char *fend, int col) { int kind = (col < w_col_ncols) ? w_col_kinds[col] : CK_NONE; if (col == 0) { /* Column 0 = BGP flag(s) + spaces + prefix (+ trailing spaces). The * prefix is the last whitespace-delimited token, so a v6 prefix that * starts with a hex letter (e.g. fe80::/10) is not mis-split; the flag * region is everything before it, trailing spaces are preserved. */ const char *pe = fend, *ps; while (pe > p && pe[-1] == ' ') pe--; ps = pe; while (ps > p && ps[-1] != ' ') ps--; w_emit_flag(p, (size_t)(ps - p)); if (pe > ps) printf("%s%.*s%s", w_field_seq(WF_PREFIX), (int)(pe - ps), ps, WC_RESET); if (fend > pe) printf("%.*s", (int)(fend - pe), pe); } else { w_emit_cell(p, (size_t)(fend - p), kind); } } /* Print a raw pWhoIs reply with the Cartographer colour scheme. Handles the * default "Label: value" output and the tabular -a / -A route views (header * row learns the columns, rows below are coloured per column). Unrecognised * lines pass through; with colour off this is a straight fputs. */ /* Colourise a JSON / JSONP (type=json, type=jsonp) reply. Keys take the label * colour, values the colour of their field group (numeric values of unknown * keys read as numbers), structural punctuation is dimmed, and whitespace is * emitted verbatim so both the pretty and compact forms keep their shape. */ static void w_print_json_colorized(const char *reply) { const char *p = reply; int expect_key = 1; char key[64]; key[0] = '\0'; while (*p) { unsigned char c = (unsigned char)*p; if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { putchar(c); p++; continue; } if (c == '{' || c == '}' || c == '[' || c == ']' || c == ':' || c == ',') { printf("%s%c%s", WC_DIM, c, WC_RESET); if (c == ':') expect_key = 0; else if (c == ',') expect_key = 1; else if (c == '{') expect_key = 1; else if (c == '[') expect_key = 0; p++; continue; } if (c == '"') { /* a quoted string: key or value */ const char *s = p + 1, *q = s; while (*q && *q != '"') { if (*q == '\\' && q[1]) q += 2; else q++; } if (expect_key) { size_t klen = (size_t)(q - s); if (klen < sizeof(key)) { memcpy(key, s, klen); key[klen] = '\0'; } else key[0] = '\0'; printf("%s\"%.*s\"%s", w_field_seq(WF_LABEL), (int)(q - s), s, WC_RESET); } else { int f = w_pwhois_field(key); if (f != WF_NONE) printf("%s\"%.*s\"%s", w_field_seq(f), (int)(q - s), s, WC_RESET); else printf("\"%.*s\"", (int)(q - s), s); } p = *q ? q + 1 : q; continue; } { /* bare value: number/true/false/null */ const char *s = p; int f; size_t tl; while (*p && *p != ',' && *p != '}' && *p != ']' && *p != ':' && *p != ' ' && *p != '\t' && *p != '\n' && *p != '\r') p++; tl = (size_t)(p - s); f = w_pwhois_field(key); if (f != WF_NONE) printf("%s%.*s%s", w_field_seq(f), (int)tl, s, WC_RESET); else if (tl && (s[0] == '-' || (s[0] >= '0' && s[0] <= '9'))) printf("%s%.*s%s", w_field_seq(WF_ASN), (int)tl, s, WC_RESET); else printf("%.*s", (int)tl, s); /* true / false / null */ } } } static void w_print_pwhois_colorized(const char *reply) { const char *p = reply; if (!w_use_color) { fputs(reply, stdout); return; } /* JSON / JSONP replies (type=json, type=jsonp) begin with '{' or '['. */ { const char *j = reply; while (*j == ' ' || *j == '\t' || *j == '\n' || *j == '\r') j++; if (*j == '{' || *j == '[') { w_print_json_colorized(reply); return; } } w_col_ncols = 0; while (*p) { const char *eol = strchr(p, '\n'); size_t linelen = eol ? (size_t)(eol - p) : strlen(p); const char *nl = eol ? "\n" : ""; const char *bar = (const char *)memchr(p, '|', linelen); if (bar) { /* A table line: header if the first non-space char is a letter * (column names), otherwise a data row ('*' flag or a digit). */ const char *q = p; while (q < p + linelen && *q == ' ') q++; int is_hdr = (q < p + linelen) && (((*q >= 'A') && (*q <= 'Z')) || ((*q >= 'a') && (*q <= 'z'))); if (is_hdr) w_render_row(p, linelen, w_header_cb); else w_render_row(p, linelen, w_data_cb); fputs(nl, stdout); } else { const char *colon = memchr(p, ':', linelen); if (colon && colon > p) { char label[64]; size_t llen = (size_t)(colon - p); const char *val = colon + 1; size_t vlen; while (val < p + linelen && *val == ' ') val++; vlen = (size_t)(p + linelen - val); if (llen < sizeof(label)) { int f; const char *lab; memcpy(label, p, llen); label[llen] = '\0'; lab = label; while (*lab == ' ') lab++; /* stats labels are indented */ f = w_pwhois_field(lab); printf("%s%s:%s ", w_field_seq(WF_LABEL), label, WC_RESET); if (f != WF_NONE) printf("%s%.*s%s%s", w_field_seq(f), (int)vlen, val, WC_RESET, nl); else if (w_value_numeric(val, vlen)) printf("%s%.*s%s%s", w_field_seq(WF_ASN), (int)vlen, val, WC_RESET, nl); else printf("%.*s%s", (int)vlen, val, nl); if (!eol) break; p = eol + 1; continue; } } printf("%.*s%s", (int)linelen, p, nl); /* passthrough */ } if (!eol) break; p = eol + 1; } } /* --- free-form status colouring (whob -s) --------------------------------- */ static int w_tok_is_month(const char *t, size_t len) { static const char *m[] = { "jan","feb","mar","apr","may","jun", "jul","aug","sep","oct","nov","dec" }; int i; if (len != 3) return 0; for (i = 0; i < 12; i++) if (strncasecmp(t, m[i], 3) == 0) return 1; return 0; } /* A date component after a month: a run of digits with optional ':' / '-' / ','. */ static int w_tok_is_datepart(const char *t, size_t len) { size_t i; int digit = 0; if (!len) return 0; for (i = 0; i < len; i++) { char c = t[i]; if (c >= '0' && c <= '9') digit = 1; else if (c != ':' && c != '-' && c != ',') return 0; } return digit; } static int w_tok_is_number(const char *t, size_t len) { size_t i; if (!len) return 0; for (i = 0; i < len; i++) if (!(t[i] >= '0' && t[i] <= '9')) return 0; return 1; } /* Colour the pWhoIs server status/version banner (-s): counts in the number * colour, "Mon DD YYYY HH:MM:SS" date runs in the date colour, a leading * "(version)" token in the label colour; descriptive prose passes through. */ static void w_print_status_colorized(const char *reply) { const char *p = reply; if (!w_use_color) { fputs(reply, stdout); return; } while (*p) { const char *eol = strchr(p, '\n'); const char *end = eol ? eol : p + strlen(p); const char *q = p; while (q < end) { const char *t; size_t tl; if (*q == ' ') { putchar(' '); q++; continue; } t = q; while (q < end && *q != ' ') q++; tl = (size_t)(q - t); if (w_tok_is_month(t, tl)) { /* month + following date-part tokens share the date colour */ printf("%s%.*s", w_field_seq(WF_DATE), (int)tl, t); while (q < end) { const char *sp = q, *t2; size_t tl2; while (q < end && *q == ' ') q++; t2 = q; while (q < end && *q != ' ') q++; tl2 = (size_t)(q - t2); if (tl2 && w_tok_is_datepart(t2, tl2)) { fwrite(sp, 1, (size_t)(t2 - sp), stdout); /* gap */ printf("%.*s", (int)tl2, t2); } else { printf("%s", WC_RESET); q = sp; /* re-handle from the gap */ break; } } printf("%s", WC_RESET); } else if (w_tok_is_number(t, tl)) { printf("%s%.*s%s", w_field_seq(WF_ASN), (int)tl, t, WC_RESET); } else if (tl > 2 && t[0] == '(' && memchr(t, '.', tl) && strchr("0123456789", t[1])) { /* a version like "(2.2.2.3-fix)" — not "(c)" */ printf("%s%.*s%s", w_field_seq(WF_LABEL), (int)tl, t, WC_RESET); } else { printf("%.*s", (int)tl, t); } } if (!eol) break; putchar('\n'); p = eol + 1; } } /* ==== --annotate: enrich IP addresses found in arbitrary text ============== * A Unix filter (`traceroute x | whob --annotate`, `grep denied fw.log | ...`). * Reads stdin (or --annotate=FILE) in chunks, finds IPv4/IPv6 literals with * inet_pton validation (so version numbers, times and domain names are never * mistaken for addresses; ip:port, CIDR and trailing punctuation are handled), * resolves the DISTINCT addresses through batched pWhoIs bulk lookups, and * re-emits every line verbatim with " [ASn Org-Name]" after each address * ("[?]" when pWhoIs has nothing). Colourised on a terminal like the rest. */ static int annotate_mode = 0; static char annotate_file[1024] = ""; #define ANN_BATCH 500 /* addresses per pWhoIs bulk request */ #define ANN_CHUNK 2000 /* input lines buffered per pass */ struct ann_ent { char ip[INET6_ADDRSTRLEN]; int asn; int have; char org[100]; char asorg[100]; char net[32]; char prefix[48]; char city[50]; char region[50]; char country[50]; char cc[4]; }; /* -F/--fields: which pWhoIs fields the annotation carries, printed in this order */ enum { AF_ASN=1, AF_ORG=2, AF_ASORG=4, AF_NET=8, AF_PREFIX=16, AF_CITY=32, AF_REGION=64, AF_COUNTRY=128, AF_CC=256, AF_PLACE=512 }; #define AF_DEFAULT (AF_ASN|AF_ORG) #define AF_ALL (AF_ASN|AF_ORG|AF_ASORG|AF_NET|AF_PREFIX|AF_CITY|AF_REGION|AF_COUNTRY|AF_CC|AF_PLACE) static unsigned ann_fields = AF_DEFAULT; static int ann_parse_fields(const char *spec) /* 0 = bad token */ { static const struct { const char *name; unsigned bit; } tok[] = { {"asn",AF_ASN},{"org",AF_ORG},{"asorg",AF_ASORG},{"net",AF_NET},{"prefix",AF_PREFIX}, {"city",AF_CITY},{"region",AF_REGION},{"country",AF_COUNTRY},{"cc",AF_CC},{"place",AF_PLACE},{"all",AF_ALL} }; unsigned f = (*spec == '+' || *spec == '-') ? AF_DEFAULT : 0; char buf[256], *p, *save = NULL; snprintf(buf, sizeof buf, "%s", spec); for (p = strtok_r(buf, ",", &save); p; p = strtok_r(NULL, ",", &save)) { int sign = 1; size_t i; unsigned bit = 0; if (*p == '+') p++; else if (*p == '-') { sign = 0; p++; } for (i = 0; i < sizeof tok / sizeof tok[0]; i++) if (strcasecmp(p, tok[i].name) == 0) bit = tok[i].bit; if (!bit) return 0; if (sign) f |= bit; else f &= ~bit; } if (!f) return 0; ann_fields = f; return 1; } static struct ann_ent *ann_cache = NULL; static int ann_n = 0, ann_cap = 0, ann_sorted = 0; /* [0,ann_sorted) is sorted */ static int ann_cmp(const void *a, const void *b) { return strcmp(((const struct ann_ent *)a)->ip, ((const struct ann_ent *)b)->ip); } static int ann_find(const char *ip) /* index or -1 */ { struct ann_ent key, *e; int i; memset(&key, 0, sizeof key); snprintf(key.ip, sizeof key.ip, "%s", ip); if (ann_sorted && (e = bsearch(&key, ann_cache, (size_t)ann_sorted, sizeof *ann_cache, ann_cmp))) return (int)(e - ann_cache); for (i = ann_sorted; i < ann_n; i++) /* unsorted tail */ if (strcmp(ann_cache[i].ip, ip) == 0) return i; return -1; } static int ann_add(const char *ip) /* index or -1 */ { if (ann_n >= ann_cap) { int nc = ann_cap ? ann_cap * 2 : 512; struct ann_ent *p = realloc(ann_cache, (size_t)nc * sizeof *p); if (!p) return -1; ann_cache = p; ann_cap = nc; } memset(&ann_cache[ann_n], 0, sizeof ann_cache[ann_n]); snprintf(ann_cache[ann_n].ip, sizeof ann_cache[ann_n].ip, "%s", ip); return ann_n++; } static void ann_sort(void) { if (ann_n > 1) qsort(ann_cache, (size_t)ann_n, sizeof *ann_cache, ann_cmp); ann_sorted = ann_n; } /* canonical text of an address literal in tok[0..len): 4, 6, or 0 if none */ static int ann_canon(const char *tok, size_t len, char *out, size_t outsz) { char buf[INET6_ADDRSTRLEN + 8]; struct in_addr a4; struct in6_addr a6; if (!len || len >= sizeof buf) return 0; memcpy(buf, tok, len); buf[len] = '\0'; if (strchr(buf, ':')) { if (inet_pton(AF_INET6, buf, &a6) == 1 && inet_ntop(AF_INET6, &a6, out, (socklen_t)outsz)) return 6; return 0; } if (inet_pton(AF_INET, buf, &a4) == 1 && inet_ntop(AF_INET, &a4, out, (socklen_t)outsz)) return 4; return 0; } static int ann_ischar(char c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || c == '.' || c == ':'; } /* move the insertion point past an attached "/len" (CIDR) or "]" [+ ":port"] * so the annotation never lands inside a prefix or a bracketed address */ static size_t ann_extend(const char *s, size_t n, size_t ins) { size_t k; if (ins < n && s[ins] == '/') { for (k = ins + 1; k < n && s[k] >= '0' && s[k] <= '9'; k++) ; if (k > ins + 1) ins = k; } else if (ins < n && s[ins] == ']') { ins++; if (ins < n && s[ins] == ':') { for (k = ins + 1; k < n && s[k] >= '0' && s[k] <= '9'; k++) ; if (k > ins + 1) ins = k; } } return ins; } /* Next address literal in s at/after *pos. Returns the family and sets * *ins (where the annotation goes: right after the address, or after an * attached :port / /len / ]) and canon; advances *pos past the token. */ static int ann_next(const char *s, size_t *pos, size_t *ins, char *canon, size_t csz) { size_t i = *pos, n = strlen(s); while (i < n) { size_t b, e, t, k; int fam, dig = 0, sep = 0; while (i < n && !ann_ischar(s[i])) i++; if (i >= n) break; b = i; while (i < n && ann_ischar(s[i])) i++; e = i; for (t = b; t < e; t++) { if (s[t] >= '0' && s[t] <= '9') dig = 1; if (s[t] == '.' || s[t] == ':') sep = 1; } if (!dig || !sep) continue; if ((fam = ann_canon(s + b, e - b, canon, csz))) { *ins = ann_extend(s, n, e); *pos = *ins > e ? *ins : e; return fam; } for (t = e; t > b && t + 2 > e && (s[t-1] == '.' || s[t-1] == ':'); t--) /* trailing punctuation */ if ((fam = ann_canon(s + b, t - 1 - b, canon, csz))) { *ins = t - 1; *pos = e; return fam; } k = e; /* v4 with :port */ while (k > b && s[k-1] != ':') k--; if (k > b && ann_canon(s + b, k - 1 - b, canon, csz) == 4) { *ins = ann_extend(s, n, e); *pos = *ins > e ? *ins : e; return 4; } { /* BSD/macOS netstat "a.b.c.d.port" */ int dots = 0; size_t p4 = b; for (t = b; t < e; t++) if (s[t] == '.') { if (++dots == 4) p4 = t; } if (dots == 4 && p4 > b && ann_canon(s + b, p4 - b, canon, csz) == 4) { *ins = ann_extend(s, n, e); *pos = *ins > e ? *ins : e; return 4; } } } *pos = n; return 0; } static int ann_prefix(const char *line, const char *pfx, const char **val) { size_t l = strlen(pfx); if (strncasecmp(line, pfx, l) != 0) return 0; line += l; while (*line == ' ' || *line == '\t') line++; *val = line; return 1; } /* one pWhoIs bulk request for the cache entries whose indices are in pend[] */ /* copy a reply value, treating pWhoIs's literal "NULL" as absent */ static void ann_val(char *dst, size_t sz, const char *v) { size_t n = strcspn(v, "\r"); if (n == 4 && strncasecmp(v, "NULL", 4) == 0) { dst[0] = '\0'; return; } snprintf(dst, sz, "%.*s", (int)n, v); } static void ann_resolve(whois_session_params *wsess, const int *pend, int np) { size_t qlen = 96 + (size_t)np * (INET6_ADDRSTRLEN + 1); char *q, *reply, *serv; token_t *ls; int i, cur = -1; if (np <= 0) return; serv = (strlen(wsess->pw_serv) > 0) ? wsess->pw_serv : pwhois_server; if (!(q = malloc(qlen))) return; snprintf(q, qlen, "begin\napp=\"%s %s ANNOTATE\"\n", appname, version); for (i = 0; i < np; i++) { strcat(q, ann_cache[pend[i]].ip); strcat(q, "\n"); } strcat(q, "end\n"); reply = w_ask(wsess, serv, q, NULL); free(q); if (!reply) return; if (strncasecmp(reply, "Error:", 6) == 0) { /* e.g. daily query limit exceeded */ static int shown = 0; const char *m = reply; while (strncasecmp(m, "Error:", 6) == 0) { m += 6; while (*m == ' ') m++; } /* server doubles the prefix */ if (!shown) { fflush(stdout); fprintf(stderr, "%s: pWhoIs: %.*s\n", appname, (int)strcspn(m, "\r\n"), m); shown = 1; } free(reply); return; } ls = tokens(reply, "\n"); for (i = 0; ls[i].ptr; i++) { const char *v; char canon[INET6_ADDRSTRLEN]; if (ann_prefix(ls[i].ptr, "IP:", &v)) { int k; cur = -1; if (!ann_canon(v, strcspn(v, " \t\r"), canon, sizeof canon)) continue; for (k = 0; k < np; k++) if (strcmp(ann_cache[pend[k]].ip, canon) == 0) { cur = pend[k]; ann_cache[cur].have = 1; break; } } else if (cur >= 0 && ann_prefix(ls[i].ptr, "Origin-AS:", &v)) { ann_cache[cur].asn = atoi(v); } else if (cur >= 0 && ann_prefix(ls[i].ptr, "Org-Name:", &v)) { ann_val(ann_cache[cur].org, sizeof ann_cache[cur].org, v); } else if (cur >= 0 && ann_prefix(ls[i].ptr, "Net-Name:", &v)) { ann_val(ann_cache[cur].net, sizeof ann_cache[cur].net, v); } else if (cur >= 0 && ann_prefix(ls[i].ptr, "AS-Org-Name:", &v)) { ann_val(ann_cache[cur].asorg, sizeof ann_cache[cur].asorg, v); } else if (cur >= 0 && ann_prefix(ls[i].ptr, "Prefix:", &v)) { ann_val(ann_cache[cur].prefix, sizeof ann_cache[cur].prefix, v); } else if (cur >= 0 && ann_prefix(ls[i].ptr, "City:", &v)) { ann_val(ann_cache[cur].city, sizeof ann_cache[cur].city, v); } else if (cur >= 0 && ann_prefix(ls[i].ptr, "Region:", &v)) { ann_val(ann_cache[cur].region, sizeof ann_cache[cur].region, v); } else if (cur >= 0 && ann_prefix(ls[i].ptr, "Country-Code:", &v)) { ann_val(ann_cache[cur].cc, sizeof ann_cache[cur].cc, v); } else if (cur >= 0 && ann_prefix(ls[i].ptr, "Country:", &v)) { ann_val(ann_cache[cur].country, sizeof ann_cache[cur].country, v); } } free(ls); free(reply); } /* one selected field, coloured on a terminal; returns 1 if something was printed */ static int ann_emit_field(const char *v, int kind, int first) { if (!v || !v[0]) return 0; if (!first) putchar(' '); if (w_use_color) printf("%s%s%s", w_field_seq(kind), v, WC_RESET); else fputs(v, stdout); return 1; } /* emit one input line with an annotation after each address */ static void ann_emit_line(const char *s) { size_t pos = 0, ins, last = 0; char canon[INET6_ADDRSTRLEN]; while (ann_next(s, &pos, &ins, canon, sizeof canon)) { int idx = ann_find(canon), n = 0; const struct ann_ent *e = (idx >= 0) ? &ann_cache[idx] : NULL; fwrite(s + last, 1, ins - last, stdout); if (w_use_color) printf(" %s[%s", WC_DIM, WC_RESET); else fputs(" [", stdout); if (e && e->have) { char asn[16] = "", place[200] = ""; if (e->asn) snprintf(asn, sizeof asn, "AS%d", e->asn); if (ann_fields & AF_PLACE) { size_t l = 0; if (e->city[0]) { snprintf(place + l, sizeof place - l, "%s", e->city); l = strlen(place); } if (e->region[0] && strcmp(e->region, e->city) != 0) { snprintf(place + l, sizeof place - l, "%s%s", l ? ", " : "", e->region); l = strlen(place); } if (e->country[0]) { snprintf(place + l, sizeof place - l, "%s%s", l ? ", " : "", e->country); l = strlen(place); } if (e->cc[0]) snprintf(place + l, sizeof place - l, "%s(%s)", l ? " " : "", e->cc); } if (ann_fields & AF_ASN) n += ann_emit_field(asn, WF_ASN, n == 0); if (ann_fields & AF_ORG) n += ann_emit_field(e->org, WF_ORG, n == 0); if (ann_fields & AF_ASORG) n += ann_emit_field(e->asorg, WF_ORG, n == 0); if (ann_fields & AF_NET) n += ann_emit_field(e->net, WF_LABEL, n == 0); if (ann_fields & AF_PREFIX) n += ann_emit_field(e->prefix, WF_PREFIX, n == 0); if (ann_fields & AF_CITY) n += ann_emit_field(e->city, WF_GEO, n == 0); if (ann_fields & AF_REGION) n += ann_emit_field(e->region, WF_GEO, n == 0); if (ann_fields & AF_COUNTRY) n += ann_emit_field(e->country, WF_GEO, n == 0); if (ann_fields & AF_CC) n += ann_emit_field(e->cc, WF_GEO, n == 0); if (ann_fields & AF_PLACE) n += ann_emit_field(place, WF_GEO, n == 0); } if (!n) { if (w_use_color) printf("%s?%s", WC_DIM, WC_RESET); else putchar('?'); } if (w_use_color) printf("%s]%s", WC_DIM, WC_RESET); else putchar(']'); last = ins; } fputs(s + last, stdout); } /* chunked driver: collect -> resolve (batched) -> emit, repeat to EOF */ static void w_annotate_stream(whois_session_params *wsess, FILE *in) { char **lines = malloc(sizeof(char *) * ANN_CHUNK); char buf[8192]; int nl, i; if (!lines) return; for (;;) { int pend[ANN_BATCH], np = 0; for (nl = 0; nl < ANN_CHUNK && fgets(buf, sizeof buf, in); ) { if ((lines[nl] = strdup(buf))) nl++; } if (nl == 0) break; for (i = 0; i < nl; i++) { /* pass 1: new addresses */ size_t pos = 0, ins; char canon[INET6_ADDRSTRLEN]; while (ann_next(lines[i], &pos, &ins, canon, sizeof canon)) { int idx; if (ann_find(canon) >= 0) continue; if ((idx = ann_add(canon)) < 0) continue; pend[np++] = idx; if (np == ANN_BATCH) { ann_resolve(wsess, pend, np); np = 0; ann_sort(); } } } if (np) ann_resolve(wsess, pend, np); ann_sort(); for (i = 0; i < nl; i++) { ann_emit_line(lines[i]); free(lines[i]); } /* pass 2 */ fflush(stdout); } free(lines); } static int w_display_pwhois_gigo(whois_session_params * wsess, char *user_query) { char *reply; char *serv; if (strlen(wsess->pw_serv) > 0) serv = wsess->pw_serv; else serv = pwhois_server; if (wsess->w_noisy) { if (w_ecolor()) fprintf(stderr, "Querying '%s%s%s' for: '%s%s%s'\n", w_field_seq(WF_LABEL), serv, WC_RESET, w_field_seq(WF_ASN), user_query, WC_RESET); else fprintf(stderr, "Querying '%s' for: '%s'\n", serv, user_query); } reply = w_ask(wsess, serv, user_query, NULL); if (!reply) { if (wsess->w_noisy) fprintf(stderr,"No reply from %s.\n",serv); return -1; } w_print_pwhois_colorized(reply); free(reply); return 0; } #endif int w_lookup_all_pwhois_bulk(whois_session_params * wsess, struct ip_list_array *iplist) { token_t *responses; char *reply; const char *bulk_begin = "begin\n"; const char *bulk_end = "end\n"; char *bulk_ip_query = NULL; int i = 0, k = 0, entity_id = 0; unsigned int j = 0; char *value = NULL; if (!iplist) return -1; if ((uintptr_t)(*iplist).application) { bulk_ip_query = (char *)malloc(((strlen((*iplist).application) * sizeof(char)) +10) + ((strlen(bulk_begin) + strlen(bulk_end) + 1) * sizeof(char)) + (16 * (*iplist).numItems)); } else bulk_ip_query = (char *)malloc(((strlen(appname) * sizeof(char)) +10) + ((strlen(version) + strlen(bulk_begin) + strlen(bulk_end) + 1) * sizeof(char)) + (16 * (*iplist).numItems)); *bulk_ip_query = '\0'; /* clean up the response data set in case the caller doesn't (and we return error) */ for (i = 0; i < (*iplist).numItems; i++) { (*iplist).asn[(i)] = 0; memset((*iplist).netName[i],0,sizeof((*iplist).netName[i])); memset((*iplist).orgName[i],0,sizeof((*iplist).orgName[i])); } /* prepare the text-string-based query */ strcat(bulk_ip_query,bulk_begin); if ((uintptr_t)(*iplist).application) { strcat(bulk_ip_query, "app=\""); strcat(bulk_ip_query, (*iplist).application); strcat(bulk_ip_query, "\"\n"); } else { strcat(bulk_ip_query, "app=\""); strcat(bulk_ip_query, appname); strcat(bulk_ip_query, " "); strcat(bulk_ip_query, version); strcat(bulk_ip_query, "\"\n"); } for (i = 0; i < ((*iplist).numItems); i++) { strcat(bulk_ip_query, inet_ntoa((*iplist).ipaddr[i])); strcat(bulk_ip_query, "\n"); } strcat(bulk_ip_query, bulk_end); reply = w_ask(wsess, pwhois_server, bulk_ip_query, NULL); if (!reply) { if (wsess->w_noisy) { if(wsess->logprintfCookie) lft_printf(wsess->logprintfCookie,"No reply from %s.\n",pwhois_server); else fprintf(stderr,"No reply from %s.\n",pwhois_server); } /* clean up the response data set in case the caller doesn't */ for (i = 0; i < (*iplist).numItems; i++) (*iplist).asn[(i)] = 0; free(bulk_ip_query); return -1; } responses = tokens(reply, "\n"); for(i = 0; responses[i].ptr; i++){ value = NULL; // printf("LINE %d: '%s'\n",i, responses[i].ptr); if((value = match_prefix("IP:", responses[i].ptr)) != NULL){ /* if any keys matched, increment the id of the array */ if(k > 0){ entity_id++; k = 0; } } else if((value = match_prefix("origin-as:", responses[i].ptr)) != NULL) { rm_spaces(value); /* strip out any spaces from the ASN */ for(j = 0; j < strlen(value); j++) { if(!isdigit((unsigned char)value[j])){ if (wsess->w_noisy) { if(wsess->logprintfCookie) lft_printf(wsess->logprintfCookie,"Parse error at \'%c\': non-numeric value at position %d of %s).\n",value[j],j,value); else fprintf(stderr,"Parse error at \'%c\': non-numeric value at position %d of %s).\n",value[j],j,value); } break; } } if ((int)strtol(value, (char **)NULL, 10)) { (*iplist).asn[(entity_id)] = strtol(value, (char **)NULL, 10); k++; } else { (*iplist).asn[(entity_id)] = 0; k++; } } else if ((value = match_prefix("org-name:", responses[i].ptr))) { strncpy((*iplist).orgName[entity_id],value,sizeof((*iplist).orgName[entity_id])-1); (*iplist).orgName[entity_id][sizeof((*iplist).orgName[entity_id])-1] = '\0'; k++; } else if ((value = match_prefix("net-name:", responses[i].ptr))) { strncpy((*iplist).netName[entity_id],value,sizeof((*iplist).netName[entity_id])-1); (*iplist).netName[entity_id][sizeof((*iplist).netName[entity_id])-1] = '\0'; k++; } if(value) free(value); if ((entity_id+1) > (*iplist).numItems) break; } free(responses); free(reply); free(bulk_ip_query); return 0; } int w_lookup_all_pwhois_bulk_ext(whois_session_params * wsess, struct ext_ip_list_array *iplist) { token_t *responses; char *reply; const char *bulk_begin = "begin\n"; const char *bulk_end = "end\n"; char *bulk_ip_query = NULL; int i=0; unsigned int j=0; int k=0; int pntcnt; int entity_id = 0; char *value = NULL; if (!iplist) return -1; iplist->geoavailable=0; if ((uintptr_t)(*iplist).application) { bulk_ip_query = malloc(((strlen((*iplist).application) * sizeof(char)) +10) + ((strlen(bulk_begin) + strlen(bulk_end) + 1) * sizeof(char)) + (16 * (*iplist).numItems)); } else bulk_ip_query = malloc(((strlen(appname) * sizeof(char)) +10) + ((strlen(version) + strlen(bulk_begin) + strlen(bulk_end) + 1) * sizeof(char)) + (16 * (*iplist).numItems)); bulk_ip_query[0]=0; /* clean up the response data set in case the caller doesn't (and we return error) */ for (i = 0; i < (*iplist).numItems; i++) { (*iplist).asn[(i)] = 0; memset((*iplist).netName[i],0,sizeof((*iplist).netName[i])); memset((*iplist).orgName[i],0,sizeof((*iplist).orgName[i])); } /* prepare the text-string-based query */ strcat(bulk_ip_query,bulk_begin); if ((uintptr_t)(*iplist).application) { strcat(bulk_ip_query,"app=\""); strcat(bulk_ip_query,(*iplist).application); strcat(bulk_ip_query,"\"\n"); } else { strcat(bulk_ip_query,"app=\""); strcat(bulk_ip_query,appname); strcat(bulk_ip_query," "); strcat(bulk_ip_query,version); strcat(bulk_ip_query,"\"\n"); } for (i = 0; i < ((*iplist).numItems); i++) { strcat(bulk_ip_query,inet_ntoa((*iplist).ipaddr[i])); strcat(bulk_ip_query,"\n"); } strcat(bulk_ip_query,bulk_end); reply = w_ask(wsess, pwhois_server, bulk_ip_query, NULL); if (!reply) { if (wsess->w_noisy) { if(wsess->logprintfCookie) lft_printf(wsess->logprintfCookie,"No reply from %s.\n",pwhois_server); else fprintf(stderr,"No reply from %s.\n",pwhois_server); } /* clean up the response data set in case the caller doesn't */ for (i = 0; i < (*iplist).numItems; i++) (*iplist).asn[(i)] = 0; free(bulk_ip_query); return -1; } responses = tokens(reply, "\n"); for (i = 0; responses[i].ptr; i++) { value = NULL; // printf("LINE %d: '%s'\n",i, responses[i].ptr); if ((value = match_prefix("IP:", responses[i].ptr)) != NULL) { /* if any keys matched, increment the id of the array */ if (k > 0) { entity_id++; k = 0; } } else if ((value = match_prefix("origin-as:", responses[i].ptr)) != NULL) { rm_spaces(value); /* strip out any spaces from the ASN */ for (j = 0; j < strlen(value); j++) { if (!isdigit((unsigned char)value[j])) { if (wsess->w_noisy) { if(wsess->logprintfCookie) lft_printf(wsess->logprintfCookie,"Parse error at \'%c\': non-numeric value at position %d of %s).\n",value[j],j,value); else fprintf(stderr,"Parse error at \'%c\': non-numeric value at position %d of %s).\n",value[j],j,value); } break; } } if(atoi(value)) { (*iplist).asn[(entity_id)] = atoi(value); k++; } else { (*iplist).asn[(entity_id)] = 0; k++; } } else if ((value = match_prefix("as-org-name-source:", responses[i].ptr))) { strncpy((*iplist).asOrgNameSource[entity_id],value,20); k++; } else if ((value = match_prefix("org-name-source:", responses[i].ptr))) { strncpy((*iplist).orgNameSource[entity_id],value,20); k++; } else if ((value = match_prefix("net-name-source:", responses[i].ptr))) { strncpy((*iplist).netNameSource[entity_id],value,20); k++; } else if ((value = match_prefix("prefix:", responses[i].ptr))) { strncpy((*iplist).prefix[entity_id],value,sizeof((*iplist).prefix[entity_id])-1); (*iplist).prefix[entity_id][sizeof((*iplist).prefix[entity_id])-1] = '\0'; k++; } else if ((value = match_prefix("org-name:", responses[i].ptr))) { strncpy((*iplist).orgName[entity_id],value,sizeof((*iplist).orgName[entity_id])-1); (*iplist).orgName[entity_id][sizeof((*iplist).orgName[entity_id])-1] = '\0'; k++; } else if ((value = match_prefix("net-name:", responses[i].ptr))) { strncpy((*iplist).netName[entity_id],value,sizeof((*iplist).netName[entity_id])-1); (*iplist).netName[entity_id][sizeof((*iplist).netName[entity_id])-1] = '\0'; k++; } else if ((value = match_prefix("longitude:", responses[i].ptr))) { rm_spaces(value); /* strip out any spaces from the LONGITUDE */ for (j = 0, pntcnt = 0; j < strlen(value); j++) { if(value[j]=='.') pntcnt++; if (!isdigit((unsigned char)value[j]) && (value[j]!='.' || pntcnt>1) && !(j==0 && value[j]=='-')) { if (wsess->w_noisy) { if(wsess->logprintfCookie) lft_printf(wsess->logprintfCookie,"Parse error at \'%c\': can't parse value at position %d of %s).\n",value[j],j,value); else fprintf(stderr,"Parse error at \'%c\': can't parse value at position %d of %s).\n",value[j],j,value); } break; } } (*iplist).longitude[(entity_id)]=atof(value); iplist->geoavailable++; k++; } else if ((value = match_prefix("latitude:", responses[i].ptr))) { rm_spaces(value); /* strip out any spaces from the LONGITUDE */ for (j = 0, pntcnt = 0; j < strlen(value); j++) { if(value[j]=='.') pntcnt++; if (!isdigit((unsigned char)value[j]) && (value[j]!='.' || pntcnt>1) && !(j==0 && value[j]=='-')) { if (wsess->w_noisy) { if(wsess->logprintfCookie) lft_printf(wsess->logprintfCookie,"Parse error at \'%c\': can't parse value at position %d of %s).\n",value[j],j,value); else fprintf(stderr,"Parse error at \'%c\': can't parse value at position %d of %s).\n",value[j],j,value); } break; } } (*iplist).latitude[(entity_id)]=atof(value); iplist->geoavailable++; k++; } else if ((value = match_prefix("city:", responses[i].ptr))) { strncpy((*iplist).city[entity_id],value,sizeof((*iplist).city[entity_id])-1); (*iplist).city[entity_id][sizeof((*iplist).city[entity_id])-1] = '\0'; iplist->geoavailable++; k++; } else if ((value = match_prefix("country-code:", responses[i].ptr))) { strncpy((*iplist).country_code[entity_id],value,sizeof((*iplist).country_code[entity_id])-1); (*iplist).country_code[entity_id][sizeof((*iplist).country_code[entity_id])-1] = '\0'; k++; } else if ((value = match_prefix("country:", responses[i].ptr))) { strncpy((*iplist).country[entity_id],value,sizeof((*iplist).country[entity_id])-1); (*iplist).country[entity_id][sizeof((*iplist).country[entity_id])-1] = '\0'; iplist->geoavailable++; k++; } else if ((value = match_prefix("region:", responses[i].ptr))) { strncpy((*iplist).state[entity_id],value,sizeof((*iplist).state[entity_id])-1); (*iplist).state[entity_id][sizeof((*iplist).state[entity_id])-1] = '\0'; iplist->geoavailable++; k++; } if(value) free(value); if ((entity_id+1) > (*iplist).numItems) break; } free(responses); free(reply); free(bulk_ip_query); return 0; } static char * w_lookup_netname_other(whois_session_params * wsess, char *addr, char *serv) { token_t *ls; ip_blk_t *a = NULL, *b = NULL; char *reply, *ans = NULL; int i; int use_this = 1; if (!addr || !serv) return NULL; reply = w_ask(wsess, serv, addr, NULL); if (!reply) { if (wsess->w_noisy) { if(wsess->logprintfCookie) lft_printf(wsess->logprintfCookie,"No reply from %s.\n",serv); else fprintf(stderr,"No reply from %s.\n",serv); } return NULL; } ls = tokens(reply, "\n"); for (i = 0; ls[i].ptr; i++) { char *value=NULL; if ((value = match_prefix("inetnum:", ls[i].ptr))) { a = match_ipprefix(value); if (b) { if (((b->end - b->start) > (a->end - a->start))) { use_this = 1; free(b); b = a; a = NULL; } else { use_this = 0; free(a); a = NULL; } } else { use_this = 1; b = a; a = NULL; } free(value); } else if (use_this && (value = match_prefix("netname:", ls[i].ptr))) { if (ans) free(ans); ans = value; } } free(ls); free(reply); if (b) free(b); return ans; } char * w_lookup_netname(whois_session_params * wsess, char *addr) { token_t *ls; ip_blk_t *a = NULL, *b = NULL; char *na = NULL, *nb = NULL; char *reply, *ans = NULL; int i; int have_new, have_old; if (!addr) return NULL; reply = w_ask(wsess, arin_server, addr, NULL); if (!reply) { if (wsess->w_noisy) { if(wsess->logprintfCookie) lft_printf(wsess->logprintfCookie,"No reply from %s.\n",arin_server); else fprintf(stderr,"No reply from %s.\n",arin_server); } return NULL; } ls = tokens(reply, "\n"); ans = NULL; for (i = 0; ls[i].ptr; i++) { char *value; if ((value = match_prefix("netname:", ls[i].ptr))) { ans = value; break; } } if (!ans) { for (i = 0; ls[i].ptr; i++) { char *value; if ((value = match_inparens(ls[i].ptr))) { char *after = match_afterparens(ls[i].ptr); if (after) { na = value; a = match_iprange(after); } else { na = value; if (ls[i+1].ptr && (a = match_iprange(ls[i+1].ptr))) { /* successful match */ } else { /* Bad format */ free(na); na = NULL; continue; } } } have_new = (na && a); have_old = (nb && b); if (have_new) { if (have_old) { if (((b->end - b->start) > (a->end - a->start))) { /* keep new, discard old */ free(nb); free(b); nb = na; na = NULL; b = a; a = NULL; } else { /* keep old, discard new */ free(na); free(a); na = NULL; a = NULL; } } else { /* nothing old, save new */ nb = na; na = NULL; b = a; a = NULL; } } } /* loop */ if (na) free(na); if (a) free(a); if (b) free(b); free(ls); free(reply); if (!nb) return NULL; /* Add "!" to the beginning of the question */ na = malloc(strlen(nb) + 2); strcpy(&na[1], nb); na[0] = '!'; free(nb); reply = w_ask(wsess, arin_server, na, NULL); free(na); if (!reply) { if (wsess->w_noisy) { if(wsess->logprintfCookie) lft_printf(wsess->logprintfCookie,"No reply from %s.\n",arin_server); else fprintf(stderr,"No reply from %s.\n",arin_server); } return NULL; } ls = tokens(reply, "\n"); } for (i = 0; ls[i].ptr; i++) { char *value; if ((value = match_prefix("netname:", ls[i].ptr))) { ans = value; break; } } free(ls); free(reply); { char *other = NULL; if (ans && strstr(ans, "RIPE")) { other = w_lookup_netname_other(wsess, addr, ripe_server); } if (ans && !strncmp(ans, "APNIC", 5)) { other = w_lookup_netname_other(wsess, addr, apnic_server); } if (other) { char *together = malloc(strlen(ans) + strlen(other) + 2); together[0]=0; strcpy(together, ans); strcat(together, "/"); strcat(together, other); free(ans); ans = together; } } return ans; } static char * w_lookup_orgname_other(whois_session_params * wsess, char *addr, char *serv) { token_t *ls; ip_blk_t *a = NULL, *b = NULL; char *reply, *ans = NULL; int i; int use_this = 1; if (!addr || !serv) return NULL; reply = w_ask(wsess, serv, addr, NULL); if (!reply) { if (wsess->w_noisy) { if(wsess->logprintfCookie) lft_printf(wsess->logprintfCookie,"No reply from %s.\n",serv); else fprintf(stderr,"No reply from %s.\n",serv); } return NULL; } ls = tokens(reply, "\n"); for (i = 0; ls[i].ptr; i++) { char *value=NULL; if ((value = match_prefix("inetnum:", ls[i].ptr))) { a = match_ipprefix(value); if (b) { if (((b->end - b->start) > (a->end - a->start))) { use_this = 1; free(b); b = a; a = NULL; } else { use_this = 0; free(a); a = NULL; } } else { use_this = 1; b = a; a = NULL; } free(value); }else if (use_this && (value = match_prefix("orgname:", ls[i].ptr))) { if (ans) free(ans); ans = value; } } if (!ans) { for (i = 0; ls[i].ptr; i++) { char *value; if (use_this && (value = match_prefix("descr:", ls[i].ptr))) { if (ans) free(ans); ans = value; break; } } } free(ls); free(reply); if (b) free(b); return ans; } char * w_lookup_orgname(whois_session_params * wsess, char *addr) { token_t *ls; ip_blk_t *a = NULL, *b = NULL; char *na = NULL, *nb = NULL; char *reply, *ans = NULL; int i; int have_new, have_old; if (!addr) return NULL; reply = w_ask(wsess, arin_server, addr, NULL); if (!reply) { if (wsess->w_noisy) { if(wsess->logprintfCookie) lft_printf(wsess->logprintfCookie,"No reply from %s.\n",arin_server); else fprintf(stderr,"No reply from %s.\n",arin_server); } return NULL; } ls = tokens(reply, "\n"); for (i = 0; ls[i].ptr; i++) { char *value; if ((value = match_prefix("netname:", ls[i].ptr))) { ans = value; break; } } if (!ans) { for (i = 0; ls[i].ptr; i++) { char *value; if ((value = match_inparens(ls[i].ptr))) { char *after = match_afterparens(ls[i].ptr); if (after) { na = value; a = match_iprange(after); } else { na = value; if (ls[i+1].ptr && (a = match_iprange(ls[i+1].ptr))) { /* successful match */ } else { /* Bad format */ free(na); na = NULL; continue; } } } have_new = (na && a); have_old = (nb && b); if (have_new) { if (have_old) { if (((b->end - b->start) > (a->end - a->start))) { /* keep new, discard old */ free(nb); free(b); nb = na; na = NULL; b = a; a = NULL; } else { /* keep old, discard new */ free(na); free(a); na = NULL; a = NULL; } } else { /* nothing old, save new */ nb = na; na = NULL; b = a; a = NULL; } } } /* loop */ if (na) free(na); if (a) free(a); if (b) free(b); free(ls); free(reply); if (!nb) return NULL; /* Add "!" to the beginning of the question */ na = malloc(strlen(nb) + 2); strcpy(&na[1], nb); na[0] = '!'; free(nb); reply = w_ask(wsess, arin_server, na, NULL); free(na); if (!reply) { if (wsess->w_noisy) { if(wsess->logprintfCookie) lft_printf(wsess->logprintfCookie,"No reply from %s.\n",arin_server); else fprintf(stderr,"No reply from %s.\n",arin_server); } return NULL; } ls = tokens(reply, "\n"); } for (i = 0; ls[i].ptr; i++) { char *value; if ((value = match_prefix("orgname:", ls[i].ptr))) { if(ans) free(ans); ans = value; break; } } free(ls); free(reply); { char *other = NULL; if (ans && strstr(ans, "RIPE")) { other = w_lookup_orgname_other(wsess, addr, ripe_server); } if (ans && (!strncmp(ans, "APNIC", 5) || strstr(ans, "Asia Pacific Net") )) { other = w_lookup_orgname_other(wsess, addr, apnic_server); } if (other) { /* char *together = malloc(strlen(ans) + strlen(other) + 4); strcpy(together, other); strcat(together, " ("); strcat(together, ans); strcat(together, ")"); */ free(ans); ans = other; } } return ans; } #ifdef STANDALONE /*---------------------------------------------------------------------------*/ void lft_printf(lft_session_params * sess, const char *templ, ...) { va_list ap; char buf[1024]; (void)sess; va_start (ap, templ); vsprintf(buf, templ, ap); va_end (ap); printf("%s",buf); } /*---------------------------------------------------------------------------*/ static void usage (char *prog) { fprintf (stderr, "\nWhoB - version %s %s\n\n" " - a likable whois client from the Prefix WhoIs project\n" " visit http://www.pwhois.org\n" "\nUsage: %s [-g] [] \n" "\nMainstream Options:\n" " -g Disable GIGO mode; enable other options then submit query\n" " -R Display the Origin-AS on file at the RADB/IRR also\n" " -p Display the AS-Path learned by the pWhoIs server (pWhoIs-only)\n" " -n Display the network name on file at the registrar\n" " -t Display the date the route was last updated (pWhoIs-only)\n" " -u Display the date the route was last updated in GMT (pWhoIs-only)\n" " -o Disable display of the organization\'s name on file at the registrar\n" "\nAdvanced Options:\n" " -h host Specify your own pWhoIs-compatible server to query\n" " -f file Read from 'file' (or '-' as stdin) as bulk input to pWhoIs\n" " -c Use Cymru\'s whois server instead of pWhoIs\n" " -r Use RIPE NCC\'s RIS whois server instead of pWhoIs\n" "\nPrefix WhoIs Advanced Options:\n" " -A Display all routes transiting the target ASN (pWhoIs-only)\n" " -a Display all routes announced by the target ASN (pWhoIs-only)\n" " -P Display all routes respective to a target prefix (pWhoIs-only)\n" " -N Display all networks registered to the target ASN (pWhoIs-only)\n" " -O Display organizational contact info for the target ASN (pWhoIs-only)\n" "\nVerbosity Options and Status:\n" " -s Display the version and status of the pWhoIs server (pWhoIs-only)\n" " -V Display verbose/debug output. Use more \'V\'s for additional detail\n" " -v Display WhoB\'s version information and exit\n" " --color Force colorized output even when not writing to a terminal\n" " --no-color Disable colorized output (default: auto-detect the terminal)\n" " -e, --enrich Annotate IP addresses in text from stdin with pWhoIs ASN and org\n" " --annotate[=FILE] Same as -e; =FILE reads a file instead of stdin\n" " -F, --fields=LIST Fields for -e: asn,org,asorg,net,prefix,city,region,country,cc,place,all\n" " (default asn,org; a leading + or - adjusts the default)\n" " me|whoami Display the public IP address of this host\n" "\n" ,version,version_date,prog); fprintf(stderr,"Example: %s -gnp 1.2.3.4\n",prog); fprintf(stderr,"Returns: IP Address | ASN-by-prefix (prefix) | AS-Path | NetName | OrgName\n\n"); exit(EXIT_FAILURE); } static void show_startup_msgs (whois_session_params * wsess) { if (wsess->w_noisy) { fprintf(stderr,"WhoB version %s %s firing up...",version,version_date); if (wsess->w_noisy > 1) fprintf(stderr," (verbosity level %d)\n",wsess->w_noisy); else printf ("\n"); if (wsess->w_noisy > 1) { const char *L = "", *R = ""; if (w_ecolor()) { L = w_field_seq(WF_LABEL); R = WC_RESET; } fprintf(stderr,"Data sources:"); if ((strlen(wsess->pw_serv) > 0) && (!use_cymru || read_from_file)) fprintf(stderr," %s%s%s (pWhoIs)",L,wsess->pw_serv,R); else if ((!use_cymru || read_from_file) && !use_riswhois) fprintf(stderr," %s%s%s (pWhoIs)",L, pwhois_server,R); else if (!use_cymru) fprintf(stderr," %s%s%s (RIPE NCC)",L, ripe_ris_server,R); if (use_cymru && !read_from_file) fprintf(stderr," %s%s%s (Cymru)",L,cymru_server,R); if (display_radb_as) fprintf(stderr,", %s%s%s (RADB)",L,radb_server,R); fprintf(stderr,".\n"); if (read_from_file) { if (cymrufromfile) fprintf(stderr,"Using Cymru for bulk file resolution.\n"); else if (riswhoisfromfile) fprintf(stderr,"Using RIPE NCC for bulk file resolution.\n"); else fprintf(stderr,"Using Prefix WhoIs for bulk file resolution.\n"); } } if (show_routes_byprefix == 1 && (show_routes_byasn == 1 || show_routes_bytransitasn == 1)) { fprintf(stderr,"You may only perform routeviews one at a time. Using by-ASN.\n"); show_routes_byprefix = 0; } } } int main(int ac, char **av) { struct addrinfo ai_hints, *ai_res; char *addr = NULL; char addrbuf[INET6_ADDRSTRLEN]; struct in_addr in, pws; int ch; int user_asn = 0; char user_asn_buf[12]; /* fits full 32-bit ASN (10 digits) + NUL */ whois_session_params * wsess; memset(&hostname, 0, sizeof(hostname)); wsess = w_init(); setbuf(stdout, NULL); { static struct option w_long_options[] = { { "color", no_argument, 0, OPT_W_COLOR }, { "no-color", no_argument, 0, OPT_W_NO_COLOR }, { "no-colour",no_argument, 0, OPT_W_NO_COLOR }, { "annotate", optional_argument, 0, OPT_W_ANNOTATE }, { "enrich", optional_argument, 0, OPT_W_ANNOTATE }, { "fields", required_argument, 0, OPT_W_FIELDS }, { 0, 0, 0, 0 } }; int w_optidx = 0; while ((ch = getopt_long (ac, av, "AaCcefgNnOopPRrstuVvh:w:F:", w_long_options, &w_optidx)) != EOF) switch (ch) { case OPT_W_COLOR: w_color_mode = 1; break; case OPT_W_NO_COLOR: w_color_mode = 2; break; case 'e': /* -e / --enrich / --annotate are one feature */ case OPT_W_ANNOTATE: annotate_mode = 1; use_gigo = 0; if (optarg) snprintf(annotate_file, sizeof annotate_file, "%s", optarg); break; case 'F': case OPT_W_FIELDS: if (!ann_parse_fields(optarg)) { fprintf(stderr, "%s: -F expects a comma list of asn,org,asorg,net,prefix,city,region,country,cc,place,all (+/- adjust the default)\n", appname); exit(EXIT_FAILURE); } break; case 'a': use_gigo = 0; show_routes_byasn = 1; go_interactive = 1; break; case 'A': use_gigo = 0; show_routes_bytransitasn = 1; go_interactive = 1; break; case 'N': use_gigo = 0; show_networks_byasn = 1; go_interactive = 1; break; case 'O': use_gigo = 0; show_contacts_byasn = 1; go_interactive = 1; break; case 's': use_gigo = 0; show_server_status = 1; go_interactive = 1; break; case 'P': use_gigo = 0; show_routes_byprefix = 1; go_interactive = 1; break; case 'v': printf("\nWhoB - version %s %s\n\n", version, version_date); exit(EXIT_SUCCESS); case 'u': /* show all times in UTC */ #if defined(sun) if(putenv("TZ=GMT0") == -1) { fprintf(stderr, "%s: Unable to set TZ to UTC.",appname); } #else #if !defined(WIN32) && !defined(_WIN32) if (setenv("TZ", "GMT0", 1) == -1) { fprintf(stderr, "%s: Unable to set TZ to UTC.",appname); } #endif #endif show_cache_date = 1; break; case 't': show_cache_date = 1; break; case 'w': if (strlen(optarg) > max_hostname_input) { fprintf(stderr,"Sorry, the server name you supplied was unreasonably long.\n"); exit(EXIT_FAILURE); } if (inet_aton(optarg, &pws)) { strncpy(wsess->pw_serv, optarg, strlen(optarg)); } else { memset(&ai_hints, 0, sizeof(ai_hints)); ai_hints.ai_family = AF_INET; ai_hints.ai_socktype = SOCK_STREAM; if (getaddrinfo(optarg, NULL, &ai_hints, &ai_res) != 0) { fprintf(stderr,"Sorry, I cannot resolve \'%s\' to use as your pWhoIs server.\n", optarg); exit(EXIT_FAILURE); } memcpy(&pws, &((struct sockaddr_in *)ai_res->ai_addr)->sin_addr, sizeof(pws)); freeaddrinfo(ai_res); strncpy(wsess->pw_serv,inet_ntoa(pws),strlen(inet_ntoa(pws))); } break; case 'h': if (strlen(optarg) > max_hostname_input) { fprintf(stderr,"Sorry, the server name you supplied was unreasonably long.\n"); exit(EXIT_FAILURE); } if (inet_aton(optarg, &pws)) { strncpy(wsess->pw_serv,optarg,strlen(optarg)); } else { memset(&ai_hints, 0, sizeof(ai_hints)); ai_hints.ai_family = AF_INET; ai_hints.ai_socktype = SOCK_STREAM; if (getaddrinfo(optarg, NULL, &ai_hints, &ai_res) != 0) { fprintf(stderr,"Sorry, I cannot resolve \'%s\' to use as your pWhoIs server.\n", optarg); exit(EXIT_FAILURE); } memcpy(&pws, &((struct sockaddr_in *)ai_res->ai_addr)->sin_addr, sizeof(pws)); freeaddrinfo(ai_res); strncpy(wsess->pw_serv,inet_ntoa(pws),strlen(inet_ntoa(pws))); } break; case 'c': use_cymru = 1; /* cymrufromfile = 1; */ /* Use pwhois Cymru compatibility mode by default */ break; case 'C': use_cymru = 1; cymrufromfile = 1; break; case 'n': display_netname = 1; go_interactive = 1; break; case 'r': use_riswhois = 1; riswhoisfromfile = 1; break; case 'R': display_radb_as = 1; go_interactive = 1; break; case 'o': display_orgname = 0; go_interactive = 1; break; case 'p': display_aspath = 1; go_interactive = 1; break; case 'f': use_gigo = 0; read_from_file = 1; break; case 'V': wsess->w_noisy++; break; case 'g': use_gigo = 0; go_interactive = 1; break; default: usage (av[0]); } } /* end w_long_options scope */ /* Resolve stdout colourization exactly once, now that options are parsed * and before any output. Self-gates to an interactive terminal. */ w_detect_color(); /* --annotate: a pure filter — enrich addresses in the text and exit. */ if (annotate_mode) { FILE *in = stdin; if (annotate_file[0] && strcmp(annotate_file, "-") != 0) { in = fopen(annotate_file, "r"); if (!in) { fprintf(stderr, "%s: unable to open '%s' for reading.\n", appname, annotate_file); exit(EXIT_FAILURE); } } w_annotate_stream(wsess, in); if (in != stdin) fclose(in); exit(EXIT_SUCCESS); } /* Catch hostname input without any arguments */ if ((ac - optind) > 0) go_interactive = 1; if (go_interactive > 0) { /* Quickly check stdin for input to avoid David having to type */ /* '-f -' to pipe into to whob, even if it has args :-) */ fd_set rfds; struct timeval timev; int selretval; /* Watch stdin (fd 0) briefly for input. */ FD_ZERO(&rfds); FD_SET(0, &rfds); timev.tv_sec = 0; timev.tv_usec = 100; selretval = select(1, &rfds, NULL, NULL, &timev); if (selretval > 0) { /* select() also reports fd 0 readable at EOF / on a closed or * redirected-from-empty stdin (e.g. `whob target w_noisy) fprintf(stderr,"No input found on stdin.\n"); */ } else if (read_from_file < 1) { /* No indication we should be interactive based on arguments, so wait for STDIN */ use_gigo = 0; read_from_file = 1; use_stdin = 1; } if (((ac - optind) < 1) && (show_server_status != 1) && (go_interactive > 0)) usage (av[0]); /* Show the verbose startup information if verbosity is enabled/requested */ show_startup_msgs(wsess); if (show_server_status == 1) { w_display_pwhois_version(wsess); exit(EXIT_FAILURE); } else if ((read_from_file > 0 && use_stdin < 1) || go_interactive > 0) { if (strlen(av[optind]) > max_hostname_input) { fprintf(stderr,"Sorry, the subject name you supplied was unreasonably long.\n"); exit(EXIT_FAILURE); } else { strncpy(hostname,av[optind],strlen(av[optind])); optind++; } } if (read_from_file) { if (riswhoisfromfile) w_display_bulkfromfile_riswhois(wsess, hostname); else if (cymrufromfile) w_display_bulkfromfile_cymru(wsess, hostname); else w_display_bulkfromfile_pwhois(wsess, hostname); exit(EXIT_SUCCESS); } if (use_gigo > 0) { if (strncmp(hostname,"me",2) == 0 || strncmp(hostname,"whoami",6) == 0) { w_display_myaddress(wsess); exit(EXIT_SUCCESS); } else { w_display_pwhois_gigo(wsess, hostname); exit(EXIT_SUCCESS); } } if ((show_routes_byasn || show_routes_bytransitasn || show_contacts_byasn || show_networks_byasn) && (strlen(hostname) <= 10) && atoi(hostname) && stricontains(hostname,".") < 1) { user_asn = atoi(hostname); if (wsess->w_noisy > 1) fprintf(stderr,"Using user-supplied ASN %d for lookup.\n",user_asn); } else if (show_routes_byprefix == 1) { printf("Displaying all routes for prefix %s.\n",hostname); } else { if (strncmp(hostname,"me",2) == 0 || strncmp(hostname,"whoami",6) == 0) { w_display_myaddress(wsess); exit(EXIT_SUCCESS); } if (inet_aton(hostname, &in)) { addr = hostname; } else if (strchr(hostname, ':')) { /* IPv6 literal: pass the text form through to the lookup */ addr = hostname; } else { /* Resolve as either family; a v6 answer is used verbatim */ memset(&ai_hints, 0, sizeof(ai_hints)); ai_hints.ai_family = AF_UNSPEC; ai_hints.ai_socktype = SOCK_STREAM; if (getaddrinfo(hostname, NULL, &ai_hints, &ai_res) != 0) { fprintf(stderr,"Sorry, I cannot resolve \'%s\'\n", hostname); exit(EXIT_FAILURE); } if (ai_res->ai_family == AF_INET6) inet_ntop(AF_INET6, &((struct sockaddr_in6 *)ai_res->ai_addr)->sin6_addr, addrbuf, sizeof(addrbuf)); else inet_ntop(AF_INET, &((struct sockaddr_in *)ai_res->ai_addr)->sin_addr, addrbuf, sizeof(addrbuf)); freeaddrinfo(ai_res); addr = addrbuf; } } if (show_routes_byasn) { if (user_asn > 0) { printf("Displaying all routes whose Origin-AS is %d.\n", user_asn); snprintf(user_asn_buf,sizeof(user_asn_buf),"%d",user_asn); w_display_rvbyasn_pwhois(wsess, user_asn_buf); } else { w_lookup_all_pwhois(wsess, addr); if (atoi(wsess->consolidated_asn)) { printf("Displaying all routes whose Origin-AS is %s.\n", wsess->consolidated_asn); w_display_rvbyasn_pwhois(wsess, wsess->consolidated_asn); } else { printf("Sorry, unable to resolve the ASN for %s (%s) at this time.\n",hostname,addr); } } } else if (show_routes_bytransitasn) { if (user_asn > 0) { printf("Displaying all routes whose Transit-AS is %d.\n", user_asn); snprintf(user_asn_buf,sizeof(user_asn_buf),"%d",user_asn); w_display_rvbytransitasn_pwhois(wsess, user_asn_buf); } else { w_lookup_all_pwhois(wsess, addr); if (atoi(wsess->consolidated_asn)) { printf("Displaying all routes whose Transit-AS is %s.\n", wsess->consolidated_asn); w_display_rvbytransitasn_pwhois(wsess, wsess->consolidated_asn); } else { printf("Sorry, unable to resolve the ASN for %s (%s) at this time.\n",hostname,addr); } } } else if (show_networks_byasn) { if (user_asn > 0) { printf("Displaying all networks registered to the Origin-AS %d.\n", user_asn); snprintf(user_asn_buf,sizeof(user_asn_buf),"%d",user_asn); w_display_networksbyasn_pwhois(wsess, user_asn_buf); } else { w_lookup_all_pwhois(wsess, addr); if (atoi(wsess->consolidated_asn)) { printf("Displaying all networks registered to the Origin-AS %s.\n", wsess->consolidated_asn); w_display_networksbyasn_pwhois(wsess, wsess->consolidated_asn); } else { printf("Sorry, unable to resolve the ASN for %s (%s) at this time.\n",hostname,addr); } } } else if (show_contacts_byasn) { if (user_asn > 0) { printf("Displaying all contact info on file for Origin-AS %d.\n", user_asn); snprintf(user_asn_buf,sizeof(user_asn_buf),"%d",user_asn); w_display_contactsbyasn_pwhois(wsess, user_asn_buf); } else { w_lookup_all_pwhois(wsess, addr); if (atoi(wsess->consolidated_asn)) { printf("Displaying all contact info on file for Origin-AS %s.\n", wsess->consolidated_asn); w_display_contactsbyasn_pwhois(wsess, wsess->consolidated_asn); } else { printf("Unable to resolve the ASN for %s (%s) at this time.\n",hostname,addr); } } } else if (show_routes_byprefix) { w_display_rvbyprefix_pwhois(wsess, hostname); } else { printf("%s | Searching...", addr); if (use_cymru) { printf("\b\b\b\b\b\b\b\b\b\b\b\b"); printf("origin-as %d ", w_lookup_as_cymru(wsess, addr)); } else if (use_riswhois) { w_lookup_all_riswhois(wsess, addr); printf("\b\b\b\b\b\b\b\b\b\b\b\b"); printf("origin-as %s (%s) ", wsess->consolidated_asn, wsess->consolidated_route); } else { w_lookup_all_pwhois(wsess, addr); printf("\b\b\b\b\b\b\b\b\b\b\b\b"); printf("origin-as %s (%s) ", wsess->consolidated_asn, wsess->consolidated_route); if (show_cache_date && (strlen(wsess->tbuf) > 0)) printf("| %s ", wsess->tbuf); } if (display_radb_as) printf("| radb-as %d ", w_lookup_as(wsess, addr)); if ((display_aspath) && (!use_cymru) && (!use_riswhois)) printf("| as-path %s ", wsess->consolidated_asp); if (display_netname) { if (use_cymru) { printf("| %s ", w_lookup_netname(wsess, addr)); } else if (use_riswhois) { if (!display_orgname) printf("| %s ", wsess->consolidated_netname); } else { printf("| %s ", wsess->consolidated_netname); } } if (display_orgname) { if (use_cymru) { printf("| %s ", w_lookup_orgname(wsess, addr)); } else { printf("| %s ", wsess->consolidated_orgname); } } printf("\n"); } w_close(wsess); exit(EXIT_SUCCESS); } #endif lft-4.01/configure000755 000765 000024 00000617632 15250425146 014101 0ustar00vicstaff000000 000000 #! /bin/sh # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.73. # # Report bugs to . # # # Copyright (C) 1992-1996, 1998-2017, 2020-2026 Free Software Foundation, # Inc. # # # This configure script is free software; the Free Software Foundation # gives unlimited permission to copy, distribute and modify it. ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # contradicts POSIX and common usage. Disable this. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case e in #( e) case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac ;; esac fi # Reset variables that may have inherited troublesome values from # the environment. # IFS needs to be set, to space, tab, and newline, in precisely that order. # (If _AS_PATH_WALK were called with IFS unset, it would have the # side effect of setting IFS to empty, thus disabling word splitting.) # Quoting is to prevent editors from complaining about space-tab. as_nl=' ' export as_nl IFS=" "" $as_nl" PS1='$ ' PS2='> ' PS4='+ ' # Ensure predictable behavior from utilities with locale-dependent output. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # We cannot yet rely on "unset" to work, but we need these variables # to be unset--not just set to an empty or harmless value--now, to # avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct # also avoids known problems related to "unset" and subshell syntax # in other old shells (e.g. bash 2.01 and pdksh 5.2.14). for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH do eval test \${$as_var+y} \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done # Ensure that fds 0, 1, and 2 are open. if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi if (exec 3>&2) ; then :; else exec 2>/dev/null; fi # The user is always right. if ${PATH_SEPARATOR+false} :; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac test -r "$as_dir$0" && as_myself=$as_dir$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as 'sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then printf '%s\n' "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # Use a proper internal environment variable to ensure we don't fall # into an infinite loop, continuously re-executing ourselves. if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then _as_can_reexec=no; export _as_can_reexec; # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also # works around shells that cannot unset nonexistent variables. # Preserve -v and -x to the replacement shell. BASH_ENV=/dev/null ENV=/dev/null (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV case $- in # (((( *v*x* | *x*v* ) as_opts=-vx ;; *v* ) as_opts=-v ;; *x* ) as_opts=-x ;; * ) as_opts= ;; esac case $# in # (( 0) exec $CONFIG_SHELL $as_opts "$as_myself" ;; *) exec $CONFIG_SHELL $as_opts "$as_myself" "$@" ;; esac # Admittedly, this is quite paranoid, since all the known shells bail # out after a failed 'exec'. printf '%s\n' "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi # We don't want this to propagate to other subprocesses. { _as_can_reexec=; unset _as_can_reexec;} if test "x$CONFIG_SHELL" = x; then as_bourne_compatible="if test \${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which # contradicts POSIX and common usage. Disable this. alias -g '\${1+\"\$@\"}'='\"\$@\"' setopt NO_GLOB_SUBST else case e in #( e) case \`(set -o) 2>/dev/null\` in #( *posix*) : set -o posix ;; #( *) : ;; esac ;; esac fi " as_required="as_fn_return () { (exit \$1); } as_fn_success () { as_fn_return 0; } as_fn_failure () { as_fn_return 1; } as_fn_ret_success () { return 0; } as_fn_ret_failure () { return 1; } exitcode=0 as_fn_success || { exitcode=1; echo as_fn_success failed.; } as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } if ( set x; as_fn_ret_success y && test x = \"\$1\" ) then : else case e in #( e) exitcode=1; echo positional parameters were not saved. ;; esac fi test x\$exitcode = x0 || exit 1 blah=\$(echo \$(echo blah)) test x\"\$blah\" = xblah || exit 1 test -x / || exit 1" as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1" if (eval "$as_required") 2>/dev/null then : as_have_required=yes else case e in #( e) as_have_required=no ;; esac fi if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null then : else case e in #( e) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_found=false for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac as_found=: case $as_dir in #( /*) for as_base in sh bash ksh sh5; do # Try only shells that exist, to save several forks. as_shell=$as_dir$as_base if { test -f "$as_shell" || test -f "$as_shell.exe"; } && as_run=a "$as_shell" -c "$as_bourne_compatible""$as_required" 2>/dev/null then : CONFIG_SHELL=$as_shell as_have_required=yes if as_run=a "$as_shell" -c "$as_bourne_compatible""$as_suggested" 2>/dev/null then : break 2 fi fi done;; esac as_found=false done IFS=$as_save_IFS if $as_found then : else case e in #( e) if { test -f "$SHELL" || test -f "$SHELL.exe"; } && as_run=a "$SHELL" -c "$as_bourne_compatible""$as_required" 2>/dev/null then : CONFIG_SHELL=$SHELL as_have_required=yes fi ;; esac fi if test "x$CONFIG_SHELL" != x then : export CONFIG_SHELL # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also # works around shells that cannot unset nonexistent variables. # Preserve -v and -x to the replacement shell. BASH_ENV=/dev/null ENV=/dev/null (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV case $- in # (((( *v*x* | *x*v* ) as_opts=-vx ;; *v* ) as_opts=-v ;; *x* ) as_opts=-x ;; * ) as_opts= ;; esac case $# in # (( 0) exec $CONFIG_SHELL $as_opts "$as_myself" ;; *) exec $CONFIG_SHELL $as_opts "$as_myself" "$@" ;; esac # Admittedly, this is quite paranoid, since all the known shells bail # out after a failed 'exec'. printf '%s\n' "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi if test x$as_have_required = xno then : printf '%s\n' "$0: This script requires a shell more modern than all" printf '%s\n' "$0: the shells that I found on your system." if test ${ZSH_VERSION+y} ; then printf '%s\n' "$0: In particular, zsh $ZSH_VERSION has bugs and should" printf '%s\n' "$0: be upgraded to zsh 4.3.4 or later." else printf '%s\n' "$0: Please tell bug-autoconf@gnu.org and $0: https://pwhois.org/contact/ about your system, $0: including any error possibly output before this $0: message. Then install a modern shell, or manually run $0: the script under such a shell if you do have one." fi exit 1 fi ;; esac fi fi SHELL=${CONFIG_SHELL-/bin/sh} export SHELL # Unset more variables known to interfere with behavior of common tools. CLICOLOR_FORCE= GREP_OPTIONS= unset CLICOLOR_FORCE GREP_OPTIONS ## --------------------- ## ## M4sh Shell Functions. ## ## --------------------- ## # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`printf '%s\n' "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || printf '%s\n' X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p # as_fn_executable_p FILE # ----------------------- # Test if FILE is an executable regular file. as_fn_executable_p () { test -f "$1" && test -x "$1" } # as_fn_executable_p # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null then : eval 'as_fn_append () { eval $1+=\$2 }' else case e in #( e) as_fn_append () { eval $1=\$$1\$2 } ;; esac fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null then : eval 'as_fn_arith () { as_val=$(( $* )) }' else case e in #( e) as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } ;; esac fi # as_fn_arith # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi printf '%s\n' "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || printf '%s\n' X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits as_lineno_1=$LINENO as_lineno_1a=$LINENO as_lineno_2=$LINENO as_lineno_2a=$LINENO eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) sed -n ' p /[$]LINENO/= ' <$as_myself | sed ' t clear :clear s/[$]LINENO.*/&-/ t lineno b :lineno N :loop s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ t loop s/-\n.*// ' >$as_me.lineno && chmod +x "$as_me.lineno" || { printf '%s\n' "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } # If we had to re-execute with $CONFIG_SHELL, we're ensured to have # already done that, so ensure we don't try to do so again and fall # in an infinite loop. This has already happened in practice. _as_can_reexec=no; export _as_can_reexec # Don't try to exec as it changes $[0], causing all sort of problems # (the dirname of $[0] is not the place where we might find the # original and so on. Autoconf is especially sensitive to this). . "./$as_me.lineno" # Exit status is that of the last command. exit } rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable. # In both cases, we have to default to 'cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -pR' fi else as_ln_s='cp -pR' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated # Sed expression to map a string onto a valid variable name. as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g" as_tr_sh="eval sed '$as_sed_sh'" # deprecated test -n "$DJDIR" || exec 7<&0 &1 # Name of the host. # hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, # so uname gets run too. ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` # # Initializations. # ac_default_prefix=/usr/local ac_clean_CONFIG_STATUS= ac_clean_files= ac_config_libobj_dir=. LIBOBJS= cross_compiling=no subdirs= MFLAGS= MAKEFLAGS= # Identity of this package. PACKAGE_NAME='' PACKAGE_TARNAME='' PACKAGE_VERSION='' PACKAGE_STRING='' PACKAGE_BUGREPORT='https://pwhois.org/contact/' PACKAGE_URL='' ac_unique_file="lft" ac_unique_file="lft_ifname.h" # Factoring default headers for most tests. ac_includes_default="\ #include #ifdef HAVE_STDIO_H # include #endif #ifdef HAVE_STDLIB_H # include #endif #ifdef HAVE_STRING_H # include #endif #ifdef HAVE_INTTYPES_H # include #endif #ifdef HAVE_STDINT_H # include #endif #ifdef HAVE_STRINGS_H # include #endif #ifdef HAVE_SYS_TYPES_H # include #endif #ifdef HAVE_SYS_STAT_H # include #endif #ifdef HAVE_UNISTD_H # include #endif" ac_header_c_list= ac_subst_vars='LTLIBOBJS LIBOBJS WATCH_OBJS WHOB_LIBS PKGCONFIG ALLOCA INSTALL_DATA INSTALL_SCRIPT INSTALL_PROGRAM SET_MAKE LN_S OBJEXT EXEEXT ac_ct_CC CPPFLAGS LDFLAGS CFLAGS CC host_os host_vendor host_cpu host build_os build_vendor build_cpu build ECHO_T ECHO_N ECHO_C target_alias host_alias build_alias LIBS DEFS mandir localedir libdir psdir pdfdir dvidir htmldir infodir docdir oldincludedir includedir runstatedir localstatedir sharedstatedir sysconfdir datadir datarootdir libexecdir sbindir bindir program_transform_name prefix exec_prefix PACKAGE_URL PACKAGE_BUGREPORT PACKAGE_STRING PACKAGE_VERSION PACKAGE_TARNAME PACKAGE_NAME PATH_SEPARATOR SHELL' ac_subst_files='' ac_user_opts=' enable_option_checking enable_universal enable_gtod with_pcap with_ipv6 enable_async_dns with_cares enable_ncurses with_ncurses ' ac_precious_vars='build_alias host_alias target_alias CC CFLAGS LDFLAGS LIBS CPPFLAGS' # Initialize some variables set by options. ac_init_help= ac_init_version=false ac_unrecognized_opts= ac_unrecognized_sep= # The variables have the same names as the options, with # dashes changed to underlines. cache_file=/dev/null exec_prefix=NONE no_create= no_recursion= prefix=NONE program_prefix=NONE program_suffix=NONE program_transform_name=s,x,x, silent= site= srcdir= verbose= x_includes=NONE x_libraries=NONE # Installation directory options. # These are left unexpanded so users can "make install exec_prefix=/foo" # and all the variables that are supposed to be based on exec_prefix # by default will actually change. # Use braces instead of parens because sh, perl, etc. also accept them. # (The list follows the same order as the GNU Coding Standards.) bindir='${exec_prefix}/bin' sbindir='${exec_prefix}/sbin' libexecdir='${exec_prefix}/libexec' datarootdir='${prefix}/share' datadir='${datarootdir}' sysconfdir='${prefix}/etc' sharedstatedir='${prefix}/com' localstatedir='${prefix}/var' runstatedir='${localstatedir}/run' includedir='${prefix}/include' oldincludedir='/usr/include' docdir='${datarootdir}/doc/${PACKAGE}' infodir='${datarootdir}/info' htmldir='${docdir}' dvidir='${docdir}' pdfdir='${docdir}' psdir='${docdir}' libdir='${exec_prefix}/lib' localedir='${datarootdir}/locale' mandir='${datarootdir}/man' ac_prev= ac_dashdash= for ac_option do # If the previous option needs an argument, assign it. if test -n "$ac_prev"; then eval $ac_prev=\$ac_option ac_prev= continue fi case $ac_option in *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; *=) ac_optarg= ;; *) ac_optarg=yes ;; esac case $ac_dashdash$ac_option in --) ac_dashdash=yes ;; -bindir | --bindir | --bindi | --bind | --bin | --bi) ac_prev=bindir ;; -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) bindir=$ac_optarg ;; -build | --build | --buil | --bui | --bu) ac_prev=build_alias ;; -build=* | --build=* | --buil=* | --bui=* | --bu=*) build_alias=$ac_optarg ;; -cache-file | --cache-file | --cache-fil | --cache-fi \ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) ac_prev=cache_file ;; -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) cache_file=$ac_optarg ;; --config-cache | -C) cache_file=config.cache ;; -datadir | --datadir | --datadi | --datad) ac_prev=datadir ;; -datadir=* | --datadir=* | --datadi=* | --datad=*) datadir=$ac_optarg ;; -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ | --dataroo | --dataro | --datar) ac_prev=datarootdir ;; -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) datarootdir=$ac_optarg ;; -disable-* | --disable-*) ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf '%s\n' "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=no ;; -docdir | --docdir | --docdi | --doc | --do) ac_prev=docdir ;; -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) docdir=$ac_optarg ;; -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) ac_prev=dvidir ;; -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) dvidir=$ac_optarg ;; -enable-* | --enable-*) ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf '%s\n' "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=\$ac_optarg ;; -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ | --exec | --exe | --ex) ac_prev=exec_prefix ;; -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ | --exec=* | --exe=* | --ex=*) exec_prefix=$ac_optarg ;; -gas | --gas | --ga | --g) # Obsolete; use --with-gas. with_gas=yes ;; -help | --help | --hel | --he | -h) ac_init_help=long ;; -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) ac_init_help=recursive ;; -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) ac_init_help=short ;; -host | --host | --hos | --ho) ac_prev=host_alias ;; -host=* | --host=* | --hos=* | --ho=*) host_alias=$ac_optarg ;; -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) ac_prev=htmldir ;; -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ | --ht=*) htmldir=$ac_optarg ;; -includedir | --includedir | --includedi | --included | --include \ | --includ | --inclu | --incl | --inc) ac_prev=includedir ;; -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ | --includ=* | --inclu=* | --incl=* | --inc=*) includedir=$ac_optarg ;; -infodir | --infodir | --infodi | --infod | --info | --inf) ac_prev=infodir ;; -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) infodir=$ac_optarg ;; -libdir | --libdir | --libdi | --libd) ac_prev=libdir ;; -libdir=* | --libdir=* | --libdi=* | --libd=*) libdir=$ac_optarg ;; -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ | --libexe | --libex | --libe) ac_prev=libexecdir ;; -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ | --libexe=* | --libex=* | --libe=*) libexecdir=$ac_optarg ;; -localedir | --localedir | --localedi | --localed | --locale) ac_prev=localedir ;; -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) localedir=$ac_optarg ;; -localstatedir | --localstatedir | --localstatedi | --localstated \ | --localstate | --localstat | --localsta | --localst | --locals) ac_prev=localstatedir ;; -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) localstatedir=$ac_optarg ;; -mandir | --mandir | --mandi | --mand | --man | --ma | --m) ac_prev=mandir ;; -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) mandir=$ac_optarg ;; -nfp | --nfp | --nf) # Obsolete; use --without-fp. with_fp=no ;; -no-create | --no-create | --no-creat | --no-crea | --no-cre \ | --no-cr | --no-c | -n) no_create=yes ;; -no-recursion | --no-recursion | --no-recursio | --no-recursi \ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) no_recursion=yes ;; -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ | --oldin | --oldi | --old | --ol | --o) ac_prev=oldincludedir ;; -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) oldincludedir=$ac_optarg ;; -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) ac_prev=prefix ;; -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) prefix=$ac_optarg ;; -program-prefix | --program-prefix | --program-prefi | --program-pref \ | --program-pre | --program-pr | --program-p) ac_prev=program_prefix ;; -program-prefix=* | --program-prefix=* | --program-prefi=* \ | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) program_prefix=$ac_optarg ;; -program-suffix | --program-suffix | --program-suffi | --program-suff \ | --program-suf | --program-su | --program-s) ac_prev=program_suffix ;; -program-suffix=* | --program-suffix=* | --program-suffi=* \ | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) program_suffix=$ac_optarg ;; -program-transform-name | --program-transform-name \ | --program-transform-nam | --program-transform-na \ | --program-transform-n | --program-transform- \ | --program-transform | --program-transfor \ | --program-transfo | --program-transf \ | --program-trans | --program-tran \ | --progr-tra | --program-tr | --program-t) ac_prev=program_transform_name ;; -program-transform-name=* | --program-transform-name=* \ | --program-transform-nam=* | --program-transform-na=* \ | --program-transform-n=* | --program-transform-=* \ | --program-transform=* | --program-transfor=* \ | --program-transfo=* | --program-transf=* \ | --program-trans=* | --program-tran=* \ | --progr-tra=* | --program-tr=* | --program-t=*) program_transform_name=$ac_optarg ;; -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) ac_prev=pdfdir ;; -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) pdfdir=$ac_optarg ;; -psdir | --psdir | --psdi | --psd | --ps) ac_prev=psdir ;; -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) psdir=$ac_optarg ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) silent=yes ;; -runstatedir | --runstatedir | --runstatedi | --runstated \ | --runstate | --runstat | --runsta | --runst | --runs \ | --run | --ru | --r) ac_prev=runstatedir ;; -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \ | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \ | --run=* | --ru=* | --r=*) runstatedir=$ac_optarg ;; -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) ac_prev=sbindir ;; -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ | --sbi=* | --sb=*) sbindir=$ac_optarg ;; -sharedstatedir | --sharedstatedir | --sharedstatedi \ | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ | --sharedst | --shareds | --shared | --share | --shar \ | --sha | --sh) ac_prev=sharedstatedir ;; -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ | --sha=* | --sh=*) sharedstatedir=$ac_optarg ;; -site | --site | --sit) ac_prev=site ;; -site=* | --site=* | --sit=*) site=$ac_optarg ;; -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) ac_prev=srcdir ;; -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) srcdir=$ac_optarg ;; -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ | --syscon | --sysco | --sysc | --sys | --sy) ac_prev=sysconfdir ;; -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) sysconfdir=$ac_optarg ;; -target | --target | --targe | --targ | --tar | --ta | --t) ac_prev=target_alias ;; -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) target_alias=$ac_optarg ;; -v | -verbose | --verbose | --verbos | --verbo | --verb) verbose=yes ;; -version | --version | --versio | --versi | --vers | -V) ac_init_version=: ;; -with-* | --with-*) ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf '%s\n' "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=\$ac_optarg ;; -without-* | --without-*) ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf '%s\n' "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=no ;; --x) # Obsolete; use --with-x. with_x=yes ;; -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ | --x-incl | --x-inc | --x-in | --x-i) ac_prev=x_includes ;; -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) x_includes=$ac_optarg ;; -x-libraries | --x-libraries | --x-librarie | --x-librari \ | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) ac_prev=x_libraries ;; -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) x_libraries=$ac_optarg ;; -*) as_fn_error $? "unrecognized option: '$ac_option' Try '$0 --help' for more information" ;; *=*) ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` # Reject names that are not valid shell variable names. case $ac_envvar in #( '' | [0-9]* | *[!_$as_cr_alnum]* ) as_fn_error $? "invalid variable name: '$ac_envvar'" ;; esac eval $ac_envvar=\$ac_optarg export $ac_envvar ;; *) # FIXME: should be removed in autoconf 3.0. printf '%s\n' "$as_me: WARNING: you should use --build, --host, --target" >&2 expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && printf '%s\n' "$as_me: WARNING: invalid host type: $ac_option" >&2 : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" ;; esac done if test -n "$ac_prev"; then ac_option=--`printf '%s\n' $ac_prev | sed 's/_/-/g'` as_fn_error $? "missing argument to $ac_option" fi if test -n "$ac_unrecognized_opts"; then case $enable_option_checking in no) ;; fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; *) printf '%s\n' "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; esac fi # Check all directory arguments for consistency. for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ datadir sysconfdir sharedstatedir localstatedir includedir \ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ libdir localedir mandir runstatedir do eval ac_val=\$$ac_var # Remove trailing slashes. case $ac_val in */ ) ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` eval $ac_var=\$ac_val;; esac # Be sure to have absolute directory names. case $ac_val in [\\/$]* | ?:[\\/]* ) continue;; NONE | '' ) case $ac_var in *prefix ) continue;; esac;; esac as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" done # There might be people who depend on the old broken behavior: '$host' # used to hold the argument of --host etc. # FIXME: To remove some day. build=$build_alias host=$host_alias target=$target_alias # FIXME: To remove some day. if test "x$host_alias" != x; then if test "x$build_alias" = x; then cross_compiling=maybe elif test "x$build_alias" != "x$host_alias"; then cross_compiling=yes fi fi ac_tool_prefix= test -n "$host_alias" && ac_tool_prefix=$host_alias- test "$silent" = yes && exec 6>/dev/null ac_pwd=`pwd` && test -n "$ac_pwd" && ac_ls_di=`ls -di .` && ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || as_fn_error $? "working directory cannot be determined" test "X$ac_ls_di" = "X$ac_pwd_ls_di" || as_fn_error $? "pwd does not report name of working directory" # Find the source files, if location was not specified. if test -z "$srcdir"; then ac_srcdir_defaulted=yes # Try the directory containing this script, then the parent directory. ac_confdir=`$as_dirname -- "$as_myself" || $as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_myself" : 'X\(//\)[^/]' \| \ X"$as_myself" : 'X\(//\)$' \| \ X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || printf '%s\n' X"$as_myself" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` srcdir=$ac_confdir if test ! -r "$srcdir/$ac_unique_file"; then srcdir=.. fi else ac_srcdir_defaulted=no fi if test ! -r "$srcdir/$ac_unique_file"; then test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" fi ac_msg="sources are in $srcdir, but 'cd $srcdir' does not work" ac_abs_confdir=`( cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" pwd)` # When building in place, set srcdir=. if test "$ac_abs_confdir" = "$ac_pwd"; then srcdir=. fi # Remove unnecessary trailing slashes from srcdir. # Double slashes in file names in object file debugging info # mess up M-x gdb in Emacs. case $srcdir in */) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; esac for ac_var in $ac_precious_vars; do eval ac_env_${ac_var}_set=\${${ac_var}+set} eval ac_env_${ac_var}_value=\$${ac_var} eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} eval ac_cv_env_${ac_var}_value=\$${ac_var} done # # Report the --help message. # if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF 'configure' configures this package to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... To assign environment variables (e.g., CC, CFLAGS...), specify them as VAR=VALUE. See below for descriptions of some of the useful variables. Defaults for the options are specified in brackets. Configuration: -h, --help display this help and exit --help=short display options specific to this package --help=recursive display the short help of all the included packages -V, --version display version information and exit -q, --quiet, --silent do not print 'checking ...' messages --cache-file=FILE cache test results in FILE [disabled] -C, --config-cache alias for '--cache-file=config.cache' -n, --no-create do not create output files --srcdir=DIR find the sources in DIR [configure dir or '..'] Installation directories: --prefix=PREFIX install architecture-independent files in PREFIX [$ac_default_prefix] --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX [PREFIX] By default, 'make install' will install all the files in '$ac_default_prefix/bin', '$ac_default_prefix/lib' etc. You can specify an installation prefix other than '$ac_default_prefix' using '--prefix', for instance '--prefix=\$HOME'. For better control, use the options below. Fine tuning of the installation directories: --bindir=DIR user executables [EPREFIX/bin] --sbindir=DIR system admin executables [EPREFIX/sbin] --libexecdir=DIR program executables [EPREFIX/libexec] --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] --datadir=DIR read-only architecture-independent data [DATAROOTDIR] --infodir=DIR info documentation [DATAROOTDIR/info] --localedir=DIR locale-dependent data [DATAROOTDIR/locale] --mandir=DIR man documentation [DATAROOTDIR/man] --docdir=DIR documentation root [DATAROOTDIR/doc/PACKAGE] --htmldir=DIR html documentation [DOCDIR] --dvidir=DIR dvi documentation [DOCDIR] --pdfdir=DIR pdf documentation [DOCDIR] --psdir=DIR ps documentation [DOCDIR] _ACEOF cat <<\_ACEOF System types: --build=BUILD configure for building on BUILD [guessed] --host=HOST cross-compile to build programs to run on HOST [BUILD] _ACEOF fi if test -n "$ac_init_help"; then cat <<\_ACEOF Optional Features: --disable-option-checking ignore unrecognized --enable/--with options --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) --enable-FEATURE[=ARG] include FEATURE [ARG=yes] --enable-universal build a universal binary for arm64 and x86_64 on macOS --enable-gtod use gettimeofday() instead of pcap timestamps for per-packet timing --disable-async-dns disable asynchronous DNS via c-ares (default: auto-detect and use if available) --disable-ncurses disable ncurses interactive mode (default: auto-detect and use if available) Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) --with-pcap=PATH specify prefix path to the pcap library (e.g. /usr/local) --without-ipv6 build without IPv6 tracing (default: auto-detect; --with-ipv6 requires it) --with-cares=PATH specify prefix path to the c-ares library (e.g. /opt/homebrew) --with-ncurses=PATH specify prefix path to the ncurses library (e.g. /opt/homebrew) Some influential environment variables: CC C compiler command CFLAGS C compiler flags LDFLAGS linker flags, e.g. -L if you have libraries in a nonstandard directory LIBS libraries to pass to the linker, e.g. -l CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if you have headers in a nonstandard directory Use these variables to override the choices made by 'configure' or to help it to find libraries and programs with nonstandard names/locations. Report bugs to . _ACEOF ac_status=$? fi if test "$ac_init_help" = "recursive"; then # If there are subdirs, report their specific --help. for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue test -d "$ac_dir" || { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || continue ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`printf '%s\n' "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`printf '%s\n' "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix cd "$ac_dir" || { ac_status=$?; continue; } # Check for configure.gnu first; this name is used for a wrapper for # Metaconfig's "Configure" on case-insensitive file systems. if test -f "$ac_srcdir/configure.gnu"; then echo && $SHELL "$ac_srcdir/configure.gnu" --help=recursive elif test -f "$ac_srcdir/configure"; then echo && $SHELL "$ac_srcdir/configure" --help=recursive else printf '%s\n' "$as_me: WARNING: no configuration information is in $ac_dir" >&2 fi || ac_status=$? cd "$ac_pwd" || { ac_status=$?; break; } done fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF configure generated by GNU Autoconf 2.73 Copyright (C) 2026 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. _ACEOF exit fi ## ------------------------ ## ## Autoconf initialization. ## ## ------------------------ ## # ac_fn_c_try_compile LINENO # -------------------------- # Try to compile conftest.$ac_ext, and return whether this succeeded. ac_fn_c_try_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack rm -f conftest.$ac_objext conftest.beam if { { ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf '%s\n' "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_c_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext then : ac_retval=0 else case e in #( e) printf '%s\n' "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 ;; esac fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_compile # ac_fn_c_try_link LINENO # ----------------------- # Try to link conftest.$ac_ext, and return whether this succeeded. ac_fn_c_try_link () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack rm -f conftest.$ac_objext conftest.beam conftest$ac_exeext if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf '%s\n' "$ac_try_echo"; } >&5 (eval "$ac_link") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_c_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && { test "$cross_compiling" = yes || test -x conftest$ac_exeext } then : ac_retval=0 else case e in #( e) printf '%s\n' "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 ;; esac fi # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would # interfere with the next link command; also delete a directory that is # left behind by Apple's compiler. We do this before executing the actions. rm -rf conftest.dSYM conftest_ipa8_conftest.oo eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_link # ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES # ------------------------------------------------------- # Tests whether HEADER exists and can be compiled using the include files in # INCLUDES, setting the cache variable VAR accordingly. ac_fn_c_check_header_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 printf %s "checking for $2... " >&6; } if eval test \${$3+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 #include <$2> _ACEOF if ac_fn_c_try_compile "$LINENO" then : eval "$3=yes" else case e in #( e) eval "$3=no" ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi eval ac_res=\$$3 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 printf '%s\n' "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_header_compile # ac_fn_c_check_type LINENO TYPE VAR INCLUDES # ------------------------------------------- # Tests whether TYPE exists after having included INCLUDES, setting cache # variable VAR accordingly. ac_fn_c_check_type () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 printf %s "checking for $2... " >&6; } if eval test \${$3+y} then : printf %s "(cached) " >&6 else case e in #( e) eval "$3=no" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 int main (void) { if (sizeof ($2)) return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 int main (void) { if (sizeof (($2))) return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : else case e in #( e) eval "$3=yes" ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi eval ac_res=\$$3 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 printf '%s\n' "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_type # ac_fn_c_try_run LINENO # ---------------------- # Try to run conftest.$ac_ext, and return whether this succeeded. Assumes that # executables *can* be run. ac_fn_c_try_run () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf '%s\n' "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { ac_try='./conftest$ac_exeext' { { case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf '%s\n' "$ac_try_echo"; } >&5 (eval "$ac_try") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; } then : ac_retval=0 else case e in #( e) printf '%s\n' "$as_me: program exited with status $ac_status" >&5 printf '%s\n' "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=$ac_status ;; esac fi rm -rf conftest.dSYM conftest_ipa8_conftest.oo eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_run # ac_fn_c_check_func LINENO FUNC VAR # ---------------------------------- # Tests whether FUNC exists, setting the cache variable VAR accordingly ac_fn_c_check_func () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 printf %s "checking for $2... " >&6; } if eval test \${$3+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Define $2 to an innocuous variant, in case declares $2. For example, HP-UX 11i declares gettimeofday. */ #define $2 innocuous_$2 /* System header to define __stub macros and hopefully few prototypes, which can conflict with char $2 (void); below. */ #include #undef $2 /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char $2 (void); /* The GNU C library defines this for functions which it implements to always fail with ENOSYS. Some functions are actually named something starting with __ and the normal name is an alias. */ #if defined __stub_$2 || defined __stub___$2 choke me #endif int main (void) { return $2 (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : eval "$3=yes" else case e in #( e) eval "$3=no" ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext ;; esac fi eval ac_res=\$$3 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 printf '%s\n' "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_func # ac_fn_check_decl LINENO SYMBOL VAR INCLUDES EXTRA-OPTIONS FLAG-VAR # ------------------------------------------------------------------ # Tests whether SYMBOL is declared in INCLUDES, setting cache variable VAR # accordingly. Pass EXTRA-OPTIONS to the compiler, using FLAG-VAR. ac_fn_check_decl () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack as_decl_name=`echo $2|sed 's/ *(.*//'` { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether $as_decl_name is declared" >&5 printf %s "checking whether $as_decl_name is declared... " >&6; } if eval test \${$3+y} then : printf %s "(cached) " >&6 else case e in #( e) as_decl_use=`echo $2|sed -e 's/(/((/' -e 's/)/) 0&/' -e 's/,/) 0& (/g'` eval ac_save_FLAGS=\$$6 as_fn_append $6 " $5" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 int main (void) { #ifndef $as_decl_name #ifdef __cplusplus (void) $as_decl_use; #else (void) $as_decl_name; #endif #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : eval "$3=yes" else case e in #( e) eval "$3=no" ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext eval $6=\$ac_save_FLAGS ;; esac fi eval ac_res=\$$3 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 printf '%s\n' "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_check_decl ac_configure_args_raw= for ac_arg do case $ac_arg in *\'*) ac_arg=`printf '%s\n' "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac as_fn_append ac_configure_args_raw " '$ac_arg'" done case $ac_configure_args_raw in *$as_nl*) ac_safe_unquote= ;; *) ac_unsafe_z='|&;<>()$`\\"*?[ '' ' # This string ends in space, tab. ac_unsafe_a="$ac_unsafe_z#~" ac_safe_unquote="s/ '\\([^$ac_unsafe_a][^$ac_unsafe_z]*\\)'/ \\1/g" ac_configure_args_raw=` printf '%s\n' "$ac_configure_args_raw" | sed "$ac_safe_unquote"`;; esac cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. It was created by $as_me, which was generated by GNU Autoconf 2.73. Invocation command line was $ $0$ac_configure_args_raw _ACEOF exec 5>>config.log { cat <<_ASUNAME ## --------- ## ## Platform. ## ## --------- ## hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` uname -m = `(uname -m) 2>/dev/null || echo unknown` uname -r = `(uname -r) 2>/dev/null || echo unknown` uname -s = `(uname -s) 2>/dev/null || echo unknown` uname -v = `(uname -v) 2>/dev/null || echo unknown` /usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` /bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` /bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` /usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` /usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` /bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` /usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` /bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` _ASUNAME as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac printf '%s\n' "PATH: $as_dir" done IFS=$as_save_IFS } >&5 cat >&5 <<_ACEOF ## ----------- ## ## Core tests. ## ## ----------- ## _ACEOF # Keep a trace of the command line. # Strip out --no-create and --no-recursion so they do not pile up. # Strip out --silent because we don't want to record it for future runs. # Also quote any args containing shell meta-characters. # Make two passes to allow for proper duplicate-argument suppression. ac_configure_args= ac_configure_args0= ac_configure_args1= ac_must_keep_next=false for ac_pass in 1 2 do for ac_arg do case $ac_arg in -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) continue ;; *\'*) ac_arg=`printf '%s\n' "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac case $ac_pass in 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; 2) as_fn_append ac_configure_args1 " '$ac_arg'" if test $ac_must_keep_next = true; then ac_must_keep_next=false # Got value, back to normal. else case $ac_arg in *=* | --config-cache | -C | -disable-* | --disable-* \ | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ | -with-* | --with-* | -without-* | --without-* | --x) case "$ac_configure_args0 " in "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; esac ;; -* ) ac_must_keep_next=true ;; esac fi as_fn_append ac_configure_args " '$ac_arg'" ;; esac done done { ac_configure_args0=; unset ac_configure_args0;} { ac_configure_args1=; unset ac_configure_args1;} # Dump the cache to stdout. It can be in a pipe (this is a requirement). ac_cache_dump () { # The following way of writing the cache mishandles newlines in values, # but we know of no workaround that is simple, portable, and efficient. # So, we kill variables containing newlines. # Ultrix sh set writes to stderr and can't be redirected directly, # and sets the high bit in the cache file unless we assign to the vars. ( for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 printf '%s\n' "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space=' '; set) 2>&1` in #( *${as_nl}ac_space=\ *) # 'set' does not quote correctly, so add quotes: double-quote # substitution turns \\\\ into \\, and sed turns \\ into \. sed -n \ "s/'/'\\\\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" ;; #( *) # 'set' quotes correctly as required by POSIX, so do not add quotes. sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) } # Print debugging info to stdout. ac_dump_debugging_info () { echo printf '%s\n' "## ---------------- ## ## Cache variables. ## ## ---------------- ##" echo ac_cache_dump echo printf '%s\n' "## ----------------- ## ## Output variables. ## ## ----------------- ##" echo for ac_var in $ac_subst_vars do eval ac_val=\$$ac_var case $ac_val in *\'*) ac_val=`printf '%s\n' "$ac_val" | sed "s/'/'\\\\\\\\''/g"`;; esac printf '%s\n' "$ac_var='$ac_val'" done | sort echo if test -n "$ac_subst_files"; then printf '%s\n' "## ------------------- ## ## File substitutions. ## ## ------------------- ##" echo for ac_var in $ac_subst_files do eval ac_val=\$$ac_var case $ac_val in *\'*) ac_val=`printf '%s\n' "$ac_val" | sed "s/'/'\\\\\\\\''/g"`;; esac printf '%s\n' "$ac_var='$ac_val'" done | sort echo fi if test -s confdefs.h; then printf '%s\n' "## ----------- ## ## confdefs.h. ## ## ----------- ##" echo cat confdefs.h echo fi test "$ac_signal" != 0 && printf '%s\n' "$as_me: caught signal $ac_signal" printf '%s\n' "$as_me: exit $exit_status" } # When interrupted or exit'd, cleanup temporary files, and complete # config.log. ac_exit_trap () { exit_status= # Sanitize IFS. IFS=" "" $as_nl" # Save into config.log some information that might help in debugging. ac_dump_debugging_info >&5 eval "rm -f $ac_clean_CONFIG_STATUS core *.core core.conftest.*" && rm -f -r conftest* confdefs* conf$$* $ac_clean_files && exit $exit_status } trap 'ac_exit_trap $?' 0 for ac_signal in 1 2 13 15; do trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal done ac_signal=0 # confdefs.h avoids OS command line length limits that DEFS can exceed. rm -f -r conftest* confdefs.h printf '%s\n' "/* confdefs.h */" > confdefs.h # Predefined preprocessor variables. printf '%s\n' "#define PACKAGE_NAME \"$PACKAGE_NAME\"" >>confdefs.h printf '%s\n' "#define PACKAGE_TARNAME \"$PACKAGE_TARNAME\"" >>confdefs.h printf '%s\n' "#define PACKAGE_VERSION \"$PACKAGE_VERSION\"" >>confdefs.h printf '%s\n' "#define PACKAGE_STRING \"$PACKAGE_STRING\"" >>confdefs.h printf '%s\n' "#define PACKAGE_BUGREPORT \"$PACKAGE_BUGREPORT\"" >>confdefs.h printf '%s\n' "#define PACKAGE_URL \"$PACKAGE_URL\"" >>confdefs.h # Let the site file select an alternate cache file if it wants to. # Prefer an explicitly selected file to automatically selected ones. if test -n "$CONFIG_SITE"; then ac_site_files="$CONFIG_SITE" elif test "x$prefix" != xNONE; then ac_site_files="$prefix/share/config.site $prefix/etc/config.site" else ac_site_files="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site" fi for ac_site_file in $ac_site_files do case $ac_site_file in #( */*) : ;; #( *) : ac_site_file=./$ac_site_file ;; esac if test -f "$ac_site_file" && test -r "$ac_site_file"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 printf '%s\n' "$as_me: loading site script $ac_site_file" >&6;} sed 's/^/| /' "$ac_site_file" >&5 . "$ac_site_file" \ || { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "failed to load site script $ac_site_file See 'config.log' for more details" "$LINENO" 5; } fi done if test -r "$cache_file"; then # Some versions of bash will fail to source /dev/null (special files # actually), so we avoid doing that. DJGPP emulates it as a regular file. if test /dev/null != "$cache_file" && test -f "$cache_file"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 printf '%s\n' "$as_me: loading cache $cache_file" >&6;} case $cache_file in [\\/]* | ?:[\\/]* ) . "$cache_file";; *) . "./$cache_file";; esac fi else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 printf '%s\n' "$as_me: creating cache $cache_file" >&6;} >$cache_file fi # Test code for whether the C compiler supports C23 (global declarations) ac_c_conftest_c23_globals=' /* Does the compiler advertise conformance to C17 or earlier? Although GCC 14 does not do that, even with -std=gnu23, it is close enough, and defines __STDC_VERSION == 202000L. */ #if !defined __STDC_VERSION__ || __STDC_VERSION__ <= 201710L # error "Compiler advertises conformance to C17 or earlier" #endif // Check alignas. char alignas (double) c23_aligned_as_double; char alignas (0) c23_no_special_alignment; extern char c23_aligned_as_int; char alignas (0) alignas (int) c23_aligned_as_int; // Check alignof. enum { c23_int_alignment = alignof (int), c23_int_array_alignment = alignof (int[100]), c23_char_alignment = alignof (char) }; static_assert (0 < -alignof (int), "alignof is signed"); int function_with_unnamed_parameter (int) { return 0; } void c23_noreturn (); /* Test parsing of string and char UTF-8 literals (including hex escapes). The parens pacify GCC 15. */ bool use_u8 = (!sizeof u8"\xFF") == (!u8'\''x'\''); bool check_that_bool_works = true | false | !nullptr; #if !true # error "true does not work in #if" #endif #if false #elifdef __STDC_VERSION__ #else # error "#elifdef does not work" #endif #ifndef __has_c_attribute # error "__has_c_attribute not defined" #endif #ifndef __has_include # error "__has_include not defined" #endif #define LPAREN() ( #define FORTY_TWO(x) 42 #define VA_OPT_TEST(r, x, ...) __VA_OPT__ (FORTY_TWO r x)) static_assert (VA_OPT_TEST (LPAREN (), 0, <:-) == 42); static_assert (0b101010 == 42); static_assert (0B101010 == 42); static_assert (0xDEAD'\''BEEF == 3'\''735'\''928'\''559); static_assert (0.500'\''000'\''000 == 0.5); enum unsignedish : unsigned int { uione = 1 }; static_assert (0 < -uione); #include constexpr nullptr_t null_pointer = nullptr; static typeof (1 + 1L) two () { return 2; } static long int three () { return 3; } ' # Test code for whether the C compiler supports C23 (body of main). ac_c_conftest_c23_main=' { label_before_declaration: int arr[10] = {}; if (arr[0]) goto label_before_declaration; if (!arr[0]) goto label_at_end_of_block; label_at_end_of_block: } ok |= !null_pointer; ok |= two != three; ' # Test code for whether the C compiler supports C23 (complete). ac_c_conftest_c23_program="${ac_c_conftest_c23_globals} int main (int, char **) { int ok = 0; ${ac_c_conftest_c23_main} return ok; } " # Test code for whether the C compiler supports C89 (global declarations) ac_c_conftest_c89_globals=' /* Do not test the value of __STDC__, because some compilers define it to 0 or do not define it, while otherwise adequately conforming. */ #include #include struct stat; /* Most of the following tests are stolen from RCS 5.7 src/conf.sh. */ struct buf { int x; }; struct buf * (*rcsopen) (struct buf *, struct stat *, int); static char *e (char **p, int i) { return p[i]; } static char *f (char * (*g) (char **, int), char **p, ...) { char *s; va_list v; va_start (v,p); s = g (p, va_arg (v,int)); va_end (v); return s; } /* C89 style stringification. */ #define noexpand_stringify(a) #a const char *stringified = noexpand_stringify(arbitrary+token=sequence); /* C89 style token pasting. Exercises some of the corner cases that e.g. old MSVC gets wrong, but not very hard. */ #define noexpand_concat(a,b) a##b #define expand_concat(a,b) noexpand_concat(a,b) extern int vA; extern int vbee; #define aye A #define bee B int *pvA = &expand_concat(v,aye); int *pvbee = &noexpand_concat(v,bee); /* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has function prototypes and stuff, but not \xHH hex character constants. These do not provoke an error unfortunately, instead are silently treated as an "x". The following induces an error, until -std is added to get proper ANSI mode. Curiously \x00 != x always comes out true, for an array size at least. It is necessary to write \x00 == 0 to get something that is true only with -std. */ int osf4_cc_array ['\''\x00'\'' == 0 ? 1 : -1]; /* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters inside strings and character constants. */ #define FOO(x) '\''x'\'' int xlc6_cc_array[FOO(a) == '\''x'\'' ? 1 : -1]; int test (int i, double x); struct s1 {int (*f) (int a);}; struct s2 {int (*f) (double a);}; int pairnames (int, char **, int *(*)(struct buf *, struct stat *, int), int, int);' # Test code for whether the C compiler supports C89 (body of main). ac_c_conftest_c89_main=' ok |= (argc == 0 || f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]); ' # Test code for whether the C compiler supports C99 (global declarations) ac_c_conftest_c99_globals=' /* Does the compiler advertise C99 conformance? */ #if !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L # error "Compiler does not advertise C99 conformance" #endif // See if C++-style comments work. #include extern int puts (const char *); extern int printf (const char *, ...); extern int dprintf (int, const char *, ...); extern void *malloc (size_t); extern void free (void *); // Check varargs macros. These examples are taken from C99 6.10.3.5. // dprintf is used instead of fprintf to avoid needing to declare // FILE and stderr, and "aND" is used instead of "and" to work around // GCC bug 40564 which is irrelevant here. #define debug(...) dprintf (2, __VA_ARGS__) #define showlist(...) puts (#__VA_ARGS__) #define report(test,...) ((test) ? puts (#test) : printf (__VA_ARGS__)) static void test_varargs_macros (void) { int x = 1234; int y = 5678; debug ("Flag"); debug ("X = %d\n", x); showlist (The first, second, aND third items.); report (x>y, "x is %d but y is %d", x, y); } // Check long long types. #define BIG64 18446744073709551615ull #define BIG32 4294967295ul #define BIG_OK (BIG64 / BIG32 == 4294967297ull && BIG64 % BIG32 == 0) #if !BIG_OK #error "your preprocessor is broken" #endif #if BIG_OK #else #error "your preprocessor is broken" #endif static long long int bignum = -9223372036854775807LL; static unsigned long long int ubignum = BIG64; struct incomplete_array { int datasize; double data[]; }; struct named_init { int number; const wchar_t *name; double average; }; typedef const char *ccp; static inline int test_restrict (ccp restrict text) { // Iterate through items via the restricted pointer. // Also check for declarations in for loops. for (unsigned int i = 0; *(text+i) != '\''\0'\''; ++i) continue; return 0; } // Check varargs and va_copy. static bool test_varargs (const char *format, ...) { va_list args; va_start (args, format); va_list args_copy; va_copy (args_copy, args); const char *str = ""; int number = 0; float fnumber = 0; while (*format) { switch (*format++) { case '\''s'\'': // string str = va_arg (args_copy, const char *); break; case '\''d'\'': // int number = va_arg (args_copy, int); break; case '\''f'\'': // float fnumber = va_arg (args_copy, double); break; default: break; } } va_end (args_copy); va_end (args); return *str && number && fnumber; } ' # Test code for whether the C compiler supports C99 (body of main). ac_c_conftest_c99_main=' // Check bool. _Bool success = false; success |= (argc != 0); // Check restrict. if (test_restrict ("String literal") == 0) success = true; const char *restrict newvar = "Another string"; // Check varargs. success &= test_varargs ("s, d'\'' f .", "string", 65, 34.234); test_varargs_macros (); // Check flexible array members. static struct incomplete_array *volatile incomplete_array_pointer; struct incomplete_array *ia = incomplete_array_pointer; ia->datasize = 10; for (int i = 0; i < ia->datasize; ++i) ia->data[i] = i * 1.234; // Work around memory leak warnings. free (ia); // Check named initializers. struct named_init ni = { .number = 34, .name = L"Test wide string", .average = 543.34343, }; ni.number = 58; // Do not test for VLAs, as some otherwise-conforming compilers lack them. // C code should instead use __STDC_NO_VLA__; see Autoconf manual. // work around unused variable warnings ok |= (!success || bignum == 0LL || ubignum == 0uLL || newvar[0] == '\''x'\'' || ni.number != 58); ' # Test code for whether the C compiler supports C11 (global declarations) ac_c_conftest_c11_globals=' /* Does the compiler advertise C11 conformance? */ #if !defined __STDC_VERSION__ || __STDC_VERSION__ < 201112L # error "Compiler does not advertise C11 conformance" #endif // Check _Alignas. char _Alignas (double) aligned_as_double; char _Alignas (0) no_special_alignment; extern char aligned_as_int; char _Alignas (0) _Alignas (int) aligned_as_int; // Check _Alignof. enum { int_alignment = _Alignof (int), int_array_alignment = _Alignof (int[100]), char_alignment = _Alignof (char) }; _Static_assert (0 < -_Alignof (int), "_Alignof is signed"); // Check _Noreturn. int _Noreturn does_not_return (void) { for (;;) continue; } // Check _Static_assert. struct test_static_assert { int x; _Static_assert (sizeof (int) <= sizeof (long int), "_Static_assert does not work in struct"); long int y; }; // Check UTF-8 literals. #define u8 syntax error! char const utf8_literal[] = u8"happens to be ASCII" "another string"; // Check duplicate typedefs. typedef long *long_ptr; typedef long int *long_ptr; typedef long_ptr long_ptr; // Anonymous structures and unions -- taken from C11 6.7.2.1 Example 1. struct anonymous { union { struct { int i; int j; }; struct { int k; long int l; } w; }; int m; } v1; ' # Test code for whether the C compiler supports C11 (body of main). ac_c_conftest_c11_main=' _Static_assert ((offsetof (struct anonymous, i) == offsetof (struct anonymous, w.k)), "Anonymous union alignment botch"); v1.i = 2; v1.w.k = 5; ok |= v1.i != 5; ' # Test code for whether the C compiler supports C11 (complete). ac_c_conftest_c11_program="${ac_c_conftest_c89_globals} ${ac_c_conftest_c99_globals} ${ac_c_conftest_c11_globals} int main (int argc, char **argv) { int ok = 0; ${ac_c_conftest_c89_main} ${ac_c_conftest_c99_main} ${ac_c_conftest_c11_main} return ok; } " # Test code for whether the C compiler supports C99 (complete). ac_c_conftest_c99_program="${ac_c_conftest_c89_globals} ${ac_c_conftest_c99_globals} int main (int argc, char **argv) { int ok = 0; ${ac_c_conftest_c89_main} ${ac_c_conftest_c99_main} return ok; } " # Test code for whether the C compiler supports C89 (complete). ac_c_conftest_c89_program="${ac_c_conftest_c89_globals} int main (int argc, char **argv) { int ok = 0; ${ac_c_conftest_c89_main} return ok; } " as_fn_append ac_header_c_list " stdio.h stdio_h HAVE_STDIO_H" as_fn_append ac_header_c_list " stdlib.h stdlib_h HAVE_STDLIB_H" as_fn_append ac_header_c_list " string.h string_h HAVE_STRING_H" as_fn_append ac_header_c_list " inttypes.h inttypes_h HAVE_INTTYPES_H" as_fn_append ac_header_c_list " stdint.h stdint_h HAVE_STDINT_H" as_fn_append ac_header_c_list " strings.h strings_h HAVE_STRINGS_H" as_fn_append ac_header_c_list " sys/stat.h sys_stat_h HAVE_SYS_STAT_H" as_fn_append ac_header_c_list " sys/types.h sys_types_h HAVE_SYS_TYPES_H" as_fn_append ac_header_c_list " unistd.h unistd_h HAVE_UNISTD_H" # Auxiliary files required by this configure script. ac_aux_files="install-sh config.guess config.sub" # Locations in which to look for auxiliary files. ac_aux_dir_candidates="${srcdir}/config" # Search for a directory containing all of the required auxiliary files, # $ac_aux_files, from the $PATH-style list $ac_aux_dir_candidates. # If we don't find one directory that contains all the files we need, # we report the set of missing files from the *first* directory in # $ac_aux_dir_candidates and give up. ac_missing_aux_files="" ac_first_candidate=: printf '%s\n' "$as_me:${as_lineno-$LINENO}: looking for aux files: $ac_aux_files" >&5 as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_found=false for as_dir in $ac_aux_dir_candidates do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac as_found=: printf '%s\n' "$as_me:${as_lineno-$LINENO}: trying $as_dir" >&5 ac_aux_dir_found=yes ac_install_sh= for ac_aux in $ac_aux_files do # As a special case, if "install-sh" is required, that requirement # can be satisfied by any of "install-sh", "install.sh", or "shtool", # and $ac_install_sh is set appropriately for whichever one is found. if test x"$ac_aux" = x"install-sh" then if test -f "${as_dir}install-sh"; then printf '%s\n' "$as_me:${as_lineno-$LINENO}: ${as_dir}install-sh found" >&5 ac_install_sh="${as_dir}install-sh -c" elif test -f "${as_dir}install.sh"; then printf '%s\n' "$as_me:${as_lineno-$LINENO}: ${as_dir}install.sh found" >&5 ac_install_sh="${as_dir}install.sh -c" elif test -f "${as_dir}shtool"; then printf '%s\n' "$as_me:${as_lineno-$LINENO}: ${as_dir}shtool found" >&5 ac_install_sh="${as_dir}shtool install -c" else ac_aux_dir_found=no if $ac_first_candidate; then ac_missing_aux_files="${ac_missing_aux_files} install-sh" else break fi fi else if test -f "${as_dir}${ac_aux}"; then printf '%s\n' "$as_me:${as_lineno-$LINENO}: ${as_dir}${ac_aux} found" >&5 else ac_aux_dir_found=no if $ac_first_candidate; then ac_missing_aux_files="${ac_missing_aux_files} ${ac_aux}" else break fi fi fi done if test "$ac_aux_dir_found" = yes; then ac_aux_dir="$as_dir" break fi ac_first_candidate=false as_found=false done IFS=$as_save_IFS if $as_found then : else case e in #( e) as_fn_error $? "cannot find required auxiliary files:$ac_missing_aux_files" "$LINENO" 5 ;; esac fi # These three variables are undocumented and unsupported, # and are intended to be withdrawn in a future Autoconf release. # They can cause serious problems if a builder's source tree is in a directory # whose full name contains unusual characters. if test -f "${ac_aux_dir}config.guess"; then ac_config_guess="$SHELL ${ac_aux_dir}config.guess" fi if test -f "${ac_aux_dir}config.sub"; then ac_config_sub="$SHELL ${ac_aux_dir}config.sub" fi if test -f "$ac_aux_dir/configure"; then ac_configure="$SHELL ${ac_aux_dir}configure" fi # Check that the precious variables saved in the cache have kept the same # value. ac_cache_corrupted=false for ac_var in $ac_precious_vars; do eval ac_old_set=\$ac_cv_env_${ac_var}_set eval ac_new_set=\$ac_env_${ac_var}_set eval ac_old_val=\$ac_cv_env_${ac_var}_value eval ac_new_val=\$ac_env_${ac_var}_value case $ac_old_set,$ac_new_set in set,) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&5 printf '%s\n' "$as_me: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&2;} ac_cache_corrupted=: ;; ,set) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was not set in the previous run" >&5 printf '%s\n' "$as_me: error: '$ac_var' was not set in the previous run" >&2;} ac_cache_corrupted=: ;; ,);; *) if test "x$ac_old_val" != "x$ac_new_val"; then # differences in whitespace do not lead to failure. ac_old_val_w= for ac_val in x $ac_old_val; do ac_old_val_w="$ac_old_val_w $ac_val" done ac_new_val_w= for ac_val in x $ac_new_val; do ac_new_val_w="$ac_new_val_w $ac_val" done if test "$ac_old_val_w" != "$ac_new_val_w"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: '$ac_var' has changed since the previous run:" >&5 printf '%s\n' "$as_me: error: '$ac_var' has changed since the previous run:" >&2;} ac_cache_corrupted=: else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&5 printf '%s\n' "$as_me: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&2;} eval $ac_var=\$ac_old_val fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: former value: '$ac_old_val'" >&5 printf '%s\n' "$as_me: former value: '$ac_old_val'" >&2;} { printf '%s\n' "$as_me:${as_lineno-$LINENO}: current value: '$ac_new_val'" >&5 printf '%s\n' "$as_me: current value: '$ac_new_val'" >&2;} fi;; esac # Pass precious variables to config.status. if test "$ac_new_set" = set; then case $ac_new_val in *\'*) ac_arg=$ac_var=`printf '%s\n' "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; *) ac_arg=$ac_var=$ac_new_val ;; esac case " $ac_configure_args " in *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. *) as_fn_append ac_configure_args " '$ac_arg'" ;; esac fi done if $ac_cache_corrupted; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 printf '%s\n' "$as_me: error: changes in the environment can compromise the build" >&2;} as_fn_error $? "run '${MAKE-make} distclean' and/or 'rm $cache_file' and start over" "$LINENO" 5 fi ## -------------------- ## ## Main body of script. ## ## -------------------- ## # Determine whether it's possible to make 'echo' print without a newline. # These variables are no longer used directly by Autoconf, but are AC_SUBSTed # for compatibility with existing Makefiles. ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. xy) ECHO_C='\c';; *) echo `echo ksh88 bug on AIX 6.1` > /dev/null ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_config_headers="$ac_config_headers config/acconfig.h" # Make sure we can run config.sub. $SHELL "${ac_aux_dir}config.sub" sun4 >/dev/null 2>&1 || as_fn_error $? "cannot run $SHELL ${ac_aux_dir}config.sub" "$LINENO" 5 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking build system type" >&5 printf %s "checking build system type... " >&6; } if test ${ac_cv_build+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_build_alias=$build_alias test "x$ac_build_alias" = x && ac_build_alias=`$SHELL "${ac_aux_dir}config.guess"` test "x$ac_build_alias" = x && as_fn_error $? "cannot guess build type; you must specify one" "$LINENO" 5 ac_cv_build=`$SHELL "${ac_aux_dir}config.sub" $ac_build_alias` || as_fn_error $? "$SHELL ${ac_aux_dir}config.sub $ac_build_alias failed" "$LINENO" 5 ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_build" >&5 printf '%s\n' "$ac_cv_build" >&6; } case $ac_cv_build in *-*-*) ;; *) as_fn_error $? "invalid value of canonical build" "$LINENO" 5;; esac build=$ac_cv_build ac_save_IFS=$IFS; IFS='-' set x $ac_cv_build shift build_cpu=$1 build_vendor=$2 shift; shift # Remember, the first character of IFS is used to create $*, # except with old shells: build_os=$* IFS=$ac_save_IFS case $build_os in *\ *) build_os=`printf '%s\n' "$build_os" | sed 's/ /-/g'`;; esac { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking host system type" >&5 printf %s "checking host system type... " >&6; } if test ${ac_cv_host+y} then : printf %s "(cached) " >&6 else case e in #( e) if test "x$host_alias" = x; then ac_cv_host=$ac_cv_build else ac_cv_host=`$SHELL "${ac_aux_dir}config.sub" $host_alias` || as_fn_error $? "$SHELL ${ac_aux_dir}config.sub $host_alias failed" "$LINENO" 5 fi ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_host" >&5 printf '%s\n' "$ac_cv_host" >&6; } case $ac_cv_host in *-*-*) ;; *) as_fn_error $? "invalid value of canonical host" "$LINENO" 5;; esac host=$ac_cv_host ac_save_IFS=$IFS; IFS='-' set x $ac_cv_host shift host_cpu=$1 host_vendor=$2 shift; shift # Remember, the first character of IFS is used to create $*, # except with old shells: host_os=$* IFS=$ac_save_IFS case $host_os in *\ *) host_os=`printf '%s\n' "$host_os" | sed 's/ /-/g'`;; esac case $host_os in *cygwin* ) CYGWIN=yes;; * ) CYGWIN=no;; esac printf '%s\n' "#define HOST_SYSTEM_TYPE \"$host\"" >>confdefs.h ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. set dummy ${ac_tool_prefix}gcc; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}gcc" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf '%s\n' "$CC" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi fi if test -z "$ac_cv_prog_CC"; then ac_ct_CC=$CC # Extract the first word of "gcc", so it can be a program name with args. set dummy gcc; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="gcc" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 printf '%s\n' "$ac_ct_CC" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi else CC="$ac_cv_prog_CC" fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. set dummy ${ac_tool_prefix}cc; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}cc" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf '%s\n' "$CC" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi fi fi if test -z "$CC"; then # Extract the first word of "cc", so it can be a program name with args. set dummy cc; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else ac_prog_rejected=no as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then if test "$as_dir$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then ac_prog_rejected=yes continue fi ac_cv_prog_CC="cc" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS if test $ac_prog_rejected = yes; then # We found a bogon in the path, so make sure we never use it. set dummy $ac_cv_prog_CC shift if test $# != 0; then # We chose a different compiler from the bogus one. # However, it has the same basename, so the bogon will be chosen # first if we set CC to just the basename; use the full file name. shift ac_cv_prog_CC="$as_dir$ac_word${1+' '}$@" fi fi fi ;; esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf '%s\n' "$CC" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then for ac_prog in cl.exe do # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. set dummy $ac_tool_prefix$ac_prog; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="$ac_tool_prefix$ac_prog" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf '%s\n' "$CC" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi test -n "$CC" && break done fi if test -z "$CC"; then ac_ct_CC=$CC for ac_prog in cl.exe do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="$ac_prog" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 printf '%s\n' "$ac_ct_CC" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi test -n "$ac_ct_CC" && break done if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi fi fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}clang", so it can be a program name with args. set dummy ${ac_tool_prefix}clang; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}clang" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf '%s\n' "$CC" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi fi if test -z "$ac_cv_prog_CC"; then ac_ct_CC=$CC # Extract the first word of "clang", so it can be a program name with args. set dummy clang; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="clang" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 printf '%s\n' "$ac_ct_CC" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi else CC="$ac_cv_prog_CC" fi fi test -z "$CC" && { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "no acceptable C compiler found in \$PATH See 'config.log' for more details" "$LINENO" 5; } # Provide some information about the compiler. printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 set X $ac_compile ac_compiler=$2 for ac_option in --version -v -V -qversion -version; do { { ac_try="$ac_compiler $ac_option >&5" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf '%s\n' "$ac_try_echo"; } >&5 (eval "$ac_compiler $ac_option >&5") 2>conftest.err ac_status=$? if test -s conftest.err; then sed '10a\ ... rest of stderr output deleted ... 10q' conftest.err >conftest.er1 cat conftest.er1 >&5 fi rm -f conftest.er1 conftest.err printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } done cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" # Try to create an executable without -o first, disregard a.out. # It will help us diagnose broken compilers, and finding out an intuition # of exeext. { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 printf %s "checking whether the C compiler works... " >&6; } ac_link_default=`printf '%s\n' "$ac_link" | sed 's/ -o *conftest[^ ]*//'` # The possible output files: ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" ac_rmfiles= for ac_file in $ac_files do case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; * ) ac_rmfiles="$ac_rmfiles $ac_file";; esac done rm -f $ac_rmfiles if { { ac_try="$ac_link_default" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf '%s\n' "$ac_try_echo"; } >&5 (eval "$ac_link_default") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then : # Autoconf-2.13 could set the ac_cv_exeext variable to 'no'. # So ignore a value of 'no', otherwise this would lead to 'EXEEXT = no' # in a Makefile. We should not override ac_cv_exeext if it was cached, # so that the user can short-circuit this test for compilers unknown to # Autoconf. for ac_file in $ac_files '' do test -f "$ac_file" || continue case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; [ab].out ) # We found the default executable, but exeext='' is most # certainly right. break;; *.* ) if test ${ac_cv_exeext+y} && test "$ac_cv_exeext" != no; then :; else ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` fi # We set ac_cv_exeext here because the later test for it is not # safe: cross compilers may not add the suffix if given an '-o' # argument, so we may need to know it at that point already. # Even if this section looks crufty: it has the advantage of # actually working. break;; * ) break;; esac done test "$ac_cv_exeext" = no && ac_cv_exeext= else case e in #( e) ac_file='' ;; esac fi if test -z "$ac_file" then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } printf '%s\n' "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error 77 "C compiler cannot create executables See 'config.log' for more details" "$LINENO" 5; } else case e in #( e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf '%s\n' "yes" >&6; } ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 printf %s "checking for C compiler default output file name... " >&6; } { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 printf '%s\n' "$ac_file" >&6; } ac_exeext=$ac_cv_exeext rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out ac_clean_files=$ac_clean_files_save { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 printf %s "checking for suffix of executables... " >&6; } if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf '%s\n' "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then : # If both 'conftest.exe' and 'conftest' are 'present' (well, observable) # catch 'conftest.exe'. For instance with Cygwin, 'ls conftest' will # work properly (i.e., refer to 'conftest.exe'), while it won't with # 'rm'. for ac_file in conftest.exe conftest conftest.*; do test -f "$ac_file" || continue case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` break;; * ) break;; esac done else case e in #( e) { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of executables: cannot compile and link See 'config.log' for more details" "$LINENO" 5; } ;; esac fi rm -f conftest conftest$ac_cv_exeext { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 printf '%s\n' "$ac_cv_exeext" >&6; } rm -f conftest.$ac_ext EXEEXT=$ac_cv_exeext ac_exeext=$EXEEXT cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { FILE *f = fopen ("conftest.out", "w"); if (!f) return 1; return ferror (f) || fclose (f) != 0; ; return 0; } _ACEOF ac_clean_files="$ac_clean_files conftest.out" # Check that the compiler produces executables we can run. If not, either # the compiler is broken, or we cross compile. { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 printf %s "checking whether we are cross compiling... " >&6; } if test "$cross_compiling" != yes; then { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf '%s\n' "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } if { ac_try='./conftest$ac_cv_exeext' { { case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf '%s\n' "$ac_try_echo"; } >&5 (eval "$ac_try") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; }; then cross_compiling=no else if test "$cross_compiling" = maybe; then cross_compiling=yes else { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error 77 "cannot run C compiled programs. If you meant to cross compile, use '--host'. See 'config.log' for more details" "$LINENO" 5; } fi fi fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 printf '%s\n' "$cross_compiling" >&6; } rm -f conftest.$ac_ext conftest$ac_cv_exeext \ conftest.o conftest.obj conftest.out ac_clean_files=$ac_clean_files_save { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 printf %s "checking for suffix of object files... " >&6; } if test ${ac_cv_objext+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF rm -f conftest.o conftest.obj if { { ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf '%s\n' "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then : for ac_file in conftest.o conftest.obj conftest.*; do test -f "$ac_file" || continue; case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` break;; esac done else case e in #( e) printf '%s\n' "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of object files: cannot compile See 'config.log' for more details" "$LINENO" 5; } ;; esac fi rm -f conftest.$ac_cv_objext conftest.$ac_ext ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 printf '%s\n' "$ac_cv_objext" >&6; } OBJEXT=$ac_cv_objext ac_objext=$OBJEXT { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether the compiler supports GNU C" >&5 printf %s "checking whether the compiler supports GNU C... " >&6; } if test ${ac_cv_c_compiler_gnu+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { #ifndef __GNUC__ choke me #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_compiler_gnu=yes else case e in #( e) ac_compiler_gnu=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_cv_c_compiler_gnu=$ac_compiler_gnu ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 printf '%s\n' "$ac_cv_c_compiler_gnu" >&6; } ac_compiler_gnu=$ac_cv_c_compiler_gnu if test $ac_compiler_gnu = yes; then GCC=yes else GCC= fi ac_test_CFLAGS=${CFLAGS+y} ac_save_CFLAGS=$CFLAGS { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 printf %s "checking whether $CC accepts -g... " >&6; } if test ${ac_cv_prog_cc_g+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_save_c_werror_flag=$ac_c_werror_flag ac_c_werror_flag=yes ac_cv_prog_cc_g=no CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_g=yes else case e in #( e) CFLAGS="" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : else case e in #( e) ac_c_werror_flag=$ac_save_c_werror_flag CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_g=yes fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_c_werror_flag=$ac_save_c_werror_flag ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 printf '%s\n' "$ac_cv_prog_cc_g" >&6; } if test $ac_test_CFLAGS; then CFLAGS=$ac_save_CFLAGS elif test $ac_cv_prog_cc_g = yes; then if test "$GCC" = yes; then CFLAGS="-g -O2" else CFLAGS="-g" fi else if test "$GCC" = yes; then CFLAGS="-O2" else CFLAGS= fi fi ac_prog_cc_stdc=no if test x$ac_prog_cc_stdc = xno then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C23 features" >&5 printf %s "checking for $CC option to enable C23 features... " >&6; } if test ${ac_cv_prog_cc_c23+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_cv_prog_cc_c23=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_c_conftest_c23_program _ACEOF for ac_arg in '' -std=gnu23 do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_c23=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c23" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC ;; esac fi if test "x$ac_cv_prog_cc_c23" = xno then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf '%s\n' "unsupported" >&6; } else case e in #( e) if test "x$ac_cv_prog_cc_c23" = x then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf '%s\n' "none needed" >&6; } else case e in #( e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c23" >&5 printf '%s\n' "$ac_cv_prog_cc_c23" >&6; } CC="$CC $ac_cv_prog_cc_c23" ;; esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c23 ac_prog_cc_stdc=c23 ;; esac fi fi if test x$ac_prog_cc_stdc = xno then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C11 features" >&5 printf %s "checking for $CC option to enable C11 features... " >&6; } if test ${ac_cv_prog_cc_c11+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_cv_prog_cc_c11=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_c_conftest_c11_program _ACEOF for ac_arg in '' -std=gnu11 -std:c11 do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_c11=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c11" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC ;; esac fi if test "x$ac_cv_prog_cc_c11" = xno then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf '%s\n' "unsupported" >&6; } else case e in #( e) if test "x$ac_cv_prog_cc_c11" = x then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf '%s\n' "none needed" >&6; } else case e in #( e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c11" >&5 printf '%s\n' "$ac_cv_prog_cc_c11" >&6; } CC="$CC $ac_cv_prog_cc_c11" ;; esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c11 ac_prog_cc_stdc=c11 ;; esac fi fi if test x$ac_prog_cc_stdc = xno then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C99 features" >&5 printf %s "checking for $CC option to enable C99 features... " >&6; } if test ${ac_cv_prog_cc_c99+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_cv_prog_cc_c99=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_c_conftest_c99_program _ACEOF for ac_arg in '' -std=gnu99 -std=c99 -c99 -qlanglvl=extc1x -qlanglvl=extc99 -AC99 -D_STDC_C99= do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_c99=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c99" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC ;; esac fi if test "x$ac_cv_prog_cc_c99" = xno then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf '%s\n' "unsupported" >&6; } else case e in #( e) if test "x$ac_cv_prog_cc_c99" = x then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf '%s\n' "none needed" >&6; } else case e in #( e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5 printf '%s\n' "$ac_cv_prog_cc_c99" >&6; } CC="$CC $ac_cv_prog_cc_c99" ;; esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c99 ac_prog_cc_stdc=c99 ;; esac fi fi if test x$ac_prog_cc_stdc = xno then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C89 features" >&5 printf %s "checking for $CC option to enable C89 features... " >&6; } if test ${ac_cv_prog_cc_c89+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_cv_prog_cc_c89=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_c_conftest_c89_program _ACEOF for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_c89=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c89" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC ;; esac fi if test "x$ac_cv_prog_cc_c89" = xno then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf '%s\n' "unsupported" >&6; } else case e in #( e) if test "x$ac_cv_prog_cc_c89" = x then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf '%s\n' "none needed" >&6; } else case e in #( e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 printf '%s\n' "$ac_cv_prog_cc_c89" >&6; } CC="$CC $ac_cv_prog_cc_c89" ;; esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c89 ac_prog_cc_stdc=c89 ;; esac fi fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether ln -s works" >&5 printf %s "checking whether ln -s works... " >&6; } LN_S=$as_ln_s if test "$LN_S" = "ln -s"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf '%s\n' "yes" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no, using $LN_S" >&5 printf '%s\n' "no, using $LN_S" >&6; } fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5 printf %s "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; } set x ${MAKE-make} ac_make=`printf '%s\n' "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'` if eval test \${ac_cv_prog_make_${ac_make}_set+y} then : printf %s "(cached) " >&6 else case e in #( e) cat >conftest.make <<\_ACEOF SHELL = /bin/sh all: @printf '%s\n' '@@@%%%=$(MAKE)=@@@%%%' _ACEOF # GNU make sometimes prints "make[1]: Entering ...", which would confuse us. case `${MAKE-make} -f conftest.make 2>/dev/null` in *@@@%%%=?*=@@@%%%*) eval ac_cv_prog_make_${ac_make}_set=yes;; *) eval ac_cv_prog_make_${ac_make}_set=no;; esac rm -f conftest.make ;; esac fi if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf '%s\n' "yes" >&6; } SET_MAKE= else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } SET_MAKE="MAKE=${MAKE-make}" fi # Find a good install program. We prefer a C program (faster), # so one script is as good as another. But avoid the broken or # incompatible versions: # SysV /etc/install, /usr/sbin/install # SunOS /usr/etc/install # IRIX /sbin/install # AIX /bin/install # AmigaOS /C/install, which installs bootblocks on floppy discs # AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag # AFS /usr/afsws/bin/install, which mishandles nonexistent args # SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" # OS/2's system install, which has a completely different semantic # ./install, which can be erroneously created by make from ./install.sh. # Reject install programs that cannot install multiple files. { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5 printf %s "checking for a BSD-compatible install... " >&6; } if test -z "$INSTALL"; then if test ${ac_cv_path_install+y} then : printf %s "(cached) " >&6 else case e in #( e) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac # Account for fact that we put trailing slashes in our PATH walk. case $as_dir in #(( ./ | /[cC]/* | \ /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \ ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \ /usr/ucb/* ) ;; *) # OSF/1 and SCO ODT 3.0 have their own names for install. # Don't use installbsd from OSF/1 since it installs stuff as root # by default. for ac_prog in ginstall scoinst install; do for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_prog$ac_exec_ext"; then if test $ac_prog = install && grep dspmsg "$as_dir$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # AIX install. It has an incompatible calling convention. : elif test $ac_prog = install && grep pwplus "$as_dir$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # program-specific install script used by HP pwplus--don't use. : else rm -rf conftest.one conftest.two conftest.dir echo one > conftest.one echo two > conftest.two mkdir conftest.dir if "$as_dir$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir/" && test -s conftest.one && test -s conftest.two && test -s conftest.dir/conftest.one && test -s conftest.dir/conftest.two then ac_cv_path_install="$as_dir$ac_prog$ac_exec_ext -c" break 3 fi fi fi done done ;; esac done IFS=$as_save_IFS rm -rf conftest.one conftest.two conftest.dir ;; esac fi if test ${ac_cv_path_install+y}; then INSTALL=$ac_cv_path_install else # As a last resort, use the slow shell script. Don't cache a # value for INSTALL within a source directory, because that will # break other packages using the cache if that directory is # removed, or if the value is a relative name. INSTALL=$ac_install_sh fi fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5 printf '%s\n' "$INSTALL" >&6; } # Use test -z because SunOS4 sh mishandles braces in ${var-val}. # It thinks the first close brace ends the variable substitution. test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}' test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for getaddrinfo in -lnsl" >&5 printf %s "checking for getaddrinfo in -lnsl... " >&6; } if test ${ac_cv_lib_nsl_getaddrinfo+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lnsl $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char getaddrinfo (void); int main (void) { return getaddrinfo (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_nsl_getaddrinfo=yes else case e in #( e) ac_cv_lib_nsl_getaddrinfo=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nsl_getaddrinfo" >&5 printf '%s\n' "$ac_cv_lib_nsl_getaddrinfo" >&6; } if test "x$ac_cv_lib_nsl_getaddrinfo" = xyes then : printf '%s\n' "#define HAVE_LIBNSL 1" >>confdefs.h LIBS="-lnsl $LIBS" fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for connect in -lsocket" >&5 printf %s "checking for connect in -lsocket... " >&6; } if test ${ac_cv_lib_socket_connect+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lsocket $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char connect (void); int main (void) { return connect (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_socket_connect=yes else case e in #( e) ac_cv_lib_socket_connect=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_socket_connect" >&5 printf '%s\n' "$ac_cv_lib_socket_connect" >&6; } if test "x$ac_cv_lib_socket_connect" = xyes then : printf '%s\n' "#define HAVE_LIBSOCKET 1" >>confdefs.h LIBS="-lsocket $LIBS" fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for inet_aton in -lresolv" >&5 printf %s "checking for inet_aton in -lresolv... " >&6; } if test ${ac_cv_lib_resolv_inet_aton+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lresolv $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char inet_aton (void); int main (void) { return inet_aton (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_resolv_inet_aton=yes else case e in #( e) ac_cv_lib_resolv_inet_aton=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_resolv_inet_aton" >&5 printf '%s\n' "$ac_cv_lib_resolv_inet_aton" >&6; } if test "x$ac_cv_lib_resolv_inet_aton" = xyes then : printf '%s\n' "#define HAVE_LIBRESOLV 1" >>confdefs.h LIBS="-lresolv $LIBS" fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for sin in -lm" >&5 printf %s "checking for sin in -lm... " >&6; } if test ${ac_cv_lib_m_sin+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lm $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char sin (void); int main (void) { return sin (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_m_sin=yes else case e in #( e) ac_cv_lib_m_sin=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_m_sin" >&5 printf '%s\n' "$ac_cv_lib_m_sin" >&6; } if test "x$ac_cv_lib_m_sin" = xyes then : printf '%s\n' "#define HAVE_LIBM 1" >>confdefs.h LIBS="-lm $LIBS" fi ac_header= ac_cache= for ac_item in $ac_header_c_list do if test $ac_cache; then ac_fn_c_check_header_compile "$LINENO" $ac_header ac_cv_header_$ac_cache "$ac_includes_default" if eval test \"x\$ac_cv_header_$ac_cache\" = xyes; then printf '%s\n' "#define $ac_item 1" >> confdefs.h fi ac_header= ac_cache= elif test $ac_header; then ac_cache=$ac_item else ac_header=$ac_item fi done if test $ac_cv_header_stdlib_h = yes && test $ac_cv_header_string_h = yes then : printf '%s\n' "#define STDC_HEADERS 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "arpa/inet.h" "ac_cv_header_arpa_inet_h" "$ac_includes_default" if test "x$ac_cv_header_arpa_inet_h" = xyes then : printf '%s\n' "#define HAVE_ARPA_INET_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "fcntl.h" "ac_cv_header_fcntl_h" "$ac_includes_default" if test "x$ac_cv_header_fcntl_h" = xyes then : printf '%s\n' "#define HAVE_FCNTL_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "limits.h" "ac_cv_header_limits_h" "$ac_includes_default" if test "x$ac_cv_header_limits_h" = xyes then : printf '%s\n' "#define HAVE_LIMITS_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "netdb.h" "ac_cv_header_netdb_h" "$ac_includes_default" if test "x$ac_cv_header_netdb_h" = xyes then : printf '%s\n' "#define HAVE_NETDB_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "netinet/in.h" "ac_cv_header_netinet_in_h" "$ac_includes_default" if test "x$ac_cv_header_netinet_in_h" = xyes then : printf '%s\n' "#define HAVE_NETINET_IN_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "stdlib.h" "ac_cv_header_stdlib_h" "$ac_includes_default" if test "x$ac_cv_header_stdlib_h" = xyes then : printf '%s\n' "#define HAVE_STDLIB_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "string.h" "ac_cv_header_string_h" "$ac_includes_default" if test "x$ac_cv_header_string_h" = xyes then : printf '%s\n' "#define HAVE_STRING_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "strings.h" "ac_cv_header_strings_h" "$ac_includes_default" if test "x$ac_cv_header_strings_h" = xyes then : printf '%s\n' "#define HAVE_STRINGS_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "sys/ioctl.h" "ac_cv_header_sys_ioctl_h" "$ac_includes_default" if test "x$ac_cv_header_sys_ioctl_h" = xyes then : printf '%s\n' "#define HAVE_SYS_IOCTL_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "sys/socket.h" "ac_cv_header_sys_socket_h" "$ac_includes_default" if test "x$ac_cv_header_sys_socket_h" = xyes then : printf '%s\n' "#define HAVE_SYS_SOCKET_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "sys/time.h" "ac_cv_header_sys_time_h" "$ac_includes_default" if test "x$ac_cv_header_sys_time_h" = xyes then : printf '%s\n' "#define HAVE_SYS_TIME_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "unistd.h" "ac_cv_header_unistd_h" "$ac_includes_default" if test "x$ac_cv_header_unistd_h" = xyes then : printf '%s\n' "#define HAVE_UNISTD_H 1" >>confdefs.h fi ac_fn_c_check_type "$LINENO" "size_t" "ac_cv_type_size_t" "$ac_includes_default" if test "x$ac_cv_type_size_t" = xyes then : else case e in #( e) printf '%s\n' "#define size_t unsigned int" >>confdefs.h ;; esac fi # The Ultrix 4.2 mips builtin alloca declared by alloca.h only works # for constant arguments. Useless! { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for working alloca.h" >&5 printf %s "checking for working alloca.h... " >&6; } if test ${ac_cv_working_alloca_h+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { char *p = (char *) alloca (2 * sizeof (int)); if (p) return 0; ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_working_alloca_h=yes else case e in #( e) ac_cv_working_alloca_h=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_working_alloca_h" >&5 printf '%s\n' "$ac_cv_working_alloca_h" >&6; } if test $ac_cv_working_alloca_h = yes; then printf '%s\n' "#define HAVE_ALLOCA_H 1" >>confdefs.h fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for alloca" >&5 printf %s "checking for alloca... " >&6; } if test ${ac_cv_func_alloca_works+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_cv_func_alloca_works=$ac_cv_working_alloca_h if test "$ac_cv_func_alloca_works" != yes then : cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #ifndef alloca # ifdef __GNUC__ # define alloca __builtin_alloca # elif defined _MSC_VER # include # define alloca _alloca # else # ifdef __cplusplus extern "C" # endif void *alloca (size_t); # endif #endif int main (void) { char *p = (char *) alloca (1); if (p) return 0; ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_func_alloca_works=yes fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_func_alloca_works" >&5 printf '%s\n' "$ac_cv_func_alloca_works" >&6; } if test $ac_cv_func_alloca_works = yes; then printf '%s\n' "#define HAVE_ALLOCA 1" >>confdefs.h else # The SVR3 libPW and SVR4 libucb both contain incompatible functions # that cause trouble. Some versions do not even contain alloca or # contain a buggy version. If you still want to use their alloca, # use ar to extract alloca.o from them instead of compiling alloca.c. ALLOCA=\${LIBOBJDIR}alloca.$ac_objext printf '%s\n' "#define C_ALLOCA 1" >>confdefs.h { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking stack direction for C alloca" >&5 printf %s "checking stack direction for C alloca... " >&6; } if test ${ac_cv_c_stack_direction+y} then : printf %s "(cached) " >&6 else case e in #( e) if test "$cross_compiling" = yes then : ac_cv_c_stack_direction=0 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_includes_default int find_stack_direction (int *addr, int depth) { int dir, dummy = 0; if (! addr) addr = &dummy; *addr = addr < &dummy ? 1 : addr == &dummy ? 0 : -1; dir = depth ? find_stack_direction (addr, depth - 1) : 0; return dir + dummy; } int main (int argc, char **argv) { return find_stack_direction (0, argc + !argv + 20) < 0; } _ACEOF if ac_fn_c_try_run "$LINENO" then : ac_cv_c_stack_direction=1 else case e in #( e) ac_cv_c_stack_direction=-1 ;; esac fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_stack_direction" >&5 printf '%s\n' "$ac_cv_c_stack_direction" >&6; } printf '%s\n' "#define STACK_DIRECTION $ac_cv_c_stack_direction" >>confdefs.h fi for ac_func in strftime do : ac_fn_c_check_func "$LINENO" "strftime" "ac_cv_func_strftime" if test "x$ac_cv_func_strftime" = xyes then : printf '%s\n' "#define HAVE_STRFTIME 1" >>confdefs.h else case e in #( e) # strftime is in -lintl on SCO UNIX. { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for strftime in -lintl" >&5 printf %s "checking for strftime in -lintl... " >&6; } if test ${ac_cv_lib_intl_strftime+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lintl $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char strftime (void); int main (void) { return strftime (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_intl_strftime=yes else case e in #( e) ac_cv_lib_intl_strftime=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_intl_strftime" >&5 printf '%s\n' "$ac_cv_lib_intl_strftime" >&6; } if test "x$ac_cv_lib_intl_strftime" = xyes then : printf '%s\n' "#define HAVE_STRFTIME 1" >>confdefs.h LIBS="-lintl $LIBS" fi ;; esac fi done ac_fn_c_check_func "$LINENO" "getaddrinfo" "ac_cv_func_getaddrinfo" if test "x$ac_cv_func_getaddrinfo" = xyes then : printf '%s\n' "#define HAVE_GETADDRINFO 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "getnameinfo" "ac_cv_func_getnameinfo" if test "x$ac_cv_func_getnameinfo" = xyes then : printf '%s\n' "#define HAVE_GETNAMEINFO 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "gettimeofday" "ac_cv_func_gettimeofday" if test "x$ac_cv_func_gettimeofday" = xyes then : printf '%s\n' "#define HAVE_GETTIMEOFDAY 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "inet_ntoa" "ac_cv_func_inet_ntoa" if test "x$ac_cv_func_inet_ntoa" = xyes then : printf '%s\n' "#define HAVE_INET_NTOA 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "memset" "ac_cv_func_memset" if test "x$ac_cv_func_memset" = xyes then : printf '%s\n' "#define HAVE_MEMSET 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "select" "ac_cv_func_select" if test "x$ac_cv_func_select" = xyes then : printf '%s\n' "#define HAVE_SELECT 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "setenv" "ac_cv_func_setenv" if test "x$ac_cv_func_setenv" = xyes then : printf '%s\n' "#define HAVE_SETENV 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "socket" "ac_cv_func_socket" if test "x$ac_cv_func_socket" = xyes then : printf '%s\n' "#define HAVE_SOCKET 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "strchr" "ac_cv_func_strchr" if test "x$ac_cv_func_strchr" = xyes then : printf '%s\n' "#define HAVE_STRCHR 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "strdup" "ac_cv_func_strdup" if test "x$ac_cv_func_strdup" = xyes then : printf '%s\n' "#define HAVE_STRDUP 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "strstr" "ac_cv_func_strstr" if test "x$ac_cv_func_strstr" = xyes then : printf '%s\n' "#define HAVE_STRSTR 1" >>confdefs.h fi case "$host" in *darwin*) printf '%s\n' "#define BSD_IP_STACK 1" >>confdefs.h printf '%s\n' "#define DARWIN 1" >>confdefs.h ;; *netbsd*) printf '%s\n' "#define BSD_IP_STACK 1" >>confdefs.h printf '%s\n' "#define NETBSD 1" >>confdefs.h ;; *openbsd*) printf '%s\n' "#define OPENBSD 1" >>confdefs.h printf '%s\n' "#define BSD_IP_STACK 1" >>confdefs.h ;; *bsd*) printf '%s\n' "#define BSD_IP_STACK 1" >>confdefs.h ;; *linux*) printf '%s\n' "#define _BSD_SOURCE 1" >>confdefs.h ;; *solaris*) case "$host" in *solaris2.4*|*solaris2.5*) printf '%s\n' "#define SOLARIS_LENGTH_IN_CHECKSUM 1" >>confdefs.h ;; esac ;; esac case "$host" in *cygwin*) LIBS="-lws2_32 $LIBS" ;; *) # Check whether --enable-universal was given. if test ${enable_universal+y} then : enableval=$enable_universal; UNIVERSAL="$enableval" else case e in #( e) UNIVERSAL="no" ;; esac fi # Check whether --enable-gtod was given. if test ${enable_gtod+y} then : enableval=$enable_gtod; GTOD="$enableval" else case e in #( e) GTOD="no" ;; esac fi lft_candidate_prefixes="/opt/homebrew /usr/local /opt/local /opt/pkg" pcap_path_override=no # Check whether --with-pcap was given. if test ${with_pcap+y} then : withval=$with_pcap; { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for --with-pcap option" >&5 printf %s "checking for --with-pcap option... " >&6; } case "$withval" in yes|no) as_fn_error $? "please specify a PATH in --with-pcap option!" "$LINENO" 5 ;; *) if test '!' -d "$withval"; then as_fn_error $? "$withval does not exist!" "$LINENO" 5 else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $withval" >&5 printf '%s\n' "$withval" >&6; } pcap_path_override=yes if test -d "$withval/include"; then CFLAGS="$CFLAGS -I$withval/include" CPPFLAGS="$CPPFLAGS -I$withval/include" else CFLAGS="$CFLAGS -I$withval" CPPFLAGS="$CPPFLAGS -I$withval" fi if test -d "$withval/lib"; then LIBS="$LIBS -L$withval/lib" else LIBS="$LIBS -L$withval" fi fi ;; esac fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main (void) { u_int i = sizeof(((struct sockaddr *)0)->sa_len) ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : printf '%s\n' "#define HAVE_SOCKADDR_SA_LEN 1" >>confdefs.h fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_fn_c_check_func "$LINENO" "inet_ntop" "ac_cv_func_inet_ntop" if test "x$ac_cv_func_inet_ntop" = xyes then : printf '%s\n' "#define HAVE_INET_NTOP 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "inet_pton" "ac_cv_func_inet_pton" if test "x$ac_cv_func_inet_pton" = xyes then : printf '%s\n' "#define HAVE_INET_PTON 1" >>confdefs.h fi # Check whether --with-ipv6 was given. if test ${with_ipv6+y} then : withval=$with_ipv6; else case e in #( e) with_ipv6=auto ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $CC options to detect undeclared functions" >&5 printf %s "checking for $CC options to detect undeclared functions... " >&6; } if test ${ac_cv_c_undeclared_builtin_options+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_save_CFLAGS=$CFLAGS ac_cv_c_undeclared_builtin_options='cannot detect' for ac_arg in '' -fno-builtin; do CFLAGS="$ac_save_CFLAGS $ac_arg" # This test program should *not* compile successfully. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { (void) strchr; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : else case e in #( e) # This test program should compile successfully. # No library function is consistently available on # freestanding implementations, so test against a dummy # declaration. Include always-available headers on the # off chance that they somehow elicit warnings. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include #include extern void ac_decl (int, char *); int main (void) { (void) ac_decl (0, (char *) 0); (void) ac_decl; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : if test x"$ac_arg" = x then : ac_cv_c_undeclared_builtin_options='none needed' else case e in #( e) ac_cv_c_undeclared_builtin_options=$ac_arg ;; esac fi break fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext done CFLAGS=$ac_save_CFLAGS ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_undeclared_builtin_options" >&5 printf '%s\n' "$ac_cv_c_undeclared_builtin_options" >&6; } case $ac_cv_c_undeclared_builtin_options in #( 'cannot detect') : { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "cannot make $CC report undeclared builtins See 'config.log' for more details" "$LINENO" 5; } ;; #( 'none needed') : ac_c_undeclared_builtin_options='' ;; #( *) : ac_c_undeclared_builtin_options=$ac_cv_c_undeclared_builtin_options ;; esac { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $CC options to ignore future-version functions" >&5 printf %s "checking for $CC options to ignore future-version functions... " >&6; } if test ${ac_cv_c_future_darwin_options+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_compile_saved="$ac_compile" ac_compile="$ac_compile -Werror=unguarded-availability-new" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #if ! (defined __APPLE__ && defined __MACH__) #error "-Werror=unguarded-availability-new not needed here" #endif int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_c_future_darwin_options='-Werror=unguarded-availability-new' else case e in #( e) ac_cv_c_future_darwin_options='none needed' ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_compile="$ac_compile_saved" ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_future_darwin_options" >&5 printf '%s\n' "$ac_cv_c_future_darwin_options" >&6; } case $ac_cv_c_future_darwin_options in #( 'none needed') : ac_c_future_darwin_options='' ;; #( *) : ac_c_future_darwin_options=$ac_cv_c_future_darwin_options ;; esac if test "x$with_ipv6" = xno then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: IPv6 tracing disabled by --without-ipv6" >&5 printf '%s\n' "$as_me: IPv6 tracing disabled by --without-ipv6" >&6;} else case e in #( e) ac_fn_c_check_header_compile "$LINENO" "netinet/ip6.h" "ac_cv_header_netinet_ip6_h" "#include #include #include " if test "x$ac_cv_header_netinet_ip6_h" = xyes then : printf '%s\n' "#define HAVE_NETINET_IP6_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "netinet/icmp6.h" "ac_cv_header_netinet_icmp6_h" "#include #include #include " if test "x$ac_cv_header_netinet_icmp6_h" = xyes then : printf '%s\n' "#define HAVE_NETINET_ICMP6_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "ifaddrs.h" "ac_cv_header_ifaddrs_h" "#include #include #include " if test "x$ac_cv_header_ifaddrs_h" = xyes then : printf '%s\n' "#define HAVE_IFADDRS_H 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "getifaddrs" "ac_cv_func_getifaddrs" if test "x$ac_cv_func_getifaddrs" = xyes then : printf '%s\n' "#define HAVE_GETIFADDRS 1" >>confdefs.h fi ac_fn_c_check_type "$LINENO" "struct sockaddr_storage" "ac_cv_type_struct_sockaddr_storage" "#include " if test "x$ac_cv_type_struct_sockaddr_storage" = xyes then : printf '%s\n' "#define HAVE_STRUCT_SOCKADDR_STORAGE 1" >>confdefs.h fi if test "x$ac_cv_header_netinet_ip6_h" = xyes -a \ "x$ac_cv_header_netinet_icmp6_h" = xyes -a \ "x$ac_cv_type_struct_sockaddr_storage" = xyes then : printf '%s\n' "#define HAVE_IPV6 1" >>confdefs.h ac_fn_check_decl "$LINENO" "IPV6_HDRINCL" "ac_cv_have_decl_IPV6_HDRINCL" "#include " "$ac_c_undeclared_builtin_options$ac_c_future_darwin_options" "CFLAGS" if test "x$ac_cv_have_decl_IPV6_HDRINCL" = xyes then : printf '%s\n' "#define HAVE_IPV6_HDRINCL 1" >>confdefs.h fi else case e in #( e) if test "x$with_ipv6" = xyes then : as_fn_error $? "--with-ipv6 requested but netinet/ip6.h, netinet/icmp6.h, or sockaddr_storage is missing" "$LINENO" 5 else case e in #( e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: IPv6 tracing disabled -- missing netinet/ip6.h, netinet/icmp6.h, or sockaddr_storage" >&5 printf '%s\n' "$as_me: WARNING: IPv6 tracing disabled -- missing netinet/ip6.h, netinet/icmp6.h, or sockaddr_storage" >&2;} ;; esac fi ;; esac fi ;; esac fi have_pcap=no if test "x$pcap_path_override" = "xno" then : # Extract the first word of "pkg-config", so it can be a program name with args. set dummy pkg-config; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_PKGCONFIG+y} then : printf %s "(cached) " >&6 else case e in #( e) case $PKGCONFIG in [\\/]* | ?:[\\/]*) ac_cv_path_PKGCONFIG="$PKGCONFIG" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_PKGCONFIG="$as_dir$ac_word$ac_exec_ext" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_PKGCONFIG" && ac_cv_path_PKGCONFIG="no" ;; esac ;; esac fi PKGCONFIG=$ac_cv_path_PKGCONFIG if test -n "$PKGCONFIG"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $PKGCONFIG" >&5 printf '%s\n' "$PKGCONFIG" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi if test "x$PKGCONFIG" != "xno" then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for libpcap via pkg-config" >&5 printf %s "checking for libpcap via pkg-config... " >&6; } if $PKGCONFIG --exists libpcap 2>/dev/null then : pcap_cflags=`$PKGCONFIG --cflags libpcap` pcap_libs=`$PKGCONFIG --libs libpcap` save_CFLAGS="$CFLAGS" save_LIBS="$LIBS" CFLAGS="$CFLAGS $pcap_cflags" LIBS="$LIBS $pcap_libs" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { pcap_if_t *d; pcap_findalldevs(&d, 0); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : have_pcap=yes { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes ($pcap_libs)" >&5 printf '%s\n' "yes ($pcap_libs)" >&6; } else case e in #( e) CFLAGS="$save_CFLAGS" LIBS="$save_LIBS" { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no (link test failed; wrong architecture?)" >&5 printf '%s\n' "no (link test failed; wrong architecture?)" >&6; } ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext else case e in #( e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } ;; esac fi fi fi if test "x$have_pcap" = "xno" && test "x$pcap_path_override" = "xno" then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for libpcap in candidate prefixes" >&5 printf %s "checking for libpcap in candidate prefixes... " >&6; } for _pfx in $lft_candidate_prefixes; do if test -f "$_pfx/include/pcap.h"; then CPPFLAGS="$CPPFLAGS -I$_pfx/include" CFLAGS="$CFLAGS -I$_pfx/include" LDFLAGS="$LDFLAGS -L$_pfx/lib" { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $_pfx" >&5 printf '%s\n' "$_pfx" >&6; } break fi done if test "x$_pfx" = "x" then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: not found" >&5 printf '%s\n' "not found" >&6; } fi fi if test "x$have_pcap" = "xno" then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for pcap_findalldevs in -lpcap" >&5 printf %s "checking for pcap_findalldevs in -lpcap... " >&6; } if test ${ac_cv_lib_pcap_pcap_findalldevs+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lpcap $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char pcap_findalldevs (void); int main (void) { return pcap_findalldevs (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_pcap_pcap_findalldevs=yes else case e in #( e) ac_cv_lib_pcap_pcap_findalldevs=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pcap_pcap_findalldevs" >&5 printf '%s\n' "$ac_cv_lib_pcap_pcap_findalldevs" >&6; } if test "x$ac_cv_lib_pcap_pcap_findalldevs" = xyes then : have_pcap=yes; LIBS="-lpcap $LIBS" else case e in #( e) as_fn_error $? " libpcap was not found. It is required to build LFT. Please install the libpcap development library and re-run ./configure. On Debian/Ubuntu: sudo apt-get install libpcap-dev On RHEL/Fedora: sudo dnf install libpcap-devel On FreeBSD ports: net/libpcap On macOS Homebrew: brew install libpcap On macOS MacPorts: sudo port install libpcap Or specify its location with: ./configure --with-pcap=PATH" "$LINENO" 5 ;; esac fi ac_fn_c_check_header_compile "$LINENO" "pcap.h" "ac_cv_header_pcap_h" "$ac_includes_default" if test "x$ac_cv_header_pcap_h" = xyes then : else case e in #( e) as_fn_error $? " pcap.h was not found. The libpcap library was detected but its headers are missing. Please install the libpcap development package. On Debian/Ubuntu: sudo apt-get install libpcap-dev On RHEL/Fedora: sudo dnf install libpcap-devel On FreeBSD ports: net/libpcap On macOS Homebrew: brew install libpcap On macOS MacPorts: sudo port install libpcap Or specify its location with: ./configure --with-pcap=PATH When building libpcap from source, run both 'make install' and 'make install-incl'." "$LINENO" 5 ;; esac fi fi # Check whether --enable-async-dns was given. if test ${enable_async_dns+y} then : enableval=$enable_async_dns; enable_async_dns="$enableval" else case e in #( e) enable_async_dns="yes" ;; esac fi cares_path_override=no # Check whether --with-cares was given. if test ${with_cares+y} then : withval=$with_cares; case "$withval" in no) enable_async_dns=no ;; yes) as_fn_error $? "please specify a PATH in --with-cares option!" "$LINENO" 5 ;; *) if test '!' -d "$withval"; then as_fn_error $? "$withval does not exist!" "$LINENO" 5 fi cares_path_override=yes if test -d "$withval/include"; then CPPFLAGS="$CPPFLAGS -I$withval/include" CFLAGS="$CFLAGS -I$withval/include" else CPPFLAGS="$CPPFLAGS -I$withval" CFLAGS="$CFLAGS -I$withval" fi if test -d "$withval/lib"; then LDFLAGS="$LDFLAGS -L$withval/lib" else LDFLAGS="$LDFLAGS -L$withval" fi ;; esac fi if test "x$enable_async_dns" = "xyes" then : have_cares=no if test "x$cares_path_override" = "xno" then : if test "x$PKGCONFIG" != "xno" then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for libcares via pkg-config" >&5 printf %s "checking for libcares via pkg-config... " >&6; } if $PKGCONFIG --exists libcares 2>/dev/null then : cares_cflags=`$PKGCONFIG --cflags libcares` cares_libs=`$PKGCONFIG --libs libcares` save_CFLAGS="$CFLAGS" save_LIBS="$LIBS" CFLAGS="$CFLAGS $cares_cflags" LIBS="$LIBS $cares_libs" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { ares_channel c; ares_init(&c); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : have_cares=yes { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes ($cares_libs)" >&5 printf '%s\n' "yes ($cares_libs)" >&6; } else case e in #( e) CFLAGS="$save_CFLAGS" LIBS="$save_LIBS" { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no (link test failed; wrong architecture?)" >&5 printf '%s\n' "no (link test failed; wrong architecture?)" >&6; } ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext else case e in #( e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } ;; esac fi fi fi if test "x$have_cares" = "xno" && test "x$cares_path_override" = "xno" then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for c-ares in candidate prefixes" >&5 printf %s "checking for c-ares in candidate prefixes... " >&6; } for _pfx in $lft_candidate_prefixes; do if test -f "$_pfx/include/ares.h"; then CPPFLAGS="$CPPFLAGS -I$_pfx/include" CFLAGS="$CFLAGS -I$_pfx/include" LDFLAGS="$LDFLAGS -L$_pfx/lib" { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $_pfx" >&5 printf '%s\n' "$_pfx" >&6; } break fi done if test "x$_pfx" = "x" then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: not found" >&5 printf '%s\n' "not found" >&6; } fi fi if test "x$have_cares" = "xno" then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for ares_init in -lcares" >&5 printf %s "checking for ares_init in -lcares... " >&6; } if test ${ac_cv_lib_cares_ares_init+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lcares $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char ares_init (void); int main (void) { return ares_init (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_cares_ares_init=yes else case e in #( e) ac_cv_lib_cares_ares_init=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_cares_ares_init" >&5 printf '%s\n' "$ac_cv_lib_cares_ares_init" >&6; } if test "x$ac_cv_lib_cares_ares_init" = xyes then : have_cares=maybe fi if test "x$have_cares" = "xmaybe" then : ac_fn_c_check_header_compile "$LINENO" "ares.h" "ac_cv_header_ares_h" "$ac_includes_default" if test "x$ac_cv_header_ares_h" = xyes then : have_cares=yes LIBS="-lcares $LIBS" else case e in #( e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: c-ares library found but ares.h not found; disabling async DNS" >&5 printf '%s\n' "$as_me: WARNING: c-ares library found but ares.h not found; disabling async DNS" >&2;} ;; esac fi fi fi if test "x$have_cares" = "xyes" then : if test "x$cares_path_override" = "xyes" || test "x$PKGCONFIG" = "xno" then : case $LIBS in #( *-lcares*) : ;; #( *) : LIBS="-lcares $LIBS" ;; esac fi fi if test "x$have_cares" = "xyes" then : printf '%s\n' "#define HAVE_CARES 1" >>confdefs.h { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for c-ares 1.34+ event API (ares_process_fds)" >&5 printf %s "checking for c-ares 1.34+ event API (ares_process_fds)... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { ares_fd_events_t _e; _e.events = ARES_FD_EVENT_READ; (void)_e; ares_process_fds(0, 0, 0, 0); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf '%s\n' "yes" >&6; } printf '%s\n' "#define HAVE_ARES_PROCESS_FDS 1" >>confdefs.h else case e in #( e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no (older c-ares; using the classic ares_process poll API)" >&5 printf '%s\n' "no (older c-ares; using the classic ares_process poll API)" >&6; } ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext else case e in #( e) lft_warn_no_cares=yes ;; esac fi fi # Check whether --enable-ncurses was given. if test ${enable_ncurses+y} then : enableval=$enable_ncurses; enable_ncurses="$enableval" else case e in #( e) enable_ncurses="yes" ;; esac fi ncurses_path_override=no # Check whether --with-ncurses was given. if test ${with_ncurses+y} then : withval=$with_ncurses; case "$withval" in no) enable_ncurses=no ;; yes) as_fn_error $? "please specify a PATH in --with-ncurses option!" "$LINENO" 5 ;; *) if test '!' -d "$withval"; then as_fn_error $? "$withval does not exist!" "$LINENO" 5 fi ncurses_path_override=yes if test -d "$withval/include"; then CPPFLAGS="$CPPFLAGS -I$withval/include" CFLAGS="$CFLAGS -I$withval/include" else CPPFLAGS="$CPPFLAGS -I$withval" CFLAGS="$CFLAGS -I$withval" fi if test -d "$withval/lib"; then LDFLAGS="$LDFLAGS -L$withval/lib" else LDFLAGS="$LDFLAGS -L$withval" fi ;; esac fi if test "x$enable_ncurses" = "xyes" then : have_ncurses=no if test "x$ncurses_path_override" = "xno" then : if test "x$PKGCONFIG" != "xno" then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for ncursesw via pkg-config" >&5 printf %s "checking for ncursesw via pkg-config... " >&6; } if $PKGCONFIG --exists ncursesw 2>/dev/null then : ncurses_cflags=`$PKGCONFIG --cflags ncursesw` ncurses_libs=`$PKGCONFIG --libs ncursesw` save_CFLAGS="$CFLAGS" save_LIBS="$LIBS" CFLAGS="$CFLAGS $ncurses_cflags" LIBS="$LIBS $ncurses_libs" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { initscr(); endwin(); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : have_ncurses=yes printf '%s\n' "#define HAVE_NCURSES 1" >>confdefs.h { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes ($ncurses_libs)" >&5 printf '%s\n' "yes ($ncurses_libs)" >&6; } else case e in #( e) CFLAGS="$save_CFLAGS" LIBS="$save_LIBS" { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no (link test failed; wrong architecture?)" >&5 printf '%s\n' "no (link test failed; wrong architecture?)" >&6; } ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext else case e in #( e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } ;; esac fi if test "x$have_ncurses" = "xno" then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for ncurses via pkg-config" >&5 printf %s "checking for ncurses via pkg-config... " >&6; } if $PKGCONFIG --exists ncurses 2>/dev/null then : ncurses_cflags=`$PKGCONFIG --cflags ncurses` ncurses_libs=`$PKGCONFIG --libs ncurses` save_CFLAGS="$CFLAGS" save_LIBS="$LIBS" CFLAGS="$CFLAGS $ncurses_cflags" LIBS="$LIBS $ncurses_libs" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { initscr(); endwin(); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : have_ncurses=yes { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes ($ncurses_libs)" >&5 printf '%s\n' "yes ($ncurses_libs)" >&6; } else case e in #( e) CFLAGS="$save_CFLAGS" LIBS="$save_LIBS" { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no (link test failed; wrong architecture?)" >&5 printf '%s\n' "no (link test failed; wrong architecture?)" >&6; } ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext else case e in #( e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } ;; esac fi fi fi fi if test "x$have_ncurses" = "xno" && test "x$ncurses_path_override" = "xno" then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for ncurses in candidate prefixes" >&5 printf %s "checking for ncurses in candidate prefixes... " >&6; } for _pfx in $lft_candidate_prefixes; do if test -f "$_pfx/include/ncurses.h"; then CPPFLAGS="$CPPFLAGS -I$_pfx/include" CFLAGS="$CFLAGS -I$_pfx/include" LDFLAGS="$LDFLAGS -L$_pfx/lib" { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $_pfx" >&5 printf '%s\n' "$_pfx" >&6; } break fi done if test "x$_pfx" = "x" then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: not found" >&5 printf '%s\n' "not found" >&6; } fi fi if test "x$have_ncurses" = "xno" then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for initscr in -lncursesw" >&5 printf %s "checking for initscr in -lncursesw... " >&6; } if test ${ac_cv_lib_ncursesw_initscr+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lncursesw $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char initscr (void); int main (void) { return initscr (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_ncursesw_initscr=yes else case e in #( e) ac_cv_lib_ncursesw_initscr=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ncursesw_initscr" >&5 printf '%s\n' "$ac_cv_lib_ncursesw_initscr" >&6; } if test "x$ac_cv_lib_ncursesw_initscr" = xyes then : have_ncurses=maybe_w fi if test "x$have_ncurses" = "xmaybe_w" then : ac_fn_c_check_header_compile "$LINENO" "ncurses.h" "ac_cv_header_ncurses_h" "$ac_includes_default" if test "x$ac_cv_header_ncurses_h" = xyes then : have_ncurses=yes LIBS="-lncursesw $LIBS" printf '%s\n' "#define HAVE_NCURSES 1" >>confdefs.h else case e in #( e) have_ncurses=no { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: ncursesw library found but ncurses.h not found; disabling ncurses mode" >&5 printf '%s\n' "$as_me: WARNING: ncursesw library found but ncurses.h not found; disabling ncurses mode" >&2;} ;; esac fi fi fi if test "x$have_ncurses" = "xno" then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for initscr in -lncurses" >&5 printf %s "checking for initscr in -lncurses... " >&6; } if test ${ac_cv_lib_ncurses_initscr+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lncurses $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char initscr (void); int main (void) { return initscr (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_ncurses_initscr=yes else case e in #( e) ac_cv_lib_ncurses_initscr=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ncurses_initscr" >&5 printf '%s\n' "$ac_cv_lib_ncurses_initscr" >&6; } if test "x$ac_cv_lib_ncurses_initscr" = xyes then : have_ncurses=maybe fi if test "x$have_ncurses" = "xmaybe" then : ac_fn_c_check_header_compile "$LINENO" "ncurses.h" "ac_cv_header_ncurses_h" "$ac_includes_default" if test "x$ac_cv_header_ncurses_h" = xyes then : have_ncurses=yes LIBS="-lncurses $LIBS" printf '%s\n' "#define HAVE_NCURSES 1" >>confdefs.h else case e in #( e) have_ncurses=no { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: ncurses library found but ncurses.h not found; disabling ncurses mode" >&5 printf '%s\n' "$as_me: WARNING: ncurses library found but ncurses.h not found; disabling ncurses mode" >&2;} ;; esac fi fi fi if test "x$have_ncurses" = "xyes" then : if test "x$ncurses_path_override" = "xyes" || test "x$PKGCONFIG" = "xno" then : case $LIBS in #( *-lncurses*) : ;; #( *) : LIBS="-lncurses $LIBS" printf '%s\n' "#define HAVE_NCURSES 1" >>confdefs.h ;; esac fi fi if test "x$have_ncurses" != "xyes" then : lft_warn_no_ncurses=yes fi fi if test "x$have_ncurses" = "xyes" then : ac_fn_c_check_func "$LINENO" "mvin_wch" "ac_cv_func_mvin_wch" if test "x$ac_cv_func_mvin_wch" = xyes then : printf '%s\n' "#define HAVE_MVIN_WCH 1" >>confdefs.h fi fi WHOB_LIBS=`echo " $LIBS " | sed -e 's/ -lncursesw / /g' -e 's/ -lncurses / /g' -e 's/ -ltinfow / /g' -e 's/ -ltinfo / /g'` if test "x$have_ncurses" = "xyes" then : WATCH_OBJS="lft_watch.o" else case e in #( e) WATCH_OBJS="" ;; esac fi esac { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if we should build universal binaries" >&5 printf %s "checking if we should build universal binaries... " >&6; } if test "$UNIVERSAL" = "yes"; then case $host_os in *darwin*) CFLAGS="$CFLAGS -arch x86_64 -arch arm64" LDFLAGS="$LDFLAGS -arch x86_64 -arch arm64" printf '%s\n' "#define UNIVERSAL 1" >>confdefs.h { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes (x86_64 + arm64)" >&5 printf '%s\n' "yes (x86_64 + arm64)" >&6; } ;; *) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no (--enable-universal only applies to macOS)" >&5 printf '%s\n' "no (--enable-universal only applies to macOS)" >&6; } ;; esac else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if we should call gettimeofday for each packet" >&5 printf %s "checking if we should call gettimeofday for each packet... " >&6; } if test "$GTOD" = "yes"; then printf '%s\n' "#define USE_GTOD 1" >>confdefs.h { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf '%s\n' "yes" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi HARDEN_CFLAGS="" for lft_hflag in -fstack-protector-strong -fstack-clash-protection -fPIE; do { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts $lft_hflag" >&5 printf %s "checking whether $CC accepts $lft_hflag... " >&6; } lft_hsave="$CFLAGS" CFLAGS="$CFLAGS $lft_hflag -Werror" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf '%s\n' "yes" >&6; }; HARDEN_CFLAGS="$HARDEN_CFLAGS $lft_hflag" else case e in #( e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext CFLAGS="$lft_hsave" done { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -Wformat -Wformat-security" >&5 printf %s "checking whether $CC accepts -Wformat -Wformat-security... " >&6; } lft_hsave="$CFLAGS" CFLAGS="$CFLAGS -Wformat -Wformat-security -Werror" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf '%s\n' "yes" >&6; }; HARDEN_CFLAGS="$HARDEN_CFLAGS -Wformat -Wformat-security" else case e in #( e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext CFLAGS="$lft_hsave" { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -D_FORTIFY_SOURCE=2" >&5 printf %s "checking whether $CC accepts -D_FORTIFY_SOURCE=2... " >&6; } lft_hsave="$CFLAGS" CFLAGS="$CFLAGS -O2 -D_FORTIFY_SOURCE=2 -Werror" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { char b[8]; strncpy(b, "x", sizeof b); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf '%s\n' "yes" >&6; }; HARDEN_CFLAGS="$HARDEN_CFLAGS -D_FORTIFY_SOURCE=2" else case e in #( e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext CFLAGS="$lft_hsave" HARDEN_LDFLAGS="" lft_hsave_c="$CFLAGS" CFLAGS="$CFLAGS $HARDEN_CFLAGS -Werror" for lft_lflag in -pie -Wl,-z,relro -Wl,-z,now; do { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether the linker accepts $lft_lflag cleanly" >&5 printf %s "checking whether the linker accepts $lft_lflag cleanly... " >&6; } lft_hsave="$LDFLAGS" LDFLAGS="$LDFLAGS $lft_lflag" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf '%s\n' "yes" >&6; }; HARDEN_LDFLAGS="$HARDEN_LDFLAGS $lft_lflag" else case e in #( e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LDFLAGS="$lft_hsave" done CFLAGS="$lft_hsave_c" CFLAGS="$CFLAGS $HARDEN_CFLAGS" LDFLAGS="$LDFLAGS $HARDEN_LDFLAGS" ac_config_files="$ac_config_files Makefile" if test "x$lft_warn_old_cares" = "xyes" then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: c-ares was found but is older than 1.34 -- the event-model API LFT uses (ares_process_fds). Async DNS is DISABLED for this build; LFT falls back to synchronous name resolution (fully functional, just slower on lossy paths). Install c-ares 1.34+ and re-run ./configure, or point at a newer build with --with-cares=PATH, to enable async DNS." >&5 printf '%s\n' "$as_me: WARNING: c-ares was found but is older than 1.34 -- the event-model API LFT uses (ares_process_fds). Async DNS is DISABLED for this build; LFT falls back to synchronous name resolution (fully functional, just slower on lossy paths). Install c-ares 1.34+ and re-run ./configure, or point at a newer build with --with-cares=PATH, to enable async DNS." >&2;} fi if test "x$lft_warn_no_cares" = "xyes" then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: LFT is much faster with asynchronous DNS provided by the c-ares library, which was not detected. It is not required, but strongly recommended. Please consider installing it and re-running ./configure. On Debian/Ubuntu: sudo apt-get install libc-ares-dev On RHEL/Fedora: sudo dnf install c-ares-devel On macOS Homebrew: brew install c-ares On macOS MacPorts: sudo port install c-ares Or specify its location with: ./configure --with-cares=PATH" >&5 printf '%s\n' "$as_me: WARNING: LFT is much faster with asynchronous DNS provided by the c-ares library, which was not detected. It is not required, but strongly recommended. Please consider installing it and re-running ./configure. On Debian/Ubuntu: sudo apt-get install libc-ares-dev On RHEL/Fedora: sudo dnf install c-ares-devel On macOS Homebrew: brew install c-ares On macOS MacPorts: sudo port install c-ares Or specify its location with: ./configure --with-cares=PATH" >&2;} fi if test "x$lft_warn_no_ncurses" = "xyes" then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: LFT looks great using ncurses with a text-mode UI, which was not detected. The interactive continuous-monitoring mode (--watch) requires it. Please consider installing the ncurses development library and re-running ./configure. On Debian/Ubuntu: sudo apt-get install libncurses-dev On RHEL/Fedora: sudo dnf install ncurses-devel On macOS Homebrew: brew install ncurses On macOS MacPorts: sudo port install ncurses Or specify its location with: ./configure --with-ncurses=PATH" >&5 printf '%s\n' "$as_me: WARNING: LFT looks great using ncurses with a text-mode UI, which was not detected. The interactive continuous-monitoring mode (--watch) requires it. Please consider installing the ncurses development library and re-running ./configure. On Debian/Ubuntu: sudo apt-get install libncurses-dev On RHEL/Fedora: sudo dnf install ncurses-devel On macOS Homebrew: brew install ncurses On macOS MacPorts: sudo port install ncurses Or specify its location with: ./configure --with-ncurses=PATH" >&2;} fi cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure # tests run on this system so they can be shared between configure # scripts and configure runs, see configure's option --config-cache. # It is not useful on other systems. If it contains results you don't # want to keep, you may remove or edit it. # # config.status only pays attention to the cache file if you give it # the --recheck option to rerun configure. # # 'ac_cv_env_foo' variables (set or unset) will be overridden when # loading this file, other *unset* 'ac_cv_foo' will be assigned the # following values. _ACEOF ac_cache_dump | sed ' /^ac_cv_env_/b end t clear :clear s/^\([^=]*\)=\(.*[{}].*\)$/test ${\1+y} || &/ t end s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ :end' >>confcache if diff "$cache_file" confcache >/dev/null 2>&1; then :; else if test -w "$cache_file"; then if test "x$cache_file" != "x/dev/null"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 printf '%s\n' "$as_me: updating cache $cache_file" >&6;} if test ! -f "$cache_file" || test -h "$cache_file"; then cat confcache >"$cache_file" else case $cache_file in #( */* | ?:*) mv -f confcache "$cache_file"$$ && mv -f "$cache_file"$$ "$cache_file" ;; #( *) mv -f confcache "$cache_file" ;; esac fi fi else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 printf '%s\n' "$as_me: not updating unwritable cache $cache_file" >&6;} fi fi rm -f confcache test "x$prefix" = xNONE && prefix=$ac_default_prefix # Let make expand exec_prefix. test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' DEFS=-DHAVE_CONFIG_H ac_libobjs= ac_ltlibobjs= U= for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue # 1. Remove the extension, and $U if already installed. ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' ac_i=`printf '%s\n' "$ac_i" | sed "$ac_script"` # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR # will be set to the directory where LIBOBJS objects are built. as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' done LIBOBJS=$ac_libobjs LTLIBOBJS=$ac_ltlibobjs : "${CONFIG_STATUS=./config.status}" case $CONFIG_STATUS in #( -*) : CONFIG_STATUS=./$CONFIG_STATUS ;; #( */*) : ;; #( *) : CONFIG_STATUS=./$CONFIG_STATUS ;; esac ac_write_fail=0 ac_clean_CONFIG_STATUS='"$CONFIG_STATUS"' { printf '%s\n' "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 printf '%s\n' "$as_me: creating $CONFIG_STATUS" >&6;} as_write_fail=0 cat >"$CONFIG_STATUS" <<_ASEOF || as_write_fail=1 #! $SHELL # Generated by $as_me. # Run this file to recreate the current configuration. # Compiler output produced by configure, useful for debugging # configure, is in config.log if it exists. debug=false ac_cs_recheck=false ac_cs_silent=false SHELL=\${CONFIG_SHELL-$SHELL} export SHELL _ASEOF cat >>"$CONFIG_STATUS" <<\_ASEOF || as_write_fail=1 ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # contradicts POSIX and common usage. Disable this. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case e in #( e) case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac ;; esac fi # Reset variables that may have inherited troublesome values from # the environment. # IFS needs to be set, to space, tab, and newline, in precisely that order. # (If _AS_PATH_WALK were called with IFS unset, it would have the # side effect of setting IFS to empty, thus disabling word splitting.) # Quoting is to prevent editors from complaining about space-tab. as_nl=' ' export as_nl IFS=" "" $as_nl" PS1='$ ' PS2='> ' PS4='+ ' # Ensure predictable behavior from utilities with locale-dependent output. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # We cannot yet rely on "unset" to work, but we need these variables # to be unset--not just set to an empty or harmless value--now, to # avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct # also avoids known problems related to "unset" and subshell syntax # in other old shells (e.g. bash 2.01 and pdksh 5.2.14). for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH do eval test \${$as_var+y} \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done # Ensure that fds 0, 1, and 2 are open. if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi if (exec 3>&2) ; then :; else exec 2>/dev/null; fi # The user is always right. if ${PATH_SEPARATOR+false} :; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac test -r "$as_dir$0" && as_myself=$as_dir$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as 'sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then printf '%s\n' "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi printf '%s\n' "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null then : eval 'as_fn_append () { eval $1+=\$2 }' else case e in #( e) as_fn_append () { eval $1=\$$1\$2 } ;; esac fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null then : eval 'as_fn_arith () { as_val=$(( $* )) }' else case e in #( e) as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } ;; esac fi # as_fn_arith if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || printf '%s\n' X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable. # In both cases, we have to default to 'cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -pR' fi else as_ln_s='cp -pR' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`printf '%s\n' "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || printf '%s\n' X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi # as_fn_executable_p FILE # ----------------------- # Test if FILE is an executable regular file. as_fn_executable_p () { test -f "$1" && test -x "$1" } # as_fn_executable_p as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated # Sed expression to map a string onto a valid variable name. as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g" as_tr_sh="eval sed '$as_sed_sh'" # deprecated exec 6>&1 ## ------------------------------------- ## ## Main body of "$CONFIG_STATUS" script. ## ## ------------------------------------- ## _ASEOF test $as_write_fail = 0 && chmod +x "$CONFIG_STATUS" || ac_write_fail=1 cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 # Save the log message, to keep $0 and so on meaningful, and to # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" This file was extended by $as_me, which was generated by GNU Autoconf 2.73. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS CONFIG_LINKS = $CONFIG_LINKS CONFIG_COMMANDS = $CONFIG_COMMANDS $ $0 $@ on `(hostname || uname -n) 2>/dev/null | sed 1q` " _ACEOF case $ac_config_files in *" "*) set x $ac_config_files; shift; ac_config_files=$*;; esac case $ac_config_headers in *" "*) set x $ac_config_headers; shift; ac_config_headers=$*;; esac cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 # Files that config.status was made for. config_files="$ac_config_files" config_headers="$ac_config_headers" _ACEOF cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 ac_cs_usage="\ '$as_me' instantiates files and other configuration actions from templates according to the current configuration. Unless the files and actions are specified as TAGs, all are instantiated by default. Usage: $0 [OPTION]... [TAG]... -h, --help print this help, then exit -V, --version print version number and configuration settings, then exit --config print configuration, then exit -q, --quiet, --silent do not print progress messages -d, --debug don't remove temporary files --recheck update $as_me by reconfiguring in the same conditions --file=FILE[:TEMPLATE] instantiate the configuration file FILE --header=FILE[:TEMPLATE] instantiate the configuration header FILE Configuration files: $config_files Configuration headers: $config_headers Report bugs to ." _ACEOF ac_cs_config=`printf '%s\n' "$ac_configure_args" | sed "$ac_safe_unquote"` ac_cs_config_escaped=`printf '%s\n' "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\''/g"` cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ config.status configured by $0, generated by GNU Autoconf 2.73, with options \\"\$ac_cs_config\\" Copyright (C) 2026 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." ac_pwd='$ac_pwd' srcdir='$srcdir' INSTALL='$INSTALL' test -n "\$AWK" || { awk '' >"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 # The default lists apply if the user does not specify any file. ac_need_defaults=: while test $# != 0 do case $1 in --*=?*) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` ac_shift=: ;; --*=) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg= ac_shift=: ;; *) ac_option=$1 ac_optarg=$2 ac_shift=shift ;; esac case $ac_option in # Handling of the options. -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) ac_cs_recheck=: ;; --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) printf '%s\n' "$ac_cs_version"; exit ;; --config | --confi | --conf | --con | --co | --c ) printf '%s\n' "$ac_cs_config"; exit ;; --debug | --debu | --deb | --de | --d | -d ) debug=: ;; --file | --fil | --fi | --f ) $ac_shift case $ac_optarg in *\'*) ac_optarg=`printf '%s\n' "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; '') as_fn_error $? "missing file argument" ;; esac as_fn_append CONFIG_FILES " '$ac_optarg'" ac_need_defaults=false;; --header | --heade | --head | --hea ) $ac_shift case $ac_optarg in *\'*) ac_optarg=`printf '%s\n' "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; esac as_fn_append CONFIG_HEADERS " '$ac_optarg'" ac_need_defaults=false;; --he | --h) # Conflict between --help and --header as_fn_error $? "ambiguous option: '$1' Try '$0 --help' for more information.";; --help | --hel | -h ) printf '%s\n' "$ac_cs_usage"; exit ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil | --si | --s) ac_cs_silent=: ;; # This is an error. -*) as_fn_error $? "unrecognized option: '$1' Try '$0 --help' for more information." ;; *) as_fn_append ac_config_targets " $1" ac_need_defaults=false ;; esac shift done ac_configure_extra_args= if $ac_cs_silent; then exec 6>/dev/null ac_configure_extra_args="$ac_configure_extra_args --silent" fi _ACEOF cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 if \$ac_cs_recheck; then set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion shift \printf '%s\n' "running CONFIG_SHELL=$SHELL \$*" >&6 CONFIG_SHELL='$SHELL' export CONFIG_SHELL exec "\$@" fi _ACEOF cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 exec 5>>config.log { echo sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX ## Running $as_me. ## _ASBOX printf '%s\n' "$ac_log" } >&5 _ACEOF cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 _ACEOF cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 # Handling of arguments. for ac_config_target in $ac_config_targets do case $ac_config_target in "config/acconfig.h") CONFIG_HEADERS="$CONFIG_HEADERS config/acconfig.h" ;; "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; *) as_fn_error $? "invalid argument: '$ac_config_target'" "$LINENO" 5;; esac done # If the user did not use the arguments to specify the items to instantiate, # then the envvar interface is used. Set only those that are not. # We use the long form for the default assignment because of an extremely # bizarre bug on SunOS 4.1.3. if $ac_need_defaults; then test ${CONFIG_FILES+y} || CONFIG_FILES=$config_files test ${CONFIG_HEADERS+y} || CONFIG_HEADERS=$config_headers fi # Have a temporary directory for convenience. Make it in the build tree # simply because there is no reason against having it here, and in addition, # creating and moving files from /tmp can sometimes cause problems. # Hook for its removal unless debugging. # Note that there is a small window in which the directory will not be cleaned: # after its creation but before its name has been assigned to '$tmp'. $debug || { tmp= ac_tmp= trap 'exit_status=$? : "${ac_tmp:=$tmp}" { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status ' 0 trap 'as_fn_exit 1' 1 2 13 15 } # Create a (secure) tmp directory for tmp files. { tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && test -d "$tmp" } || { tmp=./conf$$-$RANDOM (umask 077 && mkdir "$tmp") } || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 ac_tmp=$tmp # Set up the scripts for CONFIG_FILES section. # No need to generate them if there are no CONFIG_FILES. # This happens for instance with './config.status config.h'. if test -n "$CONFIG_FILES"; then ac_cr=`echo X | tr X '\015'` # On cygwin, bash can eat \r inside `` if the user requested igncr. # But we know of no other shell where ac_cr would be empty at this # point, so we can use a bashism as a fallback. if test "x$ac_cr" = x; then eval ac_cr=\$\'\\r\' fi ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then ac_cs_awk_cr='\\r' else ac_cs_awk_cr=$ac_cr fi echo 'BEGIN {' >"$ac_tmp/subs1.awk" && _ACEOF { echo "cat >conf$$subs.awk <<_ACEOF" && echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && echo "_ACEOF" } >conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_num=`echo "$ac_subst_vars" | sed -n '$='` ac_delim='%!_!# ' for ac_last_try in false false false false false :; do . ./conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | sed -n '$='` if test $ac_delim_n = $ac_delim_num; then break elif $ac_last_try; then as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done rm -f conf$$subs.sh cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && _ACEOF sed -n ' h s/^/S["/; s/!.*/"]=/ p g s/^[^!]*!// :repl t repl s/'"$ac_delim"'$// t delim :nl h s/\(.\{148\}\)..*/\1/ t more1 s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ p n b repl :more1 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t nl :delim h s/\(.\{148\}\)..*/\1/ t more2 s/["\\]/\\&/g; s/^/"/; s/$/"/ p b :more2 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t delim ' >"$CONFIG_STATUS" || ac_write_fail=1 rm -f conf$$subs.awk cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 _ACAWK cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && for (key in S) S_is_set[key] = 1 FS = "" } { line = $ 0 nfields = split(line, field, "@") substed = 0 len = length(field[1]) for (i = 2; i < nfields; i++) { key = field[i] keylen = length(key) if (S_is_set[key]) { value = S[key] line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) len += length(value) + length(field[++i]) substed = 1 } else len += 1 + keylen } print line } _ACAWK _ACEOF cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" else cat fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 _ACEOF # VPATH may cause trouble with some makes, so we remove sole $(srcdir), # ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and # trailing colons and then remove the whole line if VPATH becomes empty # (actually we leave an empty line to preserve line numbers). if test "x$srcdir" = x.; then ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ h s/// s/^/:/ s/[ ]*$/:/ s/:\$(srcdir):/:/g s/:\${srcdir}:/:/g s/:@srcdir@:/:/g s/^:*// s/:*$// x s/\(=[ ]*\).*/\1/ G s/\n// s/^[^=]*=[ ]*$// }' fi cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 fi # test -n "$CONFIG_FILES" # Set up the scripts for CONFIG_HEADERS section. # No need to generate them if there are no CONFIG_HEADERS. # This happens for instance with './config.status Makefile'. if test -n "$CONFIG_HEADERS"; then cat >"$ac_tmp/defines.awk" <<\_ACAWK || BEGIN { _ACEOF # Transform confdefs.h into an awk script 'defines.awk', embedded as # here-document in config.status, that substitutes the proper values into # config.h.in to produce config.h. # Create a delimiter string that does not exist in confdefs.h, to ease # handling of long lines. ac_delim='%!_!# ' for ac_last_try in false false :; do ac_tt=`sed -n "/$ac_delim/p" confdefs.h` if test -z "$ac_tt"; then break elif $ac_last_try; then as_fn_error $? "could not make $CONFIG_HEADERS" "$LINENO" 5 else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done # For the awk script, D is an array of macro values keyed by name, # likewise P contains macro parameters if any. Preserve backslash # newline sequences. ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]* sed -n ' s/.\{148\}/&'"$ac_delim"'/g t rset :rset s/^[ ]*#[ ]*define[ ][ ]*/ / t def d :def s/\\$// t bsnl s/["\\]/\\&/g s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ D["\1"]=" \3"/p s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2"/p d :bsnl s/["\\]/\\&/g s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ D["\1"]=" \3\\\\\\n"\\/p t cont s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2\\\\\\n"\\/p t cont d :cont n s/.\{148\}/&'"$ac_delim"'/g t clear :clear s/\\$// t bsnlc s/["\\]/\\&/g; s/^/"/; s/$/"/p d :bsnlc s/["\\]/\\&/g; s/^/"/; s/$/\\\\\\n"\\/p b cont ' >"$CONFIG_STATUS" || ac_write_fail=1 cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 for (key in D) D_is_set[key] = 1 FS = "" } /^[\t ]*#[\t ]*(define|undef)[\t ]+$ac_word_re([\t (]|\$)/ { line = \$ 0 split(line, arg, " ") if (arg[1] == "#") { defundef = arg[2] mac1 = arg[3] } else { defundef = substr(arg[1], 2) mac1 = arg[2] } split(mac1, mac2, "(") #) macro = mac2[1] prefix = substr(line, 1, index(line, defundef) - 1) if (D_is_set[macro]) { suffix = P[macro] D[macro] while (suffix ~ /[\t ]$/) { suffix = substr(suffix, 1, length(suffix) - 1) } # Preserve the white space surrounding the "#". print prefix "define", macro suffix next } else { # Replace #undef with comments. This is necessary, for example, # in the case of _POSIX_SOURCE, which is predefined and required # on some systems where configure will not decide to define it. if (defundef == "undef") { print "/*", prefix defundef, macro, "*/" next } } } { print } _ACAWK _ACEOF cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 as_fn_error $? "could not setup config headers machinery" "$LINENO" 5 fi # test -n "$CONFIG_HEADERS" eval set X " :F $CONFIG_FILES :H $CONFIG_HEADERS " shift for ac_tag do case $ac_tag in :[FHLC]) ac_mode=$ac_tag; continue;; esac case $ac_mode$ac_tag in :[FHL]*:*);; :L* | :C*:*) as_fn_error $? "invalid tag '$ac_tag'" "$LINENO" 5;; :[FH]-) ac_tag=-:-;; :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; esac ac_save_IFS=$IFS IFS=: set x $ac_tag IFS=$ac_save_IFS shift ac_file=$1 shift case $ac_mode in :L) ac_source=$1;; :[FH]) ac_file_inputs= for ac_f do case $ac_f in -) ac_f="$ac_tmp/stdin";; *) # Look for the file first in the build tree, then in the source tree # (if the path is not absolute). The absolute path cannot be DOS-style, # because $ac_f cannot contain ':'. test -f "$ac_f" || case $ac_f in [\\/$]*) false;; *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; esac || as_fn_error 1 "cannot find input file: '$ac_f'" "$LINENO" 5;; esac case $ac_f in *\'*) ac_f=`printf '%s\n' "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac as_fn_append ac_file_inputs " '$ac_f'" done # Let's still pretend it is 'configure' which instantiates (i.e., don't # use $as_me), people would be surprised to read: # /* config.h. Generated by config.status. */ configure_input='Generated from '` printf '%s\n' "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' `' by configure.' if test x"$ac_file" != x-; then configure_input="$ac_file. $configure_input" { printf '%s\n' "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 printf '%s\n' "$as_me: creating $ac_file" >&6;} fi # Neutralize special characters interpreted by sed in replacement strings. case $configure_input in #( *\&* | *\|* | *\\* ) ac_sed_conf_input=`printf '%s\n' "$configure_input" | sed 's/[\\\\&|]/\\\\&/g'`;; #( *) ac_sed_conf_input=$configure_input;; esac case $ac_tag in *:-:* | *:-) cat >"$ac_tmp/stdin" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; esac ;; esac ac_dir=`$as_dirname -- "$ac_file" || $as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$ac_file" : 'X\(//\)[^/]' \| \ X"$ac_file" : 'X\(//\)$' \| \ X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || printf '%s\n' X"$ac_file" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` as_dir="$ac_dir"; as_fn_mkdir_p ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`printf '%s\n' "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`printf '%s\n' "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix case $ac_mode in :F) # # CONFIG_FILE # case $INSTALL in [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;; *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;; esac _ACEOF # Neutralize VPATH when '$srcdir' = '.'. # Shell code in configure.ac might set extrasub. # FIXME: do we really want to maintain this feature? cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 ac_sed_extra="$ac_vpsub $extrasub _ACEOF cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 :t /@[a-zA-Z_][a-zA-Z_0-9]*@/!b s|@configure_input@|$ac_sed_conf_input|;t t s&@top_builddir@&$ac_top_builddir_sub&;t t s&@top_build_prefix@&$ac_top_build_prefix&;t t s&@srcdir@&$ac_srcdir&;t t s&@abs_srcdir@&$ac_abs_srcdir&;t t s&@top_srcdir@&$ac_top_srcdir&;t t s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t s&@builddir@&$ac_builddir&;t t s&@abs_builddir@&$ac_abs_builddir&;t t s&@abs_top_builddir@&$ac_abs_top_builddir&;t t s&@INSTALL@&$ac_INSTALL&;t t " eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 rm -f "$ac_tmp/stdin" case $ac_file in -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; esac \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; :H) # # CONFIG_HEADER # if test x"$ac_file" != x-; then { printf '%s\n' "/* $configure_input */" >&1 \ && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" } >"$ac_tmp/config.h" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 if diff "$ac_file" "$ac_tmp/config.h" >/dev/null 2>&1; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5 printf '%s\n' "$as_me: $ac_file is unchanged" >&6;} else rm -f "$ac_file" mv "$ac_tmp/config.h" "$ac_file" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 fi else printf '%s\n' "/* $configure_input */" >&1 \ && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" \ || as_fn_error $? "could not create -" "$LINENO" 5 fi ;; esac done # for ac_tag as_fn_exit 0 _ACEOF ac_clean_CONFIG_STATUS= test $ac_write_fail = 0 || as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 # configure is writing to config.log, and then calls config.status. # config.status does its own redirection, appending to config.log. # Unfortunately, on DOS this fails, as config.log is still kept open # by configure, so config.status won't be able to write to it; its # output is simply discarded. So we exec the FD to /dev/null, # effectively closing config.log, so it can be properly (re)opened and # appended to by config.status. When coming back to configure, we # need to make the FD available again. if test "$no_create" != yes; then ac_cs_success=: case $CONFIG_STATUS in #( -*) : ac_no_opts=-- ;; #( *) : ac_no_opts= ;; esac ac_config_status_args= test "$silent" = yes && ac_config_status_args="$ac_config_status_args --quiet" exec 5>/dev/null $SHELL $ac_no_opts "$CONFIG_STATUS" $ac_config_status_args || ac_cs_success=false exec 5>>config.log # Use ||, not &&, to avoid exiting from the if with $? = 1, which # would make configure fail if this is the last instruction. $ac_cs_success || as_fn_exit 1 fi if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 printf '%s\n' "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} fi lft-4.01/lft_types.h000644 000765 000024 00000014250 15247152740 014342 0ustar00vicstaff000000 000000 /* * lft_types.h * Layer Four Traceroute * * This file is part of LFT. * * The LFT software provided in this Distribution is * Copyright 2007 VOSTROM Holdings, Inc. * * The full text of our legal notices is contained in the file called * COPYING, included with this Distribution. * */ #ifndef LFT_TYPES_H #define LFT_TYPES_H #if defined( __CYGWIN__ ) || defined( WIN32 ) || defined(_WIN32) #include "config/acconfig.win.h" #else #include "config/acconfig.h" #endif #ifdef HAVE_IPV6 #include "lft_addr.h" /* lft_addr_t for v6 per-packet fields */ #endif #include #ifndef __FAVOR_BSD # define __FAVOR_BSD 1 #endif #if defined( __CYGWIN__ ) || defined( WIN32 ) || defined(_WIN32) #define __USE_W32_SOCKETS #include #include #include #include #define LITTLE_ENDIAN 1 #define BYTE_ORDER 1 typedef signed long n_long; typedef signed short n_short; typedef signed long n_time; #include #include #include #include #ifdef __CYGWIN__ # include #else # include "include/win32/wingetopt.h" # if defined(WIN32) || defined(_WIN32) # define SIZEOF_CHAR 1 # define SIZEOF_SHORT 2 # define SIZEOF_LONG 4 # define SIZEOF_LONG_LONG 8 # include "include/libpcap/bittypes.h" # include "include/libpcap/Gnuc.h" # define bzero(buf,cnt) memset(buf,'\0',cnt); # endif #endif #include "include/net/if_arp.h" #include "include/netinet/if_ether.h" #include "include/netinet/ip.h" #include "include/netinet/ip_icmp.h" #include "include/netinet/tcp.h" #include "include/netinet/udp.h" #else #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef BSD #include #endif #ifdef BSD_IP_STACK #include #include #if !defined(DARWIN) && !defined(NETBSD) #define HAVE_SNPRINTF #define HAVE_VSNPRINTF #if !defined(OPENBSD) && !defined(__FreeBSD__) #include #endif #endif #endif #include #ifdef sun #include #include #endif #endif #include "lft_ifname.h" #include "lft_lsrr.h" #include "whois.h" #if defined(__OpenBSD__) #include #endif #if defined(__FreeBSD__) || defined(DARWIN) || defined(__APPLE__) #include #elif !defined(DARWIN) && !defined(NETBSD) #include "lft_queue.h" #endif #ifndef EXIT_FAILURE #define EXIT_FAILURE 1 #endif #ifndef EXIT_SUCCESS #define EXIT_SUCCESS 0 #endif #ifdef __cplusplus extern "C" { #endif /* holds the pseudo-header for generating checksums */ struct sumh { unsigned int src; unsigned int dst; unsigned char fill; unsigned char protocol; unsigned short len; }; /* The actual packet data */ struct trace_packet_s { struct ip ip_hdr; struct ip_lsrr lsrr; /* must go here for ip checksum to work */ struct tcphdr tcp_hdr; struct udphdr udp_hdr; int size; char *payload; int payload_len; }; /* RFC 1393 type trace IP option */ struct rfc1393_ip_option_s { u_char optcode; //=82 u_char optlength; //=12 u_short id; //number to identify icmp trace messages u_short ohc; //outbound hop count u_short rhc; //return hop count struct in_addr origip; //originator ip address } __attribute__((packed)); /* ICMP echo header */ struct icmp_echo_header_s { u_char type; u_char code; u_short checksum; u_short id; u_short sequence; } __attribute__((packed)); /* ICMP trace response header */ struct icmp_trace_reply_header_s { u_char type; u_char code; u_short checksum; u_short id; u_short unused; u_short ohc; //outbound hop count u_short rhc; //return hop count u_long ols; //output link speed u_long olmtu; //output link MTU } __attribute__((packed)); /* Trace packet for RFC 1393 type and ICMP base trace */ struct icmp_trace_packet_s { char * packet; int packet_len; struct ip * ip_hdr; struct rfc1393_ip_option_s * icmp_trace_opt; struct ip_lsrr * lsrr; struct icmp_echo_header_s * echo; char * payload; int payload_len; }; /* Packet container with additional info */ struct trace_packet_info_s { int icmp_type; /* ICMP_UNREACH code, -1 if RST reply */ int is_done; /* is this a final hop? */ short hopno; unsigned int seq; struct timeval sent; struct timeval recv; /* 0 if unreceived */ struct in_addr hopaddr; /* IP address of router */ /* copy of EvtPacketInfoParam */ int asnumber; char netname[512]; char orgname[256]; /* org-name from pwhois bulk query */ int geo_ok; /* --geojson/--kml: pWhoIs geolocation present for this responder */ double geo_lat, geo_lon; char geo_city[50], geo_region[50], geo_country[50], geo_cc[4]; struct in_addr last_hop; #ifdef HAVE_IPV6 lft_addr_t hopaddr6; /* v6 hop address when af==AF_INET6 */ lft_addr_t last_hop6; /* v6 previous-hop address */ #endif /*----------------------------*/ union { struct trace_packet_s packet; struct icmp_trace_packet_s icmp_packet; } u; SLIST_ENTRY(trace_packet_info_s) next_by_hop; SLIST_ENTRY(trace_packet_info_s) next; }; /* hop information, by ttl */ struct hop_info_s { int num_sent; int all_sent, all_rcvd; struct timeval ts_last_sent; struct timeval ts_last_recv; struct trace_packet_info_s * done_packet; unsigned short state; unsigned short flags; SLIST_HEAD(hop_packets_s, trace_packet_info_s) packets; uint16_t path_mtu; /* 0 = not determined; >0 = probe size at time of flagging */ int pmtud_blackhole; /* 1 = confirmed: responds to small probes, not large ones */ int pmtud_icmp_suppressor; /* 1 = reclassified: large probe confirmed forwarded downstream */ }; #ifdef __cplusplus } #endif #endif /*LFT_TYPES_H*/ lft-4.01/makefile.vc000644 000765 000024 00000010537 15165341545 014275 0ustar00vicstaff000000 000000 # ========================================================================= # LFT / WhoB — MSVC nmake build file # # Requirements: # Npcap SDK https://npcap.com/dist/npcap-sdk-1.13.zip (or later) # c-ares https://c-ares.org/ (build from source or vcpkg) # # Quick start (from a Visual Studio Developer Command Prompt): # set NPCAP_DIR=C:\npcap-sdk # set CARES_DIR=C:\cares # nmake -f makefile.vc # # To build a debug binary: # nmake -f makefile.vc DEBUG=1 # ========================================================================= # ------------------------------------------------------------------------- # Configurable paths — override on the nmake command line or via environment # ------------------------------------------------------------------------- # Npcap (or WinPcap) SDK root; must contain Include\ and Lib\x64\ (or Lib\) NPCAP_DIR = C:\npcap-sdk # c-ares SDK root; must contain include\ and lib\ CARES_DIR = C:\cares # Set to 1 for a debug build DEBUG = 0 # ------------------------------------------------------------------------- # Compiler / linker # ------------------------------------------------------------------------- CC = cl LINK = link # Platform-specific Npcap lib path (x64 preferred; fall back to Lib\ for x86) NPCAP_LIB = $(NPCAP_DIR)\Lib\x64 # ------------------------------------------------------------------------- # Flags # ------------------------------------------------------------------------- !if "$(DEBUG)" == "1" _OPT = /Od /Zi _DBG = /D_DEBUG _DBGPDB = _DBGSFX = d _LINKDBG = /DEBUG !else _OPT = /O2 _DBG = /DNDEBUG _DBGPDB = _DBGSFX = _LINKDBG = !endif BASE_CFLAGS = /nologo /TC /MD$(_DBGSFX) /W3 \ /DWIN32 /D_WIN32_WINNT=0x0601 \ $(_OPT) $(_DBG) \ /I"$(NPCAP_DIR)\Include" \ /I"$(CARES_DIR)\include" \ /I".\include\win32" LFT_CFLAGS = $(BASE_CFLAGS) /Fdlft.pdb WHOB_CFLAGS = $(BASE_CFLAGS) /Fdwhob.pdb /DSTANDALONE BASE_LIBS = ws2_32.lib Iphlpapi.lib advapi32.lib NPCAP_LIBS = "$(NPCAP_LIB)\wpcap.lib" "$(NPCAP_LIB)\Packet.lib" CARES_LIBS = "$(CARES_DIR)\lib\cares.lib" LFT_LIBS = $(BASE_LIBS) $(NPCAP_LIBS) $(CARES_LIBS) WHOB_LIBS = $(BASE_LIBS) $(CARES_LIBS) # ------------------------------------------------------------------------- # Object lists # ------------------------------------------------------------------------- LFT_OBJECTS = \ lft_lft.obj \ lft_lft_btcptrace.obj \ lft_lft_icmptrace.obj \ lft_lft_ifname.obj \ lft_lft_lib.obj \ lft_whois.obj \ lft_wingetopt.obj \ lft_wingettimeofday.obj \ lft_winlft_ifname.obj WHOB_OBJECTS = \ whob_whois.obj \ whob_wingetopt.obj # ------------------------------------------------------------------------- # Targets # ------------------------------------------------------------------------- all: lft.exe whob.exe clean: -if exist *.obj del *.obj -if exist *.res del *.res -if exist *.pch del *.pch -if exist *.pdb del *.pdb -if exist *.ilk del *.ilk -if exist lft.exe del lft.exe -if exist whob.exe del whob.exe lft.exe: $(LFT_OBJECTS) $(LINK) /NOLOGO $(_LINKDBG) /OUT:$@ $(LFT_OBJECTS) $(LFT_LIBS) whob.exe: $(WHOB_OBJECTS) $(LINK) /NOLOGO $(_LINKDBG) /OUT:$@ $(WHOB_OBJECTS) $(WHOB_LIBS) # ------------------------------------------------------------------------- # Compilation rules # ------------------------------------------------------------------------- lft_lft.obj: .\lft.c $(CC) /c /Fo$@ $(LFT_CFLAGS) $** lft_lft_btcptrace.obj: .\lft_btcptrace.c $(CC) /c /Fo$@ $(LFT_CFLAGS) $** lft_lft_icmptrace.obj: .\lft_icmptrace.c $(CC) /c /Fo$@ $(LFT_CFLAGS) $** lft_lft_ifname.obj: .\lft_ifname.c $(CC) /c /Fo$@ $(LFT_CFLAGS) $** lft_lft_lib.obj: .\lft_lib.c $(CC) /c /Fo$@ $(LFT_CFLAGS) $** lft_whois.obj: .\whois.c $(CC) /c /Fo$@ $(LFT_CFLAGS) $** lft_wingetopt.obj: .\include\win32\wingetopt.c $(CC) /c /Fo$@ $(LFT_CFLAGS) $** lft_wingettimeofday.obj: .\include\win32\wingettimeofday.c $(CC) /c /Fo$@ $(LFT_CFLAGS) $** lft_winlft_ifname.obj: .\include\win32\winlft_ifname.c $(CC) /c /Fo$@ $(LFT_CFLAGS) $** whob_whois.obj: .\whois.c $(CC) /c /Fo$@ $(WHOB_CFLAGS) $** whob_wingetopt.obj: .\include\win32\wingetopt.c $(CC) /c /Fo$@ $(WHOB_CFLAGS) $** lft-4.01/CHANGELOG000644 000765 000024 00000127300 15250425146 013370 0ustar00vicstaff000000 000000 LFT CHANGELOG lft 4.01 / WhoB 4.01 ---------------------- - Fixed interface selection on hosts with many network interfaces (VPN tunnels, virtual machine bridges): the interface list was truncated, so LFT could pick a device with no IPv4 address and report "ioctl: Can't assign requested address" with no replies. - lft -v and whob -v print their version to standard output. - Bug reports and contact are now through https://pwhois.org/contact/. - ./configure --without-cares and --without-ncurses now disable the feature instead of asking for a path. lft 4.0 / WhoB 4.0 ---------------------- IPv6 support: - IPv6 tracing. LFT now traces IPv6 paths end to end using the same default TCP method it uses for IPv4. The address family is matched to the target; -4 and -6 force a family, and a bare IPv6 literal or the bracketed [address]:port form selects IPv6 with a port. - IPv6 addresses print in RFC 5952 shorthand by default; --expand-addr shows the fully expanded eight-group form. - Reverse DNS, whois (ASN, network name, and organization via pWhoIs, Team Cymru, RADB, and RIPE RIS), and the IANA special-purpose registry now resolve IPv6 hops. WhoB likewise accepts IPv6 addresses and host names. - IPv6 Path MTU Discovery via ICMPv6 Packet Too Big, honoring the 1280-octet minimum link MTU. - Loose source routing (gateway waypoints) is rejected for IPv6, which has no equivalent IP option (and loose source routing has largely stopped being accepted across the Internet regardless). Path discovery and timing: - New --ecmp: expose ECMP (equal-cost multi-path) members. Varies the source port per probe and sends more probes per replied hop, so the parallel next hops at a load-balanced hop are all discovered and shown as a diamond in the text, GraphViz (-g), --svg, and --mermaid output. Silent hops are unaffected. Off by default; TCP and UDP. - Retries to an unanswered hop now use a fresh source port per attempt (TCP; attempt one keeps the hop's stable port). Middleboxes that suppress same-flow duplicates answer each retry, recovering hops previously reported [neglected]. UDP's fixed 5-tuple is unchanged. - A hop with more than one RTT sample is now summarized as min/avg/max ±stddev in integer milliseconds (e.g. "12/17/20 ±2ms") instead of listing each round-trip time separated by slashes. The "±" sets the standard deviation apart so it cannot be misread as a fourth sample; "~" replaces "±" on non-UTF-8 terminals. A single-sample hop still shows the one decimal value, and each responder in an --ecmp diamond is summarized on its own line. Applies to standard text, --ecmp member lines, and the -o/-j chart; watch mode labels jitter in its own column. Diagram output: - New --svg: emit a self-contained, styled SVG diagram of the completed trace. It uses the same device iconography and classification as GraphViz output (source, routers, AS and network seams, stateful and flag-based firewalls, cloaked no-reply hops, and the open/closed/filtered target), draws one card per hop top to bottom, and embeds the glyphs so no external icon files are needed -- it renders in any SVG viewer or browser. - New --mermaid: emit a styled Mermaid "flowchart TB" definition of the trace, with per-hop node shapes by role and per-type classDef coloring. Renders directly on platforms with native Mermaid support (such as GitHub) or in any Mermaid live editor. - Refreshed the GraphViz node icons with a new resolution-independent vector icon set (authored as SVG, exported to the 72x72 and 144x144 raster sizes the renderer consumes). The set is coherent and cleanly licensed, replacing the previous mixed raster artwork. - New --show-seams: autonomous-system and network seam boundaries are now hidden by default across all output -- the standard text and XML output, the GraphViz (-g), SVG (--svg), Mermaid (--mermaid), and ASCII (-o) diagrams, and watch mode. --show-seams reveals them, seam verification (-y) enables them automatically, and in watch mode the "s" key toggles them live (alongside "h" for hostnames). - The SVG diagram wraps long hostnames across multiple lines so fully-qualified names no longer overrun the fixed-width hop card. Verbose and machine-readable output: - Verbose SENT/RCVD lines: per-probe ports (PORTS=src->dst) on TCP and UDP in all modes including IPv6, per-probe RTT= on all RCVD lines, SEQ/PSEQ moved to line end, ICMP TYPE=/CODE= tags normalized to uppercase; XML gains sport/dport/rtt attributes. - XML output now escapes host and network names so externally supplied text is well-formed. - XML output (-x) now begins with an "" declaration before the root element, and the root now carries schema="lft-xml" schema_version="1" so parsers can check the format they are reading. The document was already well-formed; the prolog makes the encoding explicit for strict parsers. - New --json and --json-pretty: emit the trace as a JSON document (compact or indented) for jq, dashboards and scripts: run metadata, source, target, result, and one object per TTL with every responder's ASN, network name and RTT statistics. Silent TTLs and ECMP members are represented explicitly. See OUTPUT FORMATS in lft(8). - New --geojson and --kml: the trace on a map (-A is implied). Every located responder becomes a point (GeoJSON, RFC 7946) with the --json hop fields plus city, region and country; each pair of located hops becomes a great-circle segment carrying distance, RTT delta, AS change and the silent or unlocated TTLs it spans. A hop whose RTT is below the speed-of-light floor for its distance is kept but flagged as implausibly located. KML (OGC KML 2.2) opens in any KML viewer with labelled placemarks, an attribute table per hop and arched paths. --geo-color RRGGBB|rtt, --geo-width N and --geo-arc F style the path. Colorized output (WhoB): - WhoB colorizes its pWhoIs output when writing to an interactive terminal; output to a file or pipe is unchanged. The palette adapts to a light or dark terminal background and uses 24-bit color where available. - Colorization covers every pWhoIs view: the default lookup, the route tables (-a, -A) with the queried ASN highlighted in each AS-Path, the server status (-s), the full record (type=all) and the JSON forms. - New --color and --no-color force colorization on or off in both WhoB and LFT (-o). Text annotation (WhoB): - New -e / --enrich / --annotate[=FILE]: WhoB acts as a filter, writing its input back out with every IPv4 or IPv6 address followed by "[ASn Org-Name]" from pWhoIs ("[?]" when unrouted). Ports, prefixes and bracketed IPv6 are left intact, and each distinct address is looked up once. -F/--fields chooses what the annotation shows (ASN, org, AS org, net name, prefix, city/region/country). Pairs with any tool: traceroute, mtr, nmap, tcpdump, netstat, firewall logs. Security hardening: - Hardening across the packet parsers, whois client, and privilege handling: the classifier validates the IP version and every ICMP/ICMPv6 quoted-packet header is bounds-checked against the captured length before use; the 802.1Q VLAN offset is computed per packet instead of persisted across the trace; the raw-socket privilege drop is now unconditional and verified; a stale or spoofed segment from a non-target source can no longer be misfiled onto a probe; and the whois response parser is hardened against field over-reads and memory leaks. Details in SECURITY.md. - Fixed a one-byte heap buffer overflow while building the pWhoIs application-identifier string for -A/-N bulk ASN and network lookups: an operator-precedence error in the buffer sizing under-allocated by one byte. Benign on older C libraries, it aborted with "buffer overflow detected" under _FORTIFY_SOURCE on current glibc (e.g. Ubuntu 24.04), so -A and -N crashed on those systems. The buffer is now sized correctly and filled with a bounded snprintf. Build and portability: - Asynchronous DNS via c-ares now works with the version most distributions ship, not only c-ares 1.34+. LFT previously required the 1.34 event-model API (ares_process_fds) and silently fell back to synchronous resolution on older c-ares (Debian/Ubuntu ship 1.27, RHEL similar). It now uses the 1.34+ event API when present and the classic ares_process poll API otherwise, so installing the packaged libc-ares-dev is enough to enable async DNS. The event-API probe is now a link test that calls ares_process_fds, so a newer header shadowing an older linked library (common when a hand-built c-ares sits in /usr/local alongside the distribution package) is detected correctly and falls back to the classic API instead of producing a binary that fails to link. lft 3.99 / WhoB 3.99 ---------------------- - New -B / --tcp-options: send a realistic client TCP option set (MSS, SACK-permitted, timestamps, window scale) on SYN probes so they resemble a real operating system's SYN instead of a bare, option-less one. Firewalls that normalize or scrub TCP may drop option-less SYNs; -B helps LFT traverse them. Default and adaptive TCP engines only; off by default, and it changes only the packets on the wire, not LFT's output. - TCP, UDP, and ICMP traces no longer count silent near hops (TTL 1-3) toward the early give-up threshold. A stateful firewall or ICMP rate policer at the local edge often suppresses the first few hops even when the path core and the target respond; excluding them lets LFT probe far enough to reach a live hop and complete the trace instead of bailing early. A genuinely dead path still terminates normally. - whob now honors a target given on the command line even when its standard input is redirected from an empty or closed source (for example `whob host for struct winsize and TIOCGWINSZ. These were pulled in transitively on macOS but not on Linux glibc, breaking the build of --watch support out-of-the-box on Linux. - whob link line cleaned up: the whob CLI tool no longer drag-links the ncurses libraries (it never used them). Makefile / Makefile.in now define WHOB_LIBS = $(filter-out -lncurses -lncursesw,$(LIBS)) and use it in the whob target. c-ares stays in the whob link since whois.c uses it for the server-prefetch async resolver path. lft 3.97 / WhoB 3.97 ---------------------- - watch-mode UDP ECMP stability across cycles: watch_reset_session no longer zeros sess->seq_start between cycles, preserving the IP-ID base that UDP probes embed for ECMP flow-hash consistency. Without this, each watch cycle re-randomized the IP-ID base and took a different ECMP branch on multi-path networks, defeating the 3.97 UDP ECMP-stability fix. TCP (sport) and ICMP (icmp_initial_seq anchor) were already preserved correctly. - watch-mode hop count indicator no longer over-counts: with noisy>=2 (forced in watch mode for the log drawer), EVT_RECV_PACKET formerly incremented the live hop counter for every responding TTL including ahead-limit overshoot replies past the target; the handler now recounts from hop_info[] capped at num_hops+1, matching EVT_PROGRESS_OK. The watch status-bar peak counter (max_hops_seen) is now set to the current cycle's n_hops rather than maximized, so it cannot get sticky after a short path follows a long one. - watch-mode topology inheritance (--watch / -W): the first cycle is a conventional broad scan, but once the path length and RTT envelope have been stable for three consecutive cycles, subsequent cycles narrow ttl_limit to learned_num_hops + learn_margin (default 2), widen ahead_limit to the full known path width (capped at 16 to stay friendly to per-source ICMP rate limits), shrink timeout_ms to max(8 * max_observed_rtt, 200 ms), and tighten scatter_ms to 5. Auto-tune resets immediately if a cycle misses the target or the probe protocol changes, reverting to broad-scan defaults until the path stabilizes again. --no-learn disables the feature entirely; --learn-margin N (0 to 10) tunes the TTL probe margin above the learned path length. - UDP probe ECMP flow-hash stability: all probes in a UDP trace session now use a fixed destination port rather than incrementing the port with each TTL (the traditional VJ traceroute approach). Each probe is identified by a pseudorandom session-unique value embedded in the IP ID field; ICMP time-exceeded replies echo this value back in the quoted inner IP header, allowing exact probe-to-reply matching without varying the 5-tuple. The IP ID base is generated the same way as the TCP initial sequence number (rand()), ensuring it is non-zero and session-unique. This keeps every UDP probe on the same ECMP path. - ICMP probe ECMP flow-hash stability: a 2-byte compensation word appended to each ICMP echo payload keeps the ICMP checksum constant across all probes in a session. Because routers hash on the ICMP checksum field as a proxy for the missing port fields, holding the checksum constant ensures all probes follow the same ECMP path. - For completeness, our TCP tracing implementation supported ECMP flow-hash stability (and was the first tool to do this) since 1998. - Added a 'Capabilities' section to the version (-v) output to display the version of libraries against which LFT was linked at compile-time. lft 3.96 / WhoB 3.96 ---------------------- - rtt-statistics (-j N / --stats=N): per-hop RTT min/avg/max/stddev in all output modes; ASCII art candlestick chart above -o hop diagram; global Y-axis scale; jitter alert (>25 ms delta) highlighted in yellow with annotation; -o auto-activates with default 3 probes; -m/-M override probe count. - Added active Path MTU Discovery: -K / --mtu / --path-mtu starts probes at the local interface MTU (queried via SIOCGIFMTU; 1500 fallback) so that bottleneck links return ICMP "fragmentation needed" (type 3, code 4) messages identifying the path MTU; probe size steps down on each PTB received, using the next-hop MTU value or, for pre-RFC-1191 routers that return zero, the next lower RFC 1191 plateau (65535→32000→17914→8166→4352→2002→1492→1006→ 508→296→68); hops where the path MTU is constrained show [MTU: NNNN] in regular output, as pathmtu="NNNN" in XML, and as a separate row in ASCII art (-o); hops that silently drop oversized DF probes without returning a PTB show [MTU Limit] hop N dropped DF probe (NNNN bytes, no PTB received) immediately after the [neglected] line for that hop; -L is silently ignored when -K is active; documented in lft.8 with PTB terminology explained - Extended active PMTUD (-K) to ICMP probe mode (-p): open_sockets() now queries SIOCGIFMTU and sets sess->userlen for ICMP as it does for TCP/UDP; generateICMPPacket() picks up sess->userlen as the total packet size; PTB (type 3, code 4) responses are intercepted in icmp_process_packet() and handled by lft_pmtud_icmp_handle_ptb() which steps down sess->userlen the same way lft_pmtud_handle_ptb() steps down the TCP/UDP trace_packet template; icmp_check_timeouts() flags timed-out hops as pmtud_blackhole and calls lft_pmtud_verify_blackholes() (now non-static) before icmp_finish(); icmp_finish() emits per-hop [MTU Limit] / [neglected] output matching finish() behaviour; lft.8 -K section updated to reference all three probe protocols and explain the payload-inflation rationale - Migrated c-ares usage from deprecated API to c-ares 1.34+ event model: replaced ares_fds()/ares_process() with ARES_OPT_SOCK_STATE_CB / ares_process_fds() in both lft_lib.c (main trace loop) and whois.c (whois server prefetch); replaced ares_gethostbyname() with ares_getaddrinfo() in whois.c; removed all -Wdeprecated-declarations pragma suppressions; builds cleanly at -std=gnu23 with zero warnings - Annotation labels in -o ASCII art updated: stateful firewall now shows Stateful FW (U+21F6), flag-based filter shows Flag FW (U+2691), BSD TCP anomaly shows BSD Bug (U+21C4), AS boundary shows ASN Seam and network boundary shows NET Seam (both U+2573); plain ASCII fallbacks updated to [Stateful FW], [Flag FW], [BSD Bug] respectively; seam fallbacks retain x-AS-Seam-x / x-Net-Seam-x for compatibility - RTT chart x-axis label now shows end-to-end RTT derived from the target column's own min/max samples (not the global trace min/max), formatted as "End-to-End RTT Nmin–Nmax ms, N hops to proto://address:port" where proto reflects the active probe mode (tcp/udp/icmp) and port is the destination port; falls back to "N hops traced, target unreachable" when target was not reached; lft.8 -j entry updated accordingly - Updated include/win32/wingetopt.c to add getopt_long() implementation (required since 3.95 added getopt_long() throughout lft.c; Windows builds were broken at link time without it); wingetopt.h extended with struct option, no_argument / required_argument / optional_argument constants, getopt_long() declaration, and opterr / optopt globals - Rewrote makefile.vc (formerly auto-generated by the defunct Bakefile tool): now hand-maintained with NPCAP_DIR / CARES_DIR configurable paths, proper Npcap SDK linkage (wpcap.lib Packet.lib), c-ares linkage (cares.lib), correct include search paths, and a DEBUG=1 mode; documents SDK download URLs and quick-start instructions in the header comment - Removed deprecated lft.spec instructions - Updated config/install-sh - Updated config/config.guess and config/config.sub lft 3.95 / WhoB 3.95 ---------------------- - Replaced gethostbyname()/gethostbyaddr() with getaddrinfo()/getnameinfo() throughout (~50 calls) for modernization and prerequisite for IPv6 - Added asynchronous reverse-DNS via c-ares: PTR queries sent for each hop IP as it is revealed by an incoming ICMP time-exceeded response, merged into the existing select() loop via ares_fds()/ares_process(); results are ready by the time the trace completes, eliminating post-trace serial DNS latency (typically 180-260ms savings on a 10-15 hop trace) - Whois library lazy DNS resolution: w_init() registers known server hostnames in a cache but fires no lookups; a new w_prefetch() function resolves a single server on first use via c-ares when w_ask() is called, bypassing the OS resolver (and mDNSResponder on macOS) only for the service actually needed - New -Q flag disables async DNS at runtime without recompiling; documented in --help (under Advanced Resolution Options) and the manpage - -T timing display shows "resolving: X.XXs (async)" label when c-ares handled name resolution during the trace, distinguishing from the synchronous path - configure now probes common third-party prefix directories (/opt/homebrew, /usr/local, /opt/local, /opt/pkg) for both libpcap and c-ares before falling back to AC_CHECK_LIB; ./configure now finds Homebrew-installed c-ares on Apple Silicon without requiring manual LDFLAGS/CPPFLAGS - Added pkg-config support for c-ares detection (mirroring existing pcap path) - Added --with-cares=PATH configure option for manual c-ares path override - Added --disable-async-dns configure option to disable c-ares at build time - All async DNS code guarded by #ifdef HAVE_CARES; builds and runs correctly with or without c-ares installed; -Q is a safe no-op when not compiled in - c-ares initialised with ARES_OPT_TIMEOUTMS=500ms, ARES_OPT_TRIES=2 (1000ms worst-case per host); DNS results are a bonus and never block the trace -- unresolved hops display numerically as before - Updated lft.8 manpage to document -Q and async DNS behavior - Added -o ASCII art hop diagram output: horizontal per-hop columns with Unicode box-drawing headers (┌─ HOP N ─┐) with ASCII fallback ([- HOP N -]); ANSI color auto-detected via TERM/COLORTERM/isatty and suppressed by NO_COLOR; terminal-width-aware row wrapping; SOURCE and TARGET columns with port numbers - Column layout (top to bottom): header, hostname, IP address, AS number (-A), network name (-N), round-trip time, then feature annotation line - Feature labels use needlework and symbolic Unicode glyphs when the terminal supports them, with plain ASCII fallbacks for all annotations: seam boundaries, cloaked hops, firewall anomalies, and target state indicators each have a distinct glyph style in Unicode mode and a readable ASCII equivalent when Unicode is unavailable - Narrowed ICMP unreachable stop behavior: only codes 0 (net), 1 (host), 2 (protocol), 3 (port), 9 (net admin prohibited), 10 (host admin prohibited), and 13 (communication admin prohibited) halt the trace by default; all other codes (4, 5-8, 11-12, 14-15) are recorded but the trace continues; new lft_icmp_stops_trace() static inline helper in lft_lib.h shared between lft_lib.c and lft_btcptrace.c - Adaptive mode (-E/-e) now automatically implies --ignore-unreachables (break_on_icmp=0); the adaptive engine needs to probe through devices that send ICMP unreachables to perform stateful path analysis - Added getopt_long() replacing getopt(); every short flag now has a long-form equivalent (e.g. --dport, --min-probes, --max-hops, --ignore-unreachables, --adaptive, --ascii, --tcp-window); multiple aliases supported where traceroute convention warrants (--min-ttl / --first-hop, --max-hops / --ttl-max, --quiet / --silent, --length / --bytes, --device / --receive-device, --from-device / --source-device); all short flags remain fully backward-compatible - Added long-only --help option (OPT_HELP sentinel); displays usage summary and exits - Added -w (--tcp-window) to usage() help text under Advanced Tracing Options and to the manpage; the option existed and functioned but was undocumented in both places - Added "long-form options available, see lft(8)" note to --help output - Fixed manpage: -m and -M descriptions corrected from "re-attempts per host" to "probes per hop"; -M default corrected from 3 to 2 (matching actual code default); "min tll" typo corrected to "min ttl"; "Type of Serice" typo corrected to "Type of Service" - Updated manpage -i description to enumerate the specific ICMP codes that stop the trace by default and note that adaptive mode implies it - Updated manpage -E/e entry to note that adaptive mode implies --ignore-unreachables - Whois and getnameinfo DNS calls now protected by timeout wrappers: w_getaddrinfo_timed() (POSIX: alarm/SIGALRM; Windows: worker thread + WaitForSingleObject/TerminateThread) prevents whois server resolution from stalling the trace indefinitely; lft_getnameinfo_timed() applies the same Windows thread-based ceiling to getnameinfo() calls in the output path - configure now emits a deferred advisory warning (AC_MSG_WARN) when c-ares is not found, displayed at the bottom of configure output; libpcap AC_MSG_ERROR messages updated to consistent style with platform-specific install instructions including macOS Homebrew - Suppressed unresolved [AS?] and [NULL]/[NET?] annotations in -o ASCII art and GraphViz output to avoid cluttering columns where no data is available; regular output retains the annotation but normalises the label from [NULL] to [NET?] for consistency with [AS?] - Added startup privilege check: lft_check_permissions() probes for raw socket access (the same capability pcap requires) before executing the trace; if insufficient, exits immediately with platform-appropriate remediation guidance -- on Linux recommends setcap(8) with setuid-root as a last resort; on macOS/BSD instructs the setuid-root method with the resolved binary path ready to copy-paste; on Windows directs the user to run as Administrator with Npcap installed lft 3.94 / WhoB 3.94 ---------------------- - Fixed libpcap detection in configure: replaced deprecated pcap_lookupdev() (removed in libpcap 1.10) with pcap_findalldevs(), which is what the code actually calls at runtime via lft_pcap_lookupdev() - Added pkg-config support for libpcap detection, with fallback to manual detection for systems without pkg-config - Fixed memory leak in lft_pcap_lookupdev(): pcap_findalldevs() result list was never freed with pcap_freealldevs() - Updated make install to detect platform (Linux vs. others) and check for root privileges before granting capabilities: on Linux with root, uses setcap(8) to grant cap_net_raw,cap_net_admin without a setuid root binary; falls back to setuid root if setcap is unavailable; warns clearly if install is not run as root; on non-Linux platforms uses setuid root directly - Updated --enable-universal to target arm64 + x86_64 (Apple Silicon + Intel) replacing the obsolete i386 + ppc targets - Removed obsolete autoconf macros: AC_HEADER_STDC, AC_C_CONST, AC_PROG_GCC_TRADITIONAL, AC_FUNC_MALLOC, AC_FUNC_REALLOC, AC_FUNC_SELECT_ARGTYPES, AC_STRUCT_TM, AC_HEADER_TIME - Improved configure error messages for missing libpcap with platform-specific install instructions - Replaced pcap_open_live() with pcap_create()/pcap_set_snaplen()/ pcap_set_promisc()/pcap_set_timeout()/pcap_activate() for improved error reporting; activation failures now surface via pcap_geterr() with distinct fatal (ERR_PCAP_ACTIVATE_ERROR) and non-fatal (WRN_PCAP_ACTIVATE) paths - Improved raw socket permission error to display platform-appropriate hint for granting required privileges lft 3.93 / WhoB 3.93 ---------------------- - Updated lft_types.h to reflect some DARWIN (Apple) specifics lft 3.92 / WhoB 3.92 ---------------------- - Changed lft_types.h to reflect some OpenBSD specifics lft 3.91 / WhoB 3.91 ---------------------- - Fixed bugs.debian.org/cgi-bin/bugreport.cgi?bug=922430 - Updated date lft 3.9 / WhoB 3.9 ---------------------- - Fixed a bug that could indicate a target was closed when it was open - Improved support for DLT_NULL interfaces on BSD-like operating systems - Improved deprecated pcap device enumeration method - Improved GraphViz output format appearance lft 3.8 / WhoB 3.8 ---------------------- - Added support for DLT_NULL interfaces lft 3.79 / WhoB 3.79 ---------------------- - Improved feature: 'whob me' and 'whob whoami' to display your public IP lft 3.78 / WhoB 3.78 ---------------------- - Added feature: 'whob me' to display your current public IP address lft 3.77 / WhoB 3.77 ---------------------- - Added feature: 'whob -gA' to show all prefixes TRANSITING an ASN Very powerful, much-requested feature! Enjoy Many thanks to Prefix WhoIs team! lft 3.76 / WhoB 3.76 ---------------------- - Remove compiler warnings using more casts (Victor) - Autoremake ./configure script so everyone has a reasonable default lft 3.75 / WhoB 3.75 ---------------------- - Improved cross-compiling compatiblity (thanks to Gustavo Z.) lft 3.74 / WhoB 3.74 ---------------------- - Fixed source port randomizaiton with -z (thanks to Conor M.) - Fixed compilation on OpenBSD (thanks to Renaud A.) lft 3.71 / WhoB 3.71 ---------------------- - WhoB: Autodetect input from STDIN (pipe) without '-f -' - WhoB: Redirect some extraneous output to STDERR lft 3.7 / WhoB 3.7 ---------------------- - Added support for 4-byte ASNs - Added support for whob reading bulk input from stdin using '-f -' lft 3.6 / WhoB 3.6 ---------------------- - Added support for 4-byte ASNs lft 3.5 / WhoB 3.5 ---------------------- - Roy T. provided DNS speed-ups - Added GraphViz output option with -g lft 3.35 / WhoB 3.5 ---------------------- - Roy T. provided some clean-ups to avoid double free()s - Bug fixes only lft 3.33 / WhoB 3.5 ---------------------- - Fixed free(sess->hostname) bug (segfault on unresolvable hostname) - Improved error hanlding of pcap failures related to data link type - Kurt's FreeBSD fix for pcap snprintf - Bug fixes only lft 3.32 / WhoB 3.5 ---------------------- - Added support for several encapsulating protocols such as PPP - no other changes lft 3.31 / WhoB 3.5 ---------------------- - Added #define for AI_NUMERICSERV undeclared on some platforms/versions - No other changes lft 3.3 / WhoB 3.5 ---------------------- - Improved LFT target detection behind firewalls - Updated LFT for newer pcap version defaults - WhoB ignores comments [#,;] in input files - Added LFT option -f to specify fake source address - Applied many, many, many fixes from Juli M. of USA -- Thanks! - Applied many, many, many fixes from Markus Gothe of Sweden -- Thanks! lft 3.2 / WhoB 3.2 ---------------------- - Added support for 802.1q tagged VLANs - Manual page corrections (thanks to Brett) - WhoB will use Prefix WhoIs for bulk file resolution in Cymru-compatible format with '-c' but Cymru will be used with '-C' lft 3.1 / WhoB 3.1 ---------------------- - New configure options: --enable-gtod Forces LFT to use gettimeofday() on each packet instead of using the BPF timestamp. This is critical on platforms that have enabled 'fastts' or that do not have high-precision BPF timestamping. --enable-universal generates binaries including both PPC and Intel architecture (for users running Mac OS X/Darwin) - Improved compatibility with NetBSD and Darwin/Mac OS X - Added autoconf support for NetBSD - Improved compatibility with older compilers (thanks to Sean) - Updated autoconf bits and pieces - By popular request, reversed the -g option of WhoB WhoB now uses gigo mode by default unless -g is specified which turns ON its parser and enables the other various options lft 3.0 ---------------------- - Completely refactored and now a workable library - LFT has a new (-b) TCP Basic trace method that makes TCP traces NAT-friendly - LFT has a new (-p) ICMP trace method that uses echo requests to trace paths - Many memory issues fixed - Cleanup of several 2.6b5-3.0b features lft 2.6 / WhoB 2.0 ---------------------- - LFT prints 'open' in the target block if the target dest port is open - LFT indicates the reason it marked ports open/closed in verbose(2) output - LFT prints an asterisk when it retransmits a packet after a timeout - LFT uses Prefix WhoIs (bulk) or RISWHOIS (bulk) for netname resolution - LFT has a new (-u) traditional UDP-based tracing feature - LFT displays start and finish times/dates when using the (-T) option - LFT has a new (-U) feature to display all times in UTC/GMT0 - LFT only shows time spent tracing/resolving at verbosity level 1 or higher - LFT sets the ToS bit on outgoing IP datagrams when (-I) option is used - LFT gets timevals from packets instead of calling gettimeofday() - LFT won't be fooled into thinking there's a firewall on a gateway just because adaptive mode ups the state waiting for replies that never come - Improved LFT performance (removed gettimeofday() on each packet) - WhoB/library uses Prefix WhoIs to resolve OrgNames and NetNames - WhoB/library has improved support for RIPE NCC RIS and Prefix WhoIs - WhoB supports bulk resolution (-f option) from an input file - WhoB also supports one-per-line output (-cf option) from a bulk input file - WhoB will use putenv() on Solaris who is missing setenv() to set TZ - WhoB has a new (-g) feature to take input directly from the command line and print output directly from Prefix WhoIs (referred to as GIGO) - Added $DESTDIR support to Makefile (thanks Daniel) - Fixed an off-by-one bug in LFT related to ASN display encountered when a trace contains one or more neglected TTLs - Fixed an off-by-one bug in LFT's verbose output regarding TTLs - Fixed variable ordering in whois.c from 2.6b1 (thanks Erick) - Numerous platform-specific improvements - Reconfigured autoconf and segregated ./config/ - Updated autoconf components to v2.59 lft 2.5 / WhoB 1.5 ---------------------- - Inclusive of betas 2.32 to 2.4x - Added -z option to pseudo-randomize source port - Added behavior to automatically select the most appropriate interface based on routing (this was on the most wanted list) - Improved OpenBSD compatibility (IP length nonzero) - OpenBSD is now detected by autoconf (for configuring the above) - Darwin is now detected by autoconf and its definition disables some BSD features to make it compatible with MacOS X and Darwin - LFT now indicates it has reached the target by printing a 'T' character in the status display (if status is enabled) - Cleanups were made to the verbose output levels (-VVV) - Significantly revamped whois framework makes it easy to include whois functionality into other programs - Added -C and -R and -r options to force alternate ASN sources: - (r)IPE RIS - special thanks to Rene Wilhelm @ RIPE NCC - (C)ymru - (R)ADB - Default ASN source (-A) is now Prefix WhoIs (see pwhois.org) - LFT now queries for ASNs in bulk format after completing a trace if pwhois (default), RIPE NCC RIS, or Cymru is selected - Added dst/src port autoselection based on user-supplied hostname - Vastly improved standalone whois client "whob" see whob.8 (whob manpage) - Makefile now installs 'whob' no-frills whois client (try ./whob) - "Smart" mode is now referred to as "Adaptive" mode (-E) lft 2.31 -------------------- - Fixed time precision on FreeBSD 5.3 (Thanks to Kurt Jaeger) lft 2.3: -------------------- - added WSAIoctl() call to select proper IF on windows based on dest (thanks Graham!) - lowered max_retries to a default of 2 - cleaned up formatting related to -S option - cleaned up verbose output to be more friendly - cleaned up error messages - updated manpage - updated spec file lft 2.2: -------------------- - removed dependence on regex library - removed dependence on INT_MAX - fixed whois code to get the most specific netblock instead of the least specific netblock - can now specify interface by IP instead of by name lft 2.1: -------------------- - Added autoconf support - Ported to cygwin - Fixed the Solaris and BSD makefiles (manpage install errors) - Modified the whois code to resolve ASN and netname lookup problems with RIPE entries. (-A) and (-N) code... - Modified the whois code to support local-as - Vastly streamlined the (-V) verbose output to cover the important information (sequence, etc) of all packets sent and received lft 2.0 (and betas): -------------------- - (-E) and (-F) shouldn't be used at the same time, now LFT knows that - Implemented fixes that enable LFT to run under Solaris/SPARC (Jim McKim) - Changed name from FFT to LFT (layer four trace) - Added ICMP messages for codes 13-15 - Changed the (-P) option added in 1.99 to (-E) in order to force use of the new "smart" engine - Enhanced stateful firewall (packet filter) detection en route - Engine: "smart" mode now tries FIN, SYN, etc table to get packets through which dramatically improves its "find a way" capability - Engine: "smart" mode now detects the BSD bug properly - Engine: "smart" mode now detects firewalls in transit - Compilation: made lft_queue.h the default for all OSs - UI: lots of user interface changes lft 1.99-beta(s): -------------------- - Added Loose Source Routing feature (LSRR) [ <...>] - Added (-N) Netblock name lookup feature - Added (-A) ASN lookup feature - Added (-P) option to automatically send 3 probes for more stats - Added (-V) lots of verbose debugging information - Added (-F) feature to revert to sending FIN packets - Added (-T) option to show trace and resolution timings (LFT is fast) - Added BSD 4.[23] and derivative (bug) inappropriate TTL detection - Engine: Use sequence numbers as packet ID instead of destination port numbers. This achieves a number of goals: Return ICMP and TCP packets are more uniquely identified. It is now possible to check the route for specific destination port, which is important if route varies based on port (such as with transparent proxying using a load balancing switch on port 80) (thank you Ugen!) - Engine: Send SYN packets. Variety of firewalls and NAT devices won't pass through a single TCP packet that is not a part of established connection, unless it carries SYN flag only (initial packet). Pitfall: this may create useless PCB blocks on destination host, if it does not use SYN cookies or some other SYN flood protection. However it's better then not being able to use lft from behind firewalls. (thank you Ugen!) - Renamed (-H) max hops/ttl option - UI cleaned up in several areas, especially result list & RTT display - Detect local packet filter lft 1.91: --------------------- - Fixed Makefile.solaris (last time we'll see this before autoconf) - added for Solaris lft 1.9: -------------------- - Default source port is now 53/tcp (dns-xfer) - Configurable source port on command line through -p or host:port - Configurable initial destination port on command line using -d - Source and Destination ports use /etc/services for lookup - Fixed a bug that created a gethostbyaddr() call even when the user specified NOT to do reverse DNS lookups -- makes -n option way faster! - Changed the way lft auto-detects the IP address of the interface selected * now, we determine what address is assigned to the interface instead of just getting the hosts's primary/default address and using it * thanks, Aaron, for bringing this to our attention! * thanks, Lane, for coming up with a platform-independent way of making it work. lft 1.8: -------------------- - added option to suppress status indicator and only show trace (or errors) * people using lft from a web page wanted this - added option to select the network interface used as hop zero * string length of device is tested to avoid potential buffer overflows - modified the status bar to display: Send mode: ->( / ) Recv mode: <{ } Additional testing platforms for this release: - Aaron Bentley (Univ Toronto) tested lft successfully under Debian Woody Linux * Aaron also contributed a variation of net device selection -- thanks lft 1.7: -------------------- - added option to disable reverse DNS host lookups - made several areas slightly more user-friendly - modified the status bar to display: Send mode: ->( / ) Recv mode: <( ) - changed "source" port from tcp/80 to tcp/25 most firewalls today permit tcp_established implicitly, not traffic from tcp/80 explicitly, therefore we figured sending packets from tcp/25 would be more likely to get through. - modified initialization of 32-bit unsigned integer used to calculate checksum LFT now builds again under Solaris, but still miscalculates checksums - fixed round trip time calculation - several other minor tweaks - tested under RedHat Linux, Darwin 5.5 (MacOS X v10.1) lft 1.6 (nils): --------------- - Great idea. lft-4.01/config/000755 000765 000024 00000000000 15250425210 013410 5ustar00vicstaff000000 000000 lft-4.01/lft.c000644 000765 000024 00000111727 15250413042 013105 0ustar00vicstaff000000 000000 /* This file is part of LFT. The LFT software provided in this Distribution is Copyright 2007 VOSTROM Holdings, Inc. The full text of our legal notices is contained in the file called COPYING, included with this Distribution. Authors: - Victor Oppleman - Eugene Antsilevitch Contact: https://pwhois.org/contact/ Other copyrights and former authors: - Portions copyright (c) Genuity, Inc. - Portions copyright (c) Markus Gothe - Portions copyright (c) Nils McCarthy */ #include "lft_lib.h" #ifdef HAVE_NCURSES #include #include "lft_watch.h" #endif /* DNS resolver tuning: short timeout/retry so failed lookups don't stall traces. * _res is the global resolver state; on Linux/glibc it is consulted by * getnameinfo() (which LFT now uses in place of gethostbyname/gethostbyaddr). * On macOS, getnameinfo() may route through mDNSResponder which ignores _res, * so lft_lib.c wraps getnameinfo() calls in alarm(1) as a hard ceiling. * res_ninit() with a local state would not affect getnameinfo(), so direct * _res modification remains correct for the Linux/glibc path. * _res is declared in . */ #include #include #if 0 static char def_payload[] = "\0\0\0\0\0\0\0\0\0\0"; /* default payload for UDP packets */ #endif const char *version = "4.01"; /* set version string */ static const char *version_date = "(09/2026)"; /* date of this version */ /* Sentinel value for the long-only --help option. Must not collide with any * printable ASCII character used as a short option. */ #define OPT_HELP 1 #define OPT_NO_ASCII_CHART 2 #define OPT_NO_LEARN 3 /* LFT-AUTOTUNE — disable topology inheritance */ #define OPT_LEARN_MARGIN 4 /* LFT-AUTOTUNE — TTL margin above learned path */ #define OPT_EXPAND_ADDR 5 /* --expand-addr — full v6 form, not shorthand */ #define OPT_SVG 6 /* --svg — self-contained styled SVG diagram */ #define OPT_MERMAID 7 /* --mermaid — styled Mermaid flowchart */ #define OPT_ECMP 8 /* --ecmp — expose ECMP load-balanced members */ #define OPT_SHOW_SEAMS 9 /* --show-seams — show AS/net seams in diagrams+watch */ #define OPT_COLOR 10 /* --color — force ANSI colour in -o output */ #define OPT_NO_COLOR 11 /* --no-color — disable ANSI colour in -o output */ #define OPT_JSON 12 /* --json — compact JSON document of the trace */ #define OPT_JSON_PRETTY 13 /* --json-pretty — indented JSON document */ #define OPT_GEOJSON 14 /* --geojson — RFC 7946 points + path line (pWhoIs geo) */ #define OPT_KML 15 /* --kml — OGC KML 2.2 document, 3-D arched path */ #define OPT_GEO_COLOR 16 /* --geo-color RRGGBB — path line colour (both) */ #define OPT_GEO_WIDTH 17 /* --geo-width N — path line width (both) */ #define OPT_GEO_ARC 18 /* --geo-arc F — KML arch height factor, 0 = flat */ #define OPT_WATCH_BACKOFF 19 /* --watch-backoff — adaptive interval under rate limiting */ #define ECMP_PROBES_MIN 15 /* probes per hop under --ecmp (member sampling) */ static struct option long_options[] = { {"sport", required_argument, 0, 's'}, {"dport", required_argument, 0, 'd'}, {"random-sport", no_argument, 0, 'z'}, {"tcp-options", no_argument, 0, 'B'}, {"realistic", no_argument, 0, 'B'}, {"min-probes", required_argument, 0, 'm'}, {"max-probes", required_argument, 0, 'M'}, {"device", required_argument, 0, 'D'}, {"receive-device", required_argument, 0, 'D'}, {"length", required_argument, 0, 'L'}, {"bytes", required_argument, 0, 'L'}, {"fin", no_argument, 0, 'F'}, {"fins", no_argument, 0, 'F'}, {"from-device", required_argument, 0, 'f'}, {"source-device", required_argument, 0, 'f'}, {"adaptive", no_argument, 0, 'E'}, {"udp", no_argument, 0, 'u'}, {"basic-tcp", no_argument, 0, 'b'}, {"icmp", no_argument, 0, 'p'}, {"ahead", required_argument, 0, 'a'}, {"scatter", required_argument, 0, 'c'}, {"timeout", required_argument, 0, 't'}, {"min-ttl", required_argument, 0, 'l'}, {"first-hop", required_argument, 0, 'l'}, {"max-hops", required_argument, 0, 'H'}, {"ttl-max", required_argument, 0, 'H'}, {"isn", required_argument, 0, 'q'}, {"minimize-delay", no_argument, 0, 'I'}, {"ignore-unreachables", no_argument, 0, 'i'}, {"tcp-window", required_argument, 0, 'w'}, {"numeric", no_argument, 0, 'n'}, {"hostnames-only", no_argument, 0, 'h'}, {"netname", no_argument, 0, 'N'}, {"asn", no_argument, 0, 'A'}, {"no-async-dns", no_argument, 0, 'Q'}, {"ripe-riswhois", no_argument, 0, 'r'}, {"radb", no_argument, 0, 'R'}, {"cymru", no_argument, 0, 'C'}, {"time-keeping", no_argument, 0, 'T'}, {"time-keeping-utc", no_argument, 0, 'U'}, {"quiet", no_argument, 0, 'S'}, {"silent", no_argument, 0, 'S'}, {"verbose", no_argument, 0, 'V'}, {"xml", no_argument, 0, 'x'}, {"graphviz", no_argument, 0, 'g'}, {"graphviz-icons", required_argument, 0, 'G'}, {"ascii", no_argument, 0, 'o'}, {"svg", no_argument, 0, OPT_SVG}, {"mermaid", no_argument, 0, OPT_MERMAID}, {"json", no_argument, 0, OPT_JSON}, {"json-pretty", no_argument, 0, OPT_JSON_PRETTY}, {"geojson", no_argument, 0, OPT_GEOJSON}, {"kml", no_argument, 0, OPT_KML}, {"geo-color", required_argument, 0, OPT_GEO_COLOR}, {"geo-width", required_argument, 0, OPT_GEO_WIDTH}, {"geo-arc", required_argument, 0, OPT_GEO_ARC}, {"show-seams", no_argument, 0, OPT_SHOW_SEAMS}, {"color", no_argument, 0, OPT_COLOR}, {"no-color", no_argument, 0, OPT_NO_COLOR}, {"no-colour", no_argument, 0, OPT_NO_COLOR}, {"ecmp", no_argument, 0, OPT_ECMP}, {"verify-seam", no_argument, 0, 'y'}, {"version", no_argument, 0, 'v'}, {"mtu", no_argument, 0, 'K'}, {"path-mtu", no_argument, 0, 'K'}, {"stats", required_argument, 0, 'j'}, {"no-async-chart", no_argument, 0, OPT_NO_ASCII_CHART}, {"no-ascii-chart", no_argument, 0, OPT_NO_ASCII_CHART}, {"watch", optional_argument, 0, 'W'}, {"continuous", optional_argument, 0, 'W'}, {"watch-backoff", no_argument, 0, OPT_WATCH_BACKOFF}, /* LFT-AUTOTUNE — see lft_lib.c lft_auto_tune_from_learned() */ {"no-learn", no_argument, 0, OPT_NO_LEARN}, {"learn-margin", required_argument, 0, OPT_LEARN_MARGIN}, {"ipv4", no_argument, 0, '4'}, {"ipv6", no_argument, 0, '6'}, {"expand-addr", no_argument, 0, OPT_EXPAND_ADDR}, {"help", no_argument, 0, OPT_HELP}, {0, 0, 0, 0} }; static void usage(lft_session_params * sess, char *prog) { fprintf (stderr, "\nLayer Four Traceroute (LFT)\n\n" " - the alternative traceroute tool for network [reverse] engineers\n" " visit http://www.pwhois.org\n\n" "Usage: %s [] [ <...>] \n" "\nMainstream Options:\n" " -s Set source port (default: random dynamic port in 49152-65535)\n" " -d Destination port number, same as target:port (default: 443)\n" " -z Pseudo-randomize the source port number\n" " -m Minimum number of probes to send per hop\n" " -M Maximum number of probes to send per hop\n" " -D Listen-on/use a device by name or address (\"en1\" or \"1.2.3.4\")\n" " -L Set the length of the probe packet in bytes (ignored when -K is active)\n" " -K Active path MTU discovery: probe at interface MTU, reduce on PTB\n" " -j Collect N probes/hop for RTT stats (min/avg/max ±jitter); use with -o for chart\n" " --no-ascii-chart Suppress the candlestick chart from -j output (keep numerical stats)\n" " -W N Continuous monitoring mode (ncurses); N=interval seconds (default 10)\n" " --watch-backoff Lengthen the watch interval automatically when loss points to rate limiting\n" " Also: --watch=N / --continuous=N (e.g. --watch 5)\n" #ifndef HAVE_NCURSES " (unavailable — rebuild with --with-ncurses)\n" #endif " --no-learn Disable topology inheritance — every --watch cycle\n" " probes the full TTL ladder. Default: enabled (cycle 2+\n" " reuses prior path length, RTT envelope, and tighter\n" " timeouts after 3 stable cycles)\n" " --learn-margin N TTL probe margin above the learned path length\n" " (default 2; clamped 0..10)\n" "\nAdvanced Tracing Options:\n" " -F, --fins Use TCP FIN packets exclusively (defaults are SYN)\n" " -f Send using a device by name or address (spoofing allowed)\n" " -e, -E, --adaptive Use LFT's stateful engine to detect firewalls and path anomalies\n" " -u Use traditional UDP (probes) for tracing instead of TCP\n" " -b Basic TCP trace\n" " -p Use traditional ICMP (probes) for tracing instead of TCP\n" /*" -P Use RFC1393 IP option for tracing instead of TCP\n" */ " -a Number of hops forward to query before pausing to wait for replies\n" " -c Minimum number of milliseconds between subsequent probes\n" " -t Maximum RTT to wait before assuming packet was dropped\n" " -l Minimum TTL to use on outgoing packets (skips close-proximity hops)\n" " -H Maximum number of hops to traverse (max TTL of packets)\n" " -q Set the initial sequence number (ISN) manually\n" " -w Set the TCP window size in bytes (TCP traces only; default: 32768)\n" " -B, --realistic Send realistic client TCP options on SYN probes (see --tcp-options)\n" " -4 | -6 Force IPv4 or IPv6 (default: match the target)\n" " --expand-addr Show IPv6 addresses fully expanded instead of RFC 5952 shorthand\n" " -I Set the ToS field in the IP packet to minimize-delay\n" " -i Ignore ICMP unreachable messages; trace continues through firewalls\n" " -y Test destination upstream transition hop\n" "\nResolution Options:\n" " -n Display hosts numerically; disable use of the DNS resolver\n" " -h Display hosts symbolically; suppress IP address display\n" " -N Display network or AS names where appropriate\n" " -A Display AS numbers resolved by Prefix WhoIs\n" "\nAdvanced Resolution Options:\n" " -Q Disable asynchronous DNS" #ifdef HAVE_CARES "; fall back to synchronous resolver\n" #else " (not compiled in; flag accepted but has no effect)\n" #endif " -r Use RIPE NCC's RIS to resolve ASNs instead of Prefix WhoIs\n" " -R Use the RADB to resolve ASNs instead of Prefix WhoIs\n" " -C Use Cymru to resolve ASNs instead of Prefix WhoIs\n" "\nVerbosity Options and Status:\n" " -T Use execution timers and summarize where LFT spent its time\n" " -U Display all times in UTC (GMT0). Activates -T option automatically\n" " -S Disable the status bar (only show the completed trace)\n" " -v Display LFT's version information and exit\n" "\nOutput Options:\n" " -V Display verbose/debug output. Use more \'V\'s for additional detail\n" " -W Watch mode: ncurses textual user interface. Several dashboards.\n" " -x XML output\n" " -g GraphViz output\n" " -G Enable GraphViz output and override path to icons\n" " -o ASCII art hop diagram (color and Unicode auto-detected)\n" " --svg Self-contained styled SVG diagram of the trace\n" " --mermaid Styled Mermaid flowchart definition of the trace\n" " --json JSON document of the trace (compact, one line)\n" " --json-pretty JSON document of the trace, indented\n" " --geojson GeoJSON of the path: located hops + route line (pWhoIs geo)\n" " --kml KML of the path (OGC KML 2.2), drawn as 3-D arches\n" " --geo-color=RRGGBB Path color for --geojson/--kml (default 17b890; \"rtt\" = latency ramp)\n" " --geo-width=N Path line width for --geojson/--kml (default 3)\n" " --geo-arc=F KML arch height factor, 0 = flat (default 1.0)\n" " --show-seams Show AS/network seams in diagrams and watch mode (off by default)\n" " --color Force colorized -o output even when not writing to a terminal\n" " --no-color Disable colorized -o output (default: auto-detect the terminal)\n" " --ecmp Expose ECMP load-balanced members (diamonds) per hop\n" " --help Show this summary; long-form options available, see lft(8)\n" "\n" "Default is: %s -s %d -d %d -m %d -M %d -a %d -c %.0f -t %d -H %d \n\n", prog, prog, sess->sport, sess->dport, sess->retry_min, sess->retry_max, sess->ahead_limit, sess->scatter_ms, sess->timeout_ms, sess->ttl_limit); exit(EXIT_FAILURE); } static void show_version (void) { /* Compile-time options block — one big fprintf so #ifdefs can gate strings */ printf ("\n" "Layer Four Traceroute (LFT) - version %s %s\n\n" " - the alternative traceroute tool for network [reverse] engineers\n\n" " Compile-time options:\n\n" #if defined(DARWIN) || defined(__APPLE__) " + MacOS (or Darwin)\n" #endif #if defined(UNIVERSAL) " + Universal binary\n" #endif #if defined(NETBSD) " + NetBSD\n" #endif #if defined(OPENBSD) " + OpenBSD\n" #endif #if defined(BSD_IP_STACK) " + BSD IP stack\n" #endif #if defined(BSD) " + BSD platform\n" #endif #if defined(linux) " + Linux platform\n" #endif #if defined(sun) " + SUN platform\n" #endif #if !defined(sun) && !defined(linux) && !defined(BSD_IP_STACK) && !defined(OPENBSD) " (unknown architecture)\n" #endif #if defined(SCREWED_IP_LEN) " + IP length big-endian\n" #else " + IP length little-endian\n" #endif #if defined(IP_HDRINCL) " + Full IP headers for raw sockets\n" #else " + Without IP header inclusion\n" #endif #if defined( __CYGWIN__ ) || defined( WIN32 ) || defined(_WIN32) || defined( USE_GTOD ) " + Calling gettimeofday() on each packet\n" #endif " + " HOST_SYSTEM_TYPE "\n" #ifdef LFT_DONT_USE_SAFE_UID " + Privilege drop disabled (LFT_DONT_USE_SAFE_UID)\n" #endif "\n Capabilities:\n\n", version, version_date); /* Capabilities — runtime version strings require separate fprintf calls */ printf(" + Packet capture: %s\n", pcap_lib_version()); #ifdef HAVE_CARES printf(" + c-ares async DNS: c-ares %s\n", ares_version(NULL)); #else printf(" - c-ares async DNS: unavailable\n"); #endif #ifdef HAVE_NCURSES printf(" + ncurses TUI: %s\n", curses_version()); #else printf(" - ncurses TUI: unavailable\n"); #endif #ifdef HAVE_IPV6 printf(" + IPv6 tracing: enabled\n"); #else printf(" - IPv6 tracing: unavailable\n"); #endif printf("\n"); exit(EXIT_SUCCESS); } static void lft_check_permissions(const char *binpath) { /* Fast path: effective root covers both direct root and setuid-root. */ if (geteuid() == 0) return; #if defined(__CYGWIN__) || defined(WIN32) || defined(_WIN32) /* On Windows/Cygwin, raw socket access requires Administrator privileges * and the Npcap packet capture service must be installed and running. */ (void)binpath; fprintf(stderr, "\nLFT: insufficient privileges.\n\n" " On Windows, LFT must be run as Administrator, and the Npcap\n" " packet capture service must be installed and running.\n" " Right-click the terminal and choose \"Run as administrator\",\n" " or launch from an elevated command prompt.\n\n"); exit(EXIT_FAILURE); #else /* Try to open a raw socket — the same privilege that pcap will demand. */ int sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (sock >= 0) { close(sock); return; /* capabilities or other mechanism is granting access */ } if (errno != EPERM && errno != EACCES) return; /* unexpected error; let the real code surface it */ /* No permission. Emit platform-appropriate guidance. */ #if defined(DARWIN) || defined(__APPLE__) (void)binpath; /* suppress unused-variable warning on this path if needed */ fprintf(stderr, "\nLFT: insufficient privileges — raw socket and BPF access are required.\n\n" " LFT is not running as root. macOS does not support Linux-style\n" " capability grants (setcap); the setuid-root method is required:\n\n" " sudo chown root %s && sudo chmod u+s %s\n\n", binpath, binpath); #elif defined(linux) || defined(__linux__) fprintf(stderr, "\nLFT: insufficient privileges — raw socket access is required.\n\n" " LFT does not appear to have the necessary Linux capabilities\n" " (cap_net_raw, cap_net_admin).\n\n" " Recommended fix using Linux capabilities (no setuid-root needed):\n\n" " sudo setcap 'cap_net_raw,cap_net_admin=eip' %s\n\n" " To verify: getcap %s\n" " To remove: sudo setcap -r %s\n\n" " As a last resort, the traditional setuid-root method also works:\n\n" " sudo chown root %s && sudo chmod u+s %s\n\n", binpath, binpath, binpath, binpath, binpath); #elif defined(BSD_IP_STACK) fprintf(stderr, "\nLFT: insufficient privileges — raw socket access is required.\n\n" " LFT is not running as root. Use the setuid-root method:\n\n" " sudo chown root %s && sudo chmod u+s %s\n\n", binpath, binpath); #else fprintf(stderr, "\nLFT: insufficient privileges — raw socket access is required.\n\n" " LFT is not running as root. You may need to run it as root\n" " or set the setuid-root bit on the binary:\n\n" " sudo chown root %s && sudo chmod u+s %s\n\n", binpath, binpath); #endif exit(EXIT_FAILURE); #endif /* !Windows */ } extern int main (int argc, char **argv) { lft_session_params * sess; int ch; int long_index = 0; char *cp = NULL; struct timeval tb; #if defined( __CYGWIN__ ) || defined( WIN32 ) || defined(_WIN32) WORD wVersionRequested; WSADATA wsaData; wVersionRequested = MAKEWORD( 2, 2 ); WSAStartup( wVersionRequested, &wsaData ); #endif res_init(); _res.retrans = 1; /* 1s timeout per query attempt */ _res.retry = 1; /* 1 retry; default is 4 which causes 20s+ stalls */ sess = LFTSessionOpen(); setbuf(stdout, NULL); gettimeofday (&tb, NULL); /* eventually this might want to use /dev/urandom or * something on machines that have it. otherwise, * this does a fairly decent job of using the system * clock. * * multiply tv_usec (range 0-1000000) to be in range 0-2^31, * and xor to randomize the high bits of tv_sec that don't * change very much. */ srand(tb.tv_sec ^ (tb.tv_usec * 2147)); int user_set_retry_min = 0, user_set_retry_max = 0; while ((ch = getopt_long(argc, argv, "ABa:bCc:D:d:EeFf:H:hIij:KL:l:M:m:NnoPpq:QRrSs:Tt:UuVvW::xw:zgG:y46", long_options, &long_index)) != EOF) switch (ch) { case 'f': LFTSetupSendDevice(sess, optarg); break; case 'F': LFTSetupFIN(sess); break; case 'h': LFTSetupDispSymbHost(sess); break; case 'u': LFTSetupUDPMode(sess); break; case 'r': LFTSetupRISLookup(sess); break; case 'R': LFTSetupRADBLookup(sess); break; case 'C': LFTSetupCYMRULookup(sess); break; case 'd': LFTSetupDestinationPort(sess, optarg); break; case 'L': LFTSetupLengthOfPacket(sess, (int)strtol(optarg, (char **)NULL, 10)); break; case 'K': LFTSetupPMTUD(sess); break; case 'q': sess->seq_start = strtol(optarg, (char **)NULL, 10); break; case 'w': sess->win_len = (int)strtol(optarg, (char **)NULL, 10); break; case 'm': sess->retry_min = (int)strtol(optarg, (char **)NULL, 10); user_set_retry_min = 1; break; case 'M': sess->retry_max = (int)strtol(optarg, (char **)NULL, 10); user_set_retry_max = 1; break; case 'j': { int n = (int)strtol(optarg, (char **)NULL, 10); LFTSetupRTTStats(sess, n); break; } case '4': sess->force_af = AF_INET; break; case '6': #ifdef HAVE_IPV6 sess->force_af = AF_INET6; #else fprintf(stderr, "LFT was not built with IPv6 support\n"); exit(EXIT_FAILURE); #endif break; case OPT_EXPAND_ADDR: sess->addr_expand = 1; break; case OPT_NO_ASCII_CHART: sess->no_ascii_chart = 1; break; case OPT_SVG: setOutputStyle(5); break; case OPT_MERMAID: setOutputStyle(6); break; case OPT_JSON: setOutputStyle(7); break; case OPT_JSON_PRETTY: setOutputStyle(7); sess->json_pretty = 1; break; case OPT_GEOJSON: setOutputStyle(8); sess->do_aslookup = 1; /* geolocation rides the pWhoIs lookup */ break; case OPT_KML: setOutputStyle(9); sess->do_aslookup = 1; break; case OPT_GEO_COLOR: if ((strlen(optarg) == 6 && strspn(optarg, "0123456789abcdefABCDEF") == 6) || strcmp(optarg, "rtt") == 0) snprintf(sess->geo_color, sizeof sess->geo_color, "%s", optarg); else { fprintf(stderr, "lft: --geo-color expects six hex digits (RRGGBB) or \"rtt\"\n"); exit(EXIT_FAILURE); } break; case OPT_GEO_WIDTH: sess->geo_width = atoi(optarg); if (sess->geo_width < 1) sess->geo_width = 1; if (sess->geo_width > 20) sess->geo_width = 20; break; case OPT_GEO_ARC: sess->geo_arc = atof(optarg); if (sess->geo_arc < 0) sess->geo_arc = 0; if (sess->geo_arc > 5) sess->geo_arc = 5; break; case OPT_SHOW_SEAMS: sess->show_seams = 1; break; case OPT_COLOR: sess->color_mode = 1; break; case OPT_NO_COLOR: sess->color_mode = 2; break; case OPT_ECMP: sess->ecmp = 1; break; /* LFT-AUTOTUNE — runtime toggles for topology inheritance */ case OPT_WATCH_BACKOFF: sess->watch_backoff = 1; break; case OPT_NO_LEARN: sess->no_autotune = 1; break; case OPT_LEARN_MARGIN: { int m = (int)strtol(optarg, (char **)NULL, 10); if (m < 0) m = 0; if (m > 10) m = 10; sess->learn_margin = m; break; } case 'N': sess->do_netlookup = 1; break; case 'A': sess->do_aslookup = 1; break; case 'n': LFTSetupDisableResolver(sess); break; case 'T': sess->timetrace = 1; break; case 's': LFTSetupSourcePort(sess, lft_resolve_port(sess,optarg)); break; case 'E': case 'e': LFTSetupAdaptiveMode(sess); break; case 'S': sess->nostatus = 1; break; case 'D': LFTSetupDevice(sess,optarg); break; case 'a': sess->ahead_limit = (int)strtol(optarg, (char **)NULL, 10); break; case 'c': sess->scatter_ms = (int)strtol(optarg, (char **)NULL, 10); if (sess->scatter_ms < 1) sess->scatter_ms = 1; if (sess->scatter_ms > 100) sess->scatter_ms = 100; break; case 't': sess->timeout_ms = (int)strtol(optarg, (char **)NULL, 10); break; case 'p': sess->protocol = 2; break; /*case 'P': //We comment this option. Hardly any routers support this. sess->protocol = 3; break;*/ case 'b': sess->protocol = 4; break; case 'H': if (strtol(optarg, (char **)NULL, 10) > 255) sess->ttl_limit = 255; else sess->ttl_limit = (int)strtol(optarg, (char **)NULL, 10); break; case 'l': sess->ttl_min = (int)strtol(optarg, (char **)NULL, 10); sess->hop_info_length = sess->ttl_min; if (sess->ttl_min > 0) sess->ttl_min--; break; case 'i': sess->break_on_icmp = 0; break; case 'I': sess->set_tos = 1; break; case 'v': show_version(); break; case 'U': /* show all times in UTC */ LFTSetupUTCTimes(sess); break; case 'V': sess->noisy++; sess->nostatus = 1; break; case 'z': /* -z: one consistent random source port for the whole trace * (opt out of the per-hop default). Do not clobber an * explicit -s port if one was given. */ sess->random_source = 1; if (!sess->user_sport) sess->sport = rand()%16384+49152; break; case 'B': /* -B/--tcp-options: emit a realistic client SYN option set so * probes resemble a real OS SYN rather than a bare one. Takes * effect on the default and adaptive TCP engines only. */ sess->tcp_options = 1; break; case 'x': setOutputStyle(1); break; case 'g': setOutputStyle(2); break; case 'G': setOutputStyle(2); sess->graphviz_icon_path=optarg; break; case 'o': setOutputStyle(3); break; case 'y': sess->check_seam=1; break; case 'Q': #ifdef HAVE_CARES sess->async_dns_disabled = 1; #else fprintf(stderr, "LFT: -Q has no effect: c-ares was not compiled in\n"); #endif break; case 'W': { int n; /* getopt_long only binds an optional argument in the * attached forms (-WN / --watch=N); "-W 5" and "--watch 5" * leave optarg NULL and the 5 becomes a stray positional. * Peek at the next argv element and consume it as the * interval when it is a bare integer — a valid target can * never be all digits, so this is unambiguous. */ if (!optarg && optind < argc && argv[optind][0] != '\0') { const char *p = argv[optind]; int all_digits = 1; while (*p) { if (*p < '0' || *p > '9') { all_digits = 0; break; } p++; } if (all_digits) optarg = argv[optind++]; } n = optarg ? (int)strtol(optarg, (char **)NULL, 10) : 0; if (n <= 0) n = 5; /* default interval: 5 seconds */ #ifdef HAVE_NCURSES sess->watch_mode = 1; sess->watch_interval = n; setOutputStyle(4); #else fprintf(stderr, "\nLFT: Support for nCurses was not enabled at compile-time.\n" " Please re-run ./configure and rebuild with --with-ncurses\n" " if ncurses is not found automatically by autoconf.\n\n"); exit(EXIT_FAILURE); #endif break; } case OPT_HELP: usage(sess, argv[0]); break; default: usage(sess, argv[0]); } /* rtt_stats: apply probe count to retry_min/retry_max (unless user explicitly set them) */ if (sess->rtt_stats) { if (!user_set_retry_min) sess->retry_min = sess->rtt_stats; if (!user_set_retry_max) sess->retry_max = sess->rtt_stats; if (sess->retry_max < sess->retry_min) sess->retry_max = sess->retry_min; /* auto ahead_limit = 2 × retry_min so the pipeline keeps flowing without * excessive target overrun while collecting multi-probe RTT samples */ sess->ahead_limit = sess->retry_min * 2; } /* Seam verification (-y) implies showing seams; --show-seams alone shows * them without running the upstream-transition test. */ if (sess->check_seam) sess->show_seams = 1; if((argc - optind) < 1) usage(sess, argv[0]); if (sess->noisy && !outputStyleIsXML() && !outputStyleIsGraphViz() && !outputStyleIsASCII() && !outputStyleIsSVG() && !outputStyleIsMermaid() && !outputStyleIsJSON() && !outputStyleIsGeoJSON() && !outputStyleIsKML()) printf ("Layer Four Traceroute (LFT) version %s", version); if (sess->noisy > 1 && !outputStyleIsXML() && !outputStyleIsGraphViz() && !outputStyleIsASCII() && !outputStyleIsSVG() && !outputStyleIsMermaid() && !outputStyleIsJSON() && !outputStyleIsGeoJSON() && !outputStyleIsKML()) printf (" ... (verbosity level %d)",sess->noisy); if (sess->noisy && !outputStyleIsXML() && !outputStyleIsGraphViz() && !outputStyleIsASCII() && !outputStyleIsSVG() && !outputStyleIsMermaid() && !outputStyleIsJSON() && !outputStyleIsGeoJSON() && !outputStyleIsKML()) printf ("\n"); /* --ecmp: sample this many flows (varying source port) from each hop that * REPLIES, to map its ECMP members. This does NOT touch retry_max, so a * silent hop is still declared neglected after the normal give-up count * rather than being probed ecmp_probes times. Respect a larger user -m. */ if (sess->ecmp) { sess->ecmp_probes = ECMP_PROBES_MIN; if (sess->retry_min > sess->ecmp_probes) sess->ecmp_probes = sess->retry_min; } if(outputStyleIsXML()) { printf("\n"); printf("\n",sess->noisy); } sess->hostname = argv[optind++]; sess->hostname_lsrr_size = 0; while (optind < argc) { sess->hostname_lsrr[sess->hostname_lsrr_size++] = argv[optind++]; if (sess->hostname_lsrr_size > IP_LSRR_DEST) { if(outputStyleIsXML()) { printf("Unknown host: Too many LSRR hosts - maximum is 8"); printf("\n"); } else { fprintf(stderr, "LFT: Too many LSRR hosts - maximum is 8\n"); } exit(EXIT_FAILURE); } } if (sess->hostname_lsrr_size > 0) { sess->hostname_lsrr[sess->hostname_lsrr_size++] = sess->hostname; sess->hostname = sess->hostname_lsrr[0]; } /* Loose source routing is an IPv4 IP option; IPv6 has no equivalent. */ if (sess->force_af == AF_INET6 && sess->hostname_lsrr_size > 0) { if (outputStyleIsXML()) { printf("Loose source routing is not supported for IPv6"); printf("\n"); } else { fprintf(stderr, "LFT: loose source routing (gateways) is not supported for IPv6\n"); } exit(EXIT_FAILURE); } /* Target may be host:port, an IPv6 [addr]:port, or a bare IPv6 literal. * A bare v6 literal contains more than one ':' and must NOT be split on * the first colon; the bracket form is the way to attach a port to it. */ { char *portstr = NULL; if (sess->hostname[0] == '[') { char *rb = strchr(sess->hostname, ']'); if (rb) { *rb = '\0'; sess->hostname++; /* strip leading '[' */ if (*(rb + 1) == ':') portstr = rb + 2; /* "[addr]:port" */ } } else if ((cp = strchr(sess->hostname, ':')) && !strchr(cp + 1, ':')) { *cp = '\0'; /* exactly one ':' => host:port */ portstr = cp + 1; } /* (bare multi-colon v6 literal falls through: no split, -d/default port) */ if (portstr && !sess->dflag) { sess->dport = lft_resolve_port(sess, portstr); if (sess->dport > 65534) { if(outputStyleIsXML()) printf("Starting port %d is too high. Will use %d instead.", sess->dport, 65534); else fprintf(stderr, "LFT warning: Starting port %d is too high. Will use %d instead.\n", sess->dport, 65534); sess->dport = 65534; } sess->auto_ports = 0; } } { char *binpath = realpath(argv[0], NULL); lft_check_permissions(binpath ? binpath : argv[0]); if (binpath) free(binpath); } #ifdef HAVE_NCURSES if (sess->watch_mode) { WatchModeRun(sess); } else { #endif if (getOutputStyle() == 3) lft_o_spinner_start(sess); /* start live probe counter for -o */ LFTExecute(sess); #ifdef HAVE_NCURSES } #endif LFTSessionClose(sess); #if defined( __CYGWIN__ ) || defined( WIN32 ) || defined(_WIN32) WSACleanup(); #endif if(outputStyleIsXML()) printf("\n"); //getch(); return 0; } lft-4.01/include/000755 000765 000024 00000000000 15250425210 013566 5ustar00vicstaff000000 000000 lft-4.01/lft_watch.c000644 000765 000024 00000635256 15247705774 014331 0ustar00vicstaff000000 000000 /* * lft_watch.c -- ncurses continuous monitoring mode for LFT * * Compiled only when HAVE_NCURSES is defined (./configure detects ncurses * or the user provides --with-ncurses=PATH). * * This file is part of LFT. * * The LFT software provided in this Distribution is * Copyright 2007 VOSTROM Holdings, Inc. * * The full text of our legal notices is contained in the file called * COPYING, included with this Distribution. */ #include "config/acconfig.h" #ifdef HAVE_NCURSES #include "lft_watch.h" #include "lft_lib.h" #include "whois.h" /* ASCIIOutput is defined in lft_lib.c but not exposed in lft_lib.h */ void ASCIIOutput(lft_session_params *sess); #ifdef HAVE_MVIN_WCH #define NCURSES_WIDECHAR 1 /* enable cchar_t / mvin_wch for the screen capture */ #endif #include #include #include #include #include #include #include #include #include #include #include /* struct winsize, TIOCGWINSZ — Linux glibc * does not pull these in transitively the way * macOS does, so include explicitly. */ #include #include /* AF-aware address rendering for watch mode. v6 uses RFC 5952 shorthand * (or the expanded form under --expand-addr); v4 keeps inet_ntoa. Each * writes into the caller's buffer and returns it. */ #ifdef HAVE_IPV6 static const char *watch_ntop_hop(const lft_session_params *s, const struct trace_packet_info_s *tp, char *buf, size_t sz) { if (s->af == AF_INET6) { lft_ntop_fmt(&tp->hopaddr6, buf, sz, s->addr_expand); return buf; } snprintf(buf, sz, "%s", inet_ntoa(tp->hopaddr)); return buf; } static const char *watch_ntop_ss(const lft_session_params *s, const struct sockaddr_storage *ss, struct in_addr v4, char *buf, size_t sz) { if (s->af == AF_INET6) { lft_addr_t a; lft_addr_from_ss(ss, &a); lft_ntop_fmt(&a, buf, sz, s->addr_expand); return buf; } snprintf(buf, sz, "%s", inet_ntoa(v4)); return buf; } /* AF-aware "did the responding hop change" for the watch dedup. */ static int watch_hop_changed(const lft_session_params *s, const struct trace_packet_info_s *tp) { if (s->af == AF_INET6) return !lft_addr_eq(&tp->last_hop6, &tp->hopaddr6); return tp->last_hop.s_addr != tp->hopaddr.s_addr; } #else #define watch_ntop_hop(s, tp, buf, sz) \ (snprintf((buf), (sz), "%s", inet_ntoa((tp)->hopaddr)), (buf)) #define watch_ntop_ss(s, ss, v4, buf, sz) \ (snprintf((buf), (sz), "%s", inet_ntoa(v4)), (buf)) #define watch_hop_changed(s, tp) ((tp)->last_hop.s_addr != (tp)->hopaddr.s_addr) #endif /* Forward declarations for helper functions */ /* ========================================================================= * Internal helpers * ========================================================================= */ /* Braille spinner frames (Unicode, 8-frame cycle) */ static const char *SPINNER_FRAMES[WATCH_SPINNER_FRAMES] = { "\xe2\xa0\x8b", /* ⠋ */ "\xe2\xa0\x99", /* ⠙ */ "\xe2\xa0\xb9", /* ⠹ */ "\xe2\xa0\xb8", /* ⠸ */ "\xe2\xa0\xbc", /* ⠼ */ "\xe2\xa0\xb4", /* ⠴ */ "\xe2\xa0\xa6", /* ⠦ */ "\xe2\xa0\x87", /* ⠇ */ }; static const char *SPINNER_ASCII[WATCH_SPINNER_FRAMES] = { "-", "\\", "|", "/", "-", "\\", "|", "/" }; /* Block chars for the RTT history sparkline (Unicode) */ static const char *BLOCK_CHARS[] = { " ", /* 0: no data / timeout */ "\xe2\x96\x81", /* ▁ */ "\xe2\x96\x82", /* ▂ */ "\xe2\x96\x83", /* ▃ */ "\xe2\x96\x84", /* ▄ */ "\xe2\x96\x85", /* ▅ */ "\xe2\x96\x86", /* ▆ */ "\xe2\x96\x87", /* ▇ */ "\xe2\x96\x88", /* █ */ }; #define N_BLOCK_CHARS 9 /* ncurses color pair indices */ #define CP_HEADER 1 /* white on blue */ #define CP_NORMAL 2 /* default fg/bg */ #define CP_GREEN 3 /* green (good RTT) */ #define CP_YELLOW 4 /* yellow (mild) */ #define CP_ORANGE 5 /* magenta (moderate — no true orange in ANSI) */ #define CP_MAGENTA 5 /* alias: magenta for loss/cloaked markers */ #define CP_RED 6 /* red (high RTT / loss) */ #define CP_HIGHLIGHT 7 /* reverse video for flash */ #define CP_STATUS 8 /* cyan on black */ #define CP_DRAWER 9 /* white on black for log drawer */ #define CP_BLUE 10 /* blue bold for ↑ sent counter */ #define CP_LABEL 11 /* light steel blue (xterm-256 147) — column labels */ #define CP_TBLHEAD 12 /* alice blue (xterm-256 195) — timeline table headers */ #define CP_SENT 13 /* medium slate blue (xterm-256 75) — ↑ sent counter */ #define CP_KEYHINT 14 /* cornflower (xterm-256 111) — row 2 key-hint menu */ #define CP_KEYLTR 15 /* light sky blue (xterm-256 117) — bracketed letters */ /* RTT → color pair mapping (ms thresholds) — used for bar chart and host/avg */ static int rtt_color(double rtt_ms) { if (rtt_ms <= 0) return CP_RED; if (rtt_ms < 30) return CP_GREEN; if (rtt_ms < 80) return CP_YELLOW; if (rtt_ms < 150) return CP_ORANGE; return CP_RED; } /* AVG column — finer thresholds than rtt_color() */ static int avg_color(double avg_ms) { if (avg_ms < 20) return CP_GREEN; if (avg_ms < 100) return CP_NORMAL; if (avg_ms < 250) return CP_YELLOW; return CP_RED; } /* JITTER column */ static int jitter_color(double jitter_ms) { if (jitter_ms < 10) return CP_GREEN; if (jitter_ms < 30) return CP_YELLOW; return CP_RED; } /* HOST column — based on loss rate, not RTT */ static int host_color(double loss_pct) { if (loss_pct <= 0) return CP_GREEN; if (loss_pct < 10) return CP_YELLOW; return CP_RED; } /* Loss % → color pair */ static int loss_color(double loss_pct) { if (loss_pct <= 0) return CP_NORMAL; if (loss_pct < 5) return CP_YELLOW; if (loss_pct < 25) return CP_ORANGE; return CP_RED; } /* ========================================================================= * Signal handling for SIGWINCH (terminal resize) * ========================================================================= */ static volatile int watch_resize_pending = 0; /* Termination signal handlers — installed while ncurses is live so Ctrl-C * calls endwin() before dying, leaving the terminal in a clean state. */ static struct sigaction g_old_sa_sigint_watch; static struct sigaction g_old_sa_sigterm_watch; static struct sigaction g_old_sa_sighup_watch; static struct sigaction g_old_sa_sigquit_watch; static volatile sig_atomic_t g_watch_ncurses_active = 0; /* 1 = ncurses is live */ static int g_watch_atexit = 0; /* 1 = atexit registered */ static void watch_sigwinch(int sig) { (void)sig; watch_resize_pending = 1; } /* Termination signal handler: restore terminal then re-raise the signal. * endwin() is not strictly async-signal-safe per POSIX, but it is the * standard ncurses idiom (used by vim, htop, etc.) and safe in practice * on every platform LFT targets. Without it Ctrl-C leaves the terminal * in raw / cursor-hidden mode. */ static void watch_sig_fatal(int sig) { if (g_watch_ncurses_active) { endwin(); g_watch_ncurses_active = 0; } /* Restore original handlers before re-raising so the process receives * the default action (core-dump for SIGQUIT, terminate for the rest). */ sigaction(SIGINT, &g_old_sa_sigint_watch, NULL); sigaction(SIGTERM, &g_old_sa_sigterm_watch, NULL); sigaction(SIGHUP, &g_old_sa_sighup_watch, NULL); sigaction(SIGQUIT, &g_old_sa_sigquit_watch, NULL); raise(sig); } /* atexit safety-net: catches any exit() path that bypasses watch_teardown_ncurses */ static void watch_atexit(void) { if (g_watch_ncurses_active) { endwin(); g_watch_ncurses_active = 0; } } /* ========================================================================= * Spinner animation via SIGALRM * * During a blocking LFTExecute() call, no ncurses code runs so the spinner * freezes. We work around this by arming a repeating SIGALRM that writes * the next spinner frame directly to /dev/tty, bypassing both the ncurses * layer and the stdout→/dev/null redirect that is active during a trace. * * Only async-signal-safe operations are used in the handler (write + integer * arithmetic). The handler is installed with SA_RESTART so that select() * and recv() inside LFTExecute() restart transparently instead of failing * with EINTR. * ========================================================================= */ static volatile sig_atomic_t g_alarm_frame = 0; static watch_session_t *g_watch_ws = NULL; /* set in WatchModeRun for SIGALRM access */ static struct sigaction g_old_sa_alarm; static char g_alarm_cycle_text[80]; /* " tracing cycle N/∞ Xs " */ static int g_alarm_cycle_text_len = 0; /* newterm() state — ncurses is opened on /dev/tty so stdout redirect has no effect */ static SCREEN *g_ncurses_screen = NULL; static FILE *g_tty_in = NULL; static FILE *g_tty_out = NULL; /* Forward declaration — needed by watch_spinner_sigalrm before its definition */ static void watch_render_status_bar(watch_session_t *ws, int spinner_frame); /* Format integer with comma thousands separator into buf[size]. * Returns chars written (not including NUL). Stack-only, safe from signal handler. */ 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; } /* SIGALRM handler: updates the status bar via ncurses. * Because ncurses is initialised on /dev/tty (not stdout), the stdout→/dev/null * redirect active during LFTExecute does not affect ncurses output. We can * call ncurses functions and doupdate() directly from the signal handler. * Single-threaded app: no re-entrancy risk from ncurses. */ static void watch_spinner_sigalrm(int sig) { int frame; (void)sig; if (!g_watch_ws) return; frame = ((int)g_alarm_frame + 1) % WATCH_SPINNER_FRAMES; g_alarm_frame = (sig_atomic_t)frame; /* Update sticky-max / sticky-true fields; leave total_replies and * total_sent alone — watch_render_status_bar adds the current-cycle * live count on top of the accumulated baseline when in_trace=1. */ { int hops = lft_o_get_hops(); int tgt = lft_o_get_target(); (void)lft_o_consume_tfail(); /* consume flash flag even if unused */ if (g_watch_ws->max_hops_seen > hops) hops = g_watch_ws->max_hops_seen; if (g_watch_ws->target_ever_reached) tgt = 1; if (hops > g_watch_ws->max_hops_seen) g_watch_ws->max_hops_seen = hops; if (tgt) g_watch_ws->target_ever_reached = 1; } watch_render_status_bar(g_watch_ws, frame); wnoutrefresh(stdscr); doupdate(); } /* Arm the 250 ms SIGALRM spinner. */ static void watch_spinner_arm(void) { struct sigaction sa; struct itimerval itv; memset(&sa, 0, sizeof(sa)); sa.sa_handler = watch_spinner_sigalrm; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; /* restart select()/recv() on signal */ sigaction(SIGALRM, &sa, &g_old_sa_alarm); 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); } /* Disarm the SIGALRM spinner and restore the previous handler. */ static void watch_spinner_disarm(void) { struct itimerval zero; memset(&zero, 0, sizeof(zero)); setitimer(ITIMER_REAL, &zero, NULL); sigaction(SIGALRM, &g_old_sa_alarm, NULL); } /* ========================================================================= * Log drawer ring buffer * ========================================================================= */ static void watch_log_append(watch_session_t *ws, const char *msg) { int msglen = (int)strlen(msg); int i; if (msglen <= 0) return; for (i = 0; i < msglen; i++) { if (ws->log_len < WATCH_LOG_BUF) { /* Space available: write at tail, grow length */ ws->log_buf[(ws->log_head + ws->log_len) % WATCH_LOG_BUF] = msg[i]; ws->log_len++; } else { /* Buffer full: write at head (overwrites oldest byte), advance head. * log_len stays at WATCH_LOG_BUF — the ring always stays full. */ ws->log_buf[ws->log_head] = msg[i]; ws->log_head = (ws->log_head + 1) % WATCH_LOG_BUF; } } } /* ========================================================================= * Stats accumulator * ========================================================================= */ static void watch_update_hop(watch_hop_t *h, const char *host, double rtt_ms, int asnumber, const char *netname) { int idx; if (host && host[0]) snprintf(h->host, sizeof(h->host), "%s", host); h->cycles_total++; if (rtt_ms > 0) { h->cycles_seen++; h->rtt_last = rtt_ms; if (h->cycles_seen == 1) { h->rtt_min = h->rtt_max = rtt_ms; h->rtt_sum = rtt_ms; } else { if (rtt_ms < h->rtt_min) h->rtt_min = rtt_ms; if (rtt_ms > h->rtt_max) h->rtt_max = rtt_ms; h->rtt_sum += rtt_ms; } h->rtt_avg = h->rtt_sum / h->cycles_seen; h->rtt_jitter = h->rtt_max - h->rtt_min; } /* Store in circular history */ idx = h->history_head; h->rtt_history[idx] = rtt_ms; /* 0 = no reply */ h->history_head = (h->history_head + 1) % WATCH_HISTORY_LEN; if (h->history_count < WATCH_HISTORY_LEN) h->history_count++; /* whois fields */ if (asnumber > 0) snprintf(h->asn, sizeof(h->asn), "AS%d", asnumber); if (netname && netname[0] && strcmp(netname, "NULL") != 0) snprintf(h->netname, sizeof(h->netname), "%.63s", netname); } /* ========================================================================= * Display address helper — IP or PTR hostname (F4) * ========================================================================= */ /* Return the display string for a hop: hostname if show_hostnames and * one is available; otherwise the IP address, or "*" for no reply. */ static const char *hop_display_addr(const watch_hop_t *h, int show_hostnames) { if (show_hostnames && h->hostname[0]) return h->hostname; return h->host[0] ? h->host : "*"; } /* ========================================================================= * Per-IP annotation cache — implementation * * FNV-1a hash over the address bytes (4 for IPv4, 16 for IPv6), masked to * WATCH_IP_CACHE_BUCKETS-1. Collision chains are singly-linked lists. * All memory is malloc'd and freed by watch_ip_cache_free(). * ========================================================================= */ /* FNV-1a hash over the relevant address bytes */ static uint32_t watch_ip_hash(int af, const watch_ipaddr_t *addr) { uint32_t h = 2166136261u; /* FNV offset basis */ int len = (af == AF_INET6) ? 16 : 4; int i; for (i = 0; i < len; i++) { h ^= (uint32_t)addr->raw[i]; h *= 16777619u; /* FNV prime */ } return h & (WATCH_IP_CACHE_BUCKETS - 1); } static int watch_ip_equal(int af, const watch_ipaddr_t *a, const watch_ipaddr_t *b) { int len = (af == AF_INET6) ? 16 : 4; return memcmp(a->raw, b->raw, (size_t)len) == 0; } /* Parse a dotted-decimal or colon-hex string into af+addr. * Returns 1 on success, 0 if the string is not a valid IP. */ static int watch_ip_parse(const char *s, int *af_out, watch_ipaddr_t *addr_out) { memset(addr_out, 0, sizeof(*addr_out)); if (inet_pton(AF_INET6, s, &addr_out->v6) == 1) { *af_out = AF_INET6; return 1; } if (inet_pton(AF_INET, s, &addr_out->v4) == 1) { *af_out = AF_INET; return 1; } return 0; } /* Look up an IP in the cache. Returns the record or NULL if absent. */ static watch_ip_record_t *watch_ip_cache_lookup(watch_ip_cache_t *c, int af, const watch_ipaddr_t *addr) { uint32_t bucket = watch_ip_hash(af, addr); watch_ip_record_t *r = c->buckets[bucket]; while (r) { if (r->af == af && watch_ip_equal(af, &r->addr, addr)) return r; r = r->next; } return NULL; } /* Look up or create a zeroed record for the given IP. * Returns NULL only on malloc failure (extremely rare). */ static watch_ip_record_t *watch_ip_cache_get(watch_ip_cache_t *c, int af, const watch_ipaddr_t *addr) { uint32_t bucket = watch_ip_hash(af, addr); watch_ip_record_t *r = c->buckets[bucket]; while (r) { if (r->af == af && watch_ip_equal(af, &r->addr, addr)) return r; r = r->next; } /* Not found — allocate a new zeroed record and prepend to chain */ r = (watch_ip_record_t *)calloc(1, sizeof(*r)); if (!r) return NULL; r->af = af; r->addr = *addr; r->next = c->buckets[bucket]; c->buckets[bucket] = r; c->count++; return r; } /* Free all records in the cache. The cache struct itself is embedded in * watch_session_t (stack-allocated) so we only free the heap nodes. */ static void watch_ip_cache_free(watch_ip_cache_t *c) { int i; for (i = 0; i < WATCH_IP_CACHE_BUCKETS; i++) { watch_ip_record_t *r = c->buckets[i]; while (r) { watch_ip_record_t *next = r->next; free(r); r = next; } c->buckets[i] = NULL; } c->count = 0; } /* Copy annotation fields from a cache record into a hop's display copies. * Only overwrites fields that the record has (non-empty string). */ static void watch_ip_cache_copy_to_hop(const watch_ip_record_t *rec, watch_hop_t *h) { if (rec->asn[0]) snprintf(h->asn, sizeof(h->asn), "%.31s", rec->asn); if (rec->netname[0]) snprintf(h->netname, sizeof(h->netname), "%.63s", rec->netname); if (rec->orgname[0]) snprintf(h->orgname, sizeof(h->orgname), "%.63s", rec->orgname); if (rec->hostname[0]) snprintf(h->hostname, sizeof(h->hostname), "%.255s", rec->hostname); } /* ========================================================================= * pWhoIs lookup helpers (F2) * ========================================================================= */ /* Query pWhoIs for a single hop and populate the session's ip_cache record. * The hop's display fields (asn/netname/orgname) are also updated from the * result. ws->watch_whois_wsess must already be initialised before calling. * The record's whois_queried flag is set to 1 before the network call so that * errors (server down, timeout) don't cause infinite re-tries. */ static void watch_whois_query_one(watch_session_t *ws, watch_hop_t *h) { int af; watch_ipaddr_t addr; watch_ip_record_t *rec; char asn_buf[32]; if (!watch_ip_parse(h->host, &af, &addr)) return; /* not a parseable IP — skip */ rec = watch_ip_cache_get(&ws->ip_cache, af, &addr); if (!rec) return; /* malloc failure */ /* Cache-hit short-circuit: skip the network lookup only if we already * have USEFUL data cached (at least one of asn/netname/orgname is * populated). If a previous attempt came back empty — transient * pWhoIs timeout, server hiccup, partial response — keep trying on * subsequent cycles so gaps eventually fill in. IANA pre-screen * below still runs first, so private IPs never touch pWhoIs. */ if (rec->whois_queried && (rec->asn[0] || rec->netname[0] || rec->orgname[0])) { watch_ip_cache_copy_to_hop(rec, h); return; } rec->whois_queried = 1; /* mark before I/O so a live query error * doesn't cause re-entry this same cycle */ /* -------------------------------------------------------------------- * IANA pre-screen: private / special-purpose addresses (RFC1918, CGNAT, * loopback, link-local, documentation, multicast, etc.) never have a * useful pWhoIs record. Asking pWhoIs for one is worse than useless — * the server sometimes closes the TCP connection after a nonsense * query, which then breaks the NEXT hop's query too. Detect them * locally and fill the display fields with the RFC tag + registry name * (e.g. NETNAME = ASN = "RFC1918", ORGNAME = "Private-Use"), then * return without touching pWhoIs. * -------------------------------------------------------------------- */ { char iana_rfc[32] = ""; char iana_name[64] = ""; int iana_hit = 0; if (af == AF_INET) iana_hit = lft_iana_v4_lookup(addr.v4, iana_rfc, sizeof(iana_rfc), iana_name, sizeof(iana_name)); else if (af == AF_INET6) iana_hit = lft_iana_v6_lookup(&addr.v6, iana_rfc, sizeof(iana_rfc), iana_name, sizeof(iana_name)); if (iana_hit) { if (iana_rfc[0]) { snprintf(rec->asn, sizeof(rec->asn), "%.31s", iana_rfc); snprintf(rec->netname, sizeof(rec->netname), "%.63s", iana_rfc); } if (iana_name[0]) snprintf(rec->orgname, sizeof(rec->orgname), "%.63s", iana_name); watch_ip_cache_copy_to_hop(rec, h); return; } } /* Reset the shared wsess consolidated fields to the w_init() '?' * sentinel before every query. w_lookup_all_pwhois() only writes a * field whose current value starts with '?' (first-writer-wins across * consolidation sources), so without this reset the first public * hop's ASN/NETNAME/ORGNAME would be inherited by every subsequent * hop for the life of the session. */ { whois_session_params *wq = ws->watch_whois_wsess; wq->consolidated_asn[0] = wq->consolidated_asp[0] = wq->consolidated_route[0] = wq->consolidated_orgname[0] = wq->consolidated_netname[0] = '?'; wq->consolidated_asn[1] = wq->consolidated_asp[1] = wq->consolidated_route[1] = wq->consolidated_orgname[1] = wq->consolidated_netname[1] = '\0'; } if (w_lookup_all_pwhois(ws->watch_whois_wsess, h->host) != 0) return; /* Store results in the cache record */ asn_buf[0] = '\0'; if (ws->watch_whois_wsess->consolidated_asn[0] && ws->watch_whois_wsess->consolidated_asn[0] != '?') { if (ws->watch_whois_wsess->consolidated_asn[0] >= '0' && ws->watch_whois_wsess->consolidated_asn[0] <= '9') snprintf(asn_buf, sizeof(asn_buf), "AS%.*s", (int)sizeof(asn_buf) - 3, ws->watch_whois_wsess->consolidated_asn); else snprintf(asn_buf, sizeof(asn_buf), "%.*s", (int)sizeof(asn_buf) - 1, ws->watch_whois_wsess->consolidated_asn); } if (asn_buf[0]) snprintf(rec->asn, sizeof(rec->asn), "%.31s", asn_buf); if (ws->watch_whois_wsess->consolidated_netname[0] && ws->watch_whois_wsess->consolidated_netname[0] != '?' && strcmp(ws->watch_whois_wsess->consolidated_netname, "NULL") != 0) snprintf(rec->netname, sizeof(rec->netname), "%.63s", ws->watch_whois_wsess->consolidated_netname); if (ws->watch_whois_wsess->consolidated_orgname[0] && ws->watch_whois_wsess->consolidated_orgname[0] != '?' && strcmp(ws->watch_whois_wsess->consolidated_orgname, "NULL") != 0) snprintf(rec->orgname, sizeof(rec->orgname), "%.63s", ws->watch_whois_wsess->consolidated_orgname); /* Push results to the hop's display copies */ watch_ip_cache_copy_to_hop(rec, h); } /* Query pWhoIs for all current hops that haven't been queried yet. * Uses per-hop individual queries (not bulk) to avoid the pWhois response * parser's entity_id misalignment bug with sparse records (private/CGNAT IPs). * Initialises ws->watch_whois_wsess if needed. * Sets ws->whois_bulk_done = 1 when all current hops have been attempted. */ static void watch_whois_bulk_query(watch_session_t *ws) { int i; if (!ws->watch_whois_wsess) ws->watch_whois_wsess = w_init(); if (!ws->watch_whois_wsess) { ws->whois_bulk_done = 1; return; } for (i = 0; i < ws->n_hops; i++) { watch_hop_t *h = &ws->hops[i]; if (!h->host[0] || strcmp(h->host, "*") == 0) continue; watch_whois_query_one(ws, h); } ws->whois_bulk_done = 1; } /* Query pWhoIs for any hop that has an IP but no cache record yet. * Called after each cycle to pick up new hops discovered after the initial * startup query — the ip_cache prevents re-querying IPs already seen. */ static void watch_whois_update_new_hops(watch_session_t *ws) { int i; if (!ws->whois_enabled || !ws->watch_whois_wsess) return; for (i = 0; i < ws->n_hops; i++) { watch_hop_t *h = &ws->hops[i]; if (!h->host[0] || strcmp(h->host, "*") == 0) continue; watch_whois_query_one(ws, h); /* cache check + query + hop field sync */ } } /* ========================================================================= * Trace execution — sub-session per cycle * ========================================================================= */ /* After LFTExecute returns, LFT's dns_cache contains whatever c-ares managed * to resolve during the trace (async; may be incomplete if queries timed out). * Inject hostnames from the watch ip_cache for any IPs already resolved in a * previous cycle but missing/incomplete this cycle. This makes the watch * ip_cache the authoritative persistent DNS store and avoids relying on c-ares * finishing in time on every cycle. * * Called in watch_run_one_cycle() between LFTExecute() and watch_capture_from_sess(). */ static void watch_inject_cached_hostnames(watch_session_t *ws, lft_session_params *sess) { #ifdef HAVE_CARES int b; for (b = 0; b < WATCH_IP_CACHE_BUCKETS; b++) { watch_ip_record_t *rec = ws->ip_cache.buckets[b]; while (rec) { if (rec->af == AF_INET && rec->hostname[0]) { struct in_addr a = rec->addr.v4; int i, found = 0; /* Look for this IP in LFT's per-cycle dns_cache */ for (i = 0; i < sess->dns_cache_count; i++) { if (sess->dns_cache[i].addr.s_addr == a.s_addr) { found = 1; /* c-ares entry exists; fill name if still empty */ if (!sess->dns_cache[i].name[0]) { strncpy(sess->dns_cache[i].name, rec->hostname, sizeof(sess->dns_cache[i].name) - 1); sess->dns_cache[i].name[sizeof(sess->dns_cache[i].name)-1] = '\0'; } break; } } if (!found && sess->dns_cache_count < LFT_DNS_CACHE_SIZE) { /* Not yet in dns_cache this cycle — add from our cache */ sess->dns_cache[sess->dns_cache_count].addr = a; strncpy(sess->dns_cache[sess->dns_cache_count].name, rec->hostname, sizeof(sess->dns_cache[0].name) - 1); sess->dns_cache[sess->dns_cache_count].name[sizeof(sess->dns_cache[0].name)-1] = '\0'; sess->dns_cache_count++; } } rec = rec->next; } } #else (void)ws; (void)sess; /* no c-ares dns_cache to seed without HAVE_CARES */ #endif } /* Capture per-hop data from a completed sub-session into ws */ static void watch_capture_from_sess(watch_session_t *ws, lft_session_params *sub) { int hopno; struct trace_packet_info_s *tp; int maxhop; if (sub->num_hops) maxhop = sub->num_hops; else maxhop = sub->hop_info_length - 1; /* Reset n_alt counters for this cycle before scanning */ for (hopno = 0; hopno < WATCH_MAX_HOPS; hopno++) ws->hops[hopno].n_alt = 0; for (hopno = sub->ttl_min; hopno < sub->hop_info_length && hopno <= maxhop; hopno++) { double best_rtt = 0.0; int best_asn = 0; char best_host[64] = ""; char best_net[512] = ""; int first = 1; if (!sub->hop_info[hopno].all_rcvd) { /* For TCP/UDP traces the target hop (hopno == num_hops) replies with * a direct packet (RST or SYN-ACK), not ICMP TTL-exceeded, so * hop_info[num_hops].all_rcvd is always 0. Skip watch_update_hop * here; the manual target block in watch_run_one_cycle is the sole * handler for that slot — calling it here would double-count * cycles_total and insert a spurious history entry. */ if (sub->num_hops > 0 && hopno == sub->num_hops) { if (hopno >= ws->n_hops) ws->n_hops = hopno + 1; continue; } /* no reply at an intermediate hop this cycle */ watch_update_hop(&ws->hops[hopno], NULL, 0.0, 0, NULL); if (hopno >= ws->n_hops) ws->n_hops = hopno + 1; continue; } /* Gather data from all packets at this TTL */ SLIST_FOREACH(tp, &(sub->hop_info[hopno].packets), next_by_hop) { double rtt; const char *ipstr; char ipbuf[INET6_ADDRSTRLEN]; if (!tp->recv.tv_sec) continue; if (!watch_hop_changed(sub, tp)) continue; rtt = timediff_ms(tp->sent, tp->recv); ipstr = watch_ntop_hop(sub, tp, ipbuf, sizeof ipbuf); if (first) { first = 0; best_rtt = rtt; best_asn = tp->asnumber; snprintf(best_host, sizeof(best_host), "%s", ipstr); if (tp->netname[0] && strcmp(tp->netname, "NULL") != 0) snprintf(best_net, sizeof(best_net), "%s", tp->netname); } else { /* check for multipath (different IP at same TTL) */ if (strcmp(ipstr, best_host) != 0) { int n = ws->hops[hopno].n_alt; if (n < WATCH_MAX_ALT_IPS) { /* only add if not already listed */ int found = 0, j; for (j = 0; j < n; j++) { if (strcmp(ws->hops[hopno].alt_hosts[j], ipstr) == 0) { found = 1; break; } } if (!found) snprintf(ws->hops[hopno].alt_hosts[n++], 64, "%s", ipstr); ws->hops[hopno].n_alt = n; } } /* use min RTT across probes at this hop */ if (rtt > 0 && (best_rtt <= 0 || rtt < best_rtt)) best_rtt = rtt; } } if (!first) { watch_update_hop(&ws->hops[hopno], best_host, best_rtt, best_asn, best_net); if (hopno >= ws->n_hops) ws->n_hops = hopno + 1; #ifdef HAVE_CARES /* Capture PTR hostname from sub-session dns_cache (F4). * Store in both the ip_cache record (authoritative) and the * hop's display copy. */ { int di; watch_ipaddr_t haddr; int haf; if (watch_ip_parse(best_host, &haf, &haddr)) { struct in_addr hop_addr4; if (haf == AF_INET) hop_addr4 = haddr.v4; else hop_addr4.s_addr = 0; for (di = 0; di < sub->dns_cache_count; di++) { if (haf == AF_INET && sub->dns_cache[di].addr.s_addr == hop_addr4.s_addr && sub->dns_cache[di].name[0]) { watch_ip_record_t *rec = watch_ip_cache_get(&ws->ip_cache, haf, &haddr); if (rec && !rec->hostname[0]) { snprintf(rec->hostname, sizeof(rec->hostname), "%.255s", sub->dns_cache[di].name); rec->rdns_queried = 1; } snprintf(ws->hops[hopno].hostname, sizeof(ws->hops[hopno].hostname), "%s", sub->dns_cache[di].name); break; } } } } #endif } } /* Trim ws->n_hops to the last hop that has EVER responded. * This prevents no-reply hops beyond the actual path from bloating the * display when num_hops==0 (target unreachable, trace timed out, etc.). * Cloaked hops in the middle are preserved because we search backwards * from the end to find the last hop with any cycles_seen > 0. */ { int last = 0, j; for (j = ws->n_hops - 1; j >= 0; j--) { if (ws->hops[j].cycles_seen > 0) { last = j + 1; break; } } if (last > 0 && last < ws->n_hops) ws->n_hops = last; } } /* Reset the per-trace state on sess so LFTExecute can be called again. * The raw socket (send_sock) and pcap handle (pcapdescr) are preserved; * the HAVE_NCURSES guards in lft_lib.c ensure they are not re-created or * closed across cycles. */ static void watch_reset_session(lft_session_params *sess) { struct trace_packet_info_s *tp; /* TCP only: advance the ISN base by the number of probes the previous * cycle consumed, so sequence numbers climb monotonically across cycles * — contiguous, as if one long flow. Each probe's seq is * seq_start + trace_packets_num with the counter reset per cycle, so * without this bump a cycle that sends FEWER probes (auto-tune * narrowing after the path is learned) re-sends the target SYN with a * LOWER sequence number on the same 4-tuple. Stateful middleboxes * (CGNAT/pf/conntrack) track the tuple's sequence window and silently * drop the backward SYN, so the target appears dead in every narrowed * cycle. The 5-tuple is unchanged, so ECMP path stability is * unaffected. Must run BEFORE trace_packets_num is zeroed below. * * The +64 gap is load-bearing: the target retransmits its SYN-ACK / * challenge-ACK for several seconds (middleboxes may eat our kernel's * RST), and a stale ack for the PREVIOUS cycle's last probe (seq * new_base - 1) would otherwise alias into recv_packet()'s * "unincremented ACK" fallback (tp->seq == seq + 1) and be filed * against the new cycle's FIRST probe — marking the target open at * hop 1 with a negative RTT. With the gap, stale acks fall in dead * sequence space and are dropped as unmatched. */ if (sess->protocol == 0 || sess->protocol == 4) sess->seq_start += sess->trace_packets_num + 64; /* Watch mode: pick a FRESH source port each cycle (the default here, * unless -s fixed the port). The port is constant WITHIN a cycle so a * snapshot's TTLs share one 5-tuple (ECMP-consistent per cycle); it * changes across cycles so a stateful firewall keying on the 5-tuple * cannot suppress the repeated cycles as one long-lived flow (observed * as ~50% phantom loss on near hops otherwise). Per-hop source-port * variation (the one-shot default) is NOT used in watch mode — that * would break per-cycle ECMP consistency. open_sockets() (run each * cycle) writes sess->sport into the probe template and reply matching * reads sess->sport, so the new value takes effect coherently. */ if (!sess->user_sport) sess->sport = rand() % 16384 + 49152; /* Free all per-probe packet records from the previous cycle. * hop_info[].packets entries are the same pointers — freeing * trace_packets is sufficient; no separate per-hop free needed. * * IMPORTANT: the union u holds either: * u.icmp_packet (struct icmp_trace_packet_s) — used for protocol 2/3 (ICMP); * u.icmp_packet.packet is a heap pointer allocated by * generateICMPPacket() and must be freed here. * u.packet (struct trace_packet_s) — used for protocol 0/1 (TCP/UDP); * the first bytes of this struct are an embedded struct ip * (NOT a pointer). Reading u.icmp_packet.packet on a TCP * probe returns the first 8 bytes of struct ip, which is not * a heap address. Calling free() on it triggers * ___BUG_IN_CLIENT_OF_LIBMALLOC_POINTER_BEING_FREED_WAS_NOT_ALLOCATED. */ while (SLIST_FIRST(&sess->trace_packets)) { tp = SLIST_FIRST(&sess->trace_packets); SLIST_REMOVE_HEAD(&sess->trace_packets, next); if ((sess->protocol == 2 || sess->protocol == 3) && tp->u.icmp_packet.packet) { free(tp->u.icmp_packet.packet); tp->u.icmp_packet.packet = NULL; } free(tp); } sess->trace_packets_num = 0; /* Free hop_info array; open_sockets() will re-allocate it next cycle */ if (sess->hop_info) { free(sess->hop_info); sess->hop_info = NULL; } sess->hop_info_length = sess->ttl_min; /* Reset per-trace counters and state */ sess->exit_state = 0; sess->num_hops = 0; sess->num_rcvd = 0; sess->target_open = 0; sess->target_filtered = 0; sess->target_anomaly = 0; sess->trg_probe_is_sent = 0; /* DO NOT reset (or re-randomise) sess->seq_start here. For UDP it's * the IP-ID base embedded in every probe — a router on the path uses * it as part of the ECMP 5-tuple/flow hash, so re-randomising it * between cycles would route each cycle down a different ECMP branch * (defeats the 3.97 UDP ECMP-stability fix). For TCP it advances * monotonically at the top of this function; re-randomising would risk * a backward ISN on the reused tuple (middlebox drop — see above). * For ICMP it's unused (icmp_initial_seq anchors the flow). */ /* Destroy c-ares channel from the previous cycle so LFTExecute does not * leak it. LFTExecute will create a fresh channel on entry. */ #ifdef HAVE_CARES if (sess->ares_chan) { ares_destroy(sess->ares_chan); sess->ares_chan = NULL; } #endif /* Free whois session; LFTExecute will open a fresh one if needed */ if (sess->wsess) { w_close(sess->wsess); sess->wsess = NULL; } } /* Run one trace cycle by resetting per-trace state on proto_sess and * calling LFTExecute directly. The raw socket and pcap handle are * reused across cycles (HAVE_NCURSES guards in lft_lib.c prevent them * from being re-created or closed). */ static void watch_run_one_cycle(watch_session_t *ws, lft_session_params *proto_sess) { int saved_stdout, saved_stderr; int pipe_fds[2]; int use_pipe; /* 1 = capture stdout via pipe for log; 0 = /dev/null */ int i; /* Reset per-trace fields; keep send_sock, pcapdescr, and all user params */ watch_reset_session(proto_sess); proto_sess->addr_expand = ws->addr_expand; /* honor live 'e' expand toggle */ /* Always redirect stdout+stderr to a pipe so LFT's -VV output is captured * into the log drawer ring buffer. WatchModeRun() forces noisy=2 so there * is always data to capture. On pipe failure fall back to /dev/null. */ use_pipe = (proto_sess->noisy >= 1); /* always true: WatchModeRun forces ≥2 */ pipe_fds[0] = pipe_fds[1] = -1; saved_stdout = dup(STDOUT_FILENO); saved_stderr = dup(STDERR_FILENO); if (use_pipe && pipe(pipe_fds) == 0) { /* Non-blocking write end: LFT never stalls if output exceeds pipe buf */ fcntl(pipe_fds[1], F_SETFL, fcntl(pipe_fds[1], F_GETFL) | O_NONBLOCK); dup2(pipe_fds[1], STDOUT_FILENO); dup2(pipe_fds[1], STDERR_FILENO); close(pipe_fds[1]); pipe_fds[1] = -1; } else { int devnull_fd = open("/dev/null", O_WRONLY); use_pipe = 0; if (devnull_fd >= 0) { dup2(devnull_fd, STDOUT_FILENO); dup2(devnull_fd, STDERR_FILENO); close(devnull_fd); } } /* style=0 keeps EVT_ON_EXIT from triggering a second WatchModeRun */ setOutputStyle(0); /* S1: keep watch_mode=1 so lft_lib.c guards preserve the raw socket and pcap * handle across all cycles. The first LFTExecute opens them as root; after * setuid(getuid()) drops privileges the descriptors remain open and are reused * for every subsequent cycle without needing elevated privileges again. */ /* Ensure rtt_stats is set so ASCIIOutput (view 1) has per-hop chart data */ if (proto_sess->rtt_stats <= 0) proto_sess->rtt_stats = ws->probes_per_hop; /* Pre-seed proto_sess->dns_cache from our persistent ip_cache before * LFTExecute fires c-ares queries. lft_ares_fire() already has a dedup * check that skips any IP already in dns_cache, so injecting resolved IPs * here suppresses redundant PTR queries for hops we have already resolved. * Combined with the watch_mode guard in lft_lib.c (dns_cache_count is not * zeroed when watch_mode=1), stable-path hops are never re-queried after * the first cycle. */ watch_inject_cached_hostnames(ws, proto_sess); /* Prepare the cycle/interval text for the SIGALRM handler and arm it. * ncurses is on /dev/tty so SIGALRM can call ncurses directly even while * stdout is redirected to /dev/null. */ { char maxcyc[16]; if (ws->max_cycles > 0) snprintf(maxcyc, sizeof(maxcyc), "%d", ws->max_cycles); else snprintf(maxcyc, sizeof(maxcyc), "\xe2\x88\x9e"); /* ∞ */ g_alarm_cycle_text_len = snprintf(g_alarm_cycle_text, sizeof(g_alarm_cycle_text), " tracing cycle %d/%s %ds ", ws->cycle, maxcyc, ws->interval); if (g_alarm_cycle_text_len < 0 || g_alarm_cycle_text_len >= (int)sizeof(g_alarm_cycle_text)) g_alarm_cycle_text_len = 0; g_alarm_frame = (sig_atomic_t)ws->spinner_frame; lft_o_counters_activate(); watch_spinner_arm(); } /* LFT-AUTOTUNE — apply learned-path knowledge (no-op on first cycle * and when --no-learn is set or stable_cycles < 3). */ lft_auto_tune_from_learned(proto_sess); LFTExecute(proto_sess); /* Disarm the spinner alarm; stop counter accumulation. */ watch_spinner_disarm(); lft_o_counters_deactivate(); /* Accumulate cumulative counters for the status bar and SIGALRM handler. */ ws->total_replies += lft_o_get_replies(); ws->total_sent += lft_o_get_sent(); if (lft_o_get_hops() > ws->max_hops_seen) ws->max_hops_seen = lft_o_get_hops(); if (lft_o_get_target()) ws->target_ever_reached = 1; ws->spinner_frame = (int)g_alarm_frame; /* Restore stdout + stderr before ncurses refresh */ fflush(stdout); fflush(stderr); dup2(saved_stdout, STDOUT_FILENO); dup2(saved_stderr, STDERR_FILENO); close(saved_stdout); close(saved_stderr); /* Drain captured verbose output into the log ring buffer. * Both ends of the pipe are now closed from stdout/stderr's perspective; * the write end was closed after dup2, so read() returns EOF promptly. */ if (use_pipe && pipe_fds[0] >= 0) { char chunk[4096]; ssize_t nr; fcntl(pipe_fds[0], F_SETFL, O_NONBLOCK); while ((nr = read(pipe_fds[0], chunk, sizeof(chunk) - 1)) > 0) { chunk[nr] = '\0'; watch_log_append(ws, chunk); } close(pipe_fds[0]); pipe_fds[0] = -1; } /* Augment LFT's per-cycle dns_cache with hostnames already in our watch * ip_cache from previous cycles. c-ares resets dns_cache_count=0 at the * top of every LFTExecute and may not finish all queries before the trace * ends; this makes our persistent cache the reliable fallback source. */ watch_inject_cached_hostnames(ws, proto_sess); /* Extract hop data */ if (proto_sess->hop_info) watch_capture_from_sess(ws, proto_sess); /* Track current path length (not historical peak). Using a maximum * here would make the ⬡ counter sticky if a prior cycle inflated the * count via overshoot probes — set directly so the counter always * matches the table. */ ws->max_hops_seen = ws->n_hops; /* LFT-AUTOTUNE — capture learned-path data for use by the next cycle. * Only update when the cycle actually reached the target (n_hops > 0 * and target was hit), so a transient packet-loss cycle doesn't * corrupt the learned envelope. */ if (ws->n_hops > 0 && lft_o_get_target()) { int prev_num = proto_sess->learned.valid ? proto_sess->learned.num_hops : -1; int max_rtt_ms = 0; int min_rtt_ms = 0; int found_min = 0; int i; for (i = 0; i < ws->n_hops; i++) { int avg = (int)ws->hops[i].rtt_avg; if (avg <= 0) continue; if (avg > max_rtt_ms) max_rtt_ms = avg; if (!found_min || avg < min_rtt_ms) { min_rtt_ms = avg; found_min = 1; } } proto_sess->learned.num_hops = ws->n_hops; proto_sess->learned.max_rtt_ms = max_rtt_ms; proto_sess->learned.min_rtt_ms = min_rtt_ms; proto_sess->learned.last_protocol = proto_sess->protocol; proto_sess->learned.valid = 1; if (prev_num == ws->n_hops) proto_sess->learned.stable_cycles++; else proto_sess->learned.stable_cycles = 1; } else if (proto_sess->learned.valid) { /* Cycle missed the target — reset stability so we re-broaden * the trace until things settle again. */ proto_sess->learned.stable_cycles = 0; } /* Handle target: TCP RST/SYN-ACK responses are not stored in hop_info[] * (they're direct packets, not ICMP TTL-exceeded), so watch_capture_from_sess * leaves the target slot as no-reply for TCP traces. For ICMP traces the * target echo-reply IS stored in hop_info[], so watch_capture_from_sess * already called watch_update_hop for it — only do the manual update when * hop_info does NOT have a reply for the target slot (TCP/UDP cases). */ ws->target_hopno = 0; ws->target_open = 0; ws->target_filtered = 0; if (proto_sess->num_hops > 0 && #ifdef HAVE_IPV6 (proto_sess->af == AF_INET6 || proto_sess->remote_address.s_addr != 0)) { #else proto_sess->remote_address.s_addr != 0) { #endif int tgt = proto_sess->num_hops; watch_hop_t *wh = &ws->hops[tgt]; char tgt_ipbuf[INET6_ADDRSTRLEN]; const char *tgt_ip = watch_ntop_ss(proto_sess, &proto_sess->remote_ss, proto_sess->remote_address, tgt_ipbuf, sizeof tgt_ipbuf); /* Only insert synthetic target entry when watch_capture_from_sess * didn't already record a reply for this hop. */ int already_captured = (proto_sess->hop_info && tgt < proto_sess->hop_info_length && proto_sess->hop_info[tgt].all_rcvd > 0); if (!already_captured) { if (!wh->host[0]) snprintf(wh->host, sizeof(wh->host), "%s", tgt_ip); wh->cycles_total++; wh->cycles_seen++; { int idx = wh->history_head; wh->rtt_history[idx] = 0.001; wh->history_head = (wh->history_head + 1) % WATCH_HISTORY_LEN; if (wh->history_count < WATCH_HISTORY_LEN) wh->history_count++; } if (tgt >= ws->n_hops) ws->n_hops = tgt + 1; } ws->target_hopno = tgt; ws->target_open = proto_sess->target_open; ws->target_filtered = proto_sess->target_filtered; } /* Log brief completion note */ { char logmsg[128]; snprintf(logmsg, sizeof(logmsg), "cycle %d: %d hops, target_%s\n", ws->cycle, ws->n_hops, proto_sess->target_open > 0 ? "open" : (proto_sess->target_filtered ? "filtered" : (proto_sess->protocol == 2 || proto_sess->protocol == 3) ? "replied" : "closed")); watch_log_append(ws, logmsg); } /* Detect path change. Each hop remembers every address it has answered * from (WATCH_SEEN_IPS, FIFO once full); a load-balanced hop rotating * through known members is not a change, only an address never seen at * that hop is. Cloaked hops ('*') are gaps, not routing changes. */ ws->path_changed = 0; for (i = 0; i < ws->n_hops; i++) { const char *h = ws->hops[i].host; int k, known = 0; if (!h[0] || h[0] == '*') continue; for (k = 0; k < ws->n_seen[i]; k++) if (strcmp(ws->seen_ips[i][k], h) == 0) { known = 1; break; } if (known) continue; if (ws->n_seen[i] > 0 && ws->cycle > 2) { /* first two cycles seed the sets */ char logmsg[192]; if (ws->n_seen[i] >= 2) { /* already known to load-balance: another member is not a re-route */ snprintf(logmsg, sizeof(logmsg), "hop %d: new load-balanced member %s (%d known)\n", i + 1, h, ws->n_seen[i] + 1); } else { ws->path_changed = 1; ws->path_change_time = time(NULL); snprintf(logmsg, sizeof(logmsg), "PATH CHANGE at hop %d: new %s (previously %s)\n", i + 1, h, ws->seen_ips[i][0]); } watch_log_append(ws, logmsg); } if (ws->n_seen[i] < WATCH_SEEN_IPS) snprintf(ws->seen_ips[i][ws->n_seen[i]++], 64, "%s", h); else { snprintf(ws->seen_ips[i][ws->seen_next[i]], 64, "%s", h); ws->seen_next[i] = (ws->seen_next[i] + 1) % WATCH_SEEN_IPS; } } /* Multipath members seen within this cycle (several probes per hop) are * known members too — seed them so the first cycle already marks the hop * as load-balanced. */ for (i = 0; i < ws->n_hops; i++) { int a; for (a = 0; a < ws->hops[i].n_alt; a++) { const char *h = ws->hops[i].alt_hosts[a]; int k, known = 0; if (!h[0] || h[0] == '*') continue; for (k = 0; k < ws->n_seen[i]; k++) if (strcmp(ws->seen_ips[i][k], h) == 0) { known = 1; break; } if (known) continue; if (ws->n_seen[i] < WATCH_SEEN_IPS) snprintf(ws->seen_ips[i][ws->n_seen[i]++], 64, "%s", h); else { snprintf(ws->seen_ips[i][ws->seen_next[i]], 64, "%s", h); ws->seen_next[i] = (ws->seen_next[i] + 1) % WATCH_SEEN_IPS; } } } /* Rate-limit heuristic: an established hop whose recent loss (last * WATCH_RL_WINDOW cycles) is >= 50% while its lifetime loss is well below * that is being throttled by a per-source ICMP/SYN limiter, not by the * network. Note it on screen; with --watch-backoff also stretch the * interval (base << level, up to 8x) and relax it again once loss clears. */ { int rl = 0; for (i = 0; i < ws->n_hops; i++) { watch_hop_t *wh = &ws->hops[i]; int k, miss = 0, n = wh->history_count < WATCH_RL_WINDOW ? wh->history_count : WATCH_RL_WINDOW; double life; if (wh->cycles_total < WATCH_RL_WINDOW * 2 || n < WATCH_RL_WINDOW) continue; for (k = 1; k <= n; k++) { int idx = (wh->history_head - k + WATCH_HISTORY_LEN) % WATCH_HISTORY_LEN; if (wh->rtt_history[idx] == 0) miss++; } life = 1.0 - (double)wh->cycles_seen / (double)wh->cycles_total; if (miss * 2 >= n && life <= (double)miss / n / 2.0) rl++; } ws->rl_hops = rl; if (rl > 0) { ws->rl_streak++; ws->clear_streak = 0; } else { ws->clear_streak++; ws->rl_streak = 0; } if (ws->backoff_enabled && rl > 0 && ws->rl_streak >= 3 && ws->backoff_level < WATCH_BACKOFF_MAX) { char logmsg[160]; ws->backoff_level++; ws->rl_streak = 0; ws->interval = ws->base_interval << ws->backoff_level; if (ws->interval > 300) ws->interval = 300; snprintf(logmsg, sizeof logmsg, "BACK-OFF: loss rising at %d hop%s; interval now %ds (x%d)\n", rl, rl == 1 ? "" : "s", ws->interval, 1 << ws->backoff_level); watch_log_append(ws, logmsg); } else if (ws->backoff_enabled && rl == 0 && ws->backoff_level > 0 && ws->clear_streak >= WATCH_RL_WINDOW) { char logmsg[160]; ws->backoff_level--; ws->clear_streak = 0; ws->interval = ws->base_interval << ws->backoff_level; snprintf(logmsg, sizeof logmsg, "BACK-OFF: loss cleared; interval now %ds (x%d)\n", ws->interval, 1 << ws->backoff_level); watch_log_append(ws, logmsg); } if (rl > 0) { if (ws->backoff_level > 0) snprintf(ws->rl_note, sizeof ws->rl_note, "loss rising at %d hop%s - likely rate limiting of a persistent prober; interval backed off to %ds", rl, rl == 1 ? "" : "s", ws->interval); else snprintf(ws->rl_note, sizeof ws->rl_note, "loss rising at %d hop%s - likely rate limiting of a persistent prober; consider a longer interval [+]%s", rl, rl == 1 ? "" : "s", ws->backoff_enabled ? "" : " or --watch-backoff"); } else ws->rl_note[0] = '\0'; } /* Update snapshot */ for (i = 0; i < ws->n_hops; i++) snprintf(ws->path_snapshot[i], 64, "%s", ws->hops[i].host); } /* ========================================================================= * ncurses init / teardown * ========================================================================= */ static int watch_init_ncurses(watch_session_t *ws) { setlocale(LC_ALL, ""); /* enable UTF-8 so Unicode block chars render correctly */ /* Open ncurses on /dev/tty directly so that redirecting stdout to /dev/null * during LFTExecute traces has no effect on ncurses output. SIGALRM can * then call ncurses functions + doupdate() and the model stays in sync — * eliminating the blank-flash on the status bar row. */ g_tty_in = fopen("/dev/tty", "r"); g_tty_out = fopen("/dev/tty", "w"); if (!g_tty_in || !g_tty_out) { /* Fall back to stdout-based initscr if /dev/tty unavailable */ if (g_tty_in) { fclose(g_tty_in); g_tty_in = NULL; } if (g_tty_out) { fclose(g_tty_out); g_tty_out = NULL; } initscr(); } else { g_ncurses_screen = newterm(NULL, g_tty_out, g_tty_in); if (!g_ncurses_screen) { fclose(g_tty_in); g_tty_in = NULL; fclose(g_tty_out); g_tty_out = NULL; initscr(); } else { set_term(g_ncurses_screen); } } cbreak(); noecho(); keypad(stdscr, TRUE); nodelay(stdscr, TRUE); /* non-blocking getch() */ curs_set(0); /* hide cursor */ ws->term_rows = LINES; ws->term_cols = COLS; if (has_colors()) { start_color(); use_default_colors(); init_pair(CP_HEADER, COLOR_WHITE, COLOR_BLUE); init_pair(CP_NORMAL, -1, -1); init_pair(CP_GREEN, COLOR_GREEN, -1); init_pair(CP_YELLOW, COLOR_YELLOW, -1); /* CP_ORANGE — prefer xterm-256 palette colour 208 (bright orange) * when the terminal supports it; fall back to COLOR_MAGENTA on * 8-colour terminals. */ if (COLORS >= 256) init_pair(CP_ORANGE, 208, -1); else init_pair(CP_ORANGE, COLOR_MAGENTA, -1); init_pair(CP_RED, COLOR_RED, -1); init_pair(CP_HIGHLIGHT, COLOR_BLACK, COLOR_CYAN); init_pair(CP_STATUS, COLOR_CYAN, -1); init_pair(CP_DRAWER, COLOR_WHITE, -1); /* white on terminal's default bg */ init_pair(CP_BLUE, COLOR_BLUE, -1); /* ↑ sent counter */ /* CP_LABEL — light steel blue (xterm-256 147) for candlestick * column labels (HOP N / SOURCE / TARGET). Falls back to cyan * on 8-colour terminals. */ if (COLORS >= 256) init_pair(CP_LABEL, 147, -1); else init_pair(CP_LABEL, COLOR_CYAN, -1); /* CP_TBLHEAD — alice blue (xterm-256 195) for timeline table * column headers (HOP / HOST / LOSS% / …). Falls back to white * on 8-colour terminals. */ if (COLORS >= 256) init_pair(CP_TBLHEAD, 195, -1); else init_pair(CP_TBLHEAD, COLOR_WHITE, -1); /* CP_SENT — medium slate blue (xterm-256 75) for the status-bar * ↑ sent counter. Brighter than the frame's CP_BLUE so the live * count stands out against the other status-bar stats. */ if (COLORS >= 256) init_pair(CP_SENT, 75, -1); else init_pair(CP_SENT, COLOR_BLUE, -1); /* CP_KEYHINT — cornflower (xterm-256 111) for the row-2 key-hint menu */ if (COLORS >= 256) init_pair(CP_KEYHINT, 111, -1); else init_pair(CP_KEYHINT, COLOR_CYAN, -1); /* CP_KEYLTR — light sky blue (xterm-256 117) for [X] action letters */ if (COLORS >= 256) init_pair(CP_KEYLTR, 117, -1); else init_pair(CP_KEYLTR, COLOR_WHITE, -1); } return (LINES >= WATCH_MIN_ROWS) ? 0 : -1; } static void watch_teardown_ncurses(void) { endwin(); if (g_ncurses_screen) { delscreen(g_ncurses_screen); g_ncurses_screen = NULL; } if (g_tty_out) { fclose(g_tty_out); g_tty_out = NULL; } if (g_tty_in) { fclose(g_tty_in); g_tty_in = NULL; } } /* ========================================================================= * Status bar (bottom 2 rows) * ========================================================================= */ static void watch_render_status_bar(watch_session_t *ws, int spinner_frame) { int row1 = ws->term_rows - 2; int row2 = ws->term_rows - 1; const char *spin; char maxcyc[16]; char c_replies[16], c_sent[16], c_hops[16]; char center_str[320]; char buf[320]; /* row2 key hints / status message */ int i, left_w, center_sec, right_end, total_dashes, dash_l, dash_r; spin = SPINNER_FRAMES[spinner_frame % WATCH_SPINNER_FRAMES]; if (ws->max_cycles > 0) snprintf(maxcyc, sizeof(maxcyc), "%d", ws->max_cycles); else snprintf(maxcyc, sizeof(maxcyc), "\xe2\x88\x9e"); /* ∞ */ /* Format cumulative counters with thousands separators. * During a trace (in_trace=1) add the current-cycle live counts on top * of the accumulated baseline — the SIGALRM handler intentionally does * NOT write these fields, so they hold the cleanly-accumulated previous * totals until watch_run_one_cycle updates them at cycle end. */ { int disp_replies = ws->total_replies + (ws->in_trace ? lft_o_get_replies() : 0); int disp_sent = ws->total_sent + (ws->in_trace ? lft_o_get_sent() : 0); fmt_comma(c_replies, sizeof(c_replies), disp_replies); fmt_comma(c_sent, sizeof(c_sent), disp_sent); /* Left-justify counters to a MINIMUM of 4 display cols (trailing * spaces so the digits sit flush against the ↓/↑ arrows). Keeps * the footer width stable for counts 0…9999; field only grows * once a value crosses 10,000 and the comma-formatted string * naturally needs more chars. */ { char tmp[16]; if ((int)strlen(c_replies) < 4) { snprintf(tmp, sizeof(tmp), "%-4s", c_replies); snprintf(c_replies, sizeof(c_replies), "%s", tmp); } if ((int)strlen(c_sent) < 4) { snprintf(tmp, sizeof(tmp), "%-4s", c_sent); snprintf(c_sent, sizeof(c_sent), "%s", tmp); } } } fmt_comma(c_hops, sizeof(c_hops), ws->max_hops_seen); /* ------------------------------------------------------------------ */ /* Row 1: bordered status line, mirroring the top header style */ /* */ /* └──┤ ⠹ ↓N ↑N ⬡N ✔ ├────────┤ idle cycle N/∞ URL ~Ns ├────┘ */ /* ------------------------------------------------------------------ */ move(row1, 0); clrtoeol(); /* --- Measure widths to compute centering --- */ /* Left section content: spin(1) + " ↓"(3) + replies + " ↑"(3) + sent * + " ⬡ "(4) + hops + " ✔/✗"(3) — ✔/✗ glyph is 1 display col, so * the trailing " ✔" segment is 3 cols not 4. Using 4 made the right * side of the status bar fall exactly 1 col short of term_cols-1. */ int left_content_dw = 1 + 3 + (int)strlen(c_replies) + 3 + (int)strlen(c_sent) + 4 + (int)strlen(c_hops) + 3; /* " ✔" or " ✗" */ left_w = left_content_dw + 7; /* └──┤ space … space ├ */ /* Center section content: merged state+timer field + Cycle + target. * state_field is FIXED-WIDTH — its length is strlen("Idle ~Ns") for the * current interval. The idle form is the template; Tracing and Paused * write their words into the first N chars and pad the rest with '.'. * This prevents any horizontal reflow when the state transitions, so the * border dashes on either side stay visually locked. */ char idle_form[32]; char state_field[32]; int state_w; int i2; snprintf(idle_form, sizeof(idle_form), "Idle ~%ds", ws->interval); state_w = (int)strlen(idle_form); if ((size_t)state_w >= sizeof(state_field)) state_w = sizeof(state_field) - 1; if (ws->paused) { for (i2 = 0; i2 < state_w; i2++) state_field[i2] = '.'; state_field[state_w] = '\0'; if (state_w >= 6) memcpy(state_field, "Paused", 6); } else if (ws->in_trace) { for (i2 = 0; i2 < state_w; i2++) state_field[i2] = '.'; state_field[state_w] = '\0'; if (state_w >= 7) memcpy(state_field, "Tracing", 7); } else { memcpy(state_field, idle_form, state_w + 1); } /* Assemble full center string for centering-math purposes. NOTE: * maxcyc may contain "∞" (U+221E, 3 bytes but 1 display col), so we * measure display width rather than byte length — otherwise the * right-side dashes+corner come up short and the frame breaks. */ snprintf(center_str, sizeof(center_str), "%s Cycle %d/%s %s", state_field, ws->cycle, maxcyc, ws->tgt_label); /* Count display-columns in center_str (non-UTF8-continuation bytes) */ { const char *cp; int dw = 0; for (cp = center_str; *cp; cp++) if (((unsigned char)*cp & 0xC0) != 0x80) dw++; center_sec = dw + 4; /* ┤ space … space ├ */ } right_end = 3; /* ──┘ */ total_dashes = ws->term_cols - left_w - center_sec - right_end; if (total_dashes < 0) total_dashes = 0; /* Center the center section on the full terminal width */ dash_l = (ws->term_cols - center_sec) / 2 - left_w; if (dash_l < 0) dash_l = 0; if (dash_l > total_dashes) dash_l = total_dashes; dash_r = total_dashes - dash_l; /* --- Draw left section --- * Corner is └── (bottom-left closing corner) because nothing extends * below this row at col 0 — the key-hints row underneath is plain * text outside the frame. The table's side │ above meets this └ * naturally to close the box. */ if (has_colors()) attron(COLOR_PAIR(CP_BLUE)); addstr("\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80"); /* └── */ addstr("\xe2\x94\xa4"); /* ┤ */ if (has_colors()) attroff(COLOR_PAIR(CP_BLUE)); addstr(" "); /* spinner */ attron(A_BOLD); addstr(spin); attroff(A_BOLD); /* ↓ replies (green bold) */ if (has_colors()) attron(COLOR_PAIR(CP_GREEN) | A_BOLD); addstr(" \xe2\x86\x93"); addstr(c_replies); if (has_colors()) attroff(COLOR_PAIR(CP_GREEN) | A_BOLD); /* ↑ sent (dodger blue bold — brighter than frame's CP_BLUE) */ if (has_colors()) attron(COLOR_PAIR(CP_SENT) | A_BOLD); addstr(" \xe2\x86\x91"); addstr(c_sent); if (has_colors()) attroff(COLOR_PAIR(CP_SENT) | A_BOLD); /* ⬡ hops (cyan bold) */ if (has_colors()) attron(COLOR_PAIR(CP_STATUS) | A_BOLD); addstr(" \xe2\xac\xa1 "); addstr(c_hops); if (has_colors()) attroff(COLOR_PAIR(CP_STATUS) | A_BOLD); /* ✔ / ✗ target (sticky: stays ✔ if ever reached) */ if (ws->target_ever_reached) { if (has_colors()) attron(COLOR_PAIR(CP_GREEN) | A_BOLD); addstr(" \xe2\x9c\x94"); if (has_colors()) attroff(COLOR_PAIR(CP_GREEN) | A_BOLD); } else { if (has_colors()) attron(COLOR_PAIR(CP_RED) | A_BOLD); addstr(" \xe2\x9c\x97"); if (has_colors()) attroff(COLOR_PAIR(CP_RED) | A_BOLD); } /* Close left section: " ├" then dashes then "┤" center "├" dashes "──┘" */ addstr(" "); if (has_colors()) attron(COLOR_PAIR(CP_BLUE)); addstr("\xe2\x94\x9c"); /* ├ */ for (i = 0; i < dash_l; i++) addstr("\xe2\x94\x80"); /* ─ */ addstr("\xe2\x94\xa4"); /* ┤ */ if (has_colors()) attroff(COLOR_PAIR(CP_BLUE)); /* Center content — split into (state_field) + (Cycle … target) so the * paused state can blink independently at 1 Hz. */ addstr(" "); { attr_t state_attr; if (ws->paused) { /* 1-Hz blink between outline-text color bold and white bold */ state_attr = (time(NULL) & 1) ? (COLOR_PAIR(CP_SENT) | A_BOLD) : (COLOR_PAIR(CP_NORMAL) | A_BOLD); } else { state_attr = COLOR_PAIR(CP_SENT) | A_BOLD; } if (has_colors()) attron(state_attr); addstr(state_field); if (has_colors()) attroff(state_attr); if (has_colors()) attron(COLOR_PAIR(CP_SENT) | A_BOLD); printw(" Cycle %d/%s %s", ws->cycle, maxcyc, ws->tgt_label); if (has_colors()) attroff(COLOR_PAIR(CP_SENT) | A_BOLD); } addstr(" "); if (has_colors()) attron(COLOR_PAIR(CP_BLUE)); addstr("\xe2\x94\x9c"); /* ├ */ for (i = 0; i < dash_r; i++) addstr("\xe2\x94\x80"); /* ─ */ addstr("\xe2\x94\x80\xe2\x94\x80\xe2\x94\x98"); /* ──┘ (bottom-right corner) */ if (has_colors()) attroff(COLOR_PAIR(CP_BLUE)); /* ------------------------------------------------------------------ */ /* Row 2: key hints — OR transient status message if one is active */ /* Key-hint menu is centered on the line and coloured CP_KEYHINT */ /* (xterm 111 cornflower). A transient status message (e.g. the */ /* "saved to /path/file.txt" confirmation) still renders left-aligned */ /* at col 0 for visibility. */ /* ------------------------------------------------------------------ */ move(row2, 0); clrtoeol(); if (ws->status_msg[0] && difftime(time(NULL), ws->status_msg_time) < 3.0) { snprintf(buf, sizeof(buf), " %s", ws->status_msg); if (has_colors()) attron(COLOR_PAIR(CP_STATUS)); mvaddnstr(row2, 0, buf, ws->term_cols - 1); if (has_colors()) attroff(COLOR_PAIR(CP_STATUS)); } else if (ws->rl_note[0] && (time(NULL) / 6) % 2 == 0) { /* rate-limit note alternates with the key hints every 6 s while active */ snprintf(buf, sizeof(buf), " ** %s", ws->rl_note); if (has_colors()) attron(COLOR_PAIR(CP_STATUS) | A_BOLD); mvaddnstr(row2, 0, buf, ws->term_cols - 1); if (has_colors()) attroff(COLOR_PAIR(CP_STATUS) | A_BOLD); } else { ws->status_msg[0] = '\0'; /* clear expired message */ snprintf(buf, sizeof(buf), "[1-3]view [q]quit [p]%s [r]reset [c]clear [v]log [w]whois [h]host [s]seams [e]%s [x]export [+/-]interval", ws->paused ? "resume" : "pause", ws->addr_expand ? "short" : "expand"); /* Center on the row (ASCII-only string → strlen == display width) */ int menu_w = (int)strlen(buf); int menu_x = (ws->term_cols - menu_w) / 2; if (menu_x < 0) menu_x = 0; /* Render in two colours: CP_KEYLTR for chars inside [...], CP_KEYHINT * for everything else. Walk byte-by-byte flipping attrs at [ / ]. */ move(row2, menu_x); { int in_bracket = 0; const char *p; int remaining = ws->term_cols - menu_x; if (has_colors()) attron(COLOR_PAIR(CP_KEYHINT)); for (p = buf; *p && remaining > 0; p++) { if (*p == '[') { if (has_colors()) { attroff(COLOR_PAIR(CP_KEYHINT)); attron(COLOR_PAIR(CP_KEYHINT)); } addch((unsigned char)*p); if (has_colors()) { attroff(COLOR_PAIR(CP_KEYHINT)); attron(COLOR_PAIR(CP_KEYLTR)); } in_bracket = 1; } else if (*p == ']') { if (has_colors()) { attroff(COLOR_PAIR(CP_KEYLTR)); attron(COLOR_PAIR(CP_KEYHINT)); } addch((unsigned char)*p); in_bracket = 0; } else { addch((unsigned char)*p); } remaining--; } if (has_colors()) { if (in_bracket) attroff(COLOR_PAIR(CP_KEYLTR)); else attroff(COLOR_PAIR(CP_KEYHINT)); } } } /* Callers (watch_render, idle loop, SIGALRM) all call wnoutrefresh+doupdate */ } /* Forward declarations for functions defined later in this file */ static int watch_render_timeline(watch_session_t *ws, int start_row, int max_rows, int col_offset); static void watch_render_status_bar(watch_session_t *ws, int spinner_frame); /* ========================================================================= * View 1 (default): native ncurses port of ASCIIOutput candlestick chart. * * This is a self-contained copy of the ASCIIOutput logic from lft_lib.c, * ported to draw directly into the ncurses window. Having our own copy * lets us add watch-mode-specific customisations (color pairs, row limits, * hostname toggle, etc.) without touching the shared lft_lib.c output path. * * Key helpers (wa_ prefix = Watch Ascii): * wa_utf8_display_width() — count display columns in a UTF-8 string * wa_center() — center-print a string into a fixed-width field * wa_rtt_stats_t / wa_compute_rtt_stats() — per-hop RTT aggregation * wa_col_t — per-hop column descriptor (mirror of ascii_col) * wa_chart_render() — draw the candlestick chart body * wa_render_header_row() — draw ┌─ HOP N ─┐ row * watch_render_ascii_chart() — main entry: build cols[], layout, render * ========================================================================= */ /* ---- rendering context (current cursor position inside ncurses) ---- */ typedef struct { int row; /* current screen row */ int col; /* current screen column */ int max_row; /* stop rendering at this row (exclusive) */ int term_cols; /* full terminal width */ int start_col; /* left content margin (0 = no border; 1 = bordered) */ } wa_ctx_t; /* ---- UTF-8 display width (copied from lft_lib.c) ---- */ static int wa_utf8_display_width(const char *s) { int w = 0; while (*s) { unsigned char b = (unsigned char)*s++; if ((b & 0xC0) != 0x80) w++; } return w; } /* Truncate s in-place so its display width does not exceed max_w. * Truncation always lands on a UTF-8 character boundary. */ static void wa_utf8_truncate_to(char *s, int max_w) { int i = (int)strlen(s); while (i > 0 && wa_utf8_display_width(s) > max_w) { i--; /* Back up over any UTF-8 continuation bytes (10xxxxxx) */ while (i > 0 && ((unsigned char)s[i] & 0xC0) == 0x80) i--; s[i] = '\0'; } } /* Like wa_utf8_truncate_to, but when the string is shortened it ends with a * one-column ellipsis marker (U+2026) so a clipped address reads as clipped * rather than a silent mid-group cut. Used for the compact chart cells; the * timeline table carries the full address. */ static void wa_utf8_abbrev_to(char *s, int max_w) { if (max_w < 1) { s[0] = '\0'; return; } if (wa_utf8_display_width(s) <= max_w) return; wa_utf8_truncate_to(s, max_w - 1); /* leave one column for the marker */ strcat(s, "\xe2\x80\xa6"); /* … U+2026 HORIZONTAL ELLIPSIS */ } /* Advance to the next row; clears to end of old row. */ static void wa_nl(wa_ctx_t *ctx) { clrtoeol(); ctx->row++; ctx->col = ctx->start_col; if (ctx->row < ctx->max_row) move(ctx->row, ctx->start_col); } /* Write a string at the current position; update ctx->col. * Tracks display-column width (not bytes) via utf8_display_width arithmetic. * ncurses itself clips at the right margin; we just track col for layout. */ static void wa_str(wa_ctx_t *ctx, const char *s) { int dw; if (!s || !*s || ctx->row >= ctx->max_row) return; /* Measure display width — count non-continuation UTF-8 bytes */ dw = wa_utf8_display_width(s); if (ctx->col + dw > ctx->term_cols - 1) dw = ctx->term_cols - 1 - ctx->col; if (dw <= 0) return; move(ctx->row, ctx->col); addstr(s); /* ncurses clips at right margin automatically */ ctx->col += dw; } /* Printf-style wrapper. */ static void wa_printf(wa_ctx_t *ctx, const char *fmt, ...) { char buf[512]; va_list ap; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); wa_str(ctx, buf); } /* Repeat a single-byte character n times. */ static void wa_repeat_ch(wa_ctx_t *ctx, char c, int n) { int i; for (i = 0; i < n && ctx->col < ctx->term_cols - 1; i++) { move(ctx->row, ctx->col); addch((unsigned char)c); ctx->col++; } } /* Repeat a UTF-8 string n times (e.g. "─" for box drawing). */ static void wa_repeat_str(wa_ctx_t *ctx, const char *s, int n) { int i; for (i = 0; i < n && ctx->col < ctx->term_cols - 1; i++) { move(ctx->row, ctx->col); addstr(s); ctx->col++; /* each repeat is one display column */ } } /* ---- center-print string s into a field of w display columns ---- */ static void wa_center(wa_ctx_t *ctx, const char *s, int w) { int sl = wa_utf8_display_width(s); int left, right; /* Always reserve at least 1 col of padding on each side when room * exists (i.e. string length + 2 ≤ column width). This gives every * candlestick column a small visual gap so long labels don't butt up * against their neighbours or against the column's cloaked fill. */ if (sl + 2 <= w) { left = (w - sl) / 2; right = w - sl - left; if (left < 1) left = 1; if (right < 1) right = 1; } else { /* Content equals or exceeds column width — fall back to plain * centering (may emit no padding if exactly flush). */ left = (w - sl) / 2; right = w - sl - left; if (left < 0) left = 0; if (right < 0) right = 0; } wa_repeat_ch(ctx, ' ', left); wa_str(ctx, s); wa_repeat_ch(ctx, ' ', right); } /* ---- per-hop RTT statistics (copied from lft_lib.c) ---- */ typedef struct { double rtt_min; double rtt_avg; double rtt_max; double rtt_stddev; int n; } wa_rtt_stats_t; static wa_rtt_stats_t wa_compute_rtt_stats(lft_session_params *sess, int hopno) { wa_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; } /* ---- per-column descriptor (mirror of struct ascii_col) ---- */ #define WA_MAX_COLS 256 #define WA_CHART_H 8 /* candlestick body rows */ #define WA_CHART_SEP 4 /* display columns between hops */ #define WA_JITTER_WARN 25.0 #define WA_MAX_COL_W 17 /* maximum column width for candlestick/bar charts */ typedef struct { char hdr[64]; char host[256]; char sub[128]; char asn[32]; char net[64]; char rtt[32]; char ann[64]; char mtu[32]; char jitter_ann[32]; wa_rtt_stats_t stats; int jitter_warn; int is_hole; int is_target; int is_source; int is_ghost; /* 1 = hop known from history but cloaked this cycle */ int anomtype; int w; /* column display width */ int n_alt; /* number of alternate (multipath) IPs */ char alt_hosts[WATCH_MAX_ALT_IPS][64]; /* alternate IP strings */ } wa_col_t; /* ---- ncurses color helpers ---- */ /* Map semantic intent to an existing watch color pair + attribute */ static void wa_color_on(int pair, int attr) { if (has_colors()) attron(COLOR_PAIR(pair)); if (attr) attron(attr); } static void wa_color_off(int pair, int attr) { if (has_colors()) attroff(COLOR_PAIR(pair)); if (attr) attroff(attr); } /* ---- render hop-label header row for one slice of columns ---- * flip_outer=0: ┌─ HOP N ─┐ for all boxes (top row above chart) * flip_outer=1: └─ HOP N ─┐ / ┌─ HOP N ─┘ outer corners (bottom row below chart) */ static void wa_render_header_row(wa_ctx_t *ctx, const wa_col_t *cols, int start, int end, int flip_outer) { int i; for (i = start; i <= end && ctx->col < ctx->term_cols - 1; i++) { const wa_col_t *c = &cols[i]; int hlen = (int)strlen(c->hdr); /* Strip outer "[ " and " ]" to get inner label like "HOP 1" */ char label[64]; int label_len; 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 inner = c->w - 2; 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; /* bottom row: all corners flip to └ / ┘ */ const char *lc = flip_outer ? "\xe2\x94\x94" /* └ */ : "\xe2\x94\x8c"; /* ┌ */ const char *rc = flip_outer ? "\xe2\x94\x98" /* ┘ */ : "\xe2\x94\x90"; /* ┐ */ /* Frame chars (┌─┐└─┘) in cyan; label text ("HOP N" / "SOURCE" * / "TARGET") in CP_LABEL (light steel blue) for visual * distinction from frame and data text. */ wa_color_on(CP_STATUS, 0); wa_str(ctx, lc); wa_repeat_str(ctx, "\xe2\x94\x80", dashes_l); /* ─ × n */ wa_color_off(CP_STATUS, 0); wa_color_on(CP_LABEL, 0); wa_printf(ctx, " %s ", label); wa_color_off(CP_LABEL, 0); wa_color_on(CP_STATUS, 0); wa_repeat_str(ctx, "\xe2\x94\x80", dashes_r); /* ─ × n */ wa_str(ctx, rc); wa_color_off(CP_STATUS, 0); if (i < end) wa_repeat_ch(ctx, ' ', WA_CHART_SEP); } wa_nl(ctx); } /* ---- draw the candlestick chart body for one column slice ---- */ static void wa_chart_render(wa_ctx_t *ctx, const wa_col_t *cols, int start, int end, double g_min, double g_max, const char *info_str) { int i, r; double range = (g_max > g_min + 0.1) ? (g_max - g_min) : 1.0; (void)g_min; /* Y-axis prefix width */ 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 (above chart for spatial context) ---- */ wa_color_on(CP_NORMAL, A_DIM); wa_printf(ctx, "%*s ", yval_w, ""); wa_color_off(CP_NORMAL, A_DIM); wa_render_header_row(ctx, cols, start, end, 0); if (ctx->row >= ctx->max_row) return; /* ---- 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) { wa_color_on(CP_NORMAL, A_DIM); wa_printf(ctx, "%*s ", yval_w, ""); wa_color_off(CP_NORMAL, A_DIM); for (i = start; i <= end; i++) { const wa_col_t *c = &cols[i]; if (c->stats.n > 0) { char lbl[24]; snprintf(lbl, sizeof(lbl), "%dms max", (int)round(c->stats.rtt_max)); if (c->is_ghost) wa_color_on(CP_NORMAL, A_DIM); else wa_color_on(c->jitter_warn ? CP_YELLOW : CP_GREEN, 0); wa_center(ctx, lbl, c->w); if (c->is_ghost) wa_color_off(CP_NORMAL, A_DIM); else wa_color_off(c->jitter_warn ? CP_YELLOW : CP_GREEN, 0); } else if (c->is_hole) { wa_color_on(CP_NORMAL, A_DIM); { int d; wa_str(ctx, " "); for (d = 0; d < c->w - 2; d++) wa_str(ctx, "\xe2\x96\x91"); if (c->w >= 2) wa_str(ctx, " "); } /* ░ padded */ wa_color_off(CP_NORMAL, A_DIM); } else { wa_repeat_ch(ctx, ' ', c->w); } if (i < end) wa_repeat_ch(ctx, ' ', WA_CHART_SEP); } wa_nl(ctx); if (ctx->row >= ctx->max_row) return; } } /* ---- precompute per-column bar row indices ---- */ int col_max_row[WA_MAX_COLS]; int col_avg_row[WA_MAX_COLS]; int col_min_row[WA_MAX_COLS]; for (i = start; i <= end; i++) { const wa_col_t *c = &cols[i]; if (c->stats.n > 0) { col_max_row[i] = (int)round((g_max - c->stats.rtt_max) / range * (WA_CHART_H - 1)); col_avg_row[i] = (int)round((g_max - c->stats.rtt_avg) / range * (WA_CHART_H - 1)); col_min_row[i] = (int)round((g_max - c->stats.rtt_min) / range * (WA_CHART_H - 1)); if (col_max_row[i] < 0) col_max_row[i] = 0; if (col_max_row[i] >= WA_CHART_H) col_max_row[i] = WA_CHART_H - 1; if (col_avg_row[i] < 0) col_avg_row[i] = 0; if (col_avg_row[i] >= WA_CHART_H) col_avg_row[i] = WA_CHART_H - 1; if (col_min_row[i] < 0) col_min_row[i] = 0; if (col_min_row[i] >= WA_CHART_H) col_min_row[i] = WA_CHART_H - 1; 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]; if (c->stats.rtt_min != c->stats.rtt_max && col_max_row[i] == col_min_row[i]) { if (col_min_row[i] < WA_CHART_H - 1) col_min_row[i]++; else if (col_max_row[i] > 0) col_max_row[i]--; 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 < WA_CHART_H && ctx->row < ctx->max_row; r++) { /* Y-axis prefix */ wa_color_on(CP_NORMAL, A_DIM); if (r % 2 == 0) { double row_rtt = g_max - (double)r / (WA_CHART_H - 1) * range; wa_printf(ctx, "%*dms ", yval_w - 2, (int)round(row_rtt)); wa_str(ctx, "\xe2\x94\xa4 "); /* ┤ */ } else { wa_printf(ctx, "%*s ", yval_w, ""); wa_str(ctx, "\xe2\x94\x82 "); /* │ */ } wa_color_off(CP_NORMAL, A_DIM); for (i = start; i <= end && ctx->col < ctx->term_cols - 1; i++) { const wa_col_t *c = &cols[i]; int cw = c->w; if (c->stats.n == 0) { if (c->is_hole) { wa_color_on(CP_NORMAL, A_DIM); { int d; wa_str(ctx, " "); for (d = 0; d < cw - 2; d++) wa_str(ctx, "\xe2\x96\x91"); if (cw >= 2) wa_str(ctx, " "); } /* ░ padded */ wa_color_off(CP_NORMAL, A_DIM); } else { wa_repeat_ch(ctx, ' ', cw); } } else { int mr = col_max_row[i]; int ar = col_avg_row[i]; int mir = col_min_row[i]; int collapsed = (mr == mir); 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; if (in_shaft) { /* Ghost hops render entirely dim — no color distinction */ if (c->is_ghost) wa_color_on(CP_NORMAL, A_DIM); else if (is_avg) wa_color_on(c->jitter_warn ? CP_YELLOW : CP_GREEN, 0); else if (is_max || is_min) wa_color_on(CP_NORMAL, A_BOLD); else wa_color_on(CP_NORMAL, A_DIM); } /* Bars are rendered 1-col thinner on each side (bw = cw-2) * so they visually fit inside the column brackets ┌─ HOP N ─┐ * above / └─ HOP N ─┘ below rather than butting against them. */ int bw = (cw >= 2) ? (cw - 2) : cw; int pad = (cw >= 2) ? 1 : 0; if (!in_shaft) { wa_repeat_ch(ctx, ' ', cw); } else if (collapsed || is_avg) { /* full-width solid bar █ */ wa_repeat_ch(ctx, ' ', pad); for (d = 0; d < bw; d++) wa_str(ctx, "\xe2\x96\x88"); wa_repeat_ch(ctx, ' ', pad); } else if (is_max) { /* max whisker cap ──┬── */ wa_repeat_ch(ctx, ' ', pad); mid = (bw - 1) / 2; for (d = 0; d < bw; d++) wa_str(ctx, d == mid ? "\xe2\x94\xac" /* ┬ */ : "\xe2\x94\x80"); /* ─ */ wa_repeat_ch(ctx, ' ', pad); } else if (is_min) { /* min whisker cap ──┴── */ wa_repeat_ch(ctx, ' ', pad); mid = (bw - 1) / 2; for (d = 0; d < bw; d++) wa_str(ctx, d == mid ? "\xe2\x94\xb4" /* ┴ */ : "\xe2\x94\x80"); /* ─ */ wa_repeat_ch(ctx, ' ', pad); } else { /* shaft / wick: single │ centered (wa_center keeps * 1-col min padding via its built-in behavior) */ wa_center(ctx, "\xe2\x94\x82", cw); /* │ */ } if (in_shaft) { if (c->is_ghost) wa_color_off(CP_NORMAL, A_DIM); else if (is_avg) wa_color_off(c->jitter_warn ? CP_YELLOW : CP_GREEN, 0); else if (is_max || is_min) wa_color_off(CP_NORMAL, A_BOLD); else wa_color_off(CP_NORMAL, A_DIM); } } if (i < end) wa_repeat_ch(ctx, ' ', WA_CHART_SEP); } wa_nl(ctx); } if (ctx->row >= ctx->max_row) return; /* ---- min 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) { wa_color_on(CP_NORMAL, A_DIM); wa_printf(ctx, "%*s ", yval_w, ""); wa_color_off(CP_NORMAL, A_DIM); for (i = start; i <= end; i++) { const wa_col_t *c = &cols[i]; if (c->stats.n > 0) { char lbl[24]; snprintf(lbl, sizeof(lbl), "%dms min", (int)round(c->stats.rtt_min)); if (c->is_ghost) wa_color_on(CP_NORMAL, A_DIM); else wa_color_on(c->jitter_warn ? CP_YELLOW : CP_GREEN, 0); wa_center(ctx, lbl, c->w); if (c->is_ghost) wa_color_off(CP_NORMAL, A_DIM); else wa_color_off(c->jitter_warn ? CP_YELLOW : CP_GREEN, 0); } else if (c->is_hole) { wa_color_on(CP_NORMAL, A_DIM); { int d; wa_str(ctx, " "); for (d = 0; d < c->w - 2; d++) wa_str(ctx, "\xe2\x96\x91"); if (c->w >= 2) wa_str(ctx, " "); } /* ░ padded */ wa_color_off(CP_NORMAL, A_DIM); } else { wa_repeat_ch(ctx, ' ', c->w); } if (i < end) wa_repeat_ch(ctx, ' ', WA_CHART_SEP); } wa_nl(ctx); if (ctx->row >= ctx->max_row) return; } } /* ---- X-axis separator row: └─────┤ label ├───── emitted only when * info_str is non-empty (avoids a redundant blank row above the bottom * hop-label brackets). */ if (info_str && info_str[0]) { int any = 0; for (i = start; i <= end; i++) if (cols[i].stats.n > 0) { any = 1; break; } if (any) { int total_w = 0; for (i = start; i <= end; i++) { total_w += cols[i].w; if (i < end) total_w += WA_CHART_SEP; } char range_lbl[256]; snprintf(range_lbl, sizeof(range_lbl), "\xe2\x94\xa4 %s \xe2\x94\x9c", info_str); /* ┤ … ├ */ int label_disp = wa_utf8_display_width(range_lbl); 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; wa_color_on(CP_NORMAL, A_DIM); wa_printf(ctx, "%*s ", yval_w, ""); wa_str(ctx, "\xe2\x94\x94\xe2\x94\x80"); /* └─ */ wa_repeat_str(ctx, "\xe2\x94\x80", dash_l); wa_str(ctx, range_lbl); wa_repeat_str(ctx, "\xe2\x94\x80", dash_r); wa_color_off(CP_NORMAL, A_DIM); wa_nl(ctx); } } } /* * Build col[] descriptors from proto_sess and render the full candlestick * chart (one or more column slices) into ncurses rows [start_row, max_row). * Returns the number of rows consumed. */ static int watch_render_ascii_chart(watch_session_t *ws, lft_session_params *proto_sess, int start_row, int max_rows, int start_col) { wa_col_t cols[WA_MAX_COLS]; int ncols = 0; int hopno, maxhop; int reply, noreply, neglstart, neglend; int prevasn = -1, prevasn_hopno = 0; struct in_addr classbmask, masked_target, prevasn_hopaddr; int netreached = 0; int asseam_hopno = -1, netseam_hopno = -1; int lastishole = 0; int found_target = 0; struct trace_packet_info_s *tp; /* Usable content width: full terminal minus left margin minus right border col */ int term_cols = ws->term_cols - start_col - (start_col > 0 ? 1 : 0); wa_ctx_t ctx = { start_row, start_col, start_row + max_rows, ws->term_cols, start_col }; if (proto_sess == NULL || ws->n_hops == 0) return 0; /* ---- determine SEAM positions ---- */ inet_aton("255.255.0.0", &classbmask); masked_target.s_addr = proto_sess->remote_address.s_addr & classbmask.s_addr; if (proto_sess->num_hops) maxhop = proto_sess->num_hops; else maxhop = proto_sess->hop_info_length - 1; for (hopno = proto_sess->ttl_min; hopno < proto_sess->hop_info_length; hopno++) { int icmpcode = -100; if (proto_sess->hop_info[hopno].all_rcvd) { lastishole = 0; SLIST_FOREACH(tp, &(proto_sess->hop_info[hopno].packets), next_by_hop) { if (tp->recv.tv_sec) { if (hopno <= maxhop) icmpcode = tp->icmp_type; /* v4-only seam detection (class-B netreached + ASN seam). */ if (proto_sess->af != AF_INET6 && 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; if (proto_sess->do_aslookup || proto_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; prevasn = tp->asnumber; prevasn_hopno = hopno; prevasn_hopaddr = tp->hopaddr; } else if (tp->asnumber && tp->asnumber == prevasn) { 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)prevasn_hopaddr; /* ---- build SOURCE column ---- */ { wa_col_t *c = &cols[ncols++]; memset(c, 0, sizeof(*c)); c->is_source = 1; snprintf(c->hdr, sizeof(c->hdr), "[ SOURCE ]"); /* Chart always shows IP — no hostname lookup in the candlestick pane */ { char sbuf[INET6_ADDRSTRLEN]; snprintf(c->host, sizeof(c->host), "%s", watch_ntop_ss(proto_sess, &proto_sess->local_ss, proto_sess->local_address, sbuf, sizeof sbuf)); } if (proto_sess->protocol < 2 || proto_sess->protocol > 3) { size_t hlen = strlen(c->host); snprintf(c->host + hlen, sizeof(c->host) - hlen, ":%d", proto_sess->sport); } c->sub[0] = '\0'; /* host IS the IP — no sub-line needed */ c->w = wa_utf8_display_width(c->hdr); if (wa_utf8_display_width(c->host) > c->w) c->w = wa_utf8_display_width(c->host); } /* ---- build per-hop columns ---- */ neglstart = neglend = -1; reply = noreply = 0; (void)reply; (void)noreply; for (hopno = proto_sess->ttl_min; hopno < proto_sess->hop_info_length; hopno++) { int anomtype = 0; int icmpcode = -100; wa_col_t *c; if (ncols >= WA_MAX_COLS) break; if ((proto_sess->hop_info[hopno].state == HS_SEND_FIN) && (proto_sess->hop_info[hopno+1].state == HS_SEND_SYN) && (proto_sess->hop_info[hopno+1].ts_last_recv.tv_sec)) anomtype = 1; if ((proto_sess->hop_info[hopno].state != HS_SEND_SYN_ACK) && (proto_sess->hop_info[hopno+1].state == HS_SEND_SYN_ACK) && (hopno == (proto_sess->num_hops - 1))) anomtype = 2; if ((proto_sess->hop_info[hopno].flags & HF_ENDPOINT) && (noreply >= ((maxhop - proto_sess->ttl_min) / 2)) && proto_sess->num_hops > 3) anomtype = 3; if (proto_sess->hop_info[hopno].all_rcvd == 0) { /* If we have historical data for this TTL (seen in a prior cycle), * render it as a "ghost" column rather than collapsing it into a * featureless cloaked hole. This preserves RTT history, ASN, and * host data in the chart even when a router stops responding. */ if (hopno < WATCH_MAX_HOPS && ws->hops[hopno].cycles_seen > 0 && ws->hops[hopno].host[0] && strcmp(ws->hops[hopno].host, "*") != 0) { /* Flush any pending pure-hole block before the ghost column */ if (neglstart != -1 && ncols < WA_MAX_COLS) { 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); snprintf(c->host, sizeof(c->host), "cloaked"); snprintf(c->rtt, sizeof(c->rtt), neglstart == neglend ? "TTL %d" : "TTLs %d-%d", neglstart + 1, neglend + 1); c->w = wa_utf8_display_width(c->hdr); if (wa_utf8_display_width(c->host) > c->w) c->w = wa_utf8_display_width(c->host); if (wa_utf8_display_width(c->rtt) > c->w) c->w = wa_utf8_display_width(c->rtt); neglstart = neglend = -1; } /* Build ghost column from ws->hops[hopno] historical data */ if (ncols < WA_MAX_COLS) { const watch_hop_t *wh = &ws->hops[hopno]; c = &cols[ncols++]; memset(c, 0, sizeof(*c)); c->is_ghost = 1; snprintf(c->hdr, sizeof(c->hdr), "[ HOP %d ]", hopno + 1); /* Candlestick chart always shows the IP address — * hostnames only appear in the timeline table below. * This drops the sub-line, saving one chart row and * avoiding per-cycle alignment churn when hostnames * resolve (or fail to resolve) asynchronously. */ snprintf(c->host, sizeof(c->host), "%s", wh->host[0] ? wh->host : "*"); c->sub[0] = '\0'; /* ASN / NETNAME are deliberately NOT populated here — * they render in the timeline table below the chart. * Showing them in the candlestick made them flicker in * and out depending on whether a cycle had the data * cached, making the chart look unstable. */ c->asn[0] = '\0'; c->net[0] = '\0'; /* Historical RTT stats — use all-time min/avg/max */ c->stats.n = wh->cycles_seen; c->stats.rtt_min = wh->rtt_min; c->stats.rtt_max = wh->rtt_max; c->stats.rtt_avg = wh->rtt_avg; c->stats.rtt_stddev = 0.0; snprintf(c->rtt, sizeof(c->rtt), "%d/%d/%dms", (int)round(wh->rtt_min), (int)round(wh->rtt_avg), (int)round(wh->rtt_max)); c->w = wa_utf8_display_width(c->hdr); if (wa_utf8_display_width(c->host) > c->w) c->w = wa_utf8_display_width(c->host); if (wa_utf8_display_width(c->rtt) > c->w) c->w = wa_utf8_display_width(c->rtt); } continue; } if (neglstart == -1) neglstart = hopno; neglend = hopno; continue; } /* emit pending hole column */ if (neglstart != -1 && ncols < WA_MAX_COLS) { 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); 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 = wa_utf8_display_width(c->hdr); if (wa_utf8_display_width(c->host) > c->w) c->w = wa_utf8_display_width(c->host); if (wa_utf8_display_width(c->rtt) > c->w) c->w = wa_utf8_display_width(c->rtt); neglstart = neglend = -1; } SLIST_FOREACH(tp, &(proto_sess->hop_info[hopno].packets), next_by_hop) { if (!tp->recv.tv_sec) continue; if (hopno <= maxhop) icmpcode = tp->icmp_type; if (!watch_hop_changed(proto_sess, tp)) continue; if (ncols >= WA_MAX_COLS) break; c = &cols[ncols++]; memset(c, 0, sizeof(*c)); c->anomtype = anomtype; if (icmpcode == -1) { c->is_target = 1; found_target = 1; snprintf(c->hdr, sizeof(c->hdr), "[ TARGET ]"); if (proto_sess->target_open > 0) snprintf(c->ann, sizeof(c->ann), "[open]"); else if (proto_sess->target_filtered) snprintf(c->ann, sizeof(c->ann), "[filtered]"); else if (proto_sess->protocol == 2 || proto_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); if (anomtype == 1) snprintf(c->ann, sizeof(c->ann), "\xe2\x87\xb6 Stateful FW"); else if (anomtype == 2) snprintf(c->ann, sizeof(c->ann), "\xe2\x9a\x91 Flag FW"); else if (anomtype == 3) snprintf(c->ann, sizeof(c->ann), "\xe2\x87\x84 BSD Bug"); else if (ws->show_seams && hopno == asseam_hopno) snprintf(c->ann, sizeof(c->ann), "\xe2\x95\xb3 ASN Seam"); else if (ws->show_seams && hopno == netseam_hopno) snprintf(c->ann, sizeof(c->ann), "\xe2\x95\xb3 NET Seam"); } { /* When hostname display is on and we have a resolved name, * show hostname as the primary label and IP as the sub-line — * exactly matching the ghost-column behavior so a hop never * flickers between IP and hostname as it alternates live/ghost. */ const watch_hop_t *wh_live = (hopno >= 0 && hopno < WATCH_MAX_HOPS) ? &ws->hops[hopno] : NULL; /* Candlestick chart always shows the IP address — hostnames * only appear in the timeline table below. This drops the * sub-line entirely, saving one chart row. */ { char hbuf[INET6_ADDRSTRLEN]; snprintf(c->host, sizeof(c->host), "%s", watch_ntop_hop(proto_sess, tp, hbuf, sizeof hbuf)); } c->sub[0] = '\0'; (void)wh_live; } if (c->is_target && (proto_sess->protocol < 2 || proto_sess->protocol > 3)) { size_t hlen = strlen(c->host); snprintf(c->host + hlen, sizeof(c->host) - hlen, ":%d", proto_sess->dport); } /* No sub fallback: we intentionally show IP-only in the chart pane */ /* ASN / NETNAME deliberately left empty — they render in the * timeline table below. Populating them here caused them to * flicker in and out between cycles depending on pWhoIs cache * state, which made the chart look unstable. */ c->asn[0] = '\0'; c->net[0] = '\0'; /* RTT — prefer watch session's rolling stats for chart stability */ { int wi = hopno - proto_sess->ttl_min; if (wi >= 0 && wi < ws->n_hops && ws->hops[wi].cycles_seen > 0) { /* Use rolling watch stats for smoother multi-cycle display */ c->stats.n = ws->hops[wi].cycles_seen; c->stats.rtt_min = ws->hops[wi].rtt_min; c->stats.rtt_avg = ws->hops[wi].rtt_avg; c->stats.rtt_max = ws->hops[wi].rtt_max; c->stats.rtt_stddev = 0.0; snprintf(c->rtt, sizeof(c->rtt), "%d/%d/%dms", (int)round(c->stats.rtt_min), (int)round(c->stats.rtt_avg), (int)round(c->stats.rtt_max)); } else if (proto_sess->rtt_stats) { c->stats = wa_compute_rtt_stats(proto_sess, hopno); if (c->stats.n > 0) snprintf(c->rtt, sizeof(c->rtt), "%d/%d/%dms", (int)round(c->stats.rtt_min), (int)round(c->stats.rtt_avg), (int)round(c->stats.rtt_max)); } else if (tp->recv.tv_sec) { double rtt = timediff_ms(tp->sent, tp->recv); c->stats.n = 1; c->stats.rtt_min = c->stats.rtt_avg = c->stats.rtt_max = rtt; snprintf(c->rtt, sizeof(c->rtt), "%.1fms", rtt); } } if (proto_sess->pmtud && proto_sess->hop_info[hopno].path_mtu > 0) snprintf(c->mtu, sizeof(c->mtu), "[MTU: %u]", (unsigned)proto_sess->hop_info[hopno].path_mtu); c->w = wa_utf8_display_width(c->hdr); if (wa_utf8_display_width(c->host) > c->w) c->w = wa_utf8_display_width(c->host); if (wa_utf8_display_width(c->sub) > c->w) c->w = wa_utf8_display_width(c->sub); /* c->asn and c->net intentionally excluded — they no longer render * in the chart, so including them in width calcs would waste space. */ if (wa_utf8_display_width(c->rtt) > c->w) c->w = wa_utf8_display_width(c->rtt); if (wa_utf8_display_width(c->ann) > c->w) c->w = wa_utf8_display_width(c->ann); if (wa_utf8_display_width(c->mtu) > c->w) c->w = wa_utf8_display_width(c->mtu); if (found_target) break; } if (found_target) break; } /* trailing hole */ if (neglstart != -1 && ncols < WA_MAX_COLS) { wa_col_t *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); 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 = wa_utf8_display_width(c->hdr); if (wa_utf8_display_width(c->host) > c->w) c->w = wa_utf8_display_width(c->host); if (wa_utf8_display_width(c->rtt) > c->w) c->w = wa_utf8_display_width(c->rtt); } if (ncols == 0) return 0; /* ---- cap column widths at WA_MAX_COL_W ---- * The hostname is the only field that can realistically push a column * beyond 17 display columns; truncate it from the right so the label * row stays within the cap. All other fields (hdr, rtt, ann, mtu) are * fixed-format strings that are ≤17 chars in practice. */ { int ci; for (ci = 0; ci < ncols; ci++) { if (cols[ci].w > WA_MAX_COL_W) { wa_utf8_abbrev_to(cols[ci].host, WA_MAX_COL_W); cols[ci].w = WA_MAX_COL_W; } } } /* ---- global RTT bounds + jitter detection ---- */ double g_rtt_min = 0.0, g_rtt_max = 0.0; { int g_found = 0, 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 > WA_JITTER_WARN) { int jms = (int)round(max_jitter); cols[jitter_col].jitter_warn = 1; snprintf(cols[jitter_col].jitter_ann, sizeof(cols[jitter_col].jitter_ann), "\xe2\x9a\xa1 %dms \xce\x94", jms); if (cols[jitter_col].ann[0] == '\0') snprintf(cols[jitter_col].ann, sizeof(cols[jitter_col].ann), "\xe2\x9a\xa1 jitter %dms", jms); if (wa_utf8_display_width(cols[jitter_col].jitter_ann) > cols[jitter_col].w) cols[jitter_col].w = wa_utf8_display_width(cols[jitter_col].jitter_ann); if (wa_utf8_display_width(cols[jitter_col].ann) > cols[jitter_col].w) cols[jitter_col].w = wa_utf8_display_width(cols[jitter_col].ann); } } /* ---- x-axis info string ---- */ char xa_info[256] = ""; { double tgt_min = 0.0, tgt_max = 0.0; int found_tgt = 0, gi; int n_hops = ncols - 1; char tgt_namebuf[INET6_ADDRSTRLEN]; const char *tgt_name = watch_ntop_ss(proto_sess, &proto_sess->remote_ss, proto_sess->remote_address, tgt_namebuf, sizeof tgt_namebuf); char tgt_url[80]; switch (proto_sess->protocol) { case 1: snprintf(tgt_url, sizeof(tgt_url), "udp://%s:%d", tgt_name, proto_sess->dport); break; case 2: case 3: snprintf(tgt_url, sizeof(tgt_url), "icmp://%s", tgt_name); break; default: snprintf(tgt_url, sizeof(tgt_url), "tcp://%s:%d", tgt_name, proto_sess->dport); break; } for (gi = 0; gi < ncols; gi++) { if (cols[gi].is_target && cols[gi].stats.n > 0) { tgt_min = cols[gi].stats.rtt_min; tgt_max = cols[gi].stats.rtt_max; found_tgt = 1; break; } } if (found_tgt) snprintf(xa_info, sizeof(xa_info), "End-to-End RTT %d\xe2\x80\x93%dms, %d hops to %s", (int)round(tgt_min), (int)round(tgt_max), n_hops, tgt_url); else snprintf(xa_info, sizeof(xa_info), "%d hops traced, target unreachable", n_hops); } /* Y-axis prefix width */ { char ybuf[16]; snprintf(ybuf, sizeof(ybuf), "%dms", (int)ceil(g_rtt_max > 0 ? g_rtt_max : 1.0)); int y_prefix_w = (int)strlen(ybuf) + 3; int effective_w = term_cols - y_prefix_w; if (effective_w < 20) effective_w = 20; /* ---- render column slices ---- */ int start = 0; while (start < ncols && ctx.row < ctx.max_row) { int end = start; int used = cols[start].w; while (end + 1 < ncols) { int needed = used + WA_CHART_SEP + cols[end + 1].w; if (needed > effective_w) break; used = needed; end++; } /* blank row before each slice */ if (ctx.row < ctx.max_row) { clrtoeol(); wa_nl(&ctx); } /* candlestick chart — no x-axis RTT label (shown in top border) */ wa_chart_render(&ctx, cols, start, end, g_rtt_min, g_rtt_max, ""); if (ctx.row >= ctx.max_row) break; /* ---- header row (hop labels, bottom — outer corners └ / ┘) ---- */ if (y_prefix_w > 0) wa_printf(&ctx, "%*s", y_prefix_w, ""); wa_render_header_row(&ctx, cols, start, end, 1); if (ctx.row >= ctx.max_row) break; /* ---- hostname row ---- */ if (y_prefix_w > 0) wa_printf(&ctx, "%*s", y_prefix_w, ""); { int ii; for (ii = start; ii <= end && ctx.col < ctx.term_cols - 1; ii++) { wa_col_t *c = &cols[ii]; if (c->is_hole) wa_color_on(CP_NORMAL, A_DIM); else if (c->is_target) wa_color_on(CP_NORMAL, A_BOLD); else if (c->anomtype) wa_color_on(CP_RED, 0); else if (c->is_source) wa_color_on(CP_NORMAL, A_BOLD); wa_center(&ctx, c->host, c->w); if (c->is_hole) wa_color_off(CP_NORMAL, A_DIM); else if (c->is_target) wa_color_off(CP_NORMAL, A_BOLD); else if (c->anomtype) wa_color_off(CP_RED, 0); else if (c->is_source) wa_color_off(CP_NORMAL, A_BOLD); if (ii < end) wa_str(&ctx, " -> "); } } wa_nl(&ctx); if (ctx.row >= ctx.max_row) break; /* ---- alternate IP rows (multipath) ---- */ /* Show one row per alternate IP level across all columns in this slice. * Stats/bar already combine all paths; these lines just identify them. */ { int alt_idx; for (alt_idx = 0; alt_idx < WATCH_MAX_ALT_IPS; alt_idx++) { int ii, any = 0; for (ii = start; ii <= end; ii++) if (alt_idx < cols[ii].n_alt) { any = 1; break; } if (!any) break; if (ctx.row >= ctx.max_row) break; if (y_prefix_w > 0) wa_printf(&ctx, "%*s", y_prefix_w, ""); for (ii = start; ii <= end && ctx.col < ctx.term_cols - 1; ii++) { wa_col_t *ca = &cols[ii]; const char *alt = (alt_idx < ca->n_alt) ? ca->alt_hosts[alt_idx] : ""; if (alt[0]) wa_color_on(CP_STATUS, A_DIM); wa_center(&ctx, alt, ca->w); if (alt[0]) wa_color_off(CP_STATUS, A_DIM); if (ii < end) wa_repeat_ch(&ctx, ' ', WA_CHART_SEP); } wa_nl(&ctx); } } if (ctx.row >= ctx.max_row) break; /* ---- sub line (IP in parens or TTL for holes) ---- */ { int ii, any = 0; for (ii = start; ii <= end; ii++) if (cols[ii].sub[0]) { any=1; break; } if (any) { if (y_prefix_w > 0) wa_printf(&ctx, "%*s", y_prefix_w, ""); for (ii = start; ii <= end && ctx.col < ctx.term_cols - 1; ii++) { wa_col_t *c = &cols[ii]; if (c->is_hole) wa_color_on(CP_NORMAL, A_DIM); wa_center(&ctx, c->sub, c->w); if (c->is_hole) wa_color_off(CP_NORMAL, A_DIM); if (ii < end) wa_repeat_ch(&ctx, ' ', WA_CHART_SEP); } wa_nl(&ctx); if (ctx.row >= ctx.max_row) break; } } /* ---- ASN line ---- */ { int ii, any = 0; for (ii = start; ii <= end; ii++) if (cols[ii].asn[0]) { any=1; break; } if (any) { if (y_prefix_w > 0) wa_printf(&ctx, "%*s", y_prefix_w, ""); for (ii = start; ii <= end && ctx.col < ctx.term_cols - 1; ii++) { wa_col_t *c = &cols[ii]; if (c->asn[0]) wa_color_on(c->is_hole ? CP_NORMAL : CP_YELLOW, c->is_hole ? A_DIM : 0); wa_center(&ctx, c->asn, c->w); if (c->asn[0]) wa_color_off(c->is_hole ? CP_NORMAL : CP_YELLOW, c->is_hole ? A_DIM : 0); if (ii < end) wa_repeat_ch(&ctx, ' ', WA_CHART_SEP); } wa_nl(&ctx); if (ctx.row >= ctx.max_row) break; } } /* ---- net name line ---- */ { int ii, any = 0; for (ii = start; ii <= end; ii++) if (cols[ii].net[0]) { any=1; break; } if (any) { if (y_prefix_w > 0) wa_printf(&ctx, "%*s", y_prefix_w, ""); for (ii = start; ii <= end && ctx.col < ctx.term_cols - 1; ii++) { wa_col_t *c = &cols[ii]; if (c->net[0]) wa_color_on(CP_YELLOW, 0); wa_center(&ctx, c->net, c->w); if (c->net[0]) wa_color_off(CP_YELLOW, 0); if (ii < end) wa_repeat_ch(&ctx, ' ', WA_CHART_SEP); } wa_nl(&ctx); if (ctx.row >= ctx.max_row) break; } } /* ---- RTT line ---- */ { int ii, any = 0; for (ii = start; ii <= end; ii++) if (cols[ii].rtt[0]) { any=1; break; } if (any) { if (y_prefix_w > 0) wa_printf(&ctx, "%*s", y_prefix_w, ""); for (ii = start; ii <= end && ctx.col < ctx.term_cols - 1; ii++) { wa_col_t *c = &cols[ii]; if (c->rtt[0]) wa_color_on(c->is_hole ? CP_NORMAL : CP_GREEN, c->is_hole ? A_DIM : 0); wa_center(&ctx, c->rtt, c->w); if (c->rtt[0]) wa_color_off(c->is_hole ? CP_NORMAL : CP_GREEN, c->is_hole ? A_DIM : 0); if (ii < end) wa_repeat_ch(&ctx, ' ', WA_CHART_SEP); } wa_nl(&ctx); if (ctx.row >= ctx.max_row) break; } } /* ---- annotation line ---- */ { int ii, any = 0; for (ii = start; ii <= end; ii++) if (cols[ii].ann[0]) { any=1; break; } if (any) { if (y_prefix_w > 0) wa_printf(&ctx, "%*s", y_prefix_w, ""); for (ii = start; ii <= end && ctx.col < ctx.term_cols - 1; ii++) { wa_col_t *c = &cols[ii]; if (c->ann[0]) { if (c->is_target) { if (proto_sess->target_open > 0) wa_color_on(CP_GREEN, A_BOLD); else if (proto_sess->target_filtered) wa_color_on(CP_YELLOW, 0); else if (proto_sess->protocol == 2 || proto_sess->protocol == 3) wa_color_on(CP_GREEN, 0); else wa_color_on(CP_RED, A_BOLD); } else if (c->anomtype > 0) { wa_color_on(CP_RED, 0); } else { wa_color_on(CP_ORANGE, 0); } } wa_center(&ctx, c->ann, c->w); if (c->ann[0]) { if (c->is_target) { if (proto_sess->target_open > 0) wa_color_off(CP_GREEN, A_BOLD); else if (proto_sess->target_filtered) wa_color_off(CP_YELLOW, 0); else if (proto_sess->protocol == 2 || proto_sess->protocol == 3) wa_color_off(CP_GREEN, 0); else wa_color_off(CP_RED, A_BOLD); } else if (c->anomtype > 0) { wa_color_off(CP_RED, 0); } else { wa_color_off(CP_ORANGE, 0); } } if (ii < end) wa_repeat_ch(&ctx, ' ', WA_CHART_SEP); } wa_nl(&ctx); if (ctx.row >= ctx.max_row) break; } } /* ---- MTU line ---- */ if (proto_sess->pmtud) { int ii, any = 0; for (ii = start; ii <= end; ii++) if (cols[ii].mtu[0]) { any=1; break; } if (any) { if (y_prefix_w > 0) wa_printf(&ctx, "%*s", y_prefix_w, ""); for (ii = start; ii <= end && ctx.col < ctx.term_cols - 1; ii++) { wa_col_t *c = &cols[ii]; if (c->mtu[0]) wa_color_on(CP_YELLOW, 0); wa_center(&ctx, c->mtu, c->w); if (c->mtu[0]) wa_color_off(CP_YELLOW, 0); if (ii < end) wa_repeat_ch(&ctx, ' ', WA_CHART_SEP); } wa_nl(&ctx); if (ctx.row >= ctx.max_row) break; } } start = end + 1; } } return ctx.row - start_row; } /* ---- View name label for the right side of the top border ---- */ static const char *watch_view_name(int view) { switch (view) { case 2: return "Bars"; case 3: return "Status Board"; default: return "Candlestick"; } } /* ---- Shared top border row (row 0) for all views ---- * Left: ┌──┤ Layer Four Traceroute (LFT) ├ * Middle: [dash_l]┤ rtt_info ├[dash_r] (omitted when rtt_info NULL/empty) * Right: ┤ ViewName ├──┐ * * When RTT is present, it is centered in the space between LFT and VIEW * sections (equal dash_l / dash_r split). */ /* Build the centre RTT/hop summary string for the top border. * Used by all three views so they share identical header content. */ static void watch_build_rtt_info(watch_session_t *ws, char *buf, int sz) { int i, last_hop = -1; for (i = ws->n_hops - 1; i >= 0; i--) { if (ws->hops[i].rtt_avg > 0.0) { last_hop = i; break; } } if (last_hop >= 0) { snprintf(buf, (size_t)sz, "End-to-End RTT %d\xe2\x80\x93%dms, %d hops to %s", (int)round(ws->hops[last_hop].rtt_min), (int)round(ws->hops[last_hop].rtt_max), ws->n_hops, ws->tgt_label); } else { snprintf(buf, (size_t)sz, "%s %d hops", ws->tgt_label, ws->n_hops); } } static void watch_render_top_border(watch_session_t *ws, const char *rtt_info) { const char *lft_label = "Layer Four Traceroute (LFT)"; const char *view_label = watch_view_name(ws->current_view); int lft_dw = wa_utf8_display_width(lft_label); int view_dw = wa_utf8_display_width(view_label); /* ┌──┤ space LFT space ├ = lft_dw + 7 */ int lft_end = lft_dw + 7; /* ┤ space VIEW space ├──┐ = view_dw + 7 */ int view_end = view_dw + 7; int i; /* Macros for switching between frame (blue box-drawing) and text (cyan) * color pairs so we can draw a mixed-color top border in one pass. */ #define FRAME_ON() do { if (has_colors()) attron(COLOR_PAIR(CP_BLUE)); } while (0) #define FRAME_OFF() do { if (has_colors()) attroff(COLOR_PAIR(CP_BLUE)); } while (0) #define TEXT_ON() do { if (has_colors()) attron(COLOR_PAIR(CP_SENT) | A_BOLD); } while (0) #define TEXT_OFF() do { if (has_colors()) attroff(COLOR_PAIR(CP_SENT) | A_BOLD); } while (0) move(0, 0); FRAME_ON(); addstr("\xe2\x94\x8c\xe2\x94\x80\xe2\x94\x80"); /* ┌── */ addstr("\xe2\x94\xa4"); /* ┤ */ FRAME_OFF(); TEXT_ON(); addstr(" "); addstr(lft_label); addstr(" "); TEXT_OFF(); FRAME_ON(); addstr("\xe2\x94\x9c"); /* ├ */ if (rtt_info && rtt_info[0]) { int info_dw = wa_utf8_display_width(rtt_info); int rtt_sec = info_dw + 4; /* ┤ space info space ├ */ int total = ws->term_cols - lft_end - rtt_sec - view_end; if (total < 0) total = 0; int dash_l = (ws->term_cols - rtt_sec) / 2 - lft_end; if (dash_l < 0) dash_l = 0; if (dash_l > total) dash_l = total; int dash_r = total - dash_l; for (i = 0; i < dash_l; i++) addstr("\xe2\x94\x80"); /* ─ */ addstr("\xe2\x94\xa4"); /* ┤ */ FRAME_OFF(); TEXT_ON(); addstr(" "); addstr(rtt_info); addstr(" "); TEXT_OFF(); FRAME_ON(); addstr("\xe2\x94\x9c"); /* ├ */ for (i = 0; i < dash_r; i++) addstr("\xe2\x94\x80"); /* ─ */ } else { int total = ws->term_cols - lft_end - view_end; if (total < 0) total = 0; for (i = 0; i < total; i++) addstr("\xe2\x94\x80"); /* ─ */ } addstr("\xe2\x94\xa4"); /* ┤ */ FRAME_OFF(); TEXT_ON(); addstr(" "); addstr(view_label); addstr(" "); TEXT_OFF(); FRAME_ON(); addstr("\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80"); /* ├── */ addstr("\xe2\x94\x90"); /* ┐ */ FRAME_OFF(); #undef FRAME_ON #undef FRAME_OFF #undef TEXT_ON #undef TEXT_OFF } static void watch_render_view1(watch_session_t *ws, lft_session_params *proto_sess) { int chart_max, chart_rows_used; int div_row, bot_row, timeline_start, timeline_rows; int r, i; /* Framed layout: * row 0 : ┌──┤ RTT info ├──────────────────────────────────────────┐ * rows 1..N : │ candlestick chart │ * row N+1 : ├──────────────────────────────────────────────────────────┤ * rows N+2..M : │ rolling timeline table │ * row M+1 (=bot) : └──────────────────────────────────────────────────────────┘ * row M+2 : status bar row 1 * row M+3 : status bar row 2 */ bot_row = ws->term_rows - 3; /* bottom border row (above status bar) */ chart_max = (bot_row - 1) * 6 / 10; /* ~60% of usable rows for chart */ if (chart_max < 4) chart_max = 4; clear(); /* ------------------------------------------------------------------ */ /* Compute end-to-end RTT for the top border title */ /* ------------------------------------------------------------------ */ char rtt_info[256]; watch_build_rtt_info(ws, rtt_info, (int)sizeof(rtt_info)); /* ------------------------------------------------------------------ */ /* Row 0: top border */ /* ------------------------------------------------------------------ */ watch_render_top_border(ws, rtt_info); if (proto_sess == NULL || ws->n_hops == 0) { mvprintw(2, 2, "Collecting initial trace data..."); return; } /* ------------------------------------------------------------------ */ /* Chart: rows 1..chart_max, content at col 1..term_cols-2. The */ /* chart is given a FIXED slot of chart_max rows whether it uses all */ /* of them or not — that way the divider row (and the timeline table */ /* below it) stays put when the number of hops/paths changes between */ /* cycles. Without this, a growing candlestick bumps the whole table */ /* downward on each cycle and vice versa. */ /* ------------------------------------------------------------------ */ chart_rows_used = watch_render_ascii_chart(ws, proto_sess, 1, chart_max, 1); (void)chart_rows_used; /* ------------------------------------------------------------------ */ /* Divider: ├──────────────────────────────────────────────────────────┤ */ /* Fixed at row (1 + chart_max) regardless of actual chart height. */ /* ------------------------------------------------------------------ */ div_row = 1 + chart_max; if (div_row >= bot_row) div_row = bot_row - 1; if (div_row > 0 && div_row < ws->term_rows - 2) { if (has_colors()) attron(COLOR_PAIR(CP_BLUE)); move(div_row, 0); addstr("\xe2\x94\x9c"); /* ├ */ for (i = 1; i < ws->term_cols - 1; i++) addstr("\xe2\x94\x80"); /* ─ */ addstr("\xe2\x94\xa4"); /* ┤ */ if (has_colors()) attroff(COLOR_PAIR(CP_BLUE)); } timeline_start = div_row + 1; /* ------------------------------------------------------------------ */ /* Timeline: rows timeline_start..bot_row, content at col 1..end-2 */ /* The table's bottom border is the status bar itself (├──...──┤). */ /* ------------------------------------------------------------------ */ timeline_rows = bot_row - timeline_start + 1; if (timeline_rows >= 2) watch_render_timeline(ws, timeline_start, timeline_rows, 1); /* ------------------------------------------------------------------ */ /* Left │ and right │ borders for each content row */ /* The chart and timeline render with col_offset=1 / start_col=1, so */ /* col 0 and col term_cols-1 are free for the borders. Borders extend */ /* all the way down to bot_row; the status bar row below acts as the */ /* closing horizontal via ├── ... ──┤ connectors. */ /* ------------------------------------------------------------------ */ if (has_colors()) attron(COLOR_PAIR(CP_BLUE)); /* Use ncurses native ACS_VLINE + mvvline() for continuous solid sides, * in two segments so the divider row keeps its ├ and ┤ connectors. */ if (div_row > 1) mvvline(1, 0, ACS_VLINE, div_row - 1); if (bot_row > div_row) mvvline(div_row + 1, 0, ACS_VLINE, bot_row - div_row); if (div_row > 1) mvvline(1, ws->term_cols - 1, ACS_VLINE, div_row - 1); if (bot_row > div_row) mvvline(div_row + 1, ws->term_cols - 1, ACS_VLINE, bot_row - div_row); if (has_colors()) attroff(COLOR_PAIR(CP_BLUE)); (void)r; /* no longer used */ } /* ========================================================================= * View 2: A+B hybrid — native bar chart (top) + timeline table (bottom) * ========================================================================= */ /* * Native ncurses RTT bar chart — drawn directly from ws->hops[] data. * Uses Unicode block characters and ncurses color pairs for consistent * styling with the timeline table below. * Returns the number of rows consumed. */ static int watch_render_native_chart(watch_session_t *ws, int start_row, int max_rows, int col_offset) { int i, r; double g_max = 0.0; int n = ws->n_hops; int y_lbl_w = 8; /* "9999ms |" */ int bar_rows, avail_cols, col_w, bar_w; int hop_row, ip_row; int right_edge = ws->term_cols - col_offset; /* exclusive right bound */ if (n <= 0 || max_rows < 4) return 0; /* Find global RTT max across hops that have data (ignore 100% loss hops) */ for (i = 0; i < n; i++) { double lp = (ws->hops[i].cycles_total > 0) ? 100.0 * (ws->hops[i].cycles_total - ws->hops[i].cycles_seen) / ws->hops[i].cycles_total : 100.0; if (lp < 100.0 && ws->hops[i].rtt_max > g_max) g_max = ws->hops[i].rtt_max; } if (g_max <= 0.0) g_max = 100.0; g_max *= 1.15; /* 15% headroom so bars don't clip the top */ /* Row allocation: bar area + 2 label rows (hop number + IP) */ bar_rows = max_rows - 2; if (bar_rows < 2) bar_rows = 2; avail_cols = right_edge - col_offset - y_lbl_w; if (avail_cols < 1) avail_cols = 1; col_w = avail_cols / n; if (col_w < 1) col_w = 1; if (col_w > WA_MAX_COL_W) col_w = WA_MAX_COL_W; /* keep label rows readable */ bar_w = (col_w > 1) ? col_w - 1 : 1; /* 1-char gap between hop columns */ /* Draw bar rows top→bottom (row 0 = g_max ms, row bar_rows-1 = 0 ms) */ for (r = 0; r < bar_rows; r++) { int screen_row = start_row + r; double rtt_at_row; if (screen_row >= ws->term_rows - 2) break; move(screen_row, 0); clrtoeol(); /* Y-axis label: only at top, midpoint, and bottom rows */ if (has_colors()) attron(COLOR_PAIR(CP_STATUS)); rtt_at_row = g_max * (bar_rows - 1 - r) / (double)(bar_rows > 1 ? bar_rows - 1 : 1); if (r == 0) mvprintw(screen_row, col_offset, "%5.0fms |", g_max); else if (r == bar_rows / 2) mvprintw(screen_row, col_offset, "%5.0fms |", rtt_at_row); else if (r == bar_rows - 1) mvprintw(screen_row, col_offset, " 0ms |"); else mvprintw(screen_row, col_offset, " "); if (has_colors()) attroff(COLOR_PAIR(CP_STATUS)); /* Per-hop bar segments */ for (i = 0; i < n; i++) { watch_hop_t *h = &ws->hops[i]; double lp = (h->cycles_total > 0) ? 100.0 * (h->cycles_total - h->cycles_seen) / h->cycles_total : 100.0; int cx = col_offset + y_lbl_w + i * col_w; int w = (cx + bar_w <= right_edge) ? bar_w : right_edge - cx; int fill_top, max_top, j; if (cx >= right_edge) break; if (w < 1) w = 1; move(screen_row, cx); if (lp >= 100.0) { /* Cloaked hop: loss colour asterisk at the bottom row, blank above */ if (r == bar_rows - 1) { if (has_colors()) attron(COLOR_PAIR(CP_MAGENTA) | A_BOLD); addstr("*"); if (has_colors()) attroff(COLOR_PAIR(CP_MAGENTA) | A_BOLD); } else { printw("%*s", w, ""); } continue; } /* bar fills from avg RTT down; max-RTT mark appears above it */ fill_top = bar_rows - 1 - (int)(h->rtt_avg / g_max * (bar_rows - 1) + 0.5); max_top = bar_rows - 1 - (int)(h->rtt_max / g_max * (bar_rows - 1) + 0.5); if (fill_top < 0) fill_top = 0; if (max_top < 0) max_top = 0; if (max_top > fill_top) max_top = fill_top; if (r >= fill_top) { /* Filled bar (avg RTT height and below) */ if (has_colors()) attron(COLOR_PAIR(rtt_color(h->rtt_avg))); for (j = 0; j < w; j++) addstr("\xe2\x96\x88"); /* █ */ if (has_colors()) attroff(COLOR_PAIR(rtt_color(h->rtt_avg))); } else if (r == max_top && max_top < fill_top) { /* Max-RTT cap mark (▄) just above the filled bar */ if (has_colors()) attron(COLOR_PAIR(rtt_color(h->rtt_max))); addstr("\xe2\x96\x84"); /* ▄ */ if (has_colors()) attroff(COLOR_PAIR(rtt_color(h->rtt_max))); } else { printw("%*s", w, ""); } } } /* Hop-number label row */ hop_row = start_row + bar_rows; if (hop_row < ws->term_rows - 2) { move(hop_row, 0); clrtoeol(); if (has_colors()) attron(COLOR_PAIR(CP_STATUS) | A_BOLD); mvprintw(hop_row, col_offset, "%8s", ""); for (i = 0; i < n; i++) { int cx = col_offset + y_lbl_w + i * col_w; if (cx >= right_edge) break; move(hop_row, cx); printw("%-*d", col_w < 4 ? col_w : 3, i + 1); } if (has_colors()) attroff(COLOR_PAIR(CP_STATUS) | A_BOLD); } /* IP/host label row */ ip_row = start_row + bar_rows + 1; if (ip_row < ws->term_rows - 2) { move(ip_row, 0); clrtoeol(); for (i = 0; i < n; i++) { watch_hop_t *h = &ws->hops[i]; int cx = col_offset + y_lbl_w + i * col_w; int trunc_w = col_w > 1 ? col_w - 1 : 1; char trunc[64]; if (cx >= right_edge) break; if (trunc_w > (int)sizeof(trunc) - 1) trunc_w = (int)sizeof(trunc) - 1; /* Bar chart always shows the IP — hostnames only appear in * the timeline table below, so 'h' only affects that. */ snprintf(trunc, sizeof(trunc), "%-.*s", trunc_w, h->host[0] ? h->host : "*"); move(ip_row, cx); if (has_colors()) attron(COLOR_PAIR(rtt_color(h->rtt_avg))); addstr(trunc); if (has_colors()) attroff(COLOR_PAIR(rtt_color(h->rtt_avg))); } } return max_rows; } /* Render the rolling timeline table into rows [start_row .. start_row+max_rows). * col_offset: left margin (0 = full-width, 1 = inside a framed border). */ static int watch_render_timeline(watch_session_t *ws, int start_row, int max_rows, int col_offset) { int i, row = start_row; int hist_cols; /* history sparkline cells after fixed columns */ int fixed_w; /* width of fixed columns (HOP … JITTER) */ int host_w; /* HOST column: auto-widened to fit longest name */ int whois_w = 0; /* ASN(8) + NETNAME(16) [+ ORGNAME(20)] */ int show_orgname = 0; int row_end = start_row + max_rows - 1; /* right border column (content must stop before this) */ int right_col = ws->term_cols - (col_offset > 0 ? 1 : 0); if (row >= ws->term_rows - 2) return 0; /* ------------------------------------------------------------------ */ /* Compute host_w: widest hop address/name, clamped to fit. */ /* To avoid horizontal layout shift when the user presses 'h' to */ /* toggle hostnames, host_w is the max of the IP width AND the */ /* hostname width (regardless of current show_hostnames) — so the */ /* column budget is identical in both states. */ /* ------------------------------------------------------------------ */ host_w = 15; /* minimum */ for (i = 0; i < ws->n_hops; i++) { const char *ip = ws->hops[i].host[0] ? ws->hops[i].host : "*"; const char *name = ws->hops[i].hostname[0] ? ws->hops[i].hostname : ip; int w_ip = (int)strlen(ip); int w_name = (int)strlen(name); int w = (w_ip > w_name) ? w_ip : w_name; /* Target hop gets a state tag appended — budget 11 extra chars. */ if (i == ws->target_hopno && ws->target_hopno > 0) w += 11; if (w > host_w) host_w = w; } if (host_w > 64) host_w = 64; /* sanity cap */ /* Whois column widths */ if (ws->whois_enabled) { whois_w = 8 + 2 + 16 + 2; /* ORGNAME if the terminal is wide enough (evaluated after host_w) */ if (ws->term_cols - col_offset >= 1 + 3 + 2 + host_w + 2 + 5 + 2 + 6 + 2 + 6 + 2 + 6 + 2 + 4 + 2 + whois_w + 20 + 2 + 4) { whois_w += 20 + 2; show_orgname = 1; } } /* Fixed column width (everything except the sparkline) */ fixed_w = 1 + 3 + 2 + host_w + 2 + 5 + 2 + 6 + 2 + 6 + 2 + 6 + 2 + 4 + 2; /* If host_w makes fixed columns too wide, shrink host_w to fit. */ { int min_hist = 8; /* keep at least 8 sparkline cells */ int max_fixed = right_col - col_offset - whois_w - min_hist - 2; if (fixed_w > max_fixed && max_fixed > 1 + 3 + 2 + 15 + 2 + 5 + 2 + 6 + 2 + 6 + 2 + 6 + 2 + 4 + 2) { fixed_w = max_fixed; host_w = fixed_w - (1 + 3 + 2 + 2 + 5 + 2 + 6 + 2 + 6 + 2 + 6 + 2 + 4 + 2); if (host_w < 15) host_w = 15; /* recompute fixed_w with clamped host_w */ fixed_w = 1 + 3 + 2 + host_w + 2 + 5 + 2 + 6 + 2 + 6 + 2 + 6 + 2 + 4 + 2; } } hist_cols = (right_col - col_offset) - fixed_w - whois_w - 2; if (hist_cols < 0) hist_cols = 0; /* ------------------------------------------------------------------ */ /* Header row — column labels left, sparkline legend right */ /* ------------------------------------------------------------------ */ move(row, col_offset); clrtoeol(); if (has_colors()) attron(COLOR_PAIR(CP_TBLHEAD)); if (ws->whois_enabled && show_orgname) mvprintw(row, col_offset, " %-3s %-*s %-5s %-6s %-6s %-6s %-4s %-8.8s %-16.16s %-20.20s", "HOP", host_w, "HOST", "LOSS%", "MIN", "AVG", "MAX", "JIT", "ASN", "NETNAME", "ORGNAME"); else if (ws->whois_enabled) mvprintw(row, col_offset, " %-3s %-*s %-5s %-6s %-6s %-6s %-4s %-8.8s %-16.16s", "HOP", host_w, "HOST", "LOSS%", "MIN", "AVG", "MAX", "JIT", "ASN", "NETNAME"); else mvprintw(row, col_offset, " %-3s %-*s %-5s %-6s %-6s %-6s %-4s", "HOP", host_w, "HOST", "LOSS%", "MIN", "AVG", "MAX", "JIT"); if (has_colors()) attroff(COLOR_PAIR(CP_TBLHEAD)); /* Sparkline legend: ▪= 24) { /* Legend total: "▪ col_offset) { move(row, leg_col); if (has_colors()) { attron(COLOR_PAIR(CP_GREEN)); } addstr("\xe2\x96\xaa"); /* ▪ green */ if (has_colors()) { attroff(COLOR_PAIR(CP_GREEN)); attron(COLOR_PAIR(CP_NORMAL) | A_DIM); } addstr(" 0 && row <= row_end && row < ws->term_rows - 2) { int span_sec = hist_cols * (ws->interval > 0 ? ws->interval : 1); int step; int nt; int i; if (span_sec <= 30) step = 5; else if (span_sec <= 150) step = 30; else if (span_sec <= 600) step = 60; else if (span_sec <= 3000) step = 300; else step = 900; nt = span_sec / step + 1; if (nt > 4) nt = 4; if (nt < 2) nt = 2; move(row, col_offset); clrtoeol(); /* Use dim default-fg (grey) rather than dim cyan — on many terminals * dim cyan renders with a greenish tint, which the user flagged. */ if (has_colors()) attron(COLOR_PAIR(CP_NORMAL) | A_DIM); /* Pad the fixed columns to keep sparkline area aligned */ mvprintw(row, col_offset, "%*s", fixed_w + (ws->whois_enabled ? whois_w : 0), ""); /* Draw the dashed scale line across the sparkline area */ move(row, right_col - hist_cols); for (i = 0; i < hist_cols; i++) addstr("\xe2\x94\x80"); /* ─ U+2500 */ /* Helper: format an age (seconds) into a compact label */ #define WATCH_FMT_AGE(age_, lbl_) do { \ if ((age_) == 0) snprintf((lbl_), sizeof(lbl_), "current"); \ else if ((age_) >= 3600) { \ int _h = (age_) / 3600, _m = ((age_) % 3600) / 60; \ if (_m == 0) snprintf((lbl_), sizeof(lbl_), "%dh", _h); \ else snprintf((lbl_), sizeof(lbl_), "%dh%dm", _h, _m); \ } else if ((age_) >= 60) snprintf((lbl_), sizeof(lbl_), "%dm", (age_)/60); \ else snprintf((lbl_), sizeof(lbl_), "%ds", (age_)); \ } while (0) /* Left-edge anchor label: the actual total span of the sparkline * (oldest visible sample, not rounded to step). Planted at pos 0. */ { char lbl[16]; char padded[24]; int llen; WATCH_FMT_AGE(span_sec, lbl); snprintf(padded, sizeof(padded), "%s ", lbl); /* flush-left */ llen = (int)strlen(padded); if (llen < hist_cols) mvaddstr(row, right_col - hist_cols, padded); } /* Overlay step-based intermediate labels + right-edge "current". * Left-most step label is suppressed if it would overlap the span * anchor we just planted. */ for (i = 0; i < nt; i++) { int age = (nt - 1 - i) * step; int pos = hist_cols - 1 - (age / (ws->interval > 0 ? ws->interval : 1)); char lbl[16]; char padded[24]; int llen; WATCH_FMT_AGE(age, lbl); /* Leading + trailing space separates label text from dashes */ snprintf(padded, sizeof(padded), " %s ", lbl); llen = (int)strlen(padded); /* Right-align "current" at the rightmost edge */ if (age == 0) pos = hist_cols - llen; else pos = pos - (llen / 2); if (pos < 0) pos = 0; if (pos + llen > hist_cols) pos = hist_cols - llen; if (pos < 0) continue; /* Skip if this would overlap the left-edge span anchor (~10 cols) */ if (age != 0 && pos < 10) continue; mvaddstr(row, right_col - hist_cols + pos, padded); } #undef WATCH_FMT_AGE if (has_colors()) attroff(COLOR_PAIR(CP_NORMAL) | A_DIM); row++; } /* ------------------------------------------------------------------ */ /* Per-hop rows */ /* ------------------------------------------------------------------ */ for (i = 0; i < ws->n_hops && row <= row_end && row < ws->term_rows - 2; i++) { watch_hop_t *h = &ws->hops[i]; double loss_pct = (h->cycles_total > 0) ? 100.0 * (h->cycles_total - h->cycles_seen) / h->cycles_total : 0.0; char host_trunc[72]; /* large enough for host_w (max 64) + state tag */ char loss_s[8], min_s[8], avg_s[8], max_s[8], jitter_s[9]; int col; /* For the target hop, append state annotation */ if (i == ws->target_hopno && ws->target_hopno > 0) { const char *state = ws->target_open ? "[open]" : ws->target_filtered ? "[filtered]" : (ws->protocol == 2 || ws->protocol == 3) ? "[replied]" : "[closed]"; int tag_len = (int)strlen(state) + 1; /* " [TAG]" */ int name_max = host_w - tag_len; if (name_max < 5) name_max = 5; snprintf(host_trunc, sizeof(host_trunc), "%.*s %s", name_max, hop_display_addr(h, ws->show_hostnames), state); } else { snprintf(host_trunc, sizeof(host_trunc), "%.*s", host_w, hop_display_addr(h, ws->show_hostnames)); } if (loss_pct >= 100.0) snprintf(loss_s, sizeof(loss_s), "100%%"); else snprintf(loss_s, sizeof(loss_s), "%d%%", (int)round(loss_pct)); snprintf(min_s, sizeof(min_s), "%.1f", h->rtt_min); snprintf(avg_s, sizeof(avg_s), "%d", (int)round(h->rtt_avg)); snprintf(max_s, sizeof(max_s), "%.1f", h->rtt_max); snprintf(jitter_s, sizeof(jitter_s), "%d", (int)round(h->rtt_jitter)); move(row, col_offset); clrtoeol(); /* HOP number — cyan (same as ASN column values) */ if (has_colors()) attron(COLOR_PAIR(CP_STATUS)); mvprintw(row, col_offset, " %3d ", i + 1); if (has_colors()) attroff(COLOR_PAIR(CP_STATUS)); /* HOST — loss-based color; bold when any loss present */ { int hc = host_color(loss_pct); attr_t ha = (loss_pct > 0) ? (COLOR_PAIR(hc) | A_BOLD) : COLOR_PAIR(hc); if (has_colors()) attron(ha); printw("%-*s ", host_w, host_trunc); if (has_colors()) attroff(ha); } /* LOSS% — 0%=green+bold, <10%=yellow+bold, <50%=red, ≥50%=red+bold */ { attr_t la; if (loss_pct <= 0) la = COLOR_PAIR(CP_GREEN) | A_BOLD; else if (loss_pct < 10) la = COLOR_PAIR(CP_YELLOW) | A_BOLD; else if (loss_pct < 50) la = COLOR_PAIR(CP_RED); else la = COLOR_PAIR(CP_RED) | A_BOLD; if (has_colors()) attron(la); printw("%-5s ", loss_s); if (has_colors()) attroff(la); } /* MIN — dim */ if (has_colors()) attron(COLOR_PAIR(CP_NORMAL) | A_DIM); printw("%-6s ", min_s); if (has_colors()) attroff(COLOR_PAIR(CP_NORMAL) | A_DIM); /* AVG — finer thresholds */ { int ac = avg_color(h->rtt_avg); if (has_colors()) attron(COLOR_PAIR(ac)); printw("%-6s ", avg_s); if (has_colors()) attroff(COLOR_PAIR(ac)); } /* MAX — dim */ if (has_colors()) attron(COLOR_PAIR(CP_NORMAL) | A_DIM); printw("%-6s ", max_s); if (has_colors()) attroff(COLOR_PAIR(CP_NORMAL) | A_DIM); /* JITTER */ { int jc = jitter_color(h->rtt_jitter); if (has_colors()) attron(COLOR_PAIR(jc)); printw("%-4s ", jitter_s); if (has_colors()) attroff(COLOR_PAIR(jc)); } /* ASN — cyan; NETNAME — dim grey; ORGNAME — cyan (NETNAME/ORGNAME swapped) */ if (ws->whois_enabled && show_orgname) { if (has_colors()) attron(COLOR_PAIR(CP_STATUS)); printw("%-8.8s ", h->asn[0] ? h->asn : "-"); if (has_colors()) { attroff(COLOR_PAIR(CP_STATUS)); attron(COLOR_PAIR(CP_NORMAL) | A_DIM); } printw("%-16.16s ", h->netname[0] ? h->netname : "-"); if (has_colors()) { attroff(COLOR_PAIR(CP_NORMAL) | A_DIM); attron(COLOR_PAIR(CP_STATUS)); } printw("%-20.20s ", h->orgname[0] ? h->orgname : "-"); if (has_colors()) attroff(COLOR_PAIR(CP_STATUS)); } else if (ws->whois_enabled) { if (has_colors()) attron(COLOR_PAIR(CP_STATUS)); printw("%-8.8s ", h->asn[0] ? h->asn : "-"); if (has_colors()) { attroff(COLOR_PAIR(CP_STATUS)); attron(COLOR_PAIR(CP_NORMAL) | A_DIM); } printw("%-16.16s ", h->netname[0] ? h->netname : "-"); if (has_colors()) attroff(COLOR_PAIR(CP_NORMAL) | A_DIM); } /* RTT history sparkline (newest at right, stops before right border). * Colors are relative to this hop's all-time average RTT: * ≤ avg×1.2 → green * ≤ avg×1.5 → yellow * > avg×1.5 → magenta (hot/high jitter) * no reply → solid ▪ in red+bold (loss marker) * Block height scales 0→g_max across N_BLOCK_CHARS levels. */ if (hist_cols > 0) { int j; int n = h->history_count; double g_max = 0; double avg = (h->rtt_avg > 0) ? h->rtt_avg : 1.0; for (j = 0; j < n; j++) { int idx = (h->history_head - n + j + WATCH_HISTORY_LEN) % WATCH_HISTORY_LEN; if (h->rtt_history[idx] > g_max) g_max = h->rtt_history[idx]; } if (g_max <= 0) g_max = 1; col = right_col - hist_cols; move(row, col); for (j = 0; j < hist_cols; j++) { int age = hist_cols - 1 - j; int src_idx; double v; int block; int sc; if (age >= n) { addstr(" "); continue; } src_idx = (h->history_head - 1 - age + WATCH_HISTORY_LEN) % WATCH_HISTORY_LEN; v = h->rtt_history[src_idx]; if (v <= 0) { /* Loss: solid red block marker ▪ */ if (has_colors()) attron(COLOR_PAIR(CP_RED) | A_BOLD); addstr("\xe2\x96\xaa"); /* ▪ U+25AA */ if (has_colors()) attroff(COLOR_PAIR(CP_RED) | A_BOLD); } else { block = (int)(v / g_max * (N_BLOCK_CHARS - 2)) + 1; if (block >= N_BLOCK_CHARS) block = N_BLOCK_CHARS - 1; /* Color relative to this hop's average */ if (v <= avg * 1.2) sc = CP_GREEN; else if (v <= avg * 1.5) sc = CP_YELLOW; else sc = CP_MAGENTA; if (has_colors()) attron(COLOR_PAIR(sc)); addstr(BLOCK_CHARS[block]); if (has_colors()) attroff(COLOR_PAIR(sc)); } } } row++; /* Multipath alternate IPs — one sub-row per extra path, matching the * indented style LFT uses in standard (-o) output. ↳ sits inside the * hop-number column; the IP left-aligns at the HOST column start. * All three table views (1, 2, 3) share this via watch_render_timeline. */ if (h->n_alt > 0) { int k; for (k = 0; k < h->n_alt && row <= row_end && row < ws->term_rows - 2; k++) { move(row, col_offset); clrtoeol(); if (has_colors()) attron(COLOR_PAIR(CP_STATUS) | A_DIM); /* col_offset+4 → ↳ (1 col) + space (1 col) → IP at col_offset+6 = HOST */ mvprintw(row, col_offset + 4, "\xe2\x86\xb3 %-*s", host_w, h->alt_hosts[k]); if (has_colors()) attroff(COLOR_PAIR(CP_STATUS) | A_DIM); row++; } } } /* Path change notice (horizontally centered) */ if (ws->path_changed && difftime(time(NULL), ws->path_change_time) < WATCH_PATH_FLASH_SECS) { if (row <= row_end && row < ws->term_rows - 2) { char ts[32]; char banner[96]; int bw; int bx; struct tm *tm_info; time_t t = ws->path_change_time; tm_info = localtime(&t); strftime(ts, sizeof(ts), "%H:%M:%S", tm_info); snprintf(banner, sizeof(banner), " *** PATH CHANGE DETECTED at %s *** ", ts); bw = (int)strlen(banner); bx = (ws->term_cols - bw) / 2; if (bx < col_offset) bx = col_offset; move(row, col_offset); clrtoeol(); if (has_colors()) attron(COLOR_PAIR(CP_RED) | A_BOLD | A_REVERSE); mvaddstr(row, bx, banner); if (has_colors()) attroff(COLOR_PAIR(CP_RED) | A_BOLD | A_REVERSE); row++; } } return row - start_row; } /* ========================================================================= * View 3: MTR-style stats board (was view 4; old full-screen timeline and * topology views removed when collapsing 4 views → 3). * ========================================================================= */ static void watch_render_view4(watch_session_t *ws) { int i, row = 1; int r; int bot_row = ws->term_rows - 3; /* last content row; status bar closes below */ clear(); watch_render_top_border(ws, NULL); /* Header row — leading space becomes the side │ after border draw. * All variable-width columns use %-N.Ns (both pad AND truncate) so * toggling 'h' (hostname ↔ IP) never shifts subsequent columns. */ if (has_colors()) attron(COLOR_PAIR(CP_TBLHEAD)); if (ws->whois_enabled) mvprintw(row++, 0, " %-3s %-20.20s %5s %5s %5s %7s %7s %7s %7s %7s %-8.8s %-15.15s %-19.19s", "HOP", "HOST", "LOSS%", "SENT", "RECV", "MIN", "AVG", "MAX", "STDDEV", "LAST", "ASN", "NETNAME", "ORGNAME"); else mvprintw(row++, 0, " %-3s %-20.20s %5s %5s %5s %7s %7s %7s %7s %7s", "HOP", "HOST", "LOSS%", "SENT", "RECV", "MIN", "AVG", "MAX", "STDDEV", "LAST"); if (has_colors()) attroff(COLOR_PAIR(CP_TBLHEAD)); /* Per-hop rows — per-column colors match watch_render_timeline exactly * so views 1, 2, 3 all use the same palette where columns overlap. * View 3 adds SENT/RECV/STDDEV/LAST; those use AVG-style thresholds. */ for (i = 0; i < ws->n_hops && row < ws->term_rows - 2; i++) { watch_hop_t *h = &ws->hops[i]; double loss_pct = (h->cycles_total > 0) ? 100.0 * (h->cycles_total - h->cycles_seen) / h->cycles_total : 0.0; char host_trunc[72]; char loss_s[8]; /* Approximate stddev from history */ double stddev = 0; if (h->history_count > 1) { double sum2 = 0; int j; int n = h->history_count; for (j = 0; j < n; j++) { int idx = (h->history_head - n + j + WATCH_HISTORY_LEN) % WATCH_HISTORY_LEN; double v = h->rtt_history[idx]; double d = v - h->rtt_avg; sum2 += d * d; } stddev = sqrt(sum2 / n); } snprintf(host_trunc, sizeof(host_trunc), "%.20s", hop_display_addr(h, ws->show_hostnames)); if (loss_pct >= 100.0) snprintf(loss_s, sizeof(loss_s), "100%%"); else snprintf(loss_s, sizeof(loss_s), "%d%%", (int)round(loss_pct)); move(row, 0); clrtoeol(); /* HOP — cyan (same as ASN values, matching timeline) */ if (has_colors()) attron(COLOR_PAIR(CP_STATUS)); mvprintw(row, 0, " %3d ", i + 1); if (has_colors()) attroff(COLOR_PAIR(CP_STATUS)); /* HOST — loss-based color, bold if any loss */ { int hc = host_color(loss_pct); attr_t ha = (loss_pct > 0) ? (COLOR_PAIR(hc) | A_BOLD) : COLOR_PAIR(hc); if (has_colors()) attron(ha); printw("%-20.20s ", host_trunc); if (has_colors()) attroff(ha); } /* LOSS% — same thresholds as timeline */ { attr_t la; if (loss_pct <= 0) la = COLOR_PAIR(CP_GREEN) | A_BOLD; else if (loss_pct < 10) la = COLOR_PAIR(CP_YELLOW) | A_BOLD; else if (loss_pct < 50) la = COLOR_PAIR(CP_RED); else la = COLOR_PAIR(CP_RED) | A_BOLD; if (has_colors()) attron(la); printw("%5s ", loss_s); if (has_colors()) attroff(la); } /* SENT / RECV — dim (counters, not thresholds) */ if (has_colors()) attron(COLOR_PAIR(CP_NORMAL) | A_DIM); printw("%5d %5d ", h->cycles_total, h->cycles_seen); if (has_colors()) attroff(COLOR_PAIR(CP_NORMAL) | A_DIM); /* MIN — dim */ if (has_colors()) attron(COLOR_PAIR(CP_NORMAL) | A_DIM); printw("%7.1f ", h->rtt_min); if (has_colors()) attroff(COLOR_PAIR(CP_NORMAL) | A_DIM); /* AVG — threshold color */ { int ac = avg_color(h->rtt_avg); if (has_colors()) attron(COLOR_PAIR(ac)); printw("%7.1f ", h->rtt_avg); if (has_colors()) attroff(COLOR_PAIR(ac)); } /* MAX — dim */ if (has_colors()) attron(COLOR_PAIR(CP_NORMAL) | A_DIM); printw("%7.1f ", h->rtt_max); if (has_colors()) attroff(COLOR_PAIR(CP_NORMAL) | A_DIM); /* STDDEV — jitter thresholds */ { int jc = jitter_color(stddev); if (has_colors()) attron(COLOR_PAIR(jc)); printw("%7.1f ", stddev); if (has_colors()) attroff(COLOR_PAIR(jc)); } /* LAST — avg-style thresholds */ { int lc = avg_color(h->rtt_last); if (has_colors()) attron(COLOR_PAIR(lc)); printw("%7.1f ", h->rtt_last); if (has_colors()) attroff(COLOR_PAIR(lc)); } /* ASN — cyan; NETNAME — dim grey; ORGNAME — cyan (swapped) */ if (ws->whois_enabled) { if (has_colors()) attron(COLOR_PAIR(CP_STATUS)); printw("%-8.8s ", h->asn[0] ? h->asn : "-"); if (has_colors()) { attroff(COLOR_PAIR(CP_STATUS)); attron(COLOR_PAIR(CP_NORMAL) | A_DIM); } printw("%-15.15s ", h->netname[0] ? h->netname : "-"); if (has_colors()) { attroff(COLOR_PAIR(CP_NORMAL) | A_DIM); attron(COLOR_PAIR(CP_STATUS)); } printw("%-19.19s", h->orgname[0] ? h->orgname : "-"); if (has_colors()) attroff(COLOR_PAIR(CP_STATUS)); } row++; } /* Side │ borders — solid ACS_VLINE from row 1 to bot_row. Status bar row * below has └──┘ corners that close the frame, matching views 1 and 2. */ if (has_colors()) attron(COLOR_PAIR(CP_BLUE)); if (bot_row >= 1) { mvvline(1, 0, ACS_VLINE, bot_row); mvvline(1, ws->term_cols - 1, ACS_VLINE, bot_row); } if (has_colors()) attroff(COLOR_PAIR(CP_BLUE)); (void)r; } /* ========================================================================= * Log drawer * ========================================================================= */ static void watch_render_log_drawer(watch_session_t *ws) { int drawer_height = ws->term_rows / 4; int start_row = ws->term_rows - 2 - drawer_height; int visible_rows = drawer_height - 1; /* content rows below the title */ /* Content width: inside left │ + 1-space indent, up to right │ exclusive */ int content_w = ws->term_cols - 3; /* col 0=│, col 1=space, col(last)=│ */ int i; if (start_row < 0) start_row = 0; if (visible_rows < 1) visible_rows = 1; if (content_w < 1) content_w = 1; /* ------------------------------------------------------------------ */ /* Auto-resume: return to live mode after 30 s of inactivity */ /* ------------------------------------------------------------------ */ if (ws->log_scroll_offset > 0 && difftime(time(NULL), ws->log_last_scroll) >= 30.0) { ws->log_scroll_offset = 0; ws->log_anchor_nlines = 0; } /* ------------------------------------------------------------------ */ /* Linearize the ring buffer into a temporary flat array */ /* ------------------------------------------------------------------ */ char *tmp = NULL; char **lines_arr = NULL; int nlines = 0; tmp = (char *)malloc((size_t)ws->log_len + 1); { int max_ptrs = ws->log_len / 2 + 2; if (max_ptrs > 16384) max_ptrs = 16384; lines_arr = (char **)malloc((size_t)max_ptrs * sizeof(char *)); } if (tmp && lines_arr) { int max_ptrs = ws->log_len / 2 + 2; if (max_ptrs > 16384) max_ptrs = 16384; for (i = 0; i < ws->log_len; i++) tmp[i] = ws->log_buf[(ws->log_head + i) % WATCH_LOG_BUF]; tmp[ws->log_len] = '\0'; char *p = tmp; while (*p && nlines < max_ptrs) { char *eol = strchr(p, '\n'); if (eol) *eol = '\0'; lines_arr[nlines++] = p; if (eol) p = eol + 1; else break; } } /* ------------------------------------------------------------------ */ /* Set the frozen anchor on the first frame where scroll_offset > 0 */ /* ------------------------------------------------------------------ */ if (ws->log_scroll_offset > 0 && ws->log_anchor_nlines == 0) ws->log_anchor_nlines = nlines; /* Clamp scroll offset to the usable range for this anchor */ if (ws->log_scroll_offset > 0) { int max_scroll = (ws->log_anchor_nlines > visible_rows) ? ws->log_anchor_nlines - visible_rows : 0; if (ws->log_scroll_offset > max_scroll) ws->log_scroll_offset = max_scroll; } /* ------------------------------------------------------------------ */ /* Decide which lines to show */ /* ------------------------------------------------------------------ */ int first_line; if (ws->log_scroll_offset == 0) { /* Live mode: always tail the buffer */ first_line = (nlines > visible_rows) ? nlines - visible_rows : 0; } else { /* Frozen mode: anchor_nlines is the fixed reference */ first_line = ws->log_anchor_nlines - visible_rows - ws->log_scroll_offset; if (first_line < 0) first_line = 0; } /* ------------------------------------------------------------------ */ /* Title row label — two fixed-width variants, both 44 display cols: */ /* */ /* Live: "Trace Log Live · ↑k ↓j to scroll · [v] close" */ /* Scroll: "Trace Log · Line NNNNN of NNNNN · [v] close" */ /* */ /* %5d gives right-justified 5-digit numbers (max 16384 lines), so */ /* the total label width never changes between states or line counts. */ /* Unicode: · = U+00B7 ↑ = U+2191 ↓ = U+2193 ─ = U+2500 */ /* ------------------------------------------------------------------ */ { char label[256]; if (ws->log_scroll_offset == 0) { /* Live mode — 44 display cols */ snprintf(label, sizeof(label), "Trace Log Live " "\xc2\xb7" /* · */ " \xe2\x86\x91k" /* ↑k */ " \xe2\x86\x93j" /* ↓j */ " to scroll " "\xc2\xb7" /* · */ " [v] close"); } else { /* Scrolling mode — 44 display cols */ int bottom = ws->log_anchor_nlines - ws->log_scroll_offset; if (bottom < 0) bottom = 0; snprintf(label, sizeof(label), "Trace Log " "\xc2\xb7" /* · */ " Line %5d of %5d " "\xc2\xb7" /* · */ " [v] close", bottom, ws->log_anchor_nlines); } /* Count display columns (non-continuation UTF-8 bytes = display chars) */ int lw = 0; { const char *cp; for (cp = label; *cp; cp++) if (((unsigned char)*cp & 0xC0) != 0x80) lw++; } /* Total border content: ├ + ─── + space + label + fill ─s + ┤ */ /* ─── + space = 4 display cols; ├ and ┤ each = 1; fill fills the rest */ int fill = ws->term_cols - 2 - 4 - lw; if (fill < 0) fill = 0; move(start_row, 0); clrtoeol(); if (has_colors()) attron(COLOR_PAIR(CP_STATUS) | A_DIM); addstr("\xe2\x94\x9c" /* ├ */ "\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80 "); /* ─── (space) */ if (has_colors()) { attroff(COLOR_PAIR(CP_STATUS) | A_DIM); attron(COLOR_PAIR(CP_STATUS) | A_BOLD); } addstr(label); if (has_colors()) { attroff(COLOR_PAIR(CP_STATUS) | A_BOLD); attron(COLOR_PAIR(CP_STATUS) | A_DIM); } for (i = 0; i < fill; i++) addstr("\xe2\x94\x80"); /* ─ */ addstr("\xe2\x94\xa4"); /* ┤ */ if (has_colors()) attroff(COLOR_PAIR(CP_STATUS) | A_DIM); } /* ------------------------------------------------------------------ */ /* Content rows */ /* ------------------------------------------------------------------ */ { int row; for (row = 0; row < visible_rows; row++) { int screen_row = start_row + 1 + row; int line_idx = first_line + row; move(screen_row, 0); clrtoeol(); /* Left border │ */ if (has_colors()) attron(COLOR_PAIR(CP_STATUS) | A_DIM); addstr("\xe2\x94\x82"); /* │ */ if (has_colors()) attroff(COLOR_PAIR(CP_STATUS) | A_DIM); /* 1-space indent then log content, hard-truncated */ addstr(" "); if (tmp && lines_arr && line_idx >= 0 && line_idx < nlines && lines_arr[line_idx][0]) { if (has_colors()) attron(COLOR_PAIR(CP_DRAWER)); mvaddnstr(screen_row, 2, lines_arr[line_idx], content_w); if (has_colors()) attroff(COLOR_PAIR(CP_DRAWER)); } /* Right border │ */ if (has_colors()) attron(COLOR_PAIR(CP_STATUS) | A_DIM); mvaddstr(screen_row, ws->term_cols - 1, "\xe2\x94\x82"); /* │ */ if (has_colors()) attroff(COLOR_PAIR(CP_STATUS) | A_DIM); } } free(tmp); free(lines_arr); } /* ========================================================================= * View 1: A+B Hybrid * ========================================================================= */ static void watch_render_view2(watch_session_t *ws, lft_session_params *proto_sess) { int chart_rows, table_rows, chart_max; int r, i; int bot_row = ws->term_rows - 3; /* last content row; status bar closes below */ int sep; char rtt_info[256]; { int _r; for (_r = 0; _r < ws->term_rows - 2; _r++) { move(_r, 0); clrtoeol(); } } watch_build_rtt_info(ws, rtt_info, (int)sizeof(rtt_info)); watch_render_top_border(ws, rtt_info); /* How many rows for the chart vs table */ chart_max = (ws->term_rows - 5) / 2; /* at most half the usable rows */ if (chart_max < 5) chart_max = 5; chart_rows = watch_render_native_chart(ws, 1, chart_max, 1); if (chart_rows < 0) chart_rows = 0; (void)chart_rows; /* unused — sep is locked to chart_max for stability */ /* Divider: ├──...──┤ (T-junctions with side │ borders). Fixed at * (1 + chart_max) — see view 1 for rationale. */ sep = 1 + chart_max; if (sep < bot_row) { if (has_colors()) attron(COLOR_PAIR(CP_BLUE)); move(sep, 0); addstr("\xe2\x94\x9c"); /* ├ */ for (i = 1; i < ws->term_cols - 1; i++) addstr("\xe2\x94\x80"); /* ─ */ addstr("\xe2\x94\xa4"); /* ┤ */ if (has_colors()) attroff(COLOR_PAIR(CP_BLUE)); } /* Timeline table in remaining space (col_offset=1 leaves room for sides) */ table_rows = bot_row - sep; if (table_rows > 1) watch_render_timeline(ws, sep + 1, table_rows, 1); /* Side │ borders — two segments around the sep row (sep already has ├ ┤) */ if (has_colors()) attron(COLOR_PAIR(CP_BLUE)); if (sep > 1) mvvline(1, 0, ACS_VLINE, sep - 1); if (bot_row > sep) mvvline(sep + 1, 0, ACS_VLINE, bot_row - sep); if (sep > 1) mvvline(1, ws->term_cols - 1, ACS_VLINE, sep - 1); if (bot_row > sep) mvvline(sep + 1, ws->term_cols - 1, ACS_VLINE, bot_row - sep); if (has_colors()) attroff(COLOR_PAIR(CP_BLUE)); (void)r; } /* ========================================================================= * Top-level render dispatcher * ========================================================================= */ static void watch_render(watch_session_t *ws, lft_session_params *proto_sess, int spinner_frame) { /* Handle terminal resize */ if (watch_resize_pending) { endwin(); refresh(); watch_resize_pending = 0; ws->term_rows = LINES; ws->term_cols = COLS; if (LINES < WATCH_MIN_ROWS) { endwin(); fprintf(stderr, "\nLFT: The minimum height required for --watch is %d rows," " expand your terminal window if possible.\n\n", WATCH_MIN_ROWS); ws->running = 0; return; } } switch (ws->current_view) { case 2: watch_render_view2(ws, proto_sess); break; case 3: watch_render_view4(ws); break; /* Stats Board (was view 4) */ default: watch_render_view1(ws, proto_sess); break; } if (ws->log_open) watch_render_log_drawer(ws); watch_render_status_bar(ws, spinner_frame); /* ncurses model stays in sync (SIGALRM uses ncurses, not raw writes), * so no wredrawln needed — just compose and flush. */ wnoutrefresh(stdscr); doupdate(); } /* ========================================================================= * Keypress handler * ========================================================================= */ /* ========================================================================= * Screen capture (F3) — saves the current ncurses screen to .txt / .ansi * ========================================================================= */ /* Text of one screen cell as UTF-8 (or ASCII when the locale is not UTF-8). * Alternate-charset line drawing (ACS_*) is mapped to box characters instead * of leaking the raw 'x'/'q' codes; with a wide-char ncurses the cell is read * via mvin_wch so multi-byte glyphs survive too. Returns the attributes. */ static const struct { const char *utf8; char ascii; } acs_glyph[] = { {"\xe2\x94\x82",'|'},{"\xe2\x94\x80",'-'},{"\xe2\x94\x8c",'+'},{"\xe2\x94\x90",'+'}, {"\xe2\x94\x94",'+'},{"\xe2\x94\x98",'+'},{"\xe2\x94\x9c",'+'},{"\xe2\x94\xa4",'+'}, {"\xe2\x94\xb4",'+'},{"\xe2\x94\xac",'+'},{"\xe2\x94\xbc",'+'},{"\xe2\x97\x86",'*'}, {"\xe2\x80\xa2",'.'},{"\xe2\x96\x92",'#'} }; static void watch_acs_text(chtype raw, int utf8, char *out, size_t sz) { const chtype acs[] = { ACS_VLINE, ACS_HLINE, ACS_ULCORNER, ACS_URCORNER, ACS_LLCORNER, ACS_LRCORNER, ACS_LTEE, ACS_RTEE, ACS_BTEE, ACS_TTEE, ACS_PLUS, ACS_DIAMOND, ACS_BULLET, ACS_CKBOARD }; size_t i; for (i = 0; i < sizeof acs / sizeof acs[0]; i++) if ((acs[i] & A_CHARTEXT) == (raw & A_CHARTEXT)) { if (utf8) snprintf(out, sz, "%s", acs_glyph[i].utf8); else { out[0] = acs_glyph[i].ascii; out[1] = '\0'; } return; } out[0] = '+'; out[1] = '\0'; } static attr_t watch_cell_text(int r, int c, int utf8, char *out, size_t sz) { attr_t attr = 0; #ifdef HAVE_MVIN_WCH cchar_t cc; wchar_t wch[CCHARW_MAX + 1]; short pair = 0; if (mvin_wch(r, c, &cc) == OK && getcchar(&cc, wch, &attr, &pair, NULL) == OK) { attr |= COLOR_PAIR(pair); if (attr & A_ALTCHARSET) { watch_acs_text((chtype)wch[0], utf8, out, sz); return attr; } if (wch[0] == 0 || wch[0] == L' ') { out[0] = ' '; out[1] = '\0'; return attr; } if (wch[0] < 0x80 || !utf8) { out[0] = wch[0] < 0x80 ? (char)wch[0] : '?'; out[1] = '\0'; return attr; } { unsigned long u = (unsigned long)wch[0]; size_t n = 0; if (u < 0x800 && sz > 2) { out[n++] = (char)(0xC0 | (u >> 6)); out[n++] = (char)(0x80 | (u & 0x3F)); } else if (u < 0x10000 && sz > 3){ out[n++] = (char)(0xE0 | (u >> 12)); out[n++] = (char)(0x80 | ((u >> 6) & 0x3F)); out[n++] = (char)(0x80 | (u & 0x3F)); } else if (sz > 4) { out[n++] = (char)(0xF0 | (u >> 18)); out[n++] = (char)(0x80 | ((u >> 12) & 0x3F)); out[n++] = (char)(0x80 | ((u >> 6) & 0x3F)); out[n++] = (char)(0x80 | (u & 0x3F)); } out[n] = '\0'; } return attr; } #endif { chtype cell = mvinch(r, c); attr = (attr_t)(cell & ~A_CHARTEXT); if (cell & A_ALTCHARSET) watch_acs_text(cell, utf8, out, sz); else { out[0] = (char)(cell & A_CHARTEXT); if (!out[0]) out[0] = ' '; out[1] = '\0'; } } return attr; } static int watch_locale_utf8(void) { const char *v = setlocale(LC_CTYPE, NULL); return v && (strstr(v, "UTF-8") || strstr(v, "utf8") || strstr(v, "UTF8")); } static void watch_do_screen_capture(watch_session_t *ws) { /* ---- Modal dialog ---- */ int mh = 11, mw = 58; int my = (ws->term_rows - mh) / 2; int mx = (ws->term_cols - mw) / 2; WINDOW *modal; char fname[128]; int pos = 0; /* cursor in fname[] */ time_t now; struct tm *tm_now; int ch; if (my < 0) my = 0; if (mx < 0) mx = 0; modal = newwin(mh, mw, my, mx); if (!modal) return; /* Pre-fill with timestamp */ now = time(NULL); tm_now = localtime(&now); strftime(fname, sizeof(fname), "lft_watch_%Y%m%d_%H%M%S", tm_now); pos = (int)strlen(fname); keypad(modal, TRUE); for (;;) { /* Draw modal */ werase(modal); if (has_colors()) wattron(modal, COLOR_PAIR(CP_STATUS) | A_BOLD); box(modal, 0, 0); mvwprintw(modal, 0, 2, " Save Watch Screen "); if (has_colors()) wattroff(modal, COLOR_PAIR(CP_STATUS) | A_BOLD); mvwprintw(modal, 2, 2, "Filename (without extension):"); if (has_colors()) wattron(modal, A_BOLD); mvwprintw(modal, 3, 2, "> "); if (has_colors()) wattroff(modal, A_BOLD); /* Input field: mw-6 chars wide */ { int field_w = mw - 6; char disp[128]; snprintf(disp, sizeof(disp), "%-*.*s", field_w, field_w, fname); mvwaddnstr(modal, 3, 4, disp, field_w); } mvwprintw(modal, 5, 2, "Saves: %s.txt", fname); if (has_colors() && has_colors()) mvwprintw(modal, 6, 2, " %s.ansi (color)", fname); if (has_colors()) wattron(modal, COLOR_PAIR(CP_STATUS)); mvwprintw(modal, 9, 2, " [Enter] Save [Esc] Cancel "); if (has_colors()) wattroff(modal, COLOR_PAIR(CP_STATUS)); /* Position cursor in the input field */ wmove(modal, 3, 4 + pos); wrefresh(modal); ch = wgetch(modal); if (ch == 27 /* Esc */) { delwin(modal); return; /* cancelled */ } if (ch == '\n' || ch == '\r' || ch == KEY_ENTER) { break; /* save */ } if ((ch == KEY_BACKSPACE || ch == 127 || ch == '\b') && pos > 0) { fname[--pos] = '\0'; } else if (ch >= 32 && ch < 127 && pos < (int)sizeof(fname) - 2) { /* Accept printable ASCII except path separators */ if (ch != '/' && ch != '\\') { fname[pos++] = (char)ch; fname[pos] = '\0'; } } } delwin(modal); if (fname[0] == '\0') return; /* nothing typed */ /* ---- Save .txt (plain text, strip attributes) ---- */ { char txt_path[256]; FILE *fp; int r, c; snprintf(txt_path, sizeof(txt_path), "%s.txt", fname); fp = fopen(txt_path, "w"); if (fp) { int utf8 = watch_locale_utf8(); for (r = 0; r < ws->term_rows; r++) { for (c = 0; c < ws->term_cols; c++) { char cell_txt[8]; (void)watch_cell_text(r, c, utf8, cell_txt, sizeof cell_txt); fputs(cell_txt, fp); } fputc('\n', fp); } fclose(fp); snprintf(ws->status_msg, sizeof(ws->status_msg), "Saved: %s.txt", fname); ws->status_msg_time = time(NULL); } else { snprintf(ws->status_msg, sizeof(ws->status_msg), "Error: could not create %s.txt", fname); ws->status_msg_time = time(NULL); } } /* ---- Save .ansi (with color escape codes if supported) ---- */ if (has_colors()) { char ansi_path[256]; FILE *fp; int r, c; short fg_prev = -1, bg_prev = -1; snprintf(ansi_path, sizeof(ansi_path), "%s.ansi", fname); fp = fopen(ansi_path, "w"); if (fp) { int utf8 = watch_locale_utf8(); for (r = 0; r < ws->term_rows; r++) { for (c = 0; c < ws->term_cols; c++) { char cell_txt[8]; attr_t attr = watch_cell_text(r, c, utf8, cell_txt, sizeof cell_txt); short fg = -1, bg = -1; int pair_num = (int)PAIR_NUMBER(attr); if (pair_num > 0) pair_content((short)pair_num, &fg, &bg); /* Emit color codes only on change */ if (fg != fg_prev || bg != bg_prev) { fprintf(fp, "\033[0m"); if (fg >= 0 && fg <= 7) fprintf(fp, "\033[3%dm", fg); if (bg >= 0 && bg <= 7) fprintf(fp, "\033[4%dm", bg); if (attr & A_BOLD) fprintf(fp, "\033[1m"); fg_prev = fg; bg_prev = bg; } fputs(cell_txt, fp); } fprintf(fp, "\033[0m\n"); fg_prev = bg_prev = -1; } fclose(fp); /* Append ".ansi" note to status message */ { char tmp[192]; snprintf(tmp, sizeof(tmp), "%.*s + .ansi", (int)sizeof(tmp) - 9, ws->status_msg); snprintf(ws->status_msg, sizeof(ws->status_msg), "%s", tmp); } } } touchwin(stdscr); refresh(); } /* ========================================================================= * Key dispatcher * ========================================================================= */ static void watch_handle_key(watch_session_t *ws, int ch) { if (ch == ERR) return; /* no key pressed */ switch (ch) { case '1': ws->current_view = 1; break; case '2': ws->current_view = 2; break; case '3': ws->current_view = 3; break; /* Stats Board */ case 'q': case 'Q': ws->running = 0; break; case 'p': case 'P': if (ws->paused) { /* Resuming: break idle loop so trace starts immediately */ ws->paused = 0; ws->interrupt_sleep = 1; } else { ws->paused = 1; /* Pausing: idle loop drains naturally */ } break; case 'r': case 'R': { /* Reset per-hop stats and cumulative counters; keep path and cycle */ int i; for (i = 0; i < WATCH_MAX_HOPS; i++) { watch_hop_t *h = &ws->hops[i]; h->cycles_seen = 0; h->cycles_total = 0; h->rtt_min = h->rtt_avg = h->rtt_max = 0; h->rtt_jitter = h->rtt_last = h->rtt_sum = 0; h->history_head = h->history_count = 0; } ws->total_replies = 0; ws->total_sent = 0; ws->max_hops_seen = 0; ws->target_ever_reached = 0; ws->interrupt_sleep = 1; /* start next trace immediately */ break; } case 'c': case 'C': { /* Full clear: reset stats, hops, cycle, AND all annotation caches * so pWhoIs + DNS are re-fetched from scratch next cycle. */ int i; watch_ip_record_t *rec; for (i = 0; i < WATCH_MAX_HOPS; i++) { watch_hop_t *h = &ws->hops[i]; h->cycles_seen = 0; h->cycles_total = 0; h->rtt_min = h->rtt_avg = h->rtt_max = 0; h->rtt_jitter = h->rtt_last = h->rtt_sum = 0; h->history_head = h->history_count = 0; h->asn[0] = h->netname[0] = h->orgname[0] = '\0'; h->hostname[0] = '\0'; } ws->total_replies = 0; ws->total_sent = 0; ws->max_hops_seen = 0; ws->target_ever_reached = 0; ws->n_hops = 0; ws->cycle = 0; /* Flush whois + rDNS from the per-IP cache so everything is * re-queried. Hostname fields cleared unconditionally (c-ares * re-submits anyway; watch ip_cache injection will repopulate). */ for (i = 0; i < WATCH_IP_CACHE_BUCKETS; i++) { for (rec = ws->ip_cache.buckets[i]; rec; rec = rec->next) { rec->asn[0] = '\0'; rec->netname[0] = '\0'; rec->orgname[0] = '\0'; rec->hostname[0] = '\0'; rec->whois_queried = 0; rec->rdns_queried = 0; } } ws->whois_bulk_done = 0; /* re-trigger bulk pWhoIs next cycle */ clear(); wnoutrefresh(stdscr); doupdate(); ws->interrupt_sleep = 1; /* start next trace immediately */ break; } case 'v': case 'V': ws->log_open = !ws->log_open; if (ws->log_open) { /* Always start at the live tail when opening the drawer */ ws->log_scroll_offset = 0; ws->log_anchor_nlines = 0; ws->log_last_scroll = 0; } break; /* Log drawer scroll — only active when the drawer is open */ case KEY_UP: case 'k': if (ws->log_open) { ws->log_scroll_offset++; ws->log_last_scroll = time(NULL); } break; case KEY_DOWN: case 'j': if (ws->log_open) { if (ws->log_scroll_offset > 0) { ws->log_scroll_offset--; ws->log_last_scroll = time(NULL); } if (ws->log_scroll_offset == 0) ws->log_anchor_nlines = 0; /* back to live; clear anchor */ } break; case 'w': case 'W': ws->whois_enabled = !ws->whois_enabled; /* If turning back on and somehow the bulk query never ran, do it now */ if (ws->whois_enabled && !ws->whois_bulk_done) watch_whois_bulk_query(ws); break; case 'h': case 'H': ws->show_hostnames = !ws->show_hostnames; break; case 's': case 'S': ws->show_seams = !ws->show_seams; break; case 'e': case 'E': /* Toggle IPv6 shorthand vs fully-expanded address display. */ ws->addr_expand = !ws->addr_expand; break; case 'x': case 'X': watch_do_screen_capture(ws); break; case '+': case '=': if (ws->interval < 300) ws->interval++; ws->base_interval = ws->interval; ws->backoff_level = 0; break; case '-': case '_': if (ws->interval > 1) ws->interval--; ws->base_interval = ws->interval; ws->backoff_level = 0; break; default: break; } } /* ========================================================================= * Main entry point * ========================================================================= */ void WatchModeRun(lft_session_params *sess) { watch_session_t *ws; struct sigaction sa_winch, sa_old_winch; int orig_noisy; /* noisy level as passed on the CLI (before watch bumps it) */ /* Save verbosity level, then force noisy=2 for watch mode so the log * drawer ring buffer is always populated at -VV level regardless of * whether the user passed -V. The drawer auto-opens only if the user * explicitly asked for verbose output. */ orig_noisy = sess->noisy; if (sess->noisy < 2) sess->noisy = 2; /* ------------------------------------------------------------------ */ /* Validate terminal height before entering ncurses */ /* ------------------------------------------------------------------ */ { struct winsize tw; int rows = 0; const char *rows_env = getenv("LINES"); if (rows_env && atoi(rows_env) > 0) rows = atoi(rows_env); else if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &tw) == 0) rows = tw.ws_row; if (rows > 0 && rows < WATCH_MIN_ROWS) { fprintf(stderr, "\nLFT: The minimum height required for --watch is %d rows," " expand your terminal window if possible.\n\n", WATCH_MIN_ROWS); return; } } /* ------------------------------------------------------------------ */ /* Allocate watch session state */ /* ------------------------------------------------------------------ */ ws = (watch_session_t *)calloc(1, sizeof(watch_session_t)); if (!ws) { fprintf(stderr, "LFT: out of memory in WatchModeRun\n"); return; } g_watch_ws = ws; /* expose to SIGALRM handler for cumulative totals */ ws->interval = sess->watch_interval > 0 ? sess->watch_interval : WATCH_DEFAULT_INTERVAL; ws->base_interval = ws->interval; ws->backoff_enabled = sess->watch_backoff; ws->max_cycles = sess->watch_max_cycles; ws->probes_per_hop = (sess->rtt_stats > 0) ? sess->rtt_stats : WATCH_DEFAULT_PROBES; ws->protocol = sess->protocol; /* 0=TCP,1=UDP,2=ICMP,3=RFC1393 — for [REPLIED] label */ ws->addr_expand = sess->addr_expand; /* --expand-addr seeds the initial state; 'e' toggles */ ws->current_view = 1; ws->running = 1; ws->first_cycle = 1; /* Whois column (ASN / NETNAME / ORGNAME) is shown when the user has * explicitly requested lookups with -A (do_aslookup) or -N (do_netlookup); * otherwise it stays off. 'w' key toggles at any time. */ /* ASN/NETNAME/ORGNAME columns are on by default in watch mode (the * [w] key toggles them off); watch has its own per-IP whois cache, so * this does not depend on -A/-N being passed on the command line. */ ws->whois_enabled = 1; ws->show_hostnames = (sess->hostnames_only != 0) ? 1 : 0; /* off by default; -h CLI or 'h' key enables */ ws->show_seams = sess->show_seams; /* off by default; --show-seams/-y CLI or 's' key enables */ /* Build target label string */ { const char *tgt = sess->hostname ? sess->hostname : "?"; switch (sess->protocol) { case 1: snprintf(ws->tgt_label, sizeof(ws->tgt_label), "udp://%s:%d", tgt, sess->dport); break; case 2: case 3: snprintf(ws->tgt_label, sizeof(ws->tgt_label), "icmp://%s", tgt); break; default: snprintf(ws->tgt_label, sizeof(ws->tgt_label), "tcp://%s:%d", tgt, sess->dport); break; } } /* ------------------------------------------------------------------ */ /* Run the initial trace with the live counter spinner animation. */ /* lft_o_spinner_start_prefix() arms SIGALRM + writes to /dev/tty, */ /* then stdout/stderr are redirected to /dev/null so LFT's normal */ /* text output doesn't appear. lft_o_spinner_stop() erases the */ /* entire animation line cleanly before ncurses initialises. */ /* ------------------------------------------------------------------ */ { int saved_stdout, saved_stderr; /* Start the live counter animation using the same "Tracing proto://host:port " * prefix as -o mode. Writes to /dev/tty via SIGALRM so it survives the * stdout→/dev/null redirect that follows. */ lft_o_spinner_start(sess); { int init_pipe[2]; int init_use_pipe = (sess->noisy >= 1); init_pipe[0] = init_pipe[1] = -1; saved_stdout = dup(STDOUT_FILENO); saved_stderr = dup(STDERR_FILENO); if (init_use_pipe && pipe(init_pipe) == 0) { fcntl(init_pipe[1], F_SETFL, fcntl(init_pipe[1], F_GETFL) | O_NONBLOCK); dup2(init_pipe[1], STDOUT_FILENO); dup2(init_pipe[1], STDERR_FILENO); close(init_pipe[1]); init_pipe[1] = -1; } else { int dn = open("/dev/null", O_WRONLY); init_use_pipe = 0; if (dn >= 0) { dup2(dn, STDOUT_FILENO); dup2(dn, STDERR_FILENO); close(dn); } } setOutputStyle(0); /* suppress EVT_ON_EXIT re-entry */ /* S1: watch_mode stays 1 — socket + pcap reused across cycles */ /* Ensure rtt_stats is set so ASCIIOutput (view 1) has chart data */ if (sess->rtt_stats <= 0) sess->rtt_stats = ws->probes_per_hop; LFTExecute(sess); fflush(stdout); fflush(stderr); dup2(saved_stdout, STDOUT_FILENO); dup2(saved_stderr, STDERR_FILENO); close(saved_stdout); close(saved_stderr); if (init_use_pipe && init_pipe[0] >= 0) { char chunk[4096]; ssize_t nr; fcntl(init_pipe[0], F_SETFL, O_NONBLOCK); while ((nr = read(init_pipe[0], chunk, sizeof(chunk) - 1)) > 0) { chunk[nr] = '\0'; watch_log_append(ws, chunk); } close(init_pipe[0]); } } setOutputStyle(4); /* exit_state == -100 means EVT_ON_EXIT fired (normal trace completion). * Only bail out for actual socket/pcap open errors (-1 through -36). */ if (sess->exit_state >= -36 && sess->exit_state < 0) { lft_o_spinner_stop(); /* erase animation before printing error */ fprintf(stderr, "\nLFT: --watch: failed to open raw socket or pcap (exit_state=%d)." " Try: sudo chown root lft && sudo chmod u+s lft\n", sess->exit_state); goto cleanup; } /* Capture results from the initial trace */ ws->cycle = 1; ws->first_cycle = 0; if (sess->hop_info) watch_capture_from_sess(ws, sess); /* Whois is on by default — run the bulk pWhoIs query now so the * very first ncurses render has complete ASN/NETNAME/ORGNAME data. * The spinner continues animating during the whois query. */ if (ws->whois_enabled && !ws->whois_bulk_done) watch_whois_bulk_query(ws); /* Accumulate initial trace counter values. */ ws->total_replies += lft_o_get_replies(); ws->total_sent += lft_o_get_sent(); if (lft_o_get_hops() > ws->max_hops_seen) ws->max_hops_seen = lft_o_get_hops(); if (lft_o_get_target()) ws->target_ever_reached = 1; /* Track current path length (not historical peak); see comment in * watch_capture_from_sess update. */ ws->max_hops_seen = ws->n_hops; /* Erase the startup animation line completely before ncurses init. */ lft_o_spinner_stop(); } /* ------------------------------------------------------------------ */ /* Install SIGWINCH handler for terminal resize */ /* ------------------------------------------------------------------ */ memset(&sa_winch, 0, sizeof(sa_winch)); sa_winch.sa_handler = watch_sigwinch; sigemptyset(&sa_winch.sa_mask); sigaction(SIGWINCH, &sa_winch, &sa_old_winch); /* ------------------------------------------------------------------ */ /* Initialize ncurses */ /* ------------------------------------------------------------------ */ if (watch_init_ncurses(ws) < 0) { endwin(); fprintf(stderr, "\nLFT: The minimum height required for --watch is %d rows," " expand your terminal window if possible.\n\n", WATCH_MIN_ROWS); goto cleanup; } /* ------------------------------------------------------------------ */ /* Install SIGINT/SIGTERM/SIGHUP/SIGQUIT handlers */ /* ncurses hides the cursor and puts the terminal in raw mode; without */ /* these handlers a Ctrl-C leaves the terminal in a broken state. */ /* ------------------------------------------------------------------ */ { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = watch_sig_fatal; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; /* no SA_RESTART — want interrupted syscalls to fail fast */ sigaction(SIGINT, &sa, &g_old_sa_sigint_watch); sigaction(SIGTERM, &sa, &g_old_sa_sigterm_watch); sigaction(SIGHUP, &sa, &g_old_sa_sighup_watch); sigaction(SIGQUIT, &sa, &g_old_sa_sigquit_watch); g_watch_ncurses_active = 1; } if (!g_watch_atexit) { atexit(watch_atexit); g_watch_atexit = 1; } /* Auto-open the log drawer only when the user explicitly passed -V or more * on the command line. The buffer is always populated at -VV level, but * we don't force the drawer open unless the user asked for verbosity. */ if (orig_noisy >= 1) ws->log_open = 1; /* ------------------------------------------------------------------ */ /* Main watch loop — cycle 1 already run above; loop starts from cycle 2 */ /* ------------------------------------------------------------------ */ watch_render(ws, sess, ws->spinner_frame); /* show cycle 1 results */ while (ws->running) { if (ws->max_cycles > 0 && ws->cycle >= ws->max_cycles) break; /* Inter-cycle idle: 4 sub-steps per second so the spinner animates * smoothly at ~4 fps instead of freezing for a whole second. * * Keys that set ws->interrupt_sleep break out immediately (≤250ms * latency) to start the next trace without waiting the full interval. * Any other key triggers a full redraw so view/log changes appear at * once without waiting for the next trace. */ { int sub, total_steps = ws->interval * 4; ws->interrupt_sleep = 0; for (sub = 0; sub < total_steps && ws->running; sub++) { int ch, any_key = 0; ws->spinner_frame = (ws->spinner_frame + 1) % WATCH_SPINNER_FRAMES; while ((ch = getch()) != ERR) { watch_handle_key(ws, ch); any_key = 1; } /* Live 'e' toggle: the candlestick re-formats from proto_sess * each render, so reflect the current expand state at once. */ sess->addr_expand = ws->addr_expand; if (!ws->running || ws->interrupt_sleep) break; /* Sleep first on sub=0: watch_render() already drew at cycle * end so re-drawing immediately would cause a visible flash. */ usleep(250000); /* 250 ms */ if (any_key) watch_render(ws, sess, ws->spinner_frame); else { watch_render_status_bar(ws, ws->spinner_frame); wnoutrefresh(stdscr); doupdate(); } } } if (!ws->running) break; if (ws->max_cycles > 0 && ws->cycle >= ws->max_cycles) break; if (!ws->paused) { ws->cycle++; ws->in_trace = 1; /* SIGALRM owns the status bar during the trace — no pre-trace * status bar write needed here; it would just cause an extra * visible flicker on the footer row. */ /* Run one full trace cycle */ watch_run_one_cycle(ws, sess); ws->in_trace = 0; /* spinner_frame was advanced by the SIGALRM handler during the * trace; sync it back into ws so the idle loop continues from * where the alarm left off. */ ws->spinner_frame = (int)g_alarm_frame; } /* First render: show current trace data immediately so the user * never sits watching a blank screen while pWhoIs blocks. */ watch_render(ws, sess, ws->spinner_frame); if (!ws->paused) { /* Re-run bulk pWhoIs query if it hasn't completed yet (e.g. after * [c] clear reset whois_bulk_done to 0); otherwise just update any * new hop IPs that appeared since the last bulk query. */ if (ws->whois_enabled && !ws->whois_bulk_done) watch_whois_bulk_query(ws); else if (ws->whois_enabled && ws->whois_bulk_done) watch_whois_update_new_hops(ws); /* Second render only needed when whois data was updated */ if (ws->whois_enabled) watch_render(ws, sess, ws->spinner_frame); } } /* Restore termination signal handlers before teardown — normal exit path */ if (g_watch_ncurses_active) { sigaction(SIGINT, &g_old_sa_sigint_watch, NULL); sigaction(SIGTERM, &g_old_sa_sigterm_watch, NULL); sigaction(SIGHUP, &g_old_sa_sighup_watch, NULL); sigaction(SIGQUIT, &g_old_sa_sigquit_watch, NULL); g_watch_ncurses_active = 0; } watch_teardown_ncurses(); cleanup: g_watch_ws = NULL; /* Free per-IP annotation cache */ watch_ip_cache_free(&ws->ip_cache); /* Free pWhoIs session if allocated (F2) */ if (ws->watch_whois_wsess) { w_close(ws->watch_whois_wsess); ws->watch_whois_wsess = NULL; } /* Restore SIGWINCH */ sigaction(SIGWINCH, &sa_old_winch, NULL); free(ws); } #endif /* HAVE_NCURSES */ lft-4.01/lft_icmptrace.h000644 000765 000024 00000001157 15164617366 015157 0ustar00vicstaff000000 000000 #ifndef LFT_ICMPTRACE_H #define LFT_ICMPTRACE_H #include "lft_lib.h" #define ICMP_TRACE 30 int generateICMPPacket( lft_session_params * sess, LFT_CALLBACK err, LFT_CALLBACK evt, struct icmp_trace_packet_s * packet, u_char ttlval, u_short unique_id, u_short seq); void icmp_trace_main_loop(lft_session_params * sess, LFT_CALLBACK err, LFT_CALLBACK evt); #if !defined(__CYGWIN__) && !defined(WIN32) && !defined(_WIN32) void pcap_icmp_process_packet(u_char *user_data, const struct pcap_pkthdr *hdr, const u_char *packet); unsigned int icmp_pmtud_send_verify_hop(lft_session_params *sess, short nhop); #endif #endif lft-4.01/tests/000755 000765 000024 00000000000 15250425210 013305 5ustar00vicstaff000000 000000 lft-4.01/lft_ifname.h000644 000765 000024 00000002274 15231735015 014433 0ustar00vicstaff000000 000000 /* * lft_ifname.h * Layer Four Traceroute * * This file is part of LFT. * * The LFT software provided in this Distribution is * Copyright 2007 VOSTROM Holdings, Inc. * * The full text of our legal notices is contained in the file called * COPYING, included with this Distribution. * */ #ifndef LFT_IFNAME_H #define LFT_IFNAME_H #ifdef __cplusplus extern "C" { #endif #define STRNCPY(dst, src, len) { \ (dst)[(len)-1] = '\0'; \ strncpy((dst),(src),(len)-1); \ } extern u_long lft_getifaddr (const char *); extern char * lft_getifname (struct in_addr); #ifdef HAVE_IPV6 #include "lft_addr.h" /* AF-aware interface helpers (LFT 4.0), via getifaddrs where available. * lft_getifaddr_af: address of the named interface in family af (AF_INET or * AF_INET6) -> *out; returns 1 on success, 0 if not found. * lft_getifname_af: name of the interface holding addr (either family) as a * strdup'd string (caller frees), or NULL. Added alongside the v4 functions * above, which are unchanged. */ extern int lft_getifaddr_af (const char *ifname, int af, lft_addr_t *out); extern char *lft_getifname_af (const lft_addr_t *addr); #endif #ifdef __cplusplus } #endif #endif /*LFT_IFNAME_H*/ lft-4.01/whob.8000644 000765 000024 00000015167 15250002624 013205 0ustar00vicstaff000000 000000 .Dd September 1, 2026 .Dt WHOB 8 .Os WHOB .Sh NAME .Nm whob .Nd display whois-type information of interest to Internet operators .Sh SYNOPSIS .Nm whob .Op Fl AaCcegNnOopPRrstuVv .Op Fl h Ar server .Op Fl w Ar server .Op Fl f Ar file .Op Fl F Ar fields .Ar query | me | whoami .Sh DESCRIPTION .Nm queries various sources of whois information for data of interest to network operators and their tracing and debugging tools. .Pp .Nm output is designed to be easily parsed, or better yet, its functionality can be added directly into your programs (see whois.h). .Pp The only mandatory parameter is the target host name or IP address. The target may be an IPv4 or IPv6 address, or a host name that resolves to either. Options toggle the display of additional data or change the sources used to obtain it. .Pp One key advantage of .Nm is its lookup of ASN information derived from the global Internet routing table itself, as opposed to relying solely on what has been registered in the RADB/IRR (see below). This data is, by default, sourced from the global pWhoIs service. See www.pwhois.org. .Pp Options: .Bl -tag -width Ds .It Fl A Ar ASN Display all routing advertisements transiting the respective ASN. The ASN may be supplied as the target argument, or a hostname or IP address may be supplied and .Nm will resolve the ASN automatically. .It Fl a Ar ASN Display all routing advertisements made by the respective Origin-AS. The Origin-AS may be supplied as the target argument, or a hostname or IP address may be supplied and .Nm will resolve the ASN automatically. .It Fl P Ar prefix Display all routing advertisements related to the CIDR prefix supplied. .It Fl N Ar ASN Display all networks registered to the ASN supplied. .It Fl O Ar ASN Display all contact information on file for the ASN supplied. .It Fl g Disable GIGO mode. By popular request, .Nm takes input directly from the command line and passes it without modification to pWhoIs or whatever whois server is requested .Pq Fl h . The exact output is returned without any parsing. To enable parsing and the other useful options, disable GIGO mode with .Fl g . .It Fl R Display the Origin-AS on record at the RADB/IRR (Routing Arbiter Database/Internet Routing Registry) in addition to the Origin-AS provided by the prefix-based whois data source. .It Fl n Display the network name on record with the IP network allocation registry (ARIN, RIPE, APNIC, etc.). .It Fl o Suppress display of the organization name on file at the registrar. By default the organization name is shown; this flag hides it. .It Fl p Display the AS-Path from the perspective of the current pWhoIs server. The pWhoIs server may automatically exclude the initial, least-specific ASN received from the operator of the network to which it is connected (unless that ASN is the only/origin ASN or has multiple peers). This AS-Path is subjective. If you rely on it and want AS-Paths that correspond to your own network infrastructure, consider running your own pWhoIs server. See the .Fl w option and www.pwhois.org. .It Fl t Display the date the route was last cached by the pWhoIs server. .It Fl u Display dates in UTC/GMT instead of local time. Implies .Fl t (enables cache-date display as a side effect). .It Fl h Ar server .It Fl w Ar server Override the source of prefix-based whois data. Both flags are equivalent: each sets the pWhoIs-compatible server to query instead of the default pWhoIs host. Accepts a hostname or IP address. .It Fl f Ar file Read from .Ar file and submit its contents as bulk input to pWhoIs. Pass .Sq - as .Ar file to read from stdin. Input is buffered and subject to the constraints of the current pWhoIs server. Output is written to stdout (which may be redirected) and will not be parsed. Additional instructions to pWhoIs may be placed at the beginning of the file, but will only apply to the first buffer of input. The first (left-most) field on each line must be the IP address; lines may be up to 255 characters long. .It Fl c Use Cymru's whois server instead of pWhoIs for interactive queries. When used with .Fl f , uses Cymru for bulk file resolution. See www.cymru.com for details. .It Fl C Use Cymru's whois server for bulk file resolution (implies .Fl f ) . This is the explicit bulk-Cymru mode; interactive queries are unaffected. .It Fl r Use RIPE NCC RIS instead of pWhoIs. When used with .Fl f , uses RIPE NCC riswhois for bulk file resolution. See www.ripe.net/projects/ris/. .It Fl s Display the version and status of the pWhoIs server and exit. .It Fl V Display verbose/debug output. Repeat for additional verbosity .Pq Fl VV , Fl VVV . .It Fl v Display version information and exit. .It Fl -color Force colorized output even when standard output is not a terminal. By default, colorization is applied automatically only when writing to an interactive terminal (never to a file or a pipe), and the light or dark variant follows the terminal's background color. .It Fl -no-color Disable colorized output entirely, overriding the automatic detection. .It Fl e , Fl -enrich , Fl -annotate Op = Ns Ar file Act as a filter: read text from standard input (or .Ar file ) and write it back out with every IPv4 or IPv6 address followed by .Bq ASn Org-Name from pWhoIs, or .Bq \&? when it has no route for the address. Attached ports, prefixes and bracketed IPv6 are kept intact. The three spellings are equivalent; only the long forms accept .Ar =file . .It Fl F Ar list , Fl -fields Ar list Choose the fields the annotation shows, as a comma-separated list of .Cm asn , org , asorg , net , prefix , city , region , country , cc , place (city, region, country and code together) or .Cm all . The default is .Cm asn,org ; a list beginning with .Ql + or .Ql - adds to or removes from it, e.g. .Fl F Cm +net or .Fl F Cm -org,+asorg . .It Ar me | whoami Display this host's public IP address and exit. .El .Sh EXAMPLES Basic query returning ASN, prefix, net name, and org name: .Pp .Dl whob 1.2.3.4 .Pp Query with AS-Path and net name displayed, GIGO mode disabled: .Pp .Dl whob -gnp 1.2.3.4 .Pp Bulk file resolution using Cymru: .Pp .Dl whob -Cf iplist.txt .Pp Display this host's public IP: .Pp .Dl whob me .Pp Annotate a traceroute with the network that owns each hop: .Pp .Dl traceroute -n www.example.com | whob -e .Pp Summarize which networks a firewall's denied connections came from: .Pp .Dl grep -h denied /var/log/firewall.log | whob --annotate | grep -oE 'AS[0-9]+ [^]]+' | sort | uniq -c | sort -rn .Sh SEE ALSO .Xr whois 1 , .Xr lft 8 .Sh HISTORY The .Nm command first appeared in 2004. This whois framework has been a component of LFT since 2002. .Sh AUTHORS .An Victor Oppleman and .An Eugene Antsilevitch . .Sh BUGS To report bugs, use the contact form at .Lk https://pwhois.org/contact/ . lft-4.01/lft_btcptrace.h000644 000765 000024 00000000255 11053131665 015137 0ustar00vicstaff000000 000000 #ifndef LFT_BTCPTRACE_H #define LFT_BTCPTRACE_H #include "lft_lib.h" void tcp_base_trace_main_loop(lft_session_params * sess, LFT_CALLBACK err, LFT_CALLBACK evt); #endif lft-4.01/README000644 000765 000024 00000002025 15250002624 013022 0ustar00vicstaff000000 000000 # This file is part of LFT. # # The LFT software provided in this Distribution is # Copyright (c) 2001-2026 VOSTROM Holdings, Inc. # # The full text of our legal notices is contained in the file called # COPYING, included with this Distribution. 1) LFT stands for "Layer Four Traceroute" 2) We advise you to acquire the latest version of LFT before installing LFT's web site is located at https://pwhois.org/lft/ 3) Please read the "INSTALL" file for instructions on installing LFT 4) Please read the LFT manual page for instructions on using LFT (lft.8) 5) LFT also includes "whob" (a whois client for Internet operators) WhoB has its own manual page (whob.8). 6) The authors may be reached through https://pwhois.org/contact/ We welcome your feedback. We also welcome your contributions. If you're interested in helping us make LFT better, please contact us. 7) Our road map for where we're taking the software may be found in the "TODO" file. 8) LFT's history of changes can be found in the "CHANGELOG" ENJOY! lft-4.01/lft_probe6.h000644 000765 000024 00000003507 15231735015 014371 0ustar00vicstaff000000 000000 /* lft_probe6.h — IPv6 probe helpers (LFT 4.0). The pure, unit-testable cores live here and are always compiled: - lft_l3_dispatch : version-nibble-safe L3 classification (v4/v6/drop), needed even in v4-only builds so a stray v6 frame is dropped rather than misparsed as v4. - lft_icmp6_classify : ICMPv6 type/code -> reply kind. - lft_cksum6 : transport checksum over the v6 pseudo-header + payload. The live send path (lft_send_probe_v6) is added at integration time and guarded by HAVE_IPV6; it is validated live under setuid, not in unit tests. */ #ifndef LFT_PROBE6_H #define LFT_PROBE6_H #include "lft_addr.h" #include #include /* L3 version dispatch: read the IP version nibble ((*l3)>>4) and require the * minimum header length (20 v4, 40 v6). Anything else -> DROP. */ typedef enum { LFT_L3_DROP = 0, LFT_L3_V4 = 4, LFT_L3_V6 = 6 } lft_l3_kind; lft_l3_kind lft_l3_dispatch(const unsigned char *l3, size_t len); /* ICMPv6 reply classification (RFC 4443). */ typedef enum { LFT_ICMP6_OTHER = 0, LFT_ICMP6_TIME_EXCEEDED, /* type 3 */ LFT_ICMP6_TOO_BIG, /* type 2 */ LFT_ICMP6_UNREACH, /* type 1 */ LFT_ICMP6_ECHO_REPLY /* type 129/0 */ } lft_icmp6_kind; lft_icmp6_kind lft_icmp6_classify(int type, int code); /* Internet checksum over the v6 pseudo-header (RFC 2460/8200 sec 8.1) built * from src/dst/proto/ulplen, followed by the ulplen bytes of upper-layer data. * Returns the 16-bit ones-complement checksum in host order. proto is 58 * (ICMPv6), 6 (TCP), or 17 (UDP). Pure; src/dst must both be AF_INET6. */ uint16_t lft_cksum6(const lft_addr_t *src, const lft_addr_t *dst, uint8_t proto, const void *ulp, uint16_t ulplen); #endif /* LFT_PROBE6_H */ lft-4.01/TODO000644 000765 000024 00000000452 15241143643 012643 0ustar00vicstaff000000 000000 LFT TO-DO --------- Features to ADD (not in any particular order) - consider implementing per-hop platform fingerprinting to identify router and firewall OS/platform at each hop (Cisco IOS, Junos, Linux, etc.) - Fix: TCP-based tracing on windows doesn't work because of "security features" lft-4.01/lft_lib.h000644 000765 000024 00000076144 15247705774 013771 0ustar00vicstaff000000 000000 /* * lft_lib.h * Layer Four Traceroute * * This file is part of LFT. * * The LFT software provided in this Distribution is * Copyright 2007 VOSTROM Holdings, Inc. * * The full text of our legal notices is contained in the file called * COPYING, included with this Distribution. * */ #ifndef LFT_LIB_H #define LFT_LIB_H #include "lft_types.h" /* c-ares async DNS -- present only when detected at configure time */ #ifdef HAVE_CARES #include #endif #define LFT_DNS_CACHE_SIZE 64 /* max unique hop IPs per trace (ttl_limit+2) */ #define LFT_ARES_MAX_FDS 16 /* max simultaneous c-ares sockets (typically 1-2) */ /* not available in earlier darwin systems */ #ifndef AI_NUMERICSERV #define AI_NUMERICSERV 0 #endif /* As the trace progresses, each hope will attempt to work through the states one by one until it receives an answer (2 attempts per state). Whatever state "works" - will be then set up on following hops to continue from. */ #define HS_SEND_FIN 0x00 #define HS_SEND_SYN 0x01 #define HS_SEND_SYN_FIN 0x02 #define HS_SEND_RST 0x04 #define HS_SEND_SYN_ACK 0x12 #define HS_SEND_ACK 0x16 #define HS_MAX (HS_SEND_SYN) #define HF_ENDPOINT 0x01 /* default timeout value */ #define DEFAULT_TIMEOUT_MS 250 /* Give-up heuristic: silent hops at or below this TTL (i.e. nhop index <= this) * do NOT count toward the "no_reply >= ahead_limit" early-termination test. A * near-hop stateful firewall / ICMP policer commonly suppresses the first hops * even when the responsive core and the target lie just beyond; excluding them * keeps LFT probing a few hops deeper so a live frontier can reset last_return * and defer the bail. A genuinely dead path still accumulates no_reply from * hops past this index and terminates normally. Value is an nhop index: * 2 exempts nhop 0,1,2 == TTL 1,2,3. */ #define LFT_GIVEUP_NEARHOP_NHOP 2 /* Length in bytes of the -B/--tcp-options realistic client SYN option block * (MSS + SACK-permitted + Timestamps + NOP + Window-scale), kept a 4-byte * multiple so th_off stays integral. */ #define LFT_TCP_OPT_LEN 20 /* Common EtherType values */ #ifndef ETHERTYPE_IP #define ETHERTYPE_IP 0x0800 /* IP protocol */ #endif #ifndef ETHERTYPE_ARP #define ETHERTYPE_ARP 0x0806 /* Addr. resolution protocol */ #endif #ifndef ETHERTYPE_REVARP #define ETHERTYPE_REVARP 0x8035 /* reverse Addr. resolution protocol */ #endif #ifndef ETHERTYPE_VLAN #define ETHERTYPE_VLAN 0x8100 /* IEEE 802.1Q VLAN tagging */ #endif #ifndef ETHERTYPE_IPV6 #define ETHERTYPE_IPV6 0x86dd /* IPv6 */ #endif /* Sometimes-missing BPF values */ #ifndef DLT_RAW #define DLT_RAW 101 /* Raw IP */ #endif #ifndef DLT_PPP_SERIAL #define DLT_PPP_SERIAL 50 /* PPP with HDLC encapsulation */ #endif #ifndef DLT_PPP_ETHER #define DLT_PPP_ETHER 51 /* PPP over Ethernet */ #endif #ifndef DLT_LINUX_SLL #define DLT_LINUX_SLL 113 /* Linux cooked capture */ #endif #ifndef DLT_PPP #define DLT_PPP 9 /* PPP over Ethernet */ #endif /* ToS (type of service) bits we can set on the IP datagram */ #define TOSMINDELAY 0x10 #define TOSMAXTHROUGH 0x08 #define TOSMAXRELIABLE 0x04 #define TOSMINCOST 0x02 /*Errors and warnings codes*/ #define WRN_CANT_SETUP_FIN -1 #define WRN_CANT_DISP_HOST_NAMES -2 #define WRN_ADAPTIVE_DISABLED_BY_UDP -3 #define WRN_FIN_DISABLED_BY_UDP -4 #define WRN_ONLY_ONE_ASN_LOOKUP -5 #define WRN_UDP_PORT_TOO_HIGH -6 #define WRN_PACKET_LENGTH_TOO_HIGH -7 #define WRN_PACKET_LENGTH_TOO_LOW -8 #define WRN_CANT_DISABLE_RESOLVER -9 #define WRN_ALREADY_RANDOM_SPORT -10 #define WRN_ADAPTIVE_DISABLED_BY_FIN -12 #define ERR_DEVNAME_TOO_LONG -13 #define WRN_UNABLE_SETUP_UTC -14 #define WRN_GETIFFORREMOTE_SOCKET -15 #define WRN_GETIFFORREMOTE_CONNECT -16 #define WRN_GETIFFORREMOTE_SOCKNAME -17 #define ERR_UNKNOWN_HOST -18 #define ERR_RAW_SOCKET -19 #define ERR_SOCKET_BIND -20 #define WRN_WSAIOCTL -21 #define ERR_IP_HDRINCL -22 #define ERR_NOT_ENOUGH_MEM -23 #define ERR_RAW_TCP_DISABLED -24 typedef struct _badhopstateparam { const struct hop_info_s *h; short nhop; }WrnBadHopStateParam; #define WRN_BAD_HOP_STATE -25 #define WRN_NS_LOOKUP_FAILED -26 #define ERR_WIN_SELECT -27 #define ERR_WIN_RECV -28 #define ERR_WIN_WSASTARTUP -29 #define ERR_PCAP_ERROR -30 #define ERR_DISCOVER_INTERFACE -31 #define ERR_UNKNOWN_INTERFACE -32 #define ERR_UNKNOWN_SEND_INTERFACE -32 #define ERR_PCAP_DEV_UNAVAILABLE -33 #define WRN_BIOCIMMEDIATE -34 #define ERR_PCAP_NONBLOCK_ERROR -35 #define ERR_PCAP_ACTIVATE_ERROR -36 #define WRN_PCAP_ACTIVATE -37 /*Events codes and their params structures*/ #define EVT_AUTOCONFIGURED_TO_PORTS 1 #define EVT_ADDRESS_INITIALIZED 2 typedef struct _sentpacketparams { short nhop; unsigned int tseq; unsigned char flags; unsigned short tttl; unsigned short sport; /* per-probe L4 ports; 0/0 = portless (ICMP) */ unsigned short dport; }EvtSentPacketParam; #define EVT_SENT_PACKET 3 #define EVT_SHOW_PAYLOAD 4 #define EVT_SHOW_UDP_CHECKSUM 5 #define EVT_SHOW_TCP_CHECKSUM 6 #define EVT_SHOW_HOPS 7 #define EVT_SHOW_NUM_HOPS 8 #define EVT_TRACE_COMPLETED 9 #define EVT_ON_RESOLUTION 10 #define EVT_TRACE_REPORT_START 11 typedef struct _rptnoreplyparams { int hopno; int noreply; }EvtNoReplyParam; #define EVT_RPT_NO_REPLY 12 #define EVT_RPT_FRW_INSPECT_PACKS 13 #define EVT_RPT_FRW_STATE_FILTER 14 #define EVT_RPT_BSD_BUG 15 #define EVT_RPT_HOP_INFO_START 16 typedef struct _packetinfoevtparam { int asnumber; const char * netname; struct in_addr last_hop; int is_asseam; int is_netseam; int seam_traced; int is_open; int is_filtered; const struct trace_packet_info_s * tp; uint16_t path_mtu; /* 0 = none; >0 = probe size when hop was flagged */ int pmtud_blackhole; /* 1 = silent DF dropper (no PTB returned) */ int pmtud_icmp_suppressor; /* 1 = suppresses TTL-exceeded for large probes but forwards them */ }EvtPacketInfoParam; #define EVT_RPT_PACKET_INFO 17 #define EVT_RPT_PACKET_LIST_END 18 #define EVT_RPT_NO_HOPS 19 #define EVT_RPT_TIME_TRACE 20 #define EVT_ON_EXIT 21 #define EVT_TTL_NO_REPLY 22 #define EVT_PROGRESS_NO_REPLY 23 #define EVT_TTL_TOUT_RESEND 24 #define EVT_TTL_TOUT_GIVINGUP 25 typedef struct _debugchkpoint1 { int last_return; int no_reply; int need_reply; }EvtDebugCheckpoint1Param; #define EVT_DBG_CHECKPOINT1 26 #define EVT_CANT_RELIABLY_RTRIP 27 #define EVT_HAVE_UNANSWERRED_HOPS 28 #define EVT_TOO_FAR_AHEAD 29 #define EVT_HAVE_GAPS 30 #define EVT_EITHER_RESP_OR_TOUT 31 #define EVT_LOOKFOR_UNINC_ACK 32 #define EVT_LOOKFOR_OFF_BY_LEN 33 #define EVT_LOOKFOR_LAST_RESORT 34 #define EVT_SKIP_PACKET 35 typedef struct _nonseqpack { struct in_addr ipaddr; const struct trace_packet_info_s * tp; }EvtNonSeqPacketParam; #define EVT_ACK_WAS_NOT_INC 36 #define EVT_RST_REL_TO_ISN 37 #define EVT_ACK_WAS_WAY_OFF 38 #define EVT_DUPLICATE_PACKET 39 #define EVT_PROGRESS_DUPLICATE 40 typedef struct _recvpacket { struct in_addr ipaddr; struct trace_packet_info_s * tp; unsigned int seq; }EvtRecvPacketParam; #define EVT_RECV_PACKET 41 #define EVT_PROGRESS_OK 42 #define EVT_TCP_PORT_CLOSED 43 #define EVT_TCP_PORT_OPEN 44 #define EVT_PROCESS_PACKET_START 45 #define EVT_UDP_NOT_FOR_US 46 typedef struct _incomudpicmp { const struct ip * ip; const struct ip * orig_ip; const struct udphdr *udp; const struct icmp *icmp; }EvtIncomingICMPUDPParam; #define EVT_INCOMING_ICMP_UDP 47 #define EVT_RCVD_ICMP_UDP 48 typedef struct _incomtcpicmp { const struct ip * ip; const struct ip * orig_ip; const struct tcphdr *tcp; const struct icmp *icmp; }EvtIncomingICMPTCPParam; #define EVT_INCOMING_ICMP_TCP 49 #define EVT_RCVD_ICMP_TCP 50 #define EVT_RCVD_TCP 51 #define EVT_RCVD_UNKNOWN 52 #define EVT_DEVICE_SELECTED 53 #define EVT_SHOW_INITIAL_SEQNUM 54 #define EVT_TRACE_START 55 #define EVT_DBG_CHECKPOINT2 56 #define EVT_DBG_LOG_MESSAGE 57 #define EVT_PROGRESS_SKIP_PACKET 58 #define EVT_OPEN_CHECK_RESULT 59 #define ERR_BTCP_PROBE_PORT_IS_BUSY 60 #define ERR_BTCP_WRONG_PORT_VALUE 61 #define EVT_OCHECK_START 62 #define WRN_OCHECK_OPEN_SOCK 63 #define WRN_OCHECK_IOCTL 64 #define WRN_OCHECK_SELECT 65 #define WRN_OCHECK_GETERROR 66 #define WRN_OCHECK_SOCKERROR 67 #define WRN_OCHECK_TIMEOUT 68 #define EVT_OCHECK_OPEN 69 #define WRN_OCHECK_FCNTLGET 70 #define WRN_OCHECK_FCNTLSET 71 #define WRN_OCHECK_CONNECTERR 72 typedef struct _incomechoreplyicmp { const struct ip * ip; const struct icmp_echo_header_s * echo; }EvtIncomingICMPEchoParam; #define EVT_INCOMING_ICMP_Echo 73 #define EVT_RCVD_ICMP_Echo 74 typedef struct _incomicmpicmp { const struct ip * ip; const struct icmp * icmp; const struct ip * orig_ip; const struct icmp_echo_header_s * echo; }EvtIncomingICMPICMPParam; #define EVT_INCOMING_ICMP_ICMP 75 #define EVT_RCVD_ICMP_ICMP 76 #if defined(BSD_IP_STACK) && !defined(OPENBSD) && !defined(__FreeBSD__) #define SCREWED_IP_LEN #endif typedef struct btcpmapentry { int nhop; int port; int sentcount; }BasicTCPMapEntry; #ifdef __cplusplus extern "C" { #endif typedef struct _btcp_debug_info { int type; int hop; int phop; int port; struct in_addr ip; }btcp_debug_info; /* Session parameters */ typedef struct _lft_session_params { struct timeval ts_last_sent; struct timeval now; double scatter_ms; /* milleseconds between sends */ int ttl_min; /* user may request to start at a higher TTL */ int hop_info_length; unsigned short ip_id; /*not used*/ unsigned char tcp_flags; int use_fins; int seq_start; /* generate ISN internally by default */ int dport; /* set default destination to tcp/80 HTTP */ int sport; /* set default source to tcp/53 dns-xfer */ int auto_ports; /* enable port autoselection by default */ int random_source; /* -z: use one consistent random source port * (opt out of the per-hop default) */ int user_sport; /* 1 = -s gave an explicit source port (overrides * both the per-hop default and -z) */ int tcp_options; /* -B/--tcp-options: emit a realistic client TCP * option set on SYN probes so they resemble a real * OS SYN instead of a bare option-less one (default * TCP + adaptive; ignored for -b/-u/-p) */ int addr_expand; /* --expand-addr: render IPv6 fully expanded rather * than RFC 5952 shorthand (display-only) */ int set_tos; /* disable set ToS bit by default */ int userlen; /* user-requested packet length */ int payloadlen; /* the final probe payloadlength */ int win_len; int timeout_ms; /* timeout between retries */ int retry_max; /* number of retries before giving up */ int retry_min; /* minimum number of checks per hop */ int ecmp; /* --ecmp: vary source port per probe to expose ECMP * load-balanced members (diamonds); default off */ int ecmp_probes; /* --ecmp: flows to sample from each REPLYING hop * (does not affect silent-hop give-up); 0 = off */ int ahead_limit; /* number of probes we can send * without replies if we don't know * the number of hops */ int dflag; int ttl_limit; /* max # hops to traverse (highest TTL) */ /* LFT-AUTOTUNE — topology inheritance for multi-cycle traces (--watch). * After cycle 1 the actual path length and RTT envelope are known; * subsequent cycles use this to narrow ttl_limit, expand ahead_limit * to the full path width, and shrink the cloaked-hop timeout to * (8 × max_rtt). Only engages once stable_cycles >= 3. Disable at * runtime with --no-learn. See build/autotune_rollback.md for design * notes and rollback recipe. */ struct { int valid; /* 1 = at least one cycle's data captured */ int num_hops; /* path length observed last cycle */ int max_rtt_ms; /* highest per-hop avg RTT observed */ int min_rtt_ms; /* lowest per-hop avg RTT observed */ int stable_cycles; /* consecutive cycles with same num_hops */ int last_protocol; /* if protocol changes, invalidate */ } learned; int no_autotune; /* 1 = --no-learn passed; auto_tune is a no-op */ int learn_margin; /* TTL probe margin above learned_num_hops (default 2) */ int break_on_icmp; /* break on icmp other than time exceeded */ int noisy; /* disable verbose debug by default */ int nostatus; /* print status bar by default */ int userdevsel; /* by default, we'll select the device */ int senddevsel; /* by default, we'll select the device */ int resolve_names; /* dns resolution enabled by default */ int hostnames_only; /* disable printing of IP addresses */ int timetrace; /* disable tracer timing by default */ int adaptive; /* disable state engine by default */ int protocol; /* 0 - TCP, 1 - UDP, 2 - ICMP base, 3 - ICMP RFC 1393, 4 - TCP basic */ int do_netlookup; /* disable netname lookup by default */ int do_aslookup; /* disable asn lookup by default */ int use_radb; /* use RADB instead of pwhois */ int use_cymru; /* use Cymru instead of pwhois */ int use_ris; /* use RIPE NCC RIS instead of pwhois */ char *payload; int send_sock; int skip_header_len; #if defined( __CYGWIN__ ) || defined( WIN32 ) || defined(_WIN32) int recv_sock; int wsastarted; #else pcap_t * pcapdescr; #endif int UseLocalTime; int num_hops; /*int num_sent;*/ int num_rcvd; int target_open; int target_filtered; int target_anomaly; char *hostname; char *hostname_lsrr[9]; int hostname_lsrr_size; struct in_addr local_address; struct in_addr remote_address; /* AF-agnostic address storage, alongside the v4 fields above so IPv4 is * unchanged; the v6 path branches on af. Defaults: af=AF_INET, * force_af=AF_UNSPEC (set by -4/-6). */ int af; int force_af; struct sockaddr_storage local_ss; struct sockaddr_storage remote_ss; struct timeval begin_time, trace_done_time; /* The actual packet data (one of..)*/ struct trace_packet_s trace_packet; struct icmp_trace_packet_s icmp_packet; /* ICMP flow-hash stabilization: keep checksum constant across probes. * icmp_initial_seq records the sequence number of the first probe so * that each subsequent probe can embed a 2-byte compensation word in * its payload, making (compensation + seq) constant and therefore * keeping the ICMP checksum identical for every probe in the session. * Routers that include the ICMP checksum in their ECMP hash then always * forward all probes along the same path. */ u_short icmp_initial_seq; /* seq from first ICMP probe (the reference value) */ int icmp_seq_anchored; /* set to 1 after icmp_initial_seq is recorded */ /* Packet container with additional info */ /* struct trace_packet_info_s * trace_packet_info;*/ /* indexed by dport - dport NOT USED*/ /* list of packet containers */ SLIST_HEAD(packets_s, trace_packet_info_s) trace_packets; int trace_packets_num; /* Map of ports for basic TCP trace */ BasicTCPMapEntry * btcpmap; int latestmapchoice; int btcpmapsize; int btcpdpucnt; int trg_probe_is_sent; /* btcp_debug_info debugmap[1000]; */ /* int debugmapidx; */ /* hop information, by ttl */ struct hop_info_s * hop_info; const char * pcap_dev; /* data link type as in pcap_datalink() */ int pcap_datalink; const char * pcap_send_dev; const char * userdev; const char * senddev; /*WHOIS parameters*/ whois_session_params * wsess; /*User's data*/ void * UsersDataCookie; /* GraphViz subquery. Disables any output. */ int is_graphviz_subquery; int check_seam; int show_seams; /* display AS/network seams (diagrams + watch); off by default */ int color_mode; /* -o ANSI colour: 0 = auto-detect, 1 = --color (force), 2 = --no-color */ int json_pretty; /* --json-pretty: indent the JSON document (0 = compact --json) */ char geo_color[8]; /* --geo-color RRGGBB: path line colour (--geojson stroke, --kml line) */ int geo_width; /* --geo-width: path line width in px */ double geo_arc; /* --geo-arc: KML arch height factor, 0 = flat */ int src_geo_ok; /* pWhoIs geolocation of the source address, for the geo outputs */ double src_geo_lat, src_geo_lon; char src_geo_city[50], src_geo_region[50], src_geo_country[50], src_geo_cc[4]; char * graphviz_icon_path; /*Exit status. When this field has value <0 lft will end work as soon as possible*/ int exit_state; /* -Q flag: disable c-ares at runtime even if compiled in. * Always present so the flag can be parsed gracefully in all builds. */ int async_dns_disabled; /* Path MTU Discovery (-K / --mtu) */ int pmtud; /* 1 = active PMTUD mode enabled */ uint16_t pmtud_current_mtu; /* current probe size (steps down on PTB) */ uint16_t pmtud_initial_mtu; /* probe size at trace start (interface MTU) */ int pmtud_ptb_pending; /* reserved for future step-down state */ uint16_t pmtud_ptb_mtu; /* MTU value from last PTB (0 if none) */ int pmtud_verifying; /* 1 = inside lft_pmtud_verify_blackholes(), suppress finish() */ /* RTT statistics (-j N / --stats=N) */ int rtt_stats; /* 0 = off; >0 = per-hop probe count for stats mode */ int no_ascii_chart; /* 1 = suppress ASCII art chart even when -o active */ /* internal: hop index being reported (set by EVT_RPT_HOP_INFO_START) */ int _rtt_stat_hopno; /* internal: ECMP multi-member text layout (style-0 report). When a hop has * more than one distinct responder, members print one per line with the TTL * vertically centred (idx==center; center=(n-1)/2 so even counts sit one * line above middle). _seen dedups the interleaved ECMP replies. */ #define LFT_ECMP_SEEN_MAX 32 int _ecmp_n; /* distinct members at this hop (0/1 = normal inline) */ int _ecmp_center; /* line index that carries the TTL number */ int _ecmp_idx; /* distinct members emitted so far this hop */ int _ecmp_seen_n; struct in_addr _ecmp_seen[LFT_ECMP_SEEN_MAX]; #ifdef HAVE_IPV6 lft_addr_t _ecmp_seen6[LFT_ECMP_SEEN_MAX]; #endif /* Per-trace set of IPs already annotated with ASN/netname at an earlier hop. * Suppresses redundant labels when the same router IP responds at multiple * TTL levels (e.g. a CGNAT/firewall that rejects the probe at every hop). * Zeroed by calloc() in LFTSessionOpen(); reset by watch_reset_session(). */ struct in_addr shown_asn_ips[256]; int n_shown_asn_ips; #ifdef HAVE_IPV6 lft_addr_t shown_asn_ips6[256]; /* v6 companion to shown_asn_ips */ #endif /* Watch / continuous mode (-W N / --watch[=N] / --continuous[=N]). * These flags live in the struct unconditionally (not under * #ifdef HAVE_NCURSES): non-watch code paths — including the c-ares * dns-cache handling and the per-hop source-port logic — legitimately * test sess->watch_mode, so the field must exist in every build. Only * the watch UI itself (lft_watch.c) is compiled under HAVE_NCURSES. */ int watch_mode; /* 1 = ncurses continuous monitoring enabled */ int watch_interval; /* seconds between trace cycles (default 10) */ int watch_backoff; /* --watch-backoff: lengthen the interval under rate limiting */ int watch_max_cycles; /* 0 = run indefinitely */ #ifdef HAVE_CARES /* Per-IP hostname cache populated during the trace by c-ares callbacks. * print_host() reads this at display time — no blocking call needed. * In watch mode this cache persists across cycles (LFTExecute skips the * reset); attempts caps how many cycles an unresolved entry is retried * before we accept that the IP has no PTR record. */ struct { struct in_addr addr; #ifdef HAVE_IPV6 struct in6_addr addr6; /* v6 key when the session is AF_INET6 */ #endif char name[NI_MAXHOST]; /* empty = pending or no PTR; filled by callback */ int attempts; /* PTR query attempts so far (retry cap = 3) */ int pending; /* 1 = query in flight; cleared by callback */ } dns_cache[LFT_DNS_CACHE_SIZE]; int dns_cache_count; ares_channel ares_chan; /* c-ares channel; NULL if not initialized */ int ares_pending; /* count of outstanding PTR queries */ double ares_wait_ms; /* ms spent in post-trace lft_ares_wait() drain */ /* fd tracking table maintained by lft_ares_sock_state_cb(). * Replaces the deprecated ares_fds() fd_set approach. */ struct { ares_socket_t fd; unsigned int events; /* mask of ares_fd_eventflag_t */ } ares_fd_table[LFT_ARES_MAX_FDS]; int ares_nfds; /* number of active entries in ares_fd_table */ #endif /* HAVE_CARES */ }lft_session_params; extern const char * icmp_messages[]; extern const char *version; extern const char *appname; extern const int maxpacklen; /* Filtered-repeat helpers (lft_lib.c) — used by both finish() and * tcp_base_finish() to compress consecutive same-IP filtered hops. */ int hop_is_filtered_repeat (const lft_session_params *sess, int hopno); struct in_addr hop_first_rcvd_ip (const lft_session_params *sess, int hopno); int hop_first_rcvd_icmp_type (const lft_session_params *sess, int hopno); void print_filtered_run (int start_ttl, int end_ttl, const char *ipstr, int icmp_type); #ifdef HAVE_CARES /* Queue an async PTR lookup for addr (deduped); safe to call from any trace path. */ void lft_ares_queue(lft_session_params *sess, struct in_addr addr); /* Non-blocking harvest of any completed c-ares PTR replies. */ void lft_ares_poll(lft_session_params *sess); /* Blocking drain of all pending PTR queries (up to 1.2 s); call before display. */ void lft_ares_wait(lft_session_params *sess); #endif /*--------------------------- Callbacks definition ---------------------------*/ /* Paramaters: lft_session_params * sess - session handle, int code - code of error or event, const void * param - additional parameters, depend on code */ typedef void (*LFT_CALLBACK)(lft_session_params *, int, const void *); /*----------------------------------------------------------------------------*/ void LFTInitializeCallbacks(LFT_CALLBACK error_handler, LFT_CALLBACK event_handler); lft_session_params * LFTSessionOpen(void); void LFTSessionClose(lft_session_params * sess); double timediff_ms (struct timeval prior, struct timeval latter); unsigned int get_address(lft_session_params * sess, const char *host); #ifndef SCREWED_IP_LEN u_int32_t ip_cksum (const struct ip *ip); #endif u_int32_t tcp_cksum (struct ip *ip, struct tcphdr *tcp, const char * payload, int payload_len); u_int32_t tcp_cksum_opt (struct ip *ip, struct tcphdr *tcp, const char *opts, int optlen, const char * payload, int payload_len); /* Build the -B/--tcp-options realistic client SYN option block into buf (needs * >= 20 bytes); returns the option length in bytes (a 4-byte multiple), or 0 * if options should not be emitted for this probe. */ int lft_build_tcp_options (lft_session_params *sess, unsigned char flags, unsigned char *buf, int bufsz); int hop_state_up (lft_session_params * sess, short nhop); int hop_state_copy(lft_session_params * sess, short nhop); unsigned int new_seq(lft_session_params * sess); /*----------------------------------------------------------------------------*/ /* Safe setting of parameters */ /*----------------------------------------------------------------------------*/ /*Use TCP FIN packets exclusively (defaults are SYN)*/ int LFTSetupFIN(lft_session_params * sess); /*Display hosts symbolically; suppress IP address display*/ int LFTSetupDispSymbHost(lft_session_params * sess); /* IANA special-purpose address registry lookup. * Returns 1 if addr matches an IANA special-purpose prefix. * rfc_out <- the RFC reference (e.g. "RFC1918"), may be NULL * name_out <- the registry name (e.g. "Private-Use"), may be NULL */ /* LFT-AUTOTUNE — apply learned-path knowledge to trace knobs before a cycle. * No-op when sess->no_autotune is set, when sess->learned.valid is 0, or when * learned.stable_cycles < 3. Sets ttl_limit / ahead_limit / timeout_ms / * scatter_ms based on the prior cycle's observed path length and RTT bounds. */ void lft_auto_tune_from_learned(lft_session_params *sess); int lft_iana_v4_lookup(struct in_addr addr, char *rfc_out, size_t rfc_sz, char *name_out, size_t name_sz); int lft_iana_v6_lookup(const struct in6_addr *addr, char *rfc_out, size_t rfc_sz, char *name_out, size_t name_sz); /*Use traditional UDP (probes) for tracing instead of TCP*/ int LFTSetupUDPMode(lft_session_params * sess); #define ASN_LOOKUP_RIS 0 #define ASN_LOOKUP_RADB 1 #define ASN_LOOKUP_CYMRU 2 /*Use RIPE NCC's RIS to resolve ASNs instead of Prefix WhoIs*/ int LFTSetupRISLookup(lft_session_params * sess); /*Use the RADB to resolve ASNs instead of Prefix WhoIs*/ int LFTSetupRADBLookup(lft_session_params * sess); /*Use Cymru to resolve ASNs instead of Prefix WhoIs*/ int LFTSetupCYMRULookup(lft_session_params * sess); /*Destination port number (same as using target:port as target)*/ int LFTSetupDestinationPort(lft_session_params * sess, char * userport); /*Set the length of the probe packet in bytes*/ int LFTSetupLengthOfPacket(lft_session_params * sess, int plen); /*Display hosts numerically; disable use of the DNS resolver*/ int LFTSetupDisableResolver(lft_session_params * sess); /*Source port number*/ int LFTSetupSourcePort(lft_session_params * sess, int port); /*Use LFT's stateful engine to detect firewalls and path anomalies*/ int LFTSetupAdaptiveMode(lft_session_params * sess); /*Use a specific device by name or IP address (\"en1\" or \"1.2.3.4\")*/ int LFTSetupDevice(lft_session_params * sess,char * udev); /*Use a specific device by name or IP address (\"en1\" or \"1.2.3.4\")*/ int LFTSetupSendDevice(lft_session_params * sess,char * sdev); /*Enable active Path MTU Discovery (-K / --mtu): probes grow to interface MTU and step down on PTB */ int LFTSetupPMTUD(lft_session_params * sess); /*Enable RTT statistics mode (-j N / --stats=N): collect N probes per hop, show min/avg/max/stddev*/ int LFTSetupRTTStats(lft_session_params * sess, int n); /* Handle an ICMP PTB (type 3 code 4): reduce probe size and record MTU at the responding hop. * Called from process_packet() and tcp_base_recv_packet() before recv_packet(). */ void lft_pmtud_handle_ptb(lft_session_params *sess, const struct icmp *icmp, unsigned int seq, struct in_addr from); /* Handle a PTB for ICMP probe mode: same logic but resizes via sess->userlen. */ void lft_pmtud_icmp_handle_ptb(lft_session_params *sess, const struct icmp *icmp, unsigned int seq, struct in_addr from); #if !defined(__CYGWIN__) && !defined(WIN32) && !defined(_WIN32) /* Verify tentative PMTUD black holes with a small probe; demote plain non-responders. */ void lft_pmtud_verify_blackholes(lft_session_params *sess); #endif /*Display all times in UTC (GMT0). Activates -T option automatically*/ int LFTSetupUTCTimes(lft_session_params * sess); /*----------------------------------------------------------------------------*/ int lft_resolve_port (lft_session_params * sess, const char *strport); void LFTExecute(lft_session_params * sess); void lft_o_spinner_start(lft_session_params *sess); /* -o live probe counter */ void lft_o_spinner_start_prefix(const char *prefix);/* variant with caller-supplied prefix */ void lft_o_spinner_stop(void); /* erase spinner, disarm SIGALRM */ void lft_o_counters_activate(void); /* enable g_o_* counters, no SIGALRM */ void lft_o_counters_deactivate(void); /* disable g_o_* counters */ int lft_o_get_replies(void); /* read g_o_replies */ int lft_o_get_sent(void); /* read g_o_sent */ int lft_o_get_hops(void); /* read g_o_hops */ int lft_o_get_target(void); /* read g_o_target */ int lft_o_consume_tfail(void); /* read + clear g_o_timeout_flash */ void lft_o_probe_sent(void); /* call from every send path */ void lft_printf(lft_session_params * sess, const char *templ, ...); /*----------------------------------------------------------------------------*/ void setOutputStyle(int nstyle); /* 0 - ordinary output, 1 - xml output */ int outputStyleIsXML(void); int outputStyleIsGraphViz(void); int outputStyleIsASCII(void); int outputStyleIsJSON(void); int outputStyleIsGeoJSON(void); int outputStyleIsKML(void); struct ip_list_array; int lft_geo_bulk_lookup(lft_session_params *sess, struct ip_list_array *ipaslist, int maxhop); int outputStyleIsSVG(void); int outputStyleIsMermaid(void); int getOutputStyle(void); /*----------------------------------------------------------------------------*/ /* Returns 1 if the given ICMP Unreachable code warrants halting the trace. * Esoteric codes (need-fragment, source-route-fail, TOS variants, precedence, * src-isolated, routing oddities) are still recorded but do not stop probing. * Use --ignore-unreachables (-i) or adaptive mode (-E) to suppress all stops. */ static inline int lft_icmp_stops_trace(int code) { switch (code) { case 0: /* net unreachable */ case 1: /* host unreachable */ case 2: /* protocol unreachable */ case 3: /* port unreachable */ case 9: /* net administratively prohibited */ case 10: /* host administratively prohibited */ case 13: /* communication administratively prohibited */ return 1; default: return 0; } } /*----------------------------------------------------------------------------*/ #ifdef __cplusplus } #endif #endif /*LFT_LIB_H*/ lft-4.01/COPYING000644 000765 000024 00000006153 15165341545 013220 0ustar00vicstaff000000 000000 VOSTROM Public License for Open Source ---------- Copyright (c) 2001-2026 VOSTROM Holdings, Inc. This VOSTROM Holdings, Inc. (VOSTROM) Distribution (code and documentation) is made available to the open source community as a public service by VOSTROM. Contact VOSTROM at license@vostrom.com for information on other licensing arrangements (e.g. for use in proprietary applications). Under this license, this Distribution may be modified and the original version and modified versions may be copied, distributed, publicly displayed and performed provided that the following conditions are met: 1. Modified versions are distributed with source code and documentation and with permission for others to use any code and documentation (whether in original or modified versions) as granted under this license; 2. if modified, the source code, documentation, and user run-time elements should be clearly labeled by placing an identifier of origin (such as a name, initial, or other tag) after the version number; 3. users, modifiers, distributors, and others coming into possession or using the Distribution in original or modified form accept the entire risk as to the possession, use, and performance of the Distribution; 4. this copyright management information (software identifier and version number, copyright notice and license) shall be retained in all versions of the Distribution; 5. VOSTROM may make modifications to the Distribution that are substantially similar to modified versions of the Distribution, and may make, use, sell, copy, distribute, publicly display, and perform such modifications, including making such modifications available under this or other licenses, without obligation or restriction; 6. modifications incorporating code, libraries, and/or documentation subject to any other open source license may be made, and the resulting work may be distributed under the terms of such open source license if required by that open source license, but doing so will not affect this Distribution, other modifications made under this license or modifications made under other VOSTROM licensing arrangements; 7. no permission is granted to distribute, publicly display, or publicly perform modifications to the Distribution made using proprietary materials that cannot be released in source format under conditions of this license; 8. the name of VOSTROM may not be used in advertising or publicity pertaining to Distribution of the software without specific, prior written permission. This software is made available "as is", and VOSTROM DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN NO EVENT SHALL VOSTROM BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. lft-4.01/lft_pathwatch.c000644 000765 000024 00000010073 15227413256 015153 0ustar00vicstaff000000 000000 /* lft_pathwatch.c — J1 path-change alert heuristic for watch mode (-W) Pure state machine classifying per-TTL hop observations across watch cycles. No ncurses, session, or network dependencies — unit-testable standalone (tests/pathwatch_test.c). Design and rule table: Output/LFT/j1-path-change-heuristic-design.md. This file is part of LFT. */ #include #include #include "lft_pathwatch.h" /* An observation is "real" when it is a concrete IP, not a cloaked hop. */ static int pw_real(const char *ip) { return ip && ip[0] && ip[0] != '*'; } static int pw_in_set(const watch_pathwatch_t *pw, const char *ip) { int i; for (i = 0; i < pw->n_known; i++) if (strcmp(pw->known_ips[i], ip) == 0) return 1; return 0; } void pw_note_ip(watch_pathwatch_t *pw, const char *ip) { if (!pw_real(ip) || pw_in_set(pw, ip)) return; if (pw->n_known >= PW_KNOWN_MAX) return; /* set full: membership frozen */ snprintf(pw->known_ips[pw->n_known], PW_IPLEN, "%s", ip); pw->n_known++; } int pw_update_armed(int armed, int stable_cycles, int cycle) { if (armed) return 1; /* latch: never disarm mid-session */ return (stable_cycles >= 3 || cycle >= 8) ? 1 : 0; } void pw_reset(watch_pathwatch_t *pw) { memset(pw, 0, sizeof(*pw)); } pw_verdict_t pw_observe(watch_pathwatch_t *pw, char *confirmed, const char *cur, int armed, char *flap_was) { /* Rule 1: cloaked/empty observation. Pending is deliberately * preserved: rate-limited hops blink, and hysteresis counts observed * sightings, not consecutive cycles. */ if (!pw_real(cur)) return PW_IGNORE; /* Rules 2 + 3: new TTL slot, or still learning — accept silently. */ if (!pw_real(confirmed) || !armed) { snprintf(confirmed, PW_IPLEN, "%s", cur); pw_note_ip(pw, cur); return PW_DISCOVERY; } if (strcmp(cur, confirmed) == 0) { /* Rule 5: reverted to confirmed while a change was pending. */ if (pw->pending_ip[0]) { if (flap_was) snprintf(flap_was, PW_IPLEN, "%s", pw->pending_ip); pw->pending_ip[0] = '\0'; pw->pending_cycles = 0; return PW_FLAP; } /* Rule 4: steady state. */ return PW_STABLE; } /* Rule 6: second observed sighting of the pending IP — confirmed path * change. MUST be tested before set membership (rule 7): the pending * IP was noted into the set at first sighting, and membership-first * would misclassify every genuine reroute as an ECMP shift. */ if (pw->pending_ip[0] && strcmp(cur, pw->pending_ip) == 0) { pw->pending_cycles++; if (pw->pending_cycles >= 2) { snprintf(confirmed, PW_IPLEN, "%s", cur); pw->pending_ip[0] = '\0'; pw->pending_cycles = 0; /* Reset the known set to {cur}: a later reversion to the old * path is a route restore and must alert, not read as ECMP. */ pw->n_known = 0; pw_note_ip(pw, cur); return PW_CONFIRMED; } return PW_PENDING; /* unreachable at threshold 2, but * keeps the counter honest if the * threshold is ever raised */ } /* Rule 7: previously-seen IP at this TTL — multipath member shifting, * not a reroute. Quiet; caller rate-caps the log via ecmp_logged. */ if (pw_in_set(pw, cur)) { snprintf(confirmed, PW_IPLEN, "%s", cur); pw->ecmp_logged++; pw->pending_ip[0] = '\0'; pw->pending_cycles = 0; return PW_ECMP_SHIFT; } /* Rule 8: brand-new IP — start (or rebind) hysteresis. */ snprintf(pw->pending_ip, PW_IPLEN, "%s", cur); pw->pending_cycles = 1; pw_note_ip(pw, cur); return PW_PENDING; } lft-4.01/lft_xml.h000644 000765 000024 00000001537 15231735015 013775 0ustar00vicstaff000000 000000 /* lft_xml.h — XML attribute/body escaping. Pure module (no session/network). Introduced for LFT 4.0 so every externally-influenced string emitted in XML output (PTR names, whois netnames, interface names, user hostname) is well-formed and injection-safe. Unit-tested by tests/xml_test.c. */ #ifndef LFT_XML_H #define LFT_XML_H #include /* Copy in -> out, replacing the five XML metacharacters with entities: * & -> & < -> < > -> > * " -> " ' -> ' * Always NUL-terminates and never writes past out[outsz-1]. If the next * character's expansion would not fit, output stops there (no partial * entity like "&am" is ever emitted). in==NULL is treated as empty. * Returns out. */ const char *lft_xml_escape(const char *in, char *out, size_t outsz); #endif /* LFT_XML_H */ lft-4.01/icons/000755 000765 000024 00000000000 15250425210 013256 5ustar00vicstaff000000 000000 lft-4.01/whois.h000644 000765 000024 00000010037 15247152740 013461 0ustar00vicstaff000000 000000 /* * whois.h * Layer Four Traceroute * * This file is part of LFT. * * The LFT software provided in this Distribution is * Copyright 2007 VOSTROM Holdings, Inc. * * The full text of our legal notices is contained in the file called * COPYING, included with this Distribution. * */ #ifndef WHOIS_H #define WHOIS_H struct ip_list_array { struct in_addr ipaddr[1024]; int asn[1024]; char netName[1024][32]; char orgName[1024][100]; char application[1024]; int numItems; }; struct ext_ip_list_array { struct in_addr ipaddr[1024]; int asn[1024]; char prefix[1024][20]; char netName[1024][32]; char orgName[1024][100]; char application[1024]; double latitude[1024]; double longitude[1024]; char country[1024][50]; char state[1024][50]; char city[1024][50]; char country_code[1024][4]; char asOrgNameSource[1024][100]; char orgNameSource[1024][100]; char netNameSource[1024][100]; int geoavailable; int numItems; }; #ifdef __cplusplus extern "C" { #endif /* Forward-DNS cache entry used when c-ares is compiled in */ #ifdef HAVE_CARES struct w_dns_entry { const char *host; /* pointer to the static server-name string */ struct in_addr addr; /* resolved IPv4 address (zero if unresolved) */ int resolved; /* set to 1 by callback (success or failure) */ }; #endif typedef struct _whoissessionparams { int w_noisy; /* Don't show debug msgs by default */ char pw_serv[256]; /* User can specify his own pwhois server */ char consolidated_asn[256]; /* ASN returned from pwhois */ char consolidated_asp[256]; /* AS-PATH returned from pwhois */ char consolidated_route[256]; /* Prefix returned from pwhois */ char consolidated_orgname[256]; /* OrgName returned from pwhois */ char consolidated_netname[256]; /* NetName returned from pwhois */ char tbuf[128]; time_t tval; void * logprintfCookie; #ifdef HAVE_CARES struct w_dns_entry w_dns_cache[8]; /* pre-resolved whois server addresses */ int w_dns_cache_count; /* number of entries populated */ #endif }whois_session_params; /* must be called BEFORE making any queries */ whois_session_params * w_init(void); whois_session_params * w_reinit(whois_session_params * wsess); void w_close(whois_session_params * wsess); /* return the origin-asn according to the RADB in "3356" format */ int w_lookup_as(whois_session_params * wsess, char *); /* return the origin-asn according to Cyrmu in "3356" format */ int w_lookup_as_cymru(whois_session_params * wsess, char *); /* return the origin-asn according to the RIPE RIS in "3356" format */ int w_lookup_as_riswhois(whois_session_params * wsess, char *); /* return the origin-asn according to pwhois in "3356" format */ int w_lookup_as_pwhois(whois_session_params * wsess, char *); /* return the network name from the registrar in a string */ char *w_lookup_netname(whois_session_params * wsess, char *); /* return the organization name from the registrar in a string */ char *w_lookup_orgname(whois_session_params * wsess, char *); /* return a pointer to an ip_list_array (see above) containing an 'asn' to each corresponding 'ipaddr' according to Cymru */ int w_lookup_as_cymru_bulk(whois_session_params * wsess, struct ip_list_array*); /* return a pointer to an ip_list_array (see above) containing all ip_list_array vars to each corresponding 'ipaddr' according to pwhois */ int w_lookup_all_pwhois_bulk(whois_session_params * wsess, struct ip_list_array*); int w_lookup_all_pwhois_bulk_ext(whois_session_params * wsess, struct ext_ip_list_array *iplist); /* return a pointer to an ip_list_array (see above) containing all ip_list_array vars to each corresponding 'ipaddr' according to RIS whois */ int w_lookup_all_riswhois_bulk(whois_session_params * wsess, struct ip_list_array*); int w_lookup_all_pwhois(whois_session_params * wsess, char *addr); int w_lookup_all_riswhois(whois_session_params * wsess, char *addr); #ifdef __cplusplus } #endif #endif lft-4.01/lft_addr.h000644 000765 000024 00000005631 15231735015 014106 0ustar00vicstaff000000 000000 /* lft_addr.h — address-family-agnostic address value type and helpers. Pure module: no pcap, no session, no network I/O. Unit-tested by tests/addr_test.c (no root, no network). Introduced for LFT 4.0 IPv6. All parsing/formatting goes through inet_pton/inet_ntop; nothing is hand-rolled except the fully-expanded v6 form and the checksum pseudo-headers (RFC 2460/8200 sec 8.1 for v6, classic for v4). */ #ifndef LFT_ADDR_H #define LFT_ADDR_H #include #include #include #include #include typedef struct { int af; /* AF_INET or AF_INET6 */ union { struct in_addr v4; struct in6_addr v6; } a; } lft_addr_t; /* Populate out from a sockaddr_storage (v4/v6). Returns 1 on success, * 0 if ss->ss_family is neither AF_INET nor AF_INET6. */ int lft_addr_from_ss(const struct sockaddr_storage *ss, lft_addr_t *out); /* Parse a numeric address string (v4 dotted or v6, incl. "::" shorthand). * Rejects hostnames, empty, and CIDR ("/"). A v4-mapped v6 literal * (::ffff:a.b.c.d) is normalized to an AF_INET address. Returns 1 on * success and sets out->af; 0 on failure. */ int lft_addr_parse(const char *s, lft_addr_t *out); /* RFC 5952 canonical (compressed, lower-case) form. buf must be * >= INET6_ADDRSTRLEN. Returns buf. */ const char *lft_ntop(const lft_addr_t *a, char *buf, size_t sz); /* Like lft_ntop, but when expand!=0 a v6 address is rendered in the * fully-expanded 8-group form (2001:0db8:0000:...:0001). v4 is * unaffected by expand. Returns buf. */ const char *lft_ntop_fmt(const lft_addr_t *a, char *buf, size_t sz, int expand); /* 1 if the two addresses are the same family and bytes; else 0. */ int lft_addr_eq(const lft_addr_t *x, const lft_addr_t *y); /* 1 if a is an AF_INET6 address; else 0. */ int lft_addr_is_v6(const lft_addr_t *a); /* Resolve host (numeric literal or DNS name) honoring force_af * (AF_UNSPEC = accept either family). On success fills *out (sockaddr_storage) * and *out_af with the chosen family and returns 1; returns 0 on failure or a * force_af/host family mismatch. Numeric literals resolve offline (no DNS, * no AI_ADDRCONFIG gating), so this is unit-testable without a network. */ int lft_resolve_af(const char *host, int force_af, struct sockaddr_storage *out, int *out_af); /* Write the transport-checksum pseudo-header for (src,dst,proto,plen) into * out and return its length in bytes: 12 for v4 (classic), 40 for v6 * (RFC 2460/8200 sec 8.1: src[16] dst[16] plen[4] zero[3] nexthdr[1]). * plen is the upper-layer packet length in host byte order. out must have * room for >= 40 bytes. Returns 0 if src/dst families differ or are * unsupported. */ size_t lft_pack_pseudo_header(const lft_addr_t *src, const lft_addr_t *dst, uint8_t proto, uint16_t plen, void *out); #endif /* LFT_ADDR_H */ lft-4.01/lft_icmptrace.c000644 000765 000024 00000123352 15247705774 015157 0ustar00vicstaff000000 000000 #include "lft_icmptrace.h" static LFT_CALLBACK LFTErrHandler = NULL; static LFT_CALLBACK LFTEvtHandler = NULL; unsigned int icmp_rfc_send_request(lft_session_params * sess, LFT_CALLBACK err, LFT_CALLBACK evt); unsigned int icmp_base_send_hop(lft_session_params * sess, LFT_CALLBACK err, LFT_CALLBACK evt, short nhop); static void icmp_finish(lft_session_params *sess); static u_short in_cksum(u_short *addr, int len) { register int nleft = len; register u_short *w = addr; register u_short answer; register int sum = 0; while(nleft > 1) { sum += *w++; nleft -= 2; } if(nleft == 1) { u_short u = 0; *(u_char *)(&u) = *(u_char *)w; sum += u; } sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); answer = ~sum; return answer; } /* struct icmp_trace_packet_s { char * packet; int packet_len; struct ip * ip_hdr; struct rfc1393_ip_option_s * icmp_trace_opt; struct ip_lsrr * lsrr; struct icmp_echo_header_s * echo;; char * payload; int payload_len; }; */ #ifndef IPDEFTTL #define IPDEFTTL 200 #endif static const char payload_fill[]="LFT"; int generateICMPPacket( lft_session_params * sess, LFT_CALLBACK err, LFT_CALLBACK evt, struct icmp_trace_packet_s * packet, u_char ttlval, u_short unique_id, u_short seq) { char * currptr; int i,j; int userlen; (void)evt; if(packet->packet) free(packet->packet); //size of ip header packet->packet_len=20; //initial ip header if(sess->protocol==3) packet->packet_len+=12; //trace option if(sess->hostname_lsrr_size) packet->packet_len+=(sess->hostname_lsrr_size + 1)*4; //lsrr option //size of icmp header packet->packet_len+=sizeof(struct icmp_echo_header_s); //icmp header //size of payload /* Always reserve 2 bytes immediately after the ICMP echo header for a * flow-hash compensation word (see below). User payload, if any, * follows after those 2 bytes. Alignment padding applies only to the * user portion so that the overall packet layout is unchanged except * for the mandatory 2-byte prefix. */ userlen=sess->userlen; if(userlen) { userlen -= packet->packet_len + 2; /* account for the 2 adj bytes */ if(userlen<0) userlen = 0; /* Round the payload DOWN to a 4-byte multiple. sess->userlen is the * total datagram target (the interface MTU under -K PMTUD); rounding * up here pushed the total past the MTU and, with DF set, the send * failed with EMSGSIZE. Rounding down keeps packet_len <= target. */ if(userlen%4) userlen-=userlen%4; } packet->packet_len += 2 + userlen; /* 2 adj bytes + optional user payload */ if(!(packet->packet=malloc(packet->packet_len))) { err(sess, ERR_NOT_ENOUGH_MEM, NULL); return 0; } memset(packet->packet, 0,packet->packet_len); //initialize ip header currptr = packet->packet; packet->ip_hdr = (struct ip *)(void *)packet->packet; packet->ip_hdr->ip_v = 4; packet->ip_hdr->ip_hl = 5; if(sess->hostname_lsrr_size) packet->ip_hdr->ip_hl += sess->hostname_lsrr_size + 1; if(sess->protocol == 3) packet->ip_hdr->ip_hl += 3; packet->ip_hdr->ip_id = unique_id; if(sess->set_tos) packet->ip_hdr->ip_tos = TOSMINDELAY; else packet->ip_hdr->ip_tos = 0; packet->ip_hdr->ip_off = IP_DF; packet->ip_hdr->ip_len = packet->packet_len; #ifndef SCREWED_IP_LEN packet->ip_hdr->ip_off = htons(packet->ip_hdr->ip_off); packet->ip_hdr->ip_len = htons(packet->ip_hdr->ip_len); #endif packet->ip_hdr->ip_p = IPPROTO_ICMP; packet->ip_hdr->ip_src = sess->local_address; packet->ip_hdr->ip_dst = sess->remote_address; if(sess->protocol==3) packet->ip_hdr->ip_ttl = IPDEFTTL; else packet->ip_hdr->ip_ttl = ttlval; currptr += sizeof(struct ip); //initialize ip option for rfc1393 trace /* struct rfc1393_ip_option_s { u_char optcode; //=82 u_char optlength; //=12 u_short id; //number to identify icmp trace messages u_short ohc; //outbound hop count u_short rhc; //return hop count struct in_addr origip; //originator ip address }; */ if(sess->protocol == 3) { packet->icmp_trace_opt = (struct rfc1393_ip_option_s *)(void *)currptr; packet->icmp_trace_opt->optcode = 82; packet->icmp_trace_opt->optlength = 12; packet->icmp_trace_opt->id = seq; packet->icmp_trace_opt->ohc = 0; packet->icmp_trace_opt->rhc = 0xFFFF; packet->icmp_trace_opt->origip = sess->local_address; currptr+=sizeof(struct rfc1393_ip_option_s); } else packet->icmp_trace_opt = NULL; /* struct ip_lsrr { u_int8_t ipl_code; u_int8_t ipl_len; u_int8_t ipl_ptr; u_int32_t data[9]; char padding[1]; } __attribute__((packed)); */ if(sess->hostname_lsrr_size) { packet->lsrr=(struct ip_lsrr *)(void *)currptr; currptr+=(sess->hostname_lsrr_size + 1)*4; for(i = 0; i < sess->hostname_lsrr_size; i++) packet->lsrr->data[i] = get_address(sess, sess->hostname_lsrr[i]); packet->lsrr->ipl_code = IPOPT_LSRR; packet->lsrr->ipl_len = sess->hostname_lsrr_size * 4 + 3; packet->lsrr->ipl_ptr = 4; } packet->ip_hdr->ip_sum = 0; #ifndef SCREWED_IP_LEN packet->ip_hdr->ip_sum = ip_cksum(packet->ip_hdr); #endif /* struct icmp_echo_header_s { u_char type; u_char code; u_short checksum; u_short id; u_short sequence; }; */ packet->echo=(struct icmp_echo_header_s *)(void *)currptr; currptr+=sizeof(struct icmp_echo_header_s); packet->echo->type=ICMP_ECHO; packet->echo->code=0; packet->echo->checksum=0; packet->echo->id=unique_id; packet->echo->sequence=seq; /* Flow-hash compensation word — always 2 bytes immediately after the * ICMP echo header. * * ECMP routers hash flows using fixed packet fields. For ICMP echo, * the fields most commonly included in that hash are: source IP, * destination IP, protocol, type, code, identifier, and (on some * implementations) the ICMP checksum. Type, code, and identifier are * already constant for the session. The sequence number must vary per * probe so that TTL-exceeded replies can be matched to the probe that * triggered them — but a varying sequence causes the checksum to vary. * * Fix: maintain a 2-byte compensation word `adj` at the start of the * payload such that (adj + seq) equals a fixed reference value in * 16-bit one's-complement arithmetic. Because the ICMP checksum is a * one's-complement sum over all header and payload bytes, the checksum * is then identical for every probe in the session regardless of seq. * * Reference value: the sequence number of the very first probe. On * that probe adj = 0x0000. On every subsequent probe: * adj = reference_seq - seq (one's-complement 16-bit subtraction) * = reference_seq + ~seq (with carry folded back into LSB) */ packet->payload = currptr; /* payload starts at adj word */ if (!sess->icmp_seq_anchored) { sess->icmp_initial_seq = seq; sess->icmp_seq_anchored = 1; /* adj = 0 for the first probe */ ((u_char *)currptr)[0] = 0; ((u_char *)currptr)[1] = 0; } else { uint32_t tmp = (uint32_t)sess->icmp_initial_seq + (uint32_t)(~seq & 0xFFFFu); uint16_t adj = (uint16_t)((tmp & 0xFFFFu) + (tmp >> 16)); ((u_char *)currptr)[0] = (adj >> 8) & 0xFF; ((u_char *)currptr)[1] = adj & 0xFF; } currptr += 2; packet->payload_len = 2; if(userlen) { for(i=0,j=0;ipayload_len += userlen; } sess->payload = NULL; sess->payloadlen = packet->payload_len; packet->echo->checksum=in_cksum((u_short *)(void *)packet->echo, sizeof(struct icmp_echo_header_s) + packet->payload_len); /*#ifndef SCREWED_IP_LEN packet->echo->checksum = htons(packet->echo->checksum); #endif*/ return packet->packet_len; } static int icmp_check_timeouts (lft_session_params * sess, LFT_CALLBACK err, LFT_CALLBACK evt) { int nhop; int need_reply = 0; int no_reply = 0; int last_return = 0; LFTErrHandler=err; LFTEvtHandler=evt; 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 */ if(sess->protocol==2) { for(nhop = sess->ttl_min; nhop < sess->hop_info_length; nhop++) { if(!sess->hop_info[nhop].num_sent) { icmp_base_send_hop(sess, err, evt, 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) { evt(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) evt(sess,EVT_PROGRESS_NO_REPLY,NULL); if(sess->noisy > 2) evt(sess,EVT_TTL_TOUT_RESEND,&nhop); if(sess->exit_state<0) return 0; icmp_base_send_hop(sess, err, evt, nhop); return 0; } else { /* PMTUD: flag this hop as a tentative black hole */ if (sess->pmtud && sess->protocol == 2) { sess->hop_info[nhop].pmtud_blackhole = 1; sess->hop_info[nhop].path_mtu = sess->pmtud_current_mtu; } if (nhop > LFT_GIVEUP_NEARHOP_NHOP) no_reply++; /* exempt near hops (see lft_lib.h) */ } } else need_reply++; /* we have to wait for this one to timeout */ } else /* have reply */ last_return = nhop; } } else { if(!sess->hop_info[255].num_sent) { icmp_rfc_send_request(sess, err, evt); return 0; } if(sess->hop_info[255].num_sent <= sess->retry_max && !sess->hop_info[255].ts_last_recv.tv_sec) { if(sess->noisy > 4) { evt(sess,EVT_TTL_NO_REPLY,&nhop); if(sess->exit_state<0) return 0; } if(timediff_ms(sess->hop_info[255].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[255].num_sent < sess->retry_max) { if(!sess->noisy && !sess->nostatus) evt(sess,EVT_PROGRESS_NO_REPLY,NULL); if(sess->noisy > 2) evt(sess,EVT_TTL_TOUT_RESEND,&nhop); if(sess->exit_state<0) return 0; icmp_rfc_send_request(sess, err, evt); return 0; } else no_reply++; } else need_reply++; /* we have to wait for this one to timeout */ } else /* have reply */ last_return = 0; } if(sess->noisy > 4) { EvtDebugCheckpoint1Param edcp; edcp.last_return=last_return; edcp.need_reply=need_reply; edcp.no_reply=no_reply; evt(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)) evt(sess,EVT_CANT_RELIABLY_RTRIP,NULL); if(sess->exit_state<0) return 0; #if !defined(__CYGWIN__) && !defined(WIN32) && !defined(_WIN32) if (sess->pmtud && sess->protocol == 2) lft_pmtud_verify_blackholes(sess); #endif icmp_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) evt(sess,EVT_HAVE_UNANSWERRED_HOPS,NULL); if(need_reply >= sess->ahead_limit){ if(sess->noisy > 4) evt(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) evt(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++; if(sess->protocol==2) icmp_base_send_hop(sess, err, evt, nhop); else { icmp_rfc_send_request(sess, err, evt); } } else { if (sess->noisy >= 4) { evt(sess, EVT_EITHER_RESP_OR_TOUT, NULL); if(sess->exit_state < 0) return 0; } if(sess->protocol == 2) { 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) { icmp_base_send_hop(sess, err, evt, nhop); return 0; } } } else { if(sess->hop_info[255].num_sent < sess->retry_min && sess->hop_info[255].num_sent <= sess->retry_max) { icmp_rfc_send_request(sess, err, evt); 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 && sess->protocol == 2) lft_pmtud_verify_blackholes(sess); #endif icmp_finish(sess); return 1; } return 0; } unsigned int icmp_rfc_send_request(lft_session_params * sess, LFT_CALLBACK err, LFT_CALLBACK evt) { struct trace_packet_info_s *pinfo = NULL; struct sockaddr_in dest; u_short rfc_unique_seq; unsigned int nseq; dest.sin_family = AF_INET; dest.sin_addr = sess->remote_address; dest.sin_port = 0; if(!(pinfo = (struct trace_packet_info_s *)malloc(sizeof(struct trace_packet_info_s)))) { err(sess, ERR_NOT_ENOUGH_MEM, NULL); return 0; } memset(pinfo, 0, sizeof(struct trace_packet_info_s)); nseq = new_seq(sess); nseq &= 0xFFFF; rfc_unique_seq = nseq; generateICMPPacket(sess, err, evt, &pinfo->u.icmp_packet, 0, sess->icmp_packet.echo->id, rfc_unique_seq); /* Packet is ready, fire away */ if(sendto(sess->send_sock, pinfo->u.icmp_packet.packet, pinfo->u.icmp_packet.packet_len, 0, (const struct sockaddr *)(const void *)&dest, sizeof (dest)) < 0) { LFTErrHandler(sess, ERR_RAW_TCP_DISABLED, NULL); free(pinfo); return 0; } pinfo->hopno = 0; pinfo->seq = rfc_unique_seq; pinfo->sent = sess->now; SLIST_INSERT_HEAD(&(sess->trace_packets), pinfo, next); sess->trace_packets_num++; /* we use special hop_info #255 */ SLIST_INSERT_HEAD(&(sess->hop_info[255].packets), pinfo, next_by_hop); sess->hop_info[255].num_sent++; sess->hop_info[255].all_sent++; sess->hop_info[255].ts_last_sent = sess->now; return 1; } unsigned int icmp_base_send_hop(lft_session_params * sess, LFT_CALLBACK err, LFT_CALLBACK evt, short nhop) { struct trace_packet_info_s *pinfo = NULL; struct sockaddr_in dest; u_short base_unique_seq; unsigned int nseq; EvtSentPacketParam espparam; dest.sin_family = AF_INET; dest.sin_addr = sess->remote_address; dest.sin_port = 0; nseq = new_seq(sess); nseq &= 0xFFFF; base_unique_seq = nseq; sess->ts_last_sent = sess->now; if(!(pinfo = (struct trace_packet_info_s *)malloc(sizeof(struct trace_packet_info_s)))) { err(sess, ERR_NOT_ENOUGH_MEM, NULL); return 0; } memset(pinfo, 0, sizeof(struct trace_packet_info_s)); espparam.flags=0; espparam.nhop=nhop; espparam.tseq=base_unique_seq; espparam.tttl=nhop+1; espparam.sport=0; /* ICMP: portless */ espparam.dport=0; 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; } } generateICMPPacket(sess, err, evt, &pinfo->u.icmp_packet, nhop+1, sess->icmp_packet.echo->id, base_unique_seq); /* Packet is ready, fire away */ { int _sret = sendto(sess->send_sock, pinfo->u.icmp_packet.packet, pinfo->u.icmp_packet.packet_len, 0, (const struct sockaddr *)(const void *)&dest, sizeof (dest)); if (_sret < 0) { /* printf("errno=%d\n",errno); */ LFTErrHandler(sess, ERR_RAW_TCP_DISABLED, NULL); free(pinfo); return 0; } } pinfo->hopno = nhop; pinfo->seq = base_unique_seq; 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 1; } static int icmp_recv_packet (lft_session_params * sess, unsigned int seq, struct in_addr ipaddr, int icmp_type, int icmp_code, const void * pack, const struct pcap_pkthdr *hdr) { struct trace_packet_info_s *tp = NULL, *pinfo = NULL; const struct icmp_trace_reply_header_s *icmpheader = (const struct icmp_trace_reply_header_s *)pack; #if defined( __CYGWIN__ ) || defined( WIN32 ) || defined(_WIN32) gettimeofday (&(sess->now), NULL); #else sess->now.tv_sec = hdr->ts.tv_sec; sess->now.tv_usec = hdr->ts.tv_usec; #endif /* First, search every probe to find an exact sequence match */ SLIST_FOREACH(tp, &(sess->trace_packets), next) { if(tp->seq == seq) { break; } } if(!tp) { if (sess->noisy) LFTEvtHandler(sess,EVT_SKIP_PACKET,NULL); else if (!sess->nostatus) LFTEvtHandler(sess,EVT_PROGRESS_SKIP_PACKET,NULL); return 0; } 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 0; } /* Encode and set tp->icmp_type BEFORE firing EVT_RECV_PACKET so the * handler reads the correct value (race-condition fix). * Encoding mirrors lft_lib.c's recv_packet() convention: * -2 = TTL Exceeded (ICMP_TIMXCEED, type 11 code 0) * -1 = target reached (ICMP echo reply) * 0-15 = ICMP type-3 UNREACH sub-code (icmp_code) */ if (icmp_type == ICMP_TIMXCEED) tp->icmp_type = -2; else if (icmp_type == ICMP_ECHOREPLY) tp->icmp_type = -1; else if (icmp_type == ICMP_UNREACH) tp->icmp_type = icmp_code; /* preserve the actual sub-code */ else tp->icmp_type = icmp_type; /* ICMP_TRACE and any other raw types */ 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 0; /* increment received packet counter */ sess->num_rcvd++; tp->recv = sess->now; if (tp->hopno != -1) { if(sess->protocol==3 && icmp_type == ICMP_TRACE) { tp->hopno=icmpheader->ohc; sess->hop_info[tp->hopno].num_sent = sess->hop_info[255].num_sent; sess->hop_info[tp->hopno].all_sent = sess->hop_info[255].all_sent; sess->hop_info[tp->hopno].ts_last_sent = sess->hop_info[255].ts_last_sent; sess->hop_info[tp->hopno].state = sess->hop_info[255].state; sess->hop_info[tp->hopno].flags = sess->hop_info[255].flags; } sess->hop_info[tp->hopno].ts_last_recv = sess->now; sess->hop_info[tp->hopno].all_rcvd++; /* indicate this hop has a sequence anomaly */ if(icmp_type==ICMP_UNREACH || icmp_type==ICMP_TIMXCEED) sess->hop_info[tp->hopno].flags |= HF_ENDPOINT; } tp->recv = sess->now; tp->hopaddr = ipaddr; #ifdef HAVE_CARES lft_ares_queue(sess, ipaddr); /* queue PTR lookup for this hop (deduped) */ #endif /* tp->icmp_type already set above, before EVT_RECV_PACKET */ if(icmp_type == ICMP_ECHOREPLY && ipaddr.s_addr == sess->remote_address.s_addr) { if(sess->protocol==3) { tp->hopno=sess->num_hops+1; sess->hop_info[tp->hopno].num_sent = sess->hop_info[255].num_sent; sess->hop_info[tp->hopno].all_sent = sess->hop_info[255].all_sent; sess->hop_info[tp->hopno].ts_last_sent = sess->hop_info[255].ts_last_sent; sess->hop_info[tp->hopno].state = sess->hop_info[255].state; sess->hop_info[tp->hopno].flags = sess->hop_info[255].flags; sess->hop_info[tp->hopno].ts_last_recv = sess->now; sess->hop_info[tp->hopno].all_rcvd++; } if(!sess->num_hops) { sess->num_hops = tp->hopno; if(!sess->num_hops) sess->num_hops=1; } tp->is_done = 1; } else { if(icmp_type == ICMP_UNREACH || icmp_type == ICMP_TIMXCEED) return 1; if(sess->protocol == 3 && icmp_type == ICMP_TRACE) { if(icmpheader->rhc == 0xFFFF) /* outbound packet */ { if(!(pinfo = (struct trace_packet_info_s *)malloc(sizeof(struct trace_packet_info_s)))) { LFTErrHandler(sess, ERR_NOT_ENOUGH_MEM, NULL); return 0; } memcpy(pinfo, tp, sizeof(struct trace_packet_info_s)); SLIST_INSERT_HEAD(&(sess->hop_info[tp->hopno].packets), pinfo, next_by_hop); } else return 1; //while ignore return packets } } return 1; } static void icmp_process_packet(lft_session_params * sess, LFT_CALLBACK err, LFT_CALLBACK evt, const u_char *packet, const struct pcap_pkthdr *hdr) { const struct ip *ip, *orig_ip; const struct icmp *icmp; const struct icmp_echo_header_s *orig_echo, *resp_echo; const struct ether_header *eptr; const u_char *pkt_end = packet + hdr->caplen; LFTErrHandler=err; LFTEvtHandler=evt; if(sess->noisy > 4) { LFTEvtHandler(sess,EVT_PROCESS_PACKET_START,NULL); if(sess->exit_state<0) return; } icmp_check_timeouts(sess, err, evt); if(sess->exit_state<0) return; /* 802.1q VLAN (dot1q) frame: extend the L2 offset per-packet (mirrors process_packet). */ eptr = (const struct ether_header *) packet; { size_t l2 = sess->skip_header_len; if (l2 == sizeof (struct ether_header) && ntohs (eptr->ether_type) == ETHERTYPE_VLAN) l2 += 4; if (hdr->caplen <= l2) return; /* truncated before L3 */ packet += l2; } /* v4-only engine: drop truncated or non-IPv4 frames rather than misparse. */ if ((packet[0] >> 4) != 4 || packet + sizeof (struct ip) > pkt_end) return; ip = (const void *) packet; packet += 4 * ip->ip_hl; switch(ip->ip_p){ case IPPROTO_ICMP: orig_ip = ip; icmp = (const void *) packet; if((const u_char *) icmp + ICMP_MINLEN > pkt_end) return; if(icmp->icmp_type != ICMP_ECHOREPLY && icmp->icmp_type != ICMP_UNREACH && !(sess->protocol==2 && icmp->icmp_type == ICMP_TIMXCEED) && !(sess->protocol==3 && icmp->icmp_type == ICMP_TRACE)) { return; } if(icmp->icmp_type == ICMP_ECHOREPLY) { resp_echo=(const struct icmp_echo_header_s *)icmp; if(resp_echo->id != sess->icmp_packet.echo->id) return; if (sess->noisy > 2) { EvtIncomingICMPEchoParam echo; echo.ip=orig_ip; echo.echo=resp_echo; LFTEvtHandler(sess,EVT_INCOMING_ICMP_Echo,&echo); if(sess->exit_state<0) return; } if (sess->noisy > 1) { LFTEvtHandler(sess,EVT_RCVD_ICMP_Echo,resp_echo); if(sess->exit_state<0) return; } icmp_recv_packet(sess, resp_echo->sequence, orig_ip->ip_src, ICMP_ECHOREPLY, 0, NULL, hdr); return; } if(icmp->icmp_type == ICMP_UNREACH || icmp->icmp_type == ICMP_TIMXCEED) { ip = &icmp->icmp_ip; if((const u_char *) ip + sizeof (struct ip) > pkt_end) return; if(ip->ip_p != IPPROTO_ICMP) return; packet = (const u_char *) ip; packet += 4 * ip->ip_hl; orig_echo = (const struct icmp_echo_header_s *)packet; if(packet + sizeof (struct icmp_echo_header_s) > pkt_end) return; if(orig_echo->type != ICMP_ECHO) return; if(sess->icmp_packet.echo->id != orig_echo->id) { return; } /* PMTUD: intercept PTB (type 3 code 4) before marking the hop answered */ if (sess->pmtud && sess->protocol == 2 && icmp->icmp_type == ICMP_UNREACH && icmp->icmp_code == ICMP_UNREACH_NEEDFRAG) { lft_pmtud_icmp_handle_ptb(sess, icmp, orig_echo->sequence, orig_ip->ip_src); return; } if (sess->noisy > 2) { EvtIncomingICMPICMPParam icmpicmp; icmpicmp.ip=orig_ip; icmpicmp.icmp=icmp; icmpicmp.orig_ip=ip; icmpicmp.echo=orig_echo; LFTEvtHandler(sess,EVT_INCOMING_ICMP_ICMP,&icmpicmp); if(sess->exit_state<0) return; } if (sess->noisy > 1) { LFTEvtHandler(sess,EVT_RCVD_ICMP_ICMP,icmp); if(sess->exit_state<0) return; } icmp_recv_packet(sess, orig_echo->sequence, orig_ip->ip_src, icmp->icmp_type, icmp->icmp_code, NULL, hdr); return; } if(icmp->icmp_type == ICMP_TRACE) { const struct icmp_trace_reply_header_s * itrh=(const struct icmp_trace_reply_header_s *)icmp; if((const u_char *) itrh + sizeof (struct icmp_trace_reply_header_s) > pkt_end) return; icmp_recv_packet(sess, itrh->id, orig_ip->ip_src, itrh->type, 0, itrh, hdr); return; } default: if(sess->noisy > 3) LFTEvtHandler(sess,EVT_RCVD_UNKNOWN,ip); } } #if defined( __CYGWIN__ ) || defined( WIN32 ) || defined(_WIN32) void win_icmp_process(lft_session_params * sess) { fd_set fds; struct timeval tm; 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) { 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; } icmp_process_packet(sess, LFTErrHandler, LFTEvtHandler, packetbuf, NULL); } } #else void pcap_icmp_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; icmp_process_packet(sess, LFTErrHandler, LFTEvtHandler, packet, hdr); } #endif #if !defined(__CYGWIN__) && !defined(WIN32) && !defined(_WIN32) /* * Send a single ICMP verification probe to hop nhop. * Called by lft_pmtud_verify_blackholes() with sess->userlen already set * to the small verification MTU so generateICMPPacket() builds a tiny packet. */ unsigned int icmp_pmtud_send_verify_hop(lft_session_params *sess, short nhop) { /* Defensive: the ICMP engine's static handlers are only set once the * engine has processed a packet; never call the sender with NULL handlers * (would dereference a NULL function pointer in icmp_base_send_hop). */ if (!LFTErrHandler || !LFTEvtHandler) return 0; return icmp_base_send_hop(sess, LFTErrHandler, LFTEvtHandler, nhop); } #endif void icmp_trace_main_loop(lft_session_params * sess, LFT_CALLBACK err, LFT_CALLBACK evt) { LFTErrHandler=err; LFTEvtHandler=evt; #if defined( __CYGWIN__ ) || defined( WIN32 ) || defined(_WIN32) while(1) { win_icmp_process(sess); if(sess->exit_state < 0) break; if(icmp_check_timeouts(sess, err, evt)) break; if(sess->exit_state < 0) break; } #else while(pcap_dispatch(sess->pcapdescr, -1, pcap_icmp_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; } if(icmp_check_timeouts(sess,err,evt)) break; if(sess->exit_state<0) break; } #endif } /* 1 if any reply recorded at this hop is the target's echo reply */ static int icmp_hop_is_target(lft_session_params *sess, int hopno) { struct trace_packet_info_s *tp; SLIST_FOREACH(tp, &(sess->hop_info[hopno].packets), next_by_hop) if (tp->recv.tv_sec && tp->icmp_type == -1) return 1; return 0; } static void icmp_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(appname) + 1 + strlen(version) + 1); 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)); gettimeofday (&(sess->trace_done_time), NULL); /* ocres=open_check(sess); LFTEvtHandler(sess,EVT_OPEN_CHECK_RESULT,&ocres); if(ocres==1) sess->target_open=1; else sess->target_open=0; */ 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) { 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; break; } } } 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) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-truncation" snprintf(myApp, strlen(appname) + 1 + strlen(version) + 1, "%s %s", appname, version); #pragma GCC diagnostic pop strncpy((*ipaslist).application, myApp, 511); } if ((outputStyleIsGeoJSON() || outputStyleIsKML()) ? lft_geo_bulk_lookup(sess, ipaslist, maxhop) != 0 : w_lookup_all_pwhois_bulk(sess->wsess, &(*ipaslist)) != 0) if (sess->noisy) LFTErrHandler(sess, WRN_NS_LOOKUP_FAILED, NULL); } if(sess->exit_state < 0) { free(ipaslist); free(myApp); return; } } } free(myApp); #ifdef HAVE_CARES lft_ares_wait(sess); /* drain any pending PTR queries before display */ #endif 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 */ /* HF_ENDPOINT is set on every TTL-exceeded hop in ICMP mode, so the target is * recognised by its echo reply (icmp_type == -1), not by that flag. */ #define ICMP_HOP_IS_TARGET(h) icmp_hop_is_target(sess, (h)) for (hopno = sess->ttl_min; hopno <= maxhop; hopno++) { struct in_addr last_hop; /* Suppress duplicate target-hop output caused by ahead-limit overshoot * (mirrors finish() in lft_lib.c) */ if (found_target && ICMP_HOP_IS_TARGET(hopno)) { reply = 0; continue; } if (sess->hop_info[hopno].all_rcvd != 0) { if (noreply >= 1) { if (sess->pmtud && getOutputStyle() == 0) { /* Per-hop PMTUD output. * [ICMP filtered]: suppresses TTL-exceeded for large probes * but forwards them (confirmed by a downstream response). * [MTU Limit]: did not respond; packet forwarding unconfirmed. * [neglected]: 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; } //printf("hopno=%d all_rcvd=%d",hopno,sess->hop_info[hopno].all_rcvd); 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 (mirrors lft_lib.c path). * Fire whenever pwhois ran: 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; /* PMTUD per-hop annotations */ ehip.path_mtu = sess->hop_info[hopno].path_mtu; ehip.pmtud_blackhole = sess->hop_info[hopno].pmtud_blackhole; /* 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=getOutputStyle(); char hostname[100]; ehip.seam_traced=1; setOutputStyle(2); 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); ehip.is_open=subsess->target_open; ehip.is_filtered=subsess->target_filtered; LFTSessionClose(subsess); setOutputStyle(curroutputstyle); } } /* --------------- */ 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; } /* First target hop printed — guard above suppresses any * duplicate target hops from ahead-limit overshoot. */ if (ICMP_HOP_IS_TARGET(hopno)) found_target = 1; } if (reply) { noreply = 0; as_for_hop++; } else noreply++; reply = 0; } /* for(...) */ if (!sess->num_hops) { 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; } lft-4.01/lft_lsrr.h000644 000765 000024 00000001265 11053131665 014154 0ustar00vicstaff000000 000000 /* * lft_lsrr.h * Layer Four Traceroute * * This file is part of LFT. * * The LFT software provided in this Distribution is * Copyright 2007 VOSTROM Holdings, Inc. * * The full text of our legal notices is contained in the file called * COPYING, included with this Distribution. * */ #ifndef LFT_LSRR_H #define LFT_LSRR_H #if defined(sun) typedef uint8_t u_int8_t; typedef uint32_t u_int32_t; #endif struct ip_lsrr { u_int8_t ipl_code; /* IPOPT_TS */ u_int8_t ipl_len; /* size of structure (variable) */ u_int8_t ipl_ptr; /* index of current entry */ u_int32_t data[9]; char padding[1]; } __attribute__((packed)); #define IP_LSRR_DEST 8 #endif lft-4.01/lft_watch.h000644 000765 000024 00000024067 15247705774 014326 0ustar00vicstaff000000 000000 /* * lft_watch.h -- ncurses continuous monitoring mode for LFT * * This file is part of LFT. * * The LFT software provided in this Distribution is * Copyright 2007 VOSTROM Holdings, Inc. * * The full text of our legal notices is contained in the file called * COPYING, included with this Distribution. */ #ifndef LFT_WATCH_H #define LFT_WATCH_H #ifdef HAVE_NCURSES #include "lft_lib.h" #include "whois.h" /* ------------------------------------------------------------------------- * Tunables * ------------------------------------------------------------------------- */ #define WATCH_DEFAULT_INTERVAL 10 /* seconds between trace cycles */ #define WATCH_SEEN_IPS 32 /* addresses remembered per hop for path-change detection */ #define WATCH_RL_WINDOW 10 /* cycles examined for the rate-limit heuristic */ #define WATCH_BACKOFF_MAX 3 /* interval may grow to base << 3 (8x) */ #define WATCH_DEFAULT_PROBES 3 /* probes per hop when -j not given */ #define WATCH_MIN_ROWS 24 /* minimum terminal height */ #define WATCH_MAX_HOPS 256 #define WATCH_HISTORY_LEN 512 /* per-hop RTT history slots (circular) */ #define WATCH_MAX_ALT_IPS 4 /* multipath IPs per TTL */ #define WATCH_LOG_BUF 131072 /* log drawer ring buffer (bytes) — ~10 traces at -VV */ #define WATCH_PATH_FLASH_SECS 3 /* seconds to show PATH CHANGE notice */ #define WATCH_SPINNER_FRAMES 8 /* braille spinner frame count */ /* ------------------------------------------------------------------------- * Per-IP annotation cache * * A session-wide hash table keyed by IP address (IPv4 or IPv6) that stores * pWhoIs / reverse-DNS data. Every unique hop address gets exactly one * record here regardless of how many hops share it (multipath) or how many * cycles it is seen. The cache is the authoritative "have we queried this?" * source — watch_hop_t.asn/netname/orgname/hostname are just display copies * populated from the cache. * ------------------------------------------------------------------------- */ #include /* struct in_addr, struct in6_addr */ /* 16-byte address union — IPv4 occupies the first 4 bytes, rest are zero */ typedef union { struct in_addr v4; /* AF_INET */ struct in6_addr v6; /* AF_INET6 */ uint8_t raw[16]; /* for hashing / comparison */ } watch_ipaddr_t; /* One record per unique IP address */ typedef struct watch_ip_record_s { int af; /* AF_INET or AF_INET6 */ watch_ipaddr_t addr; /* hash key */ char asn[32]; /* e.g. "AS6939" */ char netname[64]; /* e.g. "HURRICANE-1" */ char orgname[64]; /* e.g. "Hurricane Electric" */ char hostname[256]; /* reverse-DNS PTR name */ int whois_queried; /* 1 = pWhoIs attempted */ int rdns_queried; /* 1 = rDNS attempted */ struct watch_ip_record_s *next; /* collision chain */ } watch_ip_record_t; #define WATCH_IP_CACHE_BUCKETS 256 /* power of 2; plenty for any trace */ typedef struct { watch_ip_record_t *buckets[WATCH_IP_CACHE_BUCKETS]; int count; /* total records allocated */ } watch_ip_cache_t; /* ------------------------------------------------------------------------- * Per-hop rolling accumulator * ------------------------------------------------------------------------- */ typedef struct { char host[64]; /* primary IP or hostname */ char alt_hosts[WATCH_MAX_ALT_IPS][64]; /* alternate IPs (multipath) */ int n_alt; /* number of alt_hosts populated */ int cycles_seen; /* cycles in which this hop replied */ int cycles_total; /* total cycles run when this hop was active */ double rtt_min; /* all-time minimum RTT across all cycles (ms) */ double rtt_avg; /* running average RTT (ms) */ double rtt_max; /* all-time maximum RTT (ms) */ double rtt_jitter; /* rtt_max - rtt_min this cycle */ double rtt_last; /* most recent RTT reading (ms) */ double rtt_sum; /* accumulator for running average */ double rtt_history[WATCH_HISTORY_LEN]; /* circular buffer, 0 = no reply */ int history_head; /* next write index */ int history_count; /* samples stored (capped at WATCH_HISTORY_LEN) */ /* Display copies — populated from the session's ip_cache on demand. * The cache is authoritative; these are just what the renderer reads. */ char asn[32]; /* AS number string, e.g. "AS7922" */ char netname[64]; /* network name from pWhoIs/ARIN */ char orgname[64]; /* organisation name from pWhoIs (F2) */ char hostname[256]; /* reverse-DNS name for hop IP (F4) */ } watch_hop_t; /* ------------------------------------------------------------------------- * Overall watch session state * ------------------------------------------------------------------------- */ typedef struct { watch_hop_t hops[WATCH_MAX_HOPS]; int n_hops; /* highest TTL seen */ int cycle; /* current cycle number (1-based) */ int max_cycles; /* 0 = run indefinitely */ int interval; /* seconds between traces */ int probes_per_hop; /* -j N value (probes per hop per cycle) */ int paused; /* 1 = traces suspended */ int running; /* 0 = quit requested */ int current_view; /* 1-4 */ int log_open; /* 1 = log drawer visible */ int whois_enabled; /* 1 = show ASN/NetName columns */ /* Cycling: during a trace, is it the first cycle or a re-run? */ int first_cycle; /* Log drawer ring buffer (captures -VV style output) */ char log_buf[WATCH_LOG_BUF]; int log_head; /* oldest byte position (ring-buffer head) */ int log_len; /* bytes currently stored */ /* Log drawer scroll state */ int log_scroll_offset; /* lines scrolled up from frozen anchor; 0 = live */ int log_anchor_nlines; /* total lines when user first scrolled (frozen anchor); * 0 means live — cleared when returning to live mode */ time_t log_last_scroll; /* wall time of last ↑/↓/k/j keypress (30 s timeout) */ /* Path change detection: every address a hop has ever answered from. * A load-balanced hop cycles through its members without alerting; * only a never-before-seen address is a path change. */ char path_snapshot[WATCH_MAX_HOPS][64]; char seen_ips[WATCH_MAX_HOPS][WATCH_SEEN_IPS][64]; int n_seen[WATCH_MAX_HOPS]; int seen_next[WATCH_MAX_HOPS]; /* FIFO replacement slot once full */ int path_changed; time_t path_change_time; /* Rate-limit heuristic (loss rising at established hops) and back-off */ int rl_hops; /* hops currently showing rate-limit-like loss */ char rl_note[160]; /* on-screen note while rl_hops > 0 */ int backoff_enabled; /* --watch-backoff */ int backoff_level; /* interval = base_interval << level */ int base_interval; /* operator-chosen interval (+/- keys adjust it) */ int rl_streak; /* consecutive cycles with rl_hops > 0 */ int clear_streak; /* consecutive cycles with rl_hops == 0 */ /* Terminal geometry */ int term_rows; int term_cols; /* In-flight trace state (set by event callback) */ int in_trace; /* 1 while LFTExecute is running */ int last_hop_flashed; /* most recent TTL that was flashed */ /* Target description for header line */ char tgt_label[128]; /* e.g. "tcp://192.0.2.40:443" */ /* Target reach state (updated each cycle) */ int target_hopno; /* hop index of the target (0 = not reached) */ int target_open; /* 1 = OPEN (SYN-ACK) */ int target_filtered; /* 1 = FILTERED (ICMP port unreachable) */ int protocol; /* LFT probe protocol (0=TCP, 1=UDP, 2=ICMP, 3=ICMP-RFC1393) */ /* TCP: !open && !filtered → [CLOSED] (RST); ICMP: !open && !filtered → [REPLIED] (Echo Reply) */ /* Per-IP annotation cache (pWhoIs + rDNS); keyed by address */ watch_ip_cache_t ip_cache; /* pWhoIs live lookup state (F2) */ whois_session_params *watch_whois_wsess; /* allocated on first 'w' press */ int whois_bulk_done; /* 1 = bulk query already completed */ /* Hostname display toggle (F4) */ int show_hostnames; /* 1 = show PTR names instead of IPs */ int show_seams; /* 1 = show AS/network seam markers ('s' toggles) */ /* IPv6 address display: 0 = RFC 5952 shorthand, 1 = fully expanded. * Seeded from --expand-addr; toggled live with the 'e' key. */ int addr_expand; /* Spinner animation frame counter (0..WATCH_SPINNER_FRAMES-1) */ int spinner_frame; /* Set by key handlers that require immediate trace start (breaks idle loop) */ int interrupt_sleep; /* Cumulative probe counters — accumulate across all cycles; reset with 'r'. * max_hops_seen and target_ever_reached are sticky-max / sticky-true. */ int total_replies; /* Σ probe replies received */ int total_sent; /* Σ probes sent */ int max_hops_seen; /* peak hop count ever seen */ int target_ever_reached; /* 1 once target replied in any cycle */ /* Transient status bar message (F3 — shown for ~3 s after a save) */ char status_msg[192]; /* if non-empty and not expired, shown in status bar */ time_t status_msg_time; /* time() when the message was set */ } watch_session_t; /* ------------------------------------------------------------------------- * Public interface — called from lft_lib.c EVT_ON_EXIT handler * ------------------------------------------------------------------------- */ void WatchModeRun(lft_session_params *sess); #endif /* HAVE_NCURSES */ #endif /* LFT_WATCH_H */ lft-4.01/lft_icons_svg.h000644 000765 000024 00000076622 15233443070 015175 0ustar00vicstaff000000 000000 /* Auto-generated: flatline device glyphs as inline SVG defs. * Consumed by SVGOutput() in lft_lib.c. Regenerate from the flatline * icon masters; do not hand-edit. */ #ifndef LFT_ICONS_SVG_H #define LFT_ICONS_SVG_H static const char LFT_SVG_DEFS[] = "" "" " <" "path id=\"path2518\" fill=\"none\" stroke=\"#3b424b\" stroke-width=\"46\" d=\"M403.269,1010.526 c-1" "1.417,11.029-21.89,29.503-53.402,33.08\"/> " ; #endif lft-4.01/lft.8000644 000765 000024 00000173074 15250002624 013035 0ustar00vicstaff000000 000000 .Dd September 1, 2026 .Dt LFT 8 .Os LFT .Sh NAME .Nm lft .Nd display the route packets take to a network host/socket using one of several layer-4 protocols and methods; optionally show heuristic network information in transitu .Sh SYNOPSIS .Nm lft .Op Fl d Ar dport .Op Fl s Ar sport .Op Fl m Ar retry min .Op Fl M Ar retry max .Op Fl a Ar ahead .Op Fl c Ar scatter ms .Op Fl t Ar timeout ms .Op Fl l Ar min ttl .Op Fl H Ar max ttl .Op Fl L Ar length .Op Fl q Ar ISN .Op Fl D Ar device .Op Fl f Ar device .Op Fl G Ar icons path .Op Fl 4 | Fl 6 .Op Fl -expand-addr .Op Fl ACEINPRSTUVbeghinpruvxyzK .Ar [ <...>] .Ar target:dport .Sh DESCRIPTION The Internet is a large and complex aggregation of network hardware, connected together by gateways. Tracking the route one's packets follow (or finding the miscreant gateway that's discarding your packets) can be difficult. (from traceroute(8)) .Pp .Nm was developed to automate a solution to the above, taking into account that modern networks contain many configurations of load balancers, proxies, and stateful firewalls. .Pp .Nm implements numerous network tracing methods and strategies. Generally, .Nm sends various types of layer-4 probes utilizing the IP protocol `time to live' field and attempts to elicit an .Tn ICMP `time exceeded in transit' response from each gateway along the path to some host. RFC 1393 Traceroute Using an IP Option is also available as one of several tracing methods. .Pp .Nm additionally listens for various messages along the way to assist network managers in ascertaining per-protocol heuristic routing information. .Nm can optionally retrieve various information about the networks it traverses using a variety of sources such as registries, routing arbiters, etc. .Pp The only mandatory parameter is the target host name or IP number. Options toggle the display of more interesting data or change the variables of the trace itself. The (-E/-e) adaptive option tries several combinations of TCP states (changing flags inside the probes it sends) in order to improve the chances of a successful trace and expose stateful packet filters. .Pp Other options are: .Bl -tag -width Ds .It Fl d Ar dport , Fl -dport Ar dport Set .Ar dport as the destination TCP port of the probes LFT generates. Default is 443. This option is useful to see if packets follow a different route based on protocol destination, a likely scenario when load balancers or proxies are involved. This option may also bypass less sophisticated packet filter configurations. .It Fl s Ar sport , Fl -sport Ar sport Set .Ar sport as the origin TCP port of every probe. This overrides the default per-hop source port and .Fl z , fixing one source port for the whole trace. By default, LFT selects source ports from the IANA dynamic port range (49152\(en65535). .It Fl z , Fl -random-sport Use one consistent random source port for the entire trace instead of the per-hop default. All TTLs then share a single 5-tuple, keeping the trace on one ECMP path. .Pp By default (neither .Fl s nor .Fl z ) , one-shot TCP traces use a distinct source port per hop, so each TTL is a separate 5-tuple. This prevents a stateful firewall from treating the trace as one flow and rate-limiting it; the trade is that hops may traverse different ECMP paths. Repeat probes to the same hop likewise each use a fresh source port, so a middlebox that answered the first probe cannot suppress the retries as duplicates of the same flow. In watch mode .Pq Fl W the source port is instead held constant within each cycle and rotated between cycles, preserving per-cycle ECMP consistency while still presenting a fresh flow each cycle. Per-hop variation applies to TCP only. .It Fl -ecmp Expose ECMP (equal-cost multi-path) load-balanced members. Many routers spread traffic across parallel links by hashing each flow's 5-tuple, so a single trace sees only one of several equal-cost next hops at a load-balanced hop. With .Fl -ecmp , .Nm varies the source port per probe and sends more probes per hop, so successive probes hash onto different paths and reveal the full set of members at each fork \(em the parallel next hops between a fork and its reconvergence form an ECMP .Dq diamond . Reply correlation is by sequence number, not port, so this is safe. Members are shown in every output mode; the GraphViz .Pq Fl g , SVG .Pq Fl -svg , and Mermaid .Pq Fl -mermaid graphs draw the diamond, and watch mode .Pq Fl W accumulates members across cycles and, with .Fl -ecmp , within each cycle as well. Off by default; TCP and UDP only. .It Fl 4 , Fl -ipv4 Force IPv4. By default the address family is matched to the target. .It Fl 6 , Fl -ipv6 Force IPv6. A bare IPv6 literal target, or the bracketed .Ar [address]:port form, also selects IPv6. .It Fl -expand-addr Show IPv6 addresses in the fully expanded eight-group form instead of the default RFC 5952 shorthand. Has no effect on IPv4. .It Fl m Ar min , Fl -min-probes Ar min Set .Ar min as the minimum number of probes to send per hop. Default is 1. .It Fl M Ar max , Fl -max-probes Ar max Set .Ar max as the maximum number of probes to send per hop. Default is 2. .It Fl a Ar ahead , Fl -ahead Ar ahead Set .Ar ahead as the number of hops forward to query before waiting for a response. Default is 5. .It Fl c Ar scatter ms , Fl -scatter Ar scatter ms Set .Ar scatter ms as the minimum number of milliseconds to wait between sending probes. Default is 20. .It Fl t Ar timeout ms , Fl -timeout Ar timeout ms Set .Ar timeout ms as the maximum number of milliseconds to wait before assuming a probe was lost/discarded. Default is 250. .It Fl l Ar min ttl , Fl -min-ttl Ar min ttl , Fl -first-hop Ar min ttl Set .Ar min ttl as the minimum TTL (time-to-live) on outgoing probes (essentially, the first hop in the line that you want to display). Default is 1. .It Fl q Ar ISN , Fl -isn Ar ISN Set .Ar ISN as the ISN (initial sequence number) of the first probe. If unset, one will be automatically generated using a pseudo-random, time-seeded algorithm. .It Fl w Ar window , Fl -tcp-window Ar window Set .Ar window as the TCP window size advertised in outgoing probe packets, in bytes. This option applies to TCP-based traces only. Default is 32768. .It Fl B , Fl -tcp-options , Fl -realistic Make TCP probes resemble connections from a real operating system rather than the bare, option-less SYNs .Nm sends by default. Some firewalls that normalize or scrub TCP (for example .Xr pf 4 with .Ic scrub ) , as well as certain next-generation and SYN-normalizing middleboxes, treat an option-less SYN as anomalous and silently drop or reset it, causing a trace to stall near the source even though the target is reachable from an ordinary client. .Fl B addresses this in two ways. .Pp First, each SYN and SYN-ACK probe carries a common client TCP option block, 20 bytes, laid out as a typical Linux client SYN and 4-byte aligned: .Bl -bullet -compact -offset indent .It Maximum Segment Size (MSS) of 1460; .It SACK-permitted; .It Timestamps (TSval set from the send time, TSecr zero on the SYN); .It a NOP for alignment; and .It Window Scale of 7. .El This makes the probe blend in with the SYNs a stateful firewall expects from legitimate clients. .Pp Second, for the default TCP engine .Fl B also applies .Em sequence realism . Without it, the initial sequence numbers (ISNs) of successive probes are the run's random base plus the probe index, so a rapid burst of SYNs to one target carries adjacent, monotonically increasing sequence numbers differing by one. A stateful firewall can read that pattern as retransmissions of a single flow rather than independent client connections, and rate-limit or drop it. With .Fl B , each probe's ISN is instead spread across the 32-bit sequence space by an invertible (bijective) mix, so the probes look like unrelated client connections. Because the mix is a bijection, every probe still receives a unique sequence number and reply correlation is unaffected: .Nm matches each reply to its probe by exact comparison against the sequence number echoed in the ICMP quotation (or the target's acknowledgement), not by any arithmetic relationship between sequence numbers. Sequence realism is applied only to the full 32-bit TCP sequence of the default engine; the 16-bit identifiers used for ICMP and UDP probe matching are never affected. .Pp .Fl B affects the default and adaptive .Pq Fl E TCP engines only; it is ignored for basic .Pq Fl b , UDP .Pq Fl u , and ICMP .Pq Fl p traces, and for non-SYN probes. The adaptive engine already randomizes each probe's sequence number, so sequence realism changes only the default engine. This behavior is off by default; enabling it does not change .Nm Ns 's output, only the packets on the wire. .It Fl L Ar length , Fl -length Ar length , Fl -bytes Ar length Set .Ar length as the size of probe packets in bytes. This includes layer-3 and layer-4 headers, but does not include layer-2 headers. For example, setting the length to 1500 would create a 1500-byte probe packet which would result in a 1514-byte frame on an Ethernet network. This option is silently ignored when .Fl K is active; probe size is managed automatically by PMTUD. .It Fl K , Fl -mtu , Fl -path-mtu Enable active Path MTU Discovery (PMTUD). When this flag is set, .Nm queries the local interface MTU via .Xr ioctl 2 .Dv SIOCGIFMTU and starts probing at that size (typically 1500 bytes for Ethernet, up to 9000 for jumbo frames). All probes carry the DF (Don't Fragment) bit, which is already .Nm Ns 's default. .Pp When a router cannot forward a probe because it exceeds an outbound link MTU, it returns an ICMP .Dq fragmentation needed message (type\ 3, code\ 4) containing the next-hop MTU. This class of message is colloquially called a .Dq PTB .Pq Packet Too Big , a term that strictly refers to ICMPv6 Type\ 2 but is widely used for both IPv4 and IPv6. .Nm records the MTU at the responding hop and reduces subsequent probe sizes accordingly. .Pp If a router silently drops an oversized DF probe without returning an ICMP message .Pq a PMTUD black hole , .Nm flags the hop as .Dq [MTU Limit] and steps down to the next RFC\ 1191 plateau value to continue probing beyond the black hole. .Pp PMTUD measures the forward-path MTU only; the return path may differ. Discovery stops at the first hop that limits the path MTU; hops beyond it see reduced-size probes that pass freely. .Pp .Sy Probe design and known limitations. .Nm implements PMTUD by inflating the payload of its existing TCP, UDP, and ICMP probes to the current MTU estimate. For TCP probes, strictly speaking, SYN, FIN, and RST segments should carry no payload; some deep-packet-inspection firewalls enforce this and will drop oversized probes before they reach a bottleneck link. UDP datagrams and ICMP echo requests may carry any payload size without protocol violation. This is a known constraint of the technique for TCP mode. .Pp An alternative approach \(em sending ICMP echo requests exclusively for MTU probing, as .Xr ping 8 does with .Fl M Ar do \(em was considered but rejected. ICMP traffic is routinely deprioritized in router control planes and discarded wholesale by enterprise firewalls, making it a less faithful representation of real traffic and no more likely to traverse a restricted path than a TCP probe. .Pp The payload-inflation approach is preferred because it exercises the same forwarding path as real application traffic. PTB messages are generated by routers at the IP layer, before any TCP inspection occurs, so the bottleneck is still correctly identified even when the probe carries an atypical payload. On the public Internet backbone \(em where MTU constraints most commonly arise \(em core routers perform IP-layer forwarding without TCP payload inspection, and the technique works reliably. Paths that traverse DPI firewalls configured to reject TCP-with-payload would equally reject any active PMTUD probing strategy; this is not a limitation specific to .Nm . .It Fl D Ar device , Fl -device Ar device , Fl -receive-device Ar device Set .Ar device as the network device or address to receive traffic. (e.g., "en1" or "1.2.3.4") If unset, .Nm will attempt to determine and acquire the appropriate interface based on routing. .It Fl f Ar device , Fl -from-device Ar device , Fl -source-device Ar device Set .Ar device as the network device or address to transmit traffic. (e.g., "en1" or "1.2.3.4") If unset, .Nm will attempt to determine and acquire the appropriate interface based on routing. This serves to operate .Nm in a passive mode where you may transmit from a (potentially) spoofed IP address on one interface, yet receive on another. This allows you to trace from a different IP address whose traffic you can see in order to intercept replies. .It Fl H Ar ttl , Fl -max-hops Ar ttl , Fl -ttl-max Ar ttl Set .Ar ttl as the maximum TTL, essentially the maximum route traversal distance in hops. Default is 30. .It Fl G Ar icons path , Fl -graphviz-icons Ar icons path Set .Ar icons path as the path to GraphViz icons in connection with GraphViz output. .It Fl I , Fl -minimize-delay Set the ToS (Type of Service) bit on outgoing IP datagrams. The ToS will be set to the differentiated services request minimize-delay. .It Fl i , Fl -ignore-unreachables Ignore ICMP unreachable messages; the trace continues through firewalls and other devices that send administratively-prohibited or host/net unreachable responses. By default, LFT stops the trace upon receiving any of the following ICMP unreachable codes: net unreachable (0), host unreachable (1), protocol unreachable (2), port unreachable (3), net administratively prohibited (9), host administratively prohibited (10), or communication administratively prohibited (13). This flag is implied by adaptive mode .Pq Fl E . .It Fl n , Fl -numeric Print addresses numerically rather than symbolically and numerically. Disables use of the DNS resolver completely. .It Fl h , Fl -hostnames-only Print addresses symbolically rather than symbolically and numerically. If the DNS resolver fails to resolve an address, the address is printed numerically. .It Fl E | Fl e , Fl -adaptive Enable use of the adaptive engine which tries several combinations of TCP states (changing flags inside the probes it sends) in order to improve the chances of a successful trace. The engine also displays other useful information such as stateful inspection firewalls or broken IP stacks encountered along the way. Adaptive mode automatically implies .Fl i .Pq Fl -ignore-unreachables , allowing the trace to continue through devices that send ICMP unreachable responses rather than halting. .It Fl F , Fl -fin , Fl -fins Enable use of TCP packets with the FIN flag set. This strategy fools unsophisticated packet filters that don't maintain a proper state table. Such devices will forward the packet to its destination rather than filter it, assuming a handshake has already taken place and the probes are part of an existing and valid TCP stream. .It Fl u , Fl -udp Enable use of UDP-based probes instead of TCP-based probes. Unlike the traditional traceroute method, which increments the UDP destination port with each TTL to identify replies, LFT sends all UDP probes to the same destination port and embeds a pseudorandom session-unique identifier in the IP .Em ID field of each probe. Replies are matched to probes via the .Em ID value echoed back in ICMP time-exceeded messages, keeping the five-tuple (source IP, destination IP, protocol, source port, destination port) constant across all TTLs. This ensures every probe follows the same path through ECMP load-balanced networks. Many of LFT's other options (such as source and destination port selection) are still available. By default, LFT's UDP probes carry a small payload (unlike TCP probes, which carry none). .It Fl N , Fl -netname Enable lookup and display of network or AS names (e.g., [GNTY-NETBLK-4]). This option queries Prefix WhoIs, RIPE NCC, or the RADB (as requested). In the case of Prefix WhoIs or RADB, the network name is displayed. In the case of RIPE NCC, the AS name is displayed. .It Fl P Enable RFC 1393 tracing method using ICMP and an IP option. RFC 1393 (Traceroute Using an IP Option), published in January 1993, defined an elegant alternative to the traditional Van Jacobson traceroute method: rather than relying on TTL expiry and ICMP Time Exceeded messages, a single outbound packet carries a Traceroute IP option (type 82) that instructs each router in the path to send an ICMP Traceroute message back to the originator. In theory, this requires only n+1 packets instead of the 2n packets used by traditional traceroute. .Pp In practice, no major router vendor \(em Cisco, Juniper, Nokia, Huawei, or any other \(em ever implemented RFC 1393 in their forwarding path. The mechanism suffered a chicken-and-egg problem: it required every router in the path to support the new IP option, whereas Van Jacobson's 1987 traceroute exploited TTL-expiry behavior already present in all routers and was universally deployed by the time RFC 1393 arrived. IP options processing also carries security risks, as options are handled in the router's slow path (control plane) and can be exploited for DoS amplification. Over time, ISPs and enterprises increasingly configured equipment to strip or drop packets carrying IP options entirely. .Pp RFC 1393 never advanced beyond Experimental status. It was formally deprecated by RFC 6814 in November 2012, which moved it to Historic. RFC 7126 (February 2014) further recommends that routers SHOULD drop packets containing the Traceroute IP option, noting zero operational impact from doing so. This option is retained in .Nm for completeness, but is unlikely to elicit responses from any production network equipment. .It Fl p , Fl -icmp Enable use of ICMP-based probes instead of TCP-based probes. This strategy is sometimes the fastest, however firewalls commonly filter ICMP at network borders. ICMP probes are echo request (ping) packets. .It Fl b , Fl -basic-tcp Enable TCP basic tracing method. Unlike the default method, the basic method generates TCP probes without relying upon sequence numbers being conveyed correctly. This makes LFT more comptabile with networks employing NAT, but is slower than the default method. TCP basic may also be used with adaptive mode (-E). .It Fl A , Fl -asn Enable lookup and display of AS (autonomous system) numbers (e.g., [1]). This option queries one of several whois servers (see options 'C' and 'r') in order to ascertain the origin ASN of the IP address in question. By default, LFT uses the pWhoIs service whose ASN data tends to be more accurate and more timely than using the RADB as it is derived from the Internet's global routing table. See www.pwhois.org .It Fl r , Fl -ripe-riswhois Force use of the RIPE NCC RIS whois service to lookup ASNs. This is an alternative source of timely ASN-related information built using the Internet's global routing table. See www.ripe.net/projects/ris .It Fl C , Fl -cymru Force use of the Cymru whois service to lookup ASNs. This is an alternative source of timely ASN-related information built using the Internet's global routing table. See www.cymru.com .It Fl R , Fl -radb Force use of the RADB whois service to lookup ASNs. This tends to be quick, but incomplete and usually inaccurate with regard to the 'actual' Internet routing table. See www.radb.net .It Fl T , Fl -time-keeping Enable display of LFT's execution timer. This option places timers on the trace itself and on lookups and name resolution to show where LFT is spending its time, waiting on resolvers, or processing trace packets. Use with -V (verbose) to display additional detail. .It Fl U , Fl -time-keeping-utc Display all times in UTC/GMT0. This option also enables the -T option automatically. .It Fl S , Fl -quiet , Fl -silent Suppress display of the real-time status bar. This option makes LFT show its completed trace output only, no-frills. .It Fl x , Fl -xml Enable XML output and suppress all other output to stdout. See .Sx OUTPUT FORMATS . .It Fl g , Fl -graphviz Enable GraphViz output and suppress all other output to stdout. .It Fl o , Fl -ascii Display the completed trace as a horizontal ASCII art hop diagram. Each hop occupies a column showing (top to bottom): the hop header, hostname, IP address, AS number (when .Fl A is used), network name (when .Fl N is used), round-trip time, and a feature annotation line for seam, firewall, or target-state information. .Pp In .Fl n mode the IP address appears on both the hostname and address lines for layout consistency. In .Fl h mode the IP address line is suppressed; TTL information for cloaked hops moves to the AS number line so it shares a row with AS data from other hops rather than occupying a mostly-blank line of its own. .Pp Cloaked hops, seam boundaries, firewall anomalies, and target state are each marked with a distinct feature label. In Unicode mode the labels are: .Sq \&\[u21F6] Stateful FW (stateful packet-inspecting firewall), .Sq \&\[u2691] Flag FW (flag-based packet filter), .Sq \&\[u21C4] BSD Bug (errant TTL reuse anomaly), .Sq \&\[u2573] ASN Seam (autonomous system boundary crossing), .Sq \&\[u2573] NET Seam (network boundary crossing), .Sq \&\[u2591]cloaked\[u2591] (no-reply hop), .Sq [OPEN] , .Sq [FILTERED] , and .Sq [CLOSED] (target port state). Plain ASCII equivalents are used on terminals that do not support Unicode. .Pp LFT automatically enables Unicode box-drawing characters and ANSI color when the terminal supports them (detected via .Ev TERM , .Ev COLORTERM , and .Xr isatty 3 ) . Set .Ev NO_COLOR to suppress color output. When the path is too wide for the terminal, the diagram wraps onto additional rows. .It Fl -svg Emit a self-contained styled SVG diagram of the completed trace to stdout and suppress all other output. The diagram is drawn top-to-bottom, one card per hop, using the same device iconography and classification as GraphViz output .Pq Fl g : source, routers, autonomous-system and network seams, stateful and flag-based firewalls, cloaked no-reply hops, and the open, closed, or filtered target. Each card shows the hostname, address, AS number .Pq Fl A , network name .Pq Fl N , round-trip time, and a feature annotation. The device glyphs are embedded in the document, so the output needs no external icon files and renders in any SVG viewer or web browser. .It Fl -mermaid Emit a styled Mermaid .Ql flowchart TB definition of the completed trace to stdout and suppress all other output. Each hop becomes a node whose shape reflects its role \(em stadium for the source and target, hexagon for a seam boundary, framed box for a firewall, rectangle for a router \(em and whose color is assigned by a per-type .Ql classDef . The definition renders directly on platforms with native Mermaid support, such as GitHub, or in any Mermaid live editor. .It Fl -json Print the trace as a single compact JSON document instead of the text report: run metadata, source, target, result, and one object per TTL with its responders, ASN, network name, RTT statistics, and any boundary or firewall classification. Silent TTLs and ECMP hops are represented explicitly. See .Sx OUTPUT FORMATS . .It Fl -json-pretty The same document, indented. .It Fl -geojson Emit the trace as a GeoJSON FeatureCollection for mapping tools: a point for every located responder (source, hops, ECMP members, target) with the .Fl -json hop fields plus city, region and country, and a great-circle segment between each pair of located hops carrying distance, RTT delta, AS change and any silent or unlocated TTLs it spans. A hop whose RTT is below the speed-of-light floor for its distance from the source is kept but flagged as implausibly located. Silent and unlocated hops are listed rather than dropped. Geolocation comes from pWhoIs, so .Fl A is implied; IPv6 hops are not located. See .Sx OUTPUT FORMATS . .It Fl -kml The same trace as a KML document (OGC KML 2.2): labelled placemarks with an attribute table per hop, the path drawn as great-circle arches scaled to hop distance, and a view framed on the route. Implausible locations are drawn hollow with a faded segment. See .Sx OUTPUT FORMATS . .It Fl -geo-color Ar RRGGBB | rtt Path color for .Fl -geojson and .Fl -kml as six hex digits (default 17b890), or .Ar rtt to color each segment by latency, green through red. .It Fl -geo-width Ar N Path line width in pixels for the geographic outputs (1-20, default 3). .It Fl -geo-arc Ar F KML arch height factor (0-5, default 1.0); 0 draws the path flat on the ground. .It Fl -show-seams Show autonomous-system and network seam boundaries: the hops where the trace crosses from one AS or network into the next. Visible in the standard text and XML .Pq Fl x output, the GraphViz .Pq Fl g , SVG .Pq Fl -svg , Mermaid .Pq Fl -mermaid , and ASCII .Pq Fl o diagrams, and in watch mode .Pq Fl W . Not displayed by default; this option reveals them. Seam verification .Pq Fl y enables it automatically. In watch mode the .Ic s key toggles seams on and off at any time. .It Fl -color Force ANSI color .Pq Fl o even when standard output is not a terminal. By default color is applied automatically only when writing to an interactive terminal (never to a file or a pipe). .It Fl -no-color Disable ANSI color .Pq Fl o , overriding the automatic detection. .It Fl j Ar N , Fl -stats Ar N Collect .Ar N round-trip time samples per hop and display a candlestick RTT chart above the hop diagram (implies .Fl o ) . Each hop column renders a box-and-whisker bar: the solid block marks the average RTT; the top cap (\[u252C]) and bottom cap (\[u2534]) mark the maximum and minimum RTTs; the thin shaft (\[u2502]) between them shows the spread. Hops with no reply are rendered as a full-width column of shade blocks (\[u2591]). A Y-axis with labeled RTT ticks appears at the left edge; the X-axis footer shows the end-to-end RTT range derived from the target hop's own min/max samples and the destination in .Ar proto\[u200B]://\[u200B]address\[u200B]:\[u200B]port format (e.g.\& .Sq End-to-End RTT 15\[u2013]81ms, 6 hops to tcp://192.0.2.40:443 ) . If the target was not reached the footer reads .Sq N hops traced, target unreachable . Columns with a jitter spread exceeding 25\~ms are highlighted in yellow with a .Sq \&\[u26A1] Nms \[u0394] annotation. .It Fl -no-ascii-chart , Fl -no-async-chart Suppress the candlestick RTT chart produced by .Fl j while still collecting and displaying per-hop RTT statistics. Useful when the terminal is too narrow for the chart, when output is piped to another tool, or when only the numerical summary (min/avg/max \(+-jitter) is needed. Has no effect unless .Fl j .Ar N is also specified. .It Fl W Ar N , Fl -watch Ns Op = Ns Ar N , Fl -continuous Ns Op = Ns Ar N Enable continuous monitoring mode, repeating the trace every .Ar N seconds (default:\~10). LFT runs in an interactive full-screen .Xr ncurses 3 display that shows per-hop RTT history, loss percentages, and network annotation (ASN, network name, organisation) in real time. .Pp Subsequent cycles auto-tune the probe range and timeouts from the previously learned path; see .Sy Topology inheritance below. .Pp .Sy View\ 1 (default) shows the .Fl o candlestick chart on the upper half of the screen with a rolling statistics table below it. .Sy View\ 2 replaces the chart with proportional RTT bar columns. .Sy View\ 3 is a full-height statistics table that shows more hops simultaneously. Switch views with .Sy 1 , .Sy 2 , or .Sy 3 . .Pp The statistics table columns are: .Bl -tag -width "NETNAME" -compact .It HOP TTL value. .It HOST IP address of the responding router (or PTR hostname when .Fl h is active; see below). .It LOSS% Packet loss percentage across all cycles observed. .It MIN / AVG / MAX Minimum, mean, and maximum round-trip time in milliseconds. .It JIT Jitter (max \- min over all cycles). .It ASN Autonomous system number from pWhoIs (e.g.\& 7922). Private and special-purpose addresses (RFC\~1918, RFC\~6598, loopback, link-local, etc.\&) are labelled with their IANA RFC tag (e.g.\& .Sq RFC1918 ) instead of a pWhoIs lookup. .It NETNAME Network name from pWhoIs (e.g.\& COMCAST-7922). .It ORGNAME Organisation name from pWhoIs (e.g.\& Comcast Cable). .It Sparkline Per-hop RTT history plotted as Unicode block characters, oldest sample on the left and newest on the right. Green indicates RTT within 20% of the hop average; yellow between 20% and 50% above average; magenta more than 50% above average; red indicates a lost probe. .El .Pp Intermediate hops that do not respond are shown as .Sq * in the HOST column. .Pp If .Fl j is not specified, each cycle collects 3 RTT samples per hop. .Pp During each trace a live counter line is displayed showing .Pq in order : replies received .Pq \[u2193]N , probes sent .Pq \[u2191]N , unique responding hops .Pq \[u2B21]\~N , and target reached status .Pq \[u2714] or \[u2717] . Before ncurses initialises this line appears on the terminal directly; once ncurses is running it occupies the bottom status row of every view. .Pp Keyboard controls: .Bl -tag -width "[+/-]" -compact .It Ic 1 \(en 3 Switch between the three display views. .It Ic p Pause or resume trace cycling. While paused, the status bar shows .Dq Paused... and no new traces run until .Ic p is pressed again. .It Ic r Reset all accumulated statistics (RTT history, loss counters, cycle count) and restart from a clean baseline. .It Ic c Clear all statistics, flush the DNS and pWhoIs cache, and redraw. This forces fresh lookups on the next cycle. .It Ic w Toggle pWhoIs annotation columns (ASN / NETNAME / ORGNAME). Annotation is enabled by default; a bulk pWhoIs query runs at startup and is refreshed as new hop addresses are discovered across cycles. Pressing this key shows or hides the annotation columns without discarding the cached data. .It Ic h Toggle hostname display: when on, the HOST column shows the PTR reverse-DNS name for each hop rather than its IP address. .It Ic s Toggle display of autonomous-system and network seam markers on the boundary hops. Off by default (see .Fl -show-seams ) . .It Ic v Open or close the log drawer, a panel at the bottom of the screen that shows the raw LFT verbose output captured during each trace cycle. .It Ic x Save the current screen to a file. LFT prompts for a filename (pre-filled with a timestamp) and writes both a plain-text .Pa .txt version and an ANSI-color .Pa .ansi version to the current directory. Borders are written as UTF-8 box-drawing characters (ASCII in a non-UTF-8 locale), including under .Xr tmux 1 and .Xr screen 1 . .It Ic + / Ic \- Increase or decrease the interval between cycles by one second. This also cancels any automatic back-off (see .Fl -watch-backoff ) . .It Ic q Quit watch mode and exit. .El .Pp Path changes are detected automatically with a heuristic designed to alert only on genuine reroutes. During an initial learning period (until the path length has been stable for three cycles, or eight cycles at most) all observations are absorbed silently. Once armed, a changed hop address must persist for two observed cycles before it is announced (2-cycle hysteresis); the confirmed change is highlighted in the status bar for three seconds and logged to the log drawer. A change that reverts after a single cycle is recorded in the log drawer as a .Sy flap , and a hop alternating among addresses previously seen at the same TTL is recorded as an .Sy ECMP shift (multipath load-balancing, limited to three log lines per hop) \(em neither raises the status-bar alert. Path length changes with no mid-path substitution are likewise noted only in the log drawer. Cloaked cycles (a hop temporarily showing .Sq * ) neither advance nor cancel a pending change. The .Ic c key clears the learned path state and re-enters the learning period. .Pp For multi-cycle traces, watch mode preserves the per-protocol ECMP flow-identifier across cycles so every cycle traverses the same ECMP branch on multi-path networks: TCP source port, UDP IP-ID base, and the ICMP sequence-number anchor are all held constant for the life of the watch session. See .Xr lft 8 notes on ECMP flow-hash stability for the underlying mechanism. .Pp .Sy Topology inheritance .Pp After the first cycle, watch mode learns the path length and RTT envelope and uses them to accelerate subsequent cycles. Once the same number of hops has been observed for three consecutive cycles, the TTL probe range narrows to .Sy learned_num_hops + .Fl -learn-margin , the in-flight probe pipeline widens to the full known path width (capped at 16), and the cloaked-hop timeout drops to .Sy max(8 \(mu max_observed_rtt, 200 ms) . A cycle that misses the target or a change in probe protocol resets the learned state, reverting to broad-scan defaults until the path stabilizes again. .Bl -tag -width "--learn-margin" .It Fl -watch-backoff Lengthen the watch interval automatically when loss points to rate limiting. Routers and targets throttle ICMP and SYN replies to a source that keeps probing, so loss at established hops climbs during a long watch even though the path is healthy. When a hop's recent loss reaches 50% while its lifetime loss is far lower, watch mode notes it on the status line; with this option it also doubles the interval (up to eight times the chosen value, capped at 300 seconds) and relaxes it again once loss clears. The .Ic + and .Ic \- keys reset the back-off. .It Fl -no-learn Disable watch-mode topology inheritance. Every cycle probes the full default TTL ladder with the conventional ahead-limit and timeout. .It Fl -learn-margin Ar N TTL probe margin above the learned path length (default\~2; clamped 0\~..\~10). A larger margin notices path lengthening sooner at the cost of probing empty TTLs. .El .Pp This option requires ncurses support compiled into LFT. If ncurses was not detected at build time, .Fl -watch prints an explanatory error and exits. The minimum supported terminal height is 24 rows. Superuser privileges (via .Xr sudo 8 or a setuid-root installation) are required. .It Fl y , Fl -verify-seam Verify the detected network seam (the autonomous-system or network boundary) by re-probing the boundary hop. Implies .Fl -show-seams , so the verified seam is displayed. .It Fl V , Fl -verbose Display verbose output. Use more V's for more info. .It Fl v , Fl -version Display version information, then exit(1). .It Fl Q , Fl -no-async-dns Disable asynchronous DNS resolution. When LFT is built with c-ares support (the default when the library is present at compile time), it transmits a reverse-DNS (PTR) query for each IP address revealed by an incoming ICMP time-exceeded response contemporaneously with the remaining probe traffic. By the time the trace completes, most or all hostnames have already been resolved, so lookups incur little or no additional time. The .Fl Q flag reverts to the traditional synchronous .Xr getnameinfo 3 path, which resolves each hop in sequence after the trace completes. This flag is useful for debugging DNS issues or for environments where parallel DNS queries are undesirable. If LFT was built without c-ares, this flag is accepted but has no effect. .It Fl -help Display a brief usage summary and exit. Long-form option equivalents (e.g., .Fl -dport , .Fl -min-probes , .Fl -ignore-unreachables ) are available for all short options and are documented in this manual page alongside their short counterparts. .El .Pp Any hosts listed after these options and before the final target host will comprise a Loose Source and Record Route .Pq LSRR . .Nm encodes the specified intermediate addresses into the IP options field of every probe, using option type 131 as defined in RFC\ 791 (the original IPv4 specification). .Pp .Sy LSRR on the modern Internet. The original purpose of source routing was to allow a sender to specify the path a packet should take through the network, useful in early research networks for debugging routing policy and deliberately routing around link failures. However, source routing can be exploited to bypass access controls, conduct reflective attacks, and probe otherwise-isolated network segments. As a consequence, virtually all ISPs and enterprise network operators block or silently strip packets carrying source route options at network ingress. RFC\ 7126 (February 2014) formally codified this practice, recommending that routers .Sy SHOULD drop packets containing LSRR or SSRR IP options, and noting zero operational impact from doing so because no production traffic legitimately uses them. .Pp .Sy Why LSRR is retained in .Nm Ns . The feature remains for controlled network environments where intermediate devices are operator-managed and IP options processing is known to be enabled: lab networks, private AS testing, research testbeds, and similar settings. In such environments, specifying intermediate waypoints can verify specific forwarding paths that are otherwise difficult to isolate. On any public Internet path it will produce no additional hops in the output. .Pp .Sy Caution: accidental LSRR activation. Because .Fl p .Pq ICMP probe mode takes no argument, any extra token appearing between .Fl p and the target is silently parsed as an LSRR intermediate hop rather than being flagged as an error. For example, .Bd -literal -offset indent lft -p 192.168.1.1 target.example.com .Ed .Pp would add 192.168.1.1 as a source route waypoint and embed a 12-byte LSRR IP option in every probe, almost certainly producing no responses from any modern router. The trace will appear to run normally but all hops will be cloaked. Verify command syntax carefully when using ICMP mode. .Sh OUTPUT FORMATS The machine-readable styles share one rule: within a schema version, keys are never removed or renamed and enumerations only gain values; new keys may be added. Numbers use a period decimal separator regardless of locale; RTTs are milliseconds; IPv6 addresses are RFC 5952 shorthand except in XML, where they are always the fully expanded form. .Ss XML (-x) A stream of self-closing .Ql elements inside a .Ql root, one per report event, each with a numeric .Ar code attribute; parse by code, not position. The root carries .Ar schema (lft-xml), .Ar schema_version (1) and .Ar verbositylevel . Warnings and errors appear as .Ql text and .Ql text . Events of the default report: .Bl -tag -width "code 16" -compact .It code 55 .Ar begtime \(em trace start time. .It code 1 .Ar sport , dport \(em autoconfigured ports. .It code 2 .Ar protocol , sport , rndsport \(em address initialised; rndsport=1 when the source port was random. .It code 11 .Ar ip , protocol , protocolname , dport | dportrange , targetanomaly \(em report header for the target. .It code 12 .Ar firstholehop , lastholehop \(em a run of TTLs that never answered. .It code 13 \(em the next gateway may statefully inspect packets. .It code 14 \(em the next gateway may use a flag-based state filter. .It code 15 \(em the 4.2-3 BSD bug heuristic fired. .It code 16 .Ar index , host , ip , asn , net , timems , timems_min , timems_avg , timems_max , icmpcode , icmpmsg , trgstate , port , pathmtu , asseam , netseam \(em one responding hop (see below). .It code 19 \(em no hops answered. .It code 20 .Ar finishtime , elapsed , tracingtime , resolvingtime , asyncdns \(em timing summary in seconds. .It code 8 .Ar numhops \(em hop count. .It code 21 \(em end of report. .El .Pp Hop attributes (code 16): .Bl -tag -width "timems_min" -compact .It index 1-based TTL. .It host Resolved name, when known. .It ip Numeric address (IPv6 fully expanded). .It asn Origin AS number, 0 when unknown. .It net Network name, or NULL. .It timems Round-trip time; with several samples .Ar timems_min , timems_avg , timems_max follow. .It icmpcode , icmpmsg ICMP type and its label when the reply was not a TTL-exceeded. .It trgstate open | closed | filtered \(em present only on the target hop. .It port Target port, on the target hop. .It pathmtu Path MTU discovered with .Fl K . .It asseam , netseam 1 when this hop is an AS or network boundary. .El Progress events (codes 23, 40, 42, 58) carry no attributes and may be ignored. .Ss JSON (--json, --json-pretty) One document; the same content compact or indented. .Bd -literal -offset indent { "schema": "lft-trace", "schema_version": 1, "generator": { "name", "version" }, "run": { "started_at", "ip_version", "protocol", "ttl_min", "ttl_max", "timing": { "total_ms", "tracing_ms", "resolving_ms" }, "options": { "ecmp_discovery", "adaptive_tcp", "resolve_names", "asn_lookup", "netname_lookup" } }, "source": { "addr", "port" }, "target": { "input", "addr", "port" }, "result": { "state", "ttl" }, "hops": [ { "ttl", "kind", "boundary", "firewall", "responders": [ { "addr", "hostname", "asn", "netname", "special_use", "rtt_ms": { "count", "min", "avg", "max", "stddev" } } ] } ] } .Ed .Pp Every key is always present; a value that does not apply is .Ql null . .Bl -tag -width "schema_version" -compact .It schema , schema_version Format identifier and version (this document: lft-trace, 1). .It generator.name , generator.version Producing program and its version. .It run.started_at UTC start time, ISO 8601 with milliseconds. .It run.ip_version 4 or 6. .It run.protocol tcp | udp | icmp | icmp_rfc1393. .It run.ttl_min , run.ttl_max First and last TTL reported; hops covers exactly this range. .It run.timing.total_ms Wall time; never less than tracing_ms + resolving_ms. .It run.timing.tracing_ms , run.timing.resolving_ms Time probing, and time resolving names/ASNs afterwards. .It run.options.ecmp_discovery .Fl -ecmp was on. .It run.options.adaptive_tcp .Fl E was on. .It run.options.resolve_names Hostnames were looked up (false makes every hostname null). .It run.options.asn_lookup , run.options.netname_lookup .Fl A , .Fl N were on (false makes asn / netname null). .It source.addr , source.port Local address and source port; port is null for ICMP. .It target.input The target as given on the command line. .It target.addr , target.port Resolved target address and destination port (null for ICMP). .It result.state open | closed | filtered | replied | unreached. .It result.ttl TTL at which the target answered; null when unreached. .It hops[].ttl 1-based TTL; hops are contiguous from ttl_min to ttl_max. .It hops[].kind router | firewall | target | silent. .It hops[].boundary asn | network | null \(em the trace crossed into a new AS or network here. .It hops[].firewall stateful | flag | bsd_bug | null. .It hops[].responders Every distinct address that answered at this TTL; empty for silent, several for ECMP. .It responders[].addr Numeric address. .It responders[].hostname Resolved name, or null. .It responders[].asn Origin AS number, or null. .It responders[].netname Network name, or null. .It responders[].special_use IANA special-purpose token (e.g. private_use, shared_address_space), or null. .It responders[].rtt_ms.count Samples measured. .It responders[].rtt_ms.min , avg , max Round-trip statistics over the samples. .It responders[].rtt_ms.stddev Population standard deviation; null when count is less than 2. .El .Ss GeoJSON (--geojson) An RFC 7946 FeatureCollection. Coordinates are [longitude, latitude] with six decimals. .Bd -literal -offset indent { "type": "FeatureCollection", "title", "lft": { "generator", "target", "target_addr", "protocol", "hops_responding", "located_points", "path_km", "silent_ttls", "unlocated": [ { "ttl", "addr", "reason" } ], "plausibility" }, "bbox": [ west, south, east, north ], "features": [ Point features..., segment features... ] } .Ed .Pp Collection members: .Bl -tag -width "located_points" -compact .It title Human title for viewers that show one. .It lft.generator , lft.target , lft.target_addr , lft.protocol As in the JSON run metadata. .It lft.hops_responding TTLs that answered, located or not. .It lft.located_points Number of Point features. .It lft.path_km Great-circle length of the drawn path; null with fewer than two points. .It lft.silent_ttls TTLs that never answered, as a range list such as 3,5-7; or null. .It lft.unlocated[] Responders without a location: ttl, addr, and reason (an IANA token, or no_geo). .It lft.plausibility One sentence stating the speed-of-light rule used below. .It bbox Bounding box over all points and path samples. .El .Pp Point features have id .Ql source , .Ql hop-T or .Ql hop-T-M (ECMP member M) and these properties, all always present: .Bl -tag -width "dist_from_source_km" -compact .It kind source | router | firewall | target. .It ttl TTL (0 for the source). .It addr , hostname , asn , netname , special_use , boundary , firewall As in the JSON responder and hop. .It target_state open | closed | filtered | replied on the target; else null. .It port Destination port on the target; else null. .It city , region , country , country_code Geolocation parts, or null. .It place The parts joined for display, e.g. "Virginia Beach, Virginia, United States of America (US)"; or null. .It rtt_count , rtt_min_ms , rtt_avg_ms , rtt_max_ms , rtt_stddev_ms As rtt_ms in JSON (stddev null under 2 samples; all null for the source). .It dist_from_source_km Great-circle distance from the located source; null when the source is not located. .It light_floor_ms Minimum possible round trip for that distance (100 km per ms). .It geo_plausible false when rtt_min_ms is below light_floor_ms. .It geo_note Explanation when geo_plausible is false; else null. .It prev_located_ttl TTL of the previous located hop (0 = source); null for the source. .It skipped_ttls Silent TTLs between the previous located hop and this one, or null. .It unlocated_between Responding but unlocated hops between the previous located hop and this one. .It ecmp_index , ecmp_members This responder's position and the member count at an ECMP hop; else null. .It display_offset , true_coordinates true and the real [lon, lat] when a coincident marker was nudged; else false, null. .It title , description One-line label and a longer summary for viewer popups. .It marker-color , marker-size , marker-symbol simplestyle hints; implausible hops are grey with a hollow symbol. .El .Pp Segment features (id .Ql seg-A-B ) join consecutive located path hops with a MultiLineString along the great circle, split at the antimeridian. Properties: .Bl -tag -width "unlocated_between" -compact .It kind Always segment. .It from_ttl , to_ttl , from_addr , to_addr , from_city , to_city , from_asn , to_asn The two endpoints. .It as_change true when the endpoints are in different ASes. .It skipped_ttls , unlocated_between Silent and unlocated TTLs this segment spans. .It distance_km Great-circle length. .It rtt_from_ms , rtt_to_ms , rtt_delta_ms Average RTT at each end and the difference. .It light_floor_ms Minimum possible round trip for distance_km. .It suspect true when either endpoint is implausibly located. .It ecmp_members Member count at the far end, or null. .It title One-line label. .It stroke , stroke-width , stroke-opacity simplestyle line hints (from .Fl -geo-color and .Fl -geo-width ; suspect segments are faded). .El .Ss KML (--kml) A KML 2.2 Document containing a LookAt framing the route, two Schemas, one Style per hop kind, and three Folders. .Bl -tag -width "Unlocated hops" -compact .It Hops One Placemark per located responder (ids as in GeoJSON) with a short name (TTL, city, RTT), a Snippet (hostname and ASN), an HTML description and SchemaData for the hop Schema. .It Path One Placemark per segment (ids seg-A-B) with an inline LineStyle, SchemaData for the segment Schema, and a LineString of great-circle samples; altitude is an arch peaking mid-segment (relativeToGround), or 0 with clampToGround when .Fl -geo-arc is 0. .It Unlocated hops Placemarks without geometry for responders that could not be located. .El .Pp The hop Schema fields are ttl, addr, hostname, asn, netname, special_use, kind, boundary, firewall, target_state, city, region, country, country_code, place, rtt_count, rtt_min_ms, rtt_avg_ms, rtt_max_ms, rtt_stddev_ms, dist_from_source_km, light_floor_ms, geo_plausible, prev_located_ttl, skipped_ttls, unlocated_between, ecmp_index, ecmp_members; the segment Schema fields are from_ttl, to_ttl, from_addr, to_addr, from_city, to_city, from_asn, to_asn, as_change, skipped_ttls, unlocated_between, distance_km, rtt_from_ms, rtt_to_ms, rtt_delta_ms, light_floor_ms, suspect. Meanings are as in GeoJSON; a field that does not apply is omitted rather than empty. Style ids: source, router, boundary, firewall, topen, tclosed, tfiltered, suspect. .Sh EXAMPLES A sample use and output might be: .Bd -literal [edge.lax]$ lft -S 4.2.2.2 TTL LFT trace to vnsc-bak.sys.gtei.net (4.2.2.2):80/tcp 1 ln-gateway.centergate.com (206.117.161.1) 0.5ms 2 isi-acg.ln.net (130.152.136.1) 2.3ms 3 isi-1-lngw2-atm.ln.net (130.152.180.21) 2.5ms 4 gigabitethernet5-0.lsanca1-cr3.bbnplanet.net (4.24.4.249) 3.0ms 5 p6-0.lsanca1-cr6.bbnplanet.net (4.24.4.2) 3.4ms 6 p6-0.lsanca2-br1.bbnplanet.net (4.24.5.49) 3.3ms 7 p15-0.snjpca1-br1.bbnplanet.net (4.24.5.58) 10.9ms 8 so-3-0-0.mtvwca1-br1.bbnplanet.net (4.24.7.33) 11.1ms 9 p7-0.mtvwca1-dc-dbe1.bbnplanet.net (4.24.9.166) 11.0ms 10 vlan40.mtvwca1-dc1-dfa1-rc1.bbnplanet.net (128.11.193.67) 11.1ms ** [neglected] no reply packets received from TTLs 11 through 20 ** [4.2-3 BSD bug?] the next gateway may errantly reply with reused TTLs 21 [target open] vnsc-bak.sys.gtei.net (4.2.2.2):80 11.2ms .Ed .Pp The (-S) option was used to suppress the real-time status bar for clean output. LFT's "**" notifiers in between hops 10 and 21 represent additional useful information: the first is a "[neglected]" indicator that lets us know that none of the probes sent with the TTLs indicated elicited responses. This could be for a variety of reasons, but the cause of this specific occurrence is described in the next informative message which indicates that this is likely the result of a bug in the 4.[23] .Tn BSD network code (and its derivatives): BSD 4.x (x < 3) sends an unreachable message using whatever TTL remains in the original datagram. Since, for gateways, the remaining TTL is zero, the .Tn ICMP "time exceeded" is guaranteed to not make it back to us. LFT does its best to identify this condition rather than print lots and lots of hops that don't exist (trying to reach a high enough TTL). .Pp Now, using the adaptive engine option: .Bd -literal [host]$ lft -S -E eggs.gnu.org:587 TTL LFT trace to eggs.gnu.org (209.51.188.92):587/tcp 1 192.0.2.1 0/1/1 \(+-0ms ** [neglected] no reply packets received from TTLs 2 through 4 5 nyiix-px.jfk01.twdx.net (198.32.160.208) 2/2/2 \(+-0ms 6 bbr02-ae-4-901.bos01.twdx.net (198.160.63.126) 7/7/7 \(+-0ms 7 dcr03-hu-0-8-0-0.bsn04.twdx.net (198.160.62.201) 7/7/7 \(+-0ms ** [firewall] the next gateway may statefully inspect packets 8 mass-ix.fsf.org (206.53.143.61) 7/7/7 \(+-0ms 9 [target open] eggs.gnu.org (209.51.188.92):587 7.4ms .Ed .Pp In the scenario above, the adaptive engine identified a stateful, packet-inspecting firewall in the path, then continued and reached the target. .Pp Adaptive mode reports a second, related condition. When the gateway just before the target answers only after the probe flags are varied \(em revealing a device that admits traffic based on TCP state \(em .Nm notes a flag-based state filter: .Bd -literal [host]$ lft -S -E mit.edu:443 TTL LFT trace to a23-50-69-136.deploy.static.akamaitechnologies.com (23.50.69.136):443/tcp 1 192.0.2.1 0/0/0 \(+-0ms ** [neglected] no reply packets received from TTLs 2 through 4 5 198.32.160.147 2/2/2 \(+-0ms 6 192.168.225.129 2/2/2 \(+-0ms 7 192.168.238.169 2/2/2 \(+-0ms ** [firewall] the next gateway may implement a flag-based state filter 8 192.168.194.131 2/2/2 \(+-0ms 9 [target closed] a23-50-69-136.deploy.static.akamaitechnologies.com (23.50.69.136):443 12.0ms .Ed .Pp A hop with more than one RTT sample is summarized as min/avg/max integer milliseconds followed by the standard deviation \(em for example .Sq 12/17/20 \(+-2ms \(em while a single-sample hop shows the one decimal value. On terminals without UTF-8 support the spread is marked .Sq ~ instead of .Sq \(+- . .Pp Another example with more options: .Bd -literal [edge.lax]$ lft -S -A -T -d 80 -s 53 www.yahoo.com TTL LFT trace to w9.scd.yahoo.com (66.218.71.88):80/tcp 1 [226] ln-gateway.centergate.com (206.117.161.1) 1.0ms 2 [226] isi-acg.ln.net (130.152.136.1) 2.0ms 3 [226] isi-1-lngw2-atm.ln.net (130.152.180.21) 3.0ms 4 [1] gigether5-0.lsanca1-cr3.bbnplanet.net (4.24.4.249) 3.0ms 5 [1] p6-0.lsanca1-cr6.bbnplanet.net (4.24.4.2) 5.0ms 6 [1] p6-0.lsanca2-br1.bbnplanet.net (4.24.5.49) 3.0ms 7 [1] p1-0.lsanca2-cr2.bbnplanet.net (4.25.112.1) 3.0ms 8 [16852] pos4-0.core1.LosAngeles1.Level3.net (209.0.227.57) 3.0ms 9 [3356] so-4-0-0.mp1.LosAngeles1.Level3.net (209.247.10.193) 3.0ms 10 [3356] so-3-0-0.mp2.SanJose1.Level3.net (64.159.1.130) 11.0ms 11 [3356] gige10-0.ipcolo4.SanJose1.Level3.net (64.159.2.42) 11.0ms 12 [3356] cust-int.level3.net (64.152.81.62) 52.0ms 13 [10310] vl17.bas2.scd.yahoo.com (66.218.64.150) 53.0ms 14 [10310] [target open] w9.scd.yahoo.com (66.218.71.88):80 54.0ms .Ed .Pp Note the -Ar above displays ASNs using the RADB as a whois source. A better option may have been to use the -A alone or perhaps -AC. .Pp And why not request netblock lookups? .Bd -literal [edge.lax]$ lft -S -N www.microsoft.com TTL LFT trace to www.us.microsoft.com (207.46.197.113):80/tcp 1 [LOS-NETTOS-BLK4] ln-gateway.centergate.com (206.117.161.1) 2.0ms 2 [LOS-NETTOS] isi-acg.ln.net (130.152.136.1) 3.0ms 3 [LOS-NETTOS] isi-1-lngw2-pos.ln.net (130.152.80.30) 5.0ms 4 [GNTY-4-0] gigether5-0.lsanca1-cr3.bbnplanet.net (4.24.4.249) 4.0ms 5 [GNTY-4-0] p6-0.lsanca1-cr6.bbnplanet.net (4.24.4.2) 3.0ms 6 [GNTY-4-0] p6-0.lsanca2-br1.bbnplanet.net (4.24.5.49) 3.0ms 7 [GNTY-4-0] p15-0.snjpca1-br1.bbnplanet.net (4.24.5.58) 10.0ms 8 [GNTY-4-0] p9-0.snjpca1-br2.bbnplanet.net (4.24.9.130) 11.0ms 9 [GNTY-4-0] so-1-0-0.sttlwa2-br1.bbnplanet.net (4.0.3.229) 27.0ms 10 [GNTY-4-0] so-0-0-0.sttlwa1-hcr1.bbnplanet.net (4.24.11.202) 28.0ms 11 [GNTY-4-0] so-7-0-0.sttlwa1-hcr2.bbnplanet.net (4.24.10.234) 28.0ms 12 [GNTY-4-0] p1-0.sttlwa1-cr2.bbnplanet.net (4.24.10.241) 29.0ms 13 [GNTY-4-0] p2-0.msseattle.bbnplanet.net (4.25.89.6) 32.0ms 14 [MICROSOFT-GLOBAL-NET] 207.46.154.9 32.0ms 15 [MICROSOFT-GLOBAL-NET] 207.46.155.17 33.0ms 16 [MICROSOFT-GLOBAL-NET] [target filtered] 207.46.129.51:80 35.0ms .Ed .Pp For a quick view of path quality, request a minimum of three probes per hop .Pq Fl m 3 . Each replying hop is summarized as min/avg/max \(+-stddev, each probe uses a fresh source port so rate-limiting middleboxes answer every attempt, and hops that load-balance across parallel links may resolve into multiple responders: .Bd -literal [edge.lax]$ lft -S -m 3 -n www.yahoo.com TTL LFT trace to 209.73.190.12:443/tcp 1 192.0.2.1 0/0/0 \(+-0ms 2 198.51.100.10 1/1/1 \(+-0ms ** [neglected] no reply packets received from TTL 3 4 184.104.193.142 10/11/12 \(+-1ms ** [neglected] no reply packets received from TTL 5 6 74.6.226.215 8.2ms 74.6.226.195 8.4ms 209.73.188.55 8.5ms 7 209.73.188.49 8.2ms 209.73.188.73 8.3ms ** [neglected] no reply packets received from TTLs 8 through 11 12 [target open] 209.73.190.12:443 8.5ms .Ed .Pp To reveal every load-balanced member at each fork, use .Fl -ecmp . It varies the source port per probe and samples each replying hop, so all equal-cost next hops appear; the members of each hop are listed together, the TTL on the first member and a vertical bar marking the rest. Note hops 6 and 7 below are spread across nine and seven parallel routers respectively: .Bd -literal [edge.lax]$ lft -S --ecmp -n www.yahoo.com TTL LFT trace to 209.73.190.12:443/tcp 1 192.0.2.1 0/0/0 \(+-0ms 2 198.51.100.10 1/1/1 \(+-0ms ** [neglected] no reply packets received from TTL 3 4 184.104.193.142 9/11/13 \(+-1ms ** [neglected] no reply packets received from TTL 5 6 74.6.226.213 8/8/8 \(+-0ms \(br 74.6.226.221 8/8/8 \(+-0ms \(br 74.6.226.237 8.2ms \(br 74.6.226.197 8.2ms \(br 74.6.226.209 8/8/8 \(+-0ms \(br 74.6.226.195 8.2ms \(br 74.6.226.227 8.1ms \(br 74.6.226.215 8.2ms \(br 74.6.226.219 8.5ms 7 209.73.188.47 8/8/8 \(+-0ms \(br 209.73.188.61 8/8/8 \(+-0ms \(br 209.73.188.73 8/8/8 \(+-0ms \(br 209.73.188.65 8/8/8 \(+-0ms \(br 209.73.188.57 8.3ms \(br 209.73.188.51 8.1ms \(br 209.73.188.49 8/8/8 \(+-0ms ** [neglected] no reply packets received from TTLs 8 through 11 12 [target open] 209.73.190.12:443 8.5ms .Ed .Pp The GraphViz .Pq Fl g , SVG .Pq Fl -svg , and Mermaid .Pq Fl -mermaid outputs draw the same members as a fork-and-join .Dq diamond . .Pp Active Path MTU Discovery .Pq Fl K probes at the interface MTU with the don't-fragment bit set and steps the probe size down whenever a router returns an ICMP .Dq fragmentation needed (IPv4) or ICMPv6 .Dq Packet Too Big . The discovered path MTU is reported on the target hop as .Sq [MTU: N] ; a hop that silently drops the oversized DF probe without returning a notification is flagged .Sq [MTU Limit] . This example traces to a public IPv6 test host deliberately configured behind a 1280-octet link (intermediate addresses shown as documentation ranges): .Bd -literal [host]$ lft -S -6 -K mtu1280.test-ipv6.com:443 TTL LFT trace to mtu1280.test-ipv6.com (2001:db8:1e::6666):443/tcp 1 2001:db8:4000:2::1 0.5ms 2 2001:db8:e:6b::1 10.8ms 3 2001:db8:30:2::1 10.2ms ** [MTU Limit] hop 4 did not respond to DF probe (1280 bytes, no PTB received) 5 2001:db8:30:3::71 10.1ms 6 [target open] 2001:db8:1e::6666:443 [MTU: 1280] 10.4ms .Ed .Pp .Pp Trace as JSON and list the distinct ASNs on the path with .Xr jq 1 : .Bd -literal -offset indent [edge.lax]$ lft --json -A www.example.com:443 | jq '[.hops[].responders[].asn | select(.)] | unique' .Ed .Pp Map the path: a KML file with latency-colored arches, and a GeoJSON file to drop onto geojson.io or a GitHub page: .Bd -literal -offset indent [edge.lax]$ lft --kml --geo-color rtt www.example.com:443 > path.kml [edge.lax]$ lft --geojson www.example.com:443 > path.geojson .Ed .Sh TROUBLESHOOTING If traces don't appear to go anywhere, there are a number of things to try. If you are receiving an error related to permissions, .Nm requires elevated privileges to open raw sockets and capture packets via .Xr pcap 3 . On Linux, the preferred approach is to grant file capabilities rather than installing a setuid root binary: .Pp .Dl setcap cap_net_raw,cap_net_admin=eip /usr/sbin/lft .Pp This requires the .Ic libcap2-bin package on Debian/Ubuntu or .Ic libcap on RHEL/Fedora. The .Ic make install target attempts this automatically and falls back to setuid root if .Xr setcap 8 is not available. On FreeBSD, .Nm must be installed setuid root. .Pp On macOS, .Nm must be installed setuid root. This is because .Nm uses two separate privilege paths: .Xr pcap 3 (via .Pa /dev/bpf* devices) for .Em capturing response packets, and a raw socket .Pq Dv SOCK_RAW for .Em sending probe packets. BPF device permissions (e.g., the ChmodBPF mechanism used by Wireshark) only cover the capture side \(em they do not grant the ability to create raw sockets, which on macOS requires root. Unlike Linux, macOS has no .Xr setcap 8 equivalent to selectively grant raw socket capability to a binary. .Pp To install setuid root: .Pp .Dl sudo chown root lft && sudo chmod u+s lft .Pp .Ic make install performs this step automatically when run as root. This is standard practice for network diagnostic tools on macOS; .Xr ping 8 and .Xr traceroute 8 ship setuid root for the same reason. .Pp If you are not seeing permissions errors but a trace still does not reach its target, first add .Fl VV to watch the actual probes: you will see .Dq SENT lines going out and .Dq RCVD lines coming back. What you see next points to the fix. .Bl -tag -width Ds .It Sy Some hops show ** [neglected] This is usually normal, not a failure. Many routers decline to answer, or rate-limit the ICMP .Dq time exceeded messages .Nm listens for, so the hop is invisible even though your packets pass through it. What matters is whether the final line reaches .Dq [target open] . .Nm already retries each hop with a fresh source port to coax replies past stateful middleboxes that ignore repeats, so many missing hops fill in on their own. To push harder, raise the probe count with .Fl m Ar 3 (or more): extra probes recover hops that answer intermittently and add a min/avg/max \(+-stddev latency spread per hop. .It Sy The trace dies near your end, but the host is up A stateful firewall or SYN-scrubbing middlebox close to you may be dropping .Nm Ap s probes. From least to most aggressive, try .Fl B (makes probes resemble a real operating-system SYN, which many scrubbers require), .Fl E (adaptive mode, which varies TCP flags to slip past stateful filters), or .Fl b (basic TCP, friendliest to NAT). If all three stall at the same near hop, that middlebox is almost certainly the cause. .It Sy Different runs show different routers at the same hop That is equal-cost multi-path (ECMP) load balancing, not an error \(em the path genuinely has parallel next hops. Add .Fl -ecmp to probe them all and render the fork as a diamond instead of a flickering single line. .It Sy Ordinary traffic works, but the trace stalls mid-path This is the classic Path MTU Discovery black hole: a link with a smaller MTU is silently dropping large packets. Add .Fl K to actively discover the path MTU; .Nm reports the reduced MTU on the hop where it shrinks and flags a silent bottleneck as .Dq [MTU Limit] . .It Sy Verbose output shows SENT but no RCVD at all Your egress may be filtering the default method. Switch protocols: .Fl u (UDP) or .Fl p (ICMP) often succeed where TCP is blocked, and vice versa. .It Sy RFC 1393 (-P) traces fail Some equipment on the path does not implement RFC 1393; there is no workaround but to choose another tracing method. .El .Pp .Sh AUTHORS Victor Oppleman, Eugene Antsilevitch, Sergey Kondryukov and other helpers around the world. .Sh REPORTING BUGS To report bugs, use the contact form at .Lk https://pwhois.org/contact/ . .Sh SEE ALSO .Xr traceroute 8 , .Xr netstat 1 , .Xr whois 1 , .Xr whob 8 .Sh HISTORY The .Nm command first appeared in 1998 as 'fft'. Renamed as a result of confusion with fast fourier transforms, .Nm stands for 'layer four traceroute.' Thanks also to Nils McCarthy for writing 'FFT', LFT's predecessor. lft-4.01/Makefile.in000644 000765 000024 00000017736 15247771366 014254 0ustar00vicstaff000000 000000 # # This file is part of LFT. # # The LFT software provided in this Distribution is # Copyright 2007 VOSTROM Holdings, Inc. # # The full text of our legal notices is contained in the file called # COPYING, included with this Distribution. # Directories where LFT will be installed: prefix=@prefix@ datarootdir = @datarootdir@ datadir = @datadir@ exec_prefix=@exec_prefix@ bindir=@bindir@ mandir=@mandir@ INSTALL=@INSTALL@ LN=@LN_S@ # Commands/References CC=@CC@ CFLAGS=@CFLAGS@ LIBS=@LIBS@ LDFLAGS=@LDFLAGS@ CAT=cat CD=cd MKDIR=mkdir -p BUILDTYPE=build RM=rm -rf SH=sh SYSTEM=unix TOOLS=tools TOUCH=touch STRIP ?= strip @SET_MAKE@ # Specifics WATCH_OBJS = @WATCH_OBJS@ OBJS = lft.o lft_ifname.o whois.o lft_lib.o lft_icmptrace.o lft_btcptrace.o lft_pathwatch.o lft_addr.o lft_xml.o lft_probe6.o $(WATCH_OBJS) # ---- Distribution manifest (Task 21 packaging guard) -------------------- # Every file that must appear in a release tarball. `make distcheck` fails # if any source the build compiles is missing here — this is the guard that # would have caught the 3.99 lft_pathwatch.c omission. Keep it current when # adding files (and add new files to revision control!). DIST_SRC = lft.c lft_ifname.c whois.c lft_lib.c lft_icmptrace.c \ lft_btcptrace.c lft_pathwatch.c lft_watch.c lft_addr.c lft_xml.c \ lft_probe6.c DIST_HDR = lft_lib.h lft_types.h lft_queue.h lft_lsrr.h lft_ifname.h \ lft_icmptrace.h lft_btcptrace.h lft_pathwatch.h lft_watch.h \ whois.h lft_addr.h lft_xml.h lft_probe6.h lft_icons_svg.h DIST_TESTS = tests/pathwatch_test.c tests/addr_test.c tests/xml_test.c \ tests/probe6_test.c tests/resolve_test.c tests/ifaddr_test.c DIST_META = configure Makefile.in makefile.vc CHANGELOG README INSTALL \ COPYING TODO lft.8 whob.8 DIST_DIRS = config include icons DISTFILES = $(DIST_SRC) $(DIST_HDR) $(DIST_TESTS) $(DIST_META) $(DIST_DIRS) # Sources the build actually compiles — each MUST be in DISTFILES. DIST_REQUIRED = $(OBJS:.o=.c) $(DIST_TESTS) # whob is a CLI-only tool — it never uses ncurses, so configure strips the # ncurses libs out of its link line (computed there, not with a GNU-make-only # function, so BSD make works too). c-ares stays (whois.c uses it for the # server-prefetch async resolver path). WHOB_LIBS = @WHOB_LIBS@ # Targets that are not files (protects them if a like-named file appears). .PHONY: all clean distclean check check-pathwatch check-addr check-xml install dist distcheck all: lft whob # Every object includes the shared session/struct headers; editing them # changes struct layout, so all objects must recompile. Without these # deps a header-only edit leaves stale objects with mismatched struct # offsets (memory corruption / crashes). $(OBJS): lft_lib.h lft_types.h lft_queue.h lft_lsrr.h # lft_lib.c inlines the SVG glyph library for --svg output. lft_lib.o: lft_icons_svg.h lft: $(OBJS) $(CC) $(CFLAGS) -o lft $(OBJS) $(LDFLAGS) $(LIBS) lft_watch.o: lft_watch.c lft_watch.h lft_pathwatch.h $(CC) $(CFLAGS) -c -o lft_watch.o lft_watch.c lft_pathwatch.o: lft_pathwatch.c lft_pathwatch.h $(CC) $(CFLAGS) -c -o lft_pathwatch.o lft_pathwatch.c # Pure AF-agnostic address + XML modules (no session/pcap deps). lft_addr.o: lft_addr.c lft_addr.h $(CC) $(CFLAGS) -c -o lft_addr.o lft_addr.c lft_xml.o: lft_xml.c lft_xml.h $(CC) $(CFLAGS) -c -o lft_xml.o lft_xml.c lft_probe6.o: lft_probe6.c lft_probe6.h lft_addr.h $(CC) $(CFLAGS) -c -o lft_probe6.o lft_probe6.c # ---- Unit test drivers — all standalone, no root/network needed --------- check: check-pathwatch check-addr check-xml check-probe6 check-resolve check-ifaddr @echo "all unit drivers passed" check-pathwatch: lft_pathwatch.c lft_pathwatch.h tests/pathwatch_test.c $(CC) $(CFLAGS) -o tests/pathwatch_test tests/pathwatch_test.c lft_pathwatch.c ./tests/pathwatch_test check-addr: lft_addr.c lft_addr.h tests/addr_test.c $(CC) $(CFLAGS) -o tests/addr_test tests/addr_test.c lft_addr.c ./tests/addr_test check-xml: lft_xml.c lft_xml.h tests/xml_test.c $(CC) $(CFLAGS) -o tests/xml_test tests/xml_test.c lft_xml.c ./tests/xml_test check-probe6: lft_probe6.c lft_probe6.h lft_addr.c lft_addr.h tests/probe6_test.c $(CC) $(CFLAGS) -o tests/probe6_test tests/probe6_test.c lft_probe6.c lft_addr.c ./tests/probe6_test check-resolve: lft_addr.c lft_addr.h tests/resolve_test.c $(CC) $(CFLAGS) -o tests/resolve_test tests/resolve_test.c lft_addr.c ./tests/resolve_test check-ifaddr: lft_ifname.c lft_ifname.h lft_addr.c lft_addr.h tests/ifaddr_test.c $(CC) $(CFLAGS) -o tests/ifaddr_test tests/ifaddr_test.c lft_ifname.c lft_addr.c ./tests/ifaddr_test whob: whois.o $(CC) $(CFLAGS) -o whob whois.c -DSTANDALONE $(LDFLAGS) $(WHOB_LIBS) install: lft lft.8 whob whob.8 @echo "LFT and WhoB" @echo " \_Stripping binaries" @$(STRIP) lft whob @echo " \_Copying files to their intended destinations" @test -d $(DESTDIR)$(bindir)/. || $(MKDIR) $(DESTDIR)$(bindir) $(INSTALL) lft $(DESTDIR)$(bindir)/lft $(INSTALL) whob $(DESTDIR)$(bindir)/whob @ if [ "$$(uname -s)" = "Linux" ]; then \ if [ "$$(id -u)" = "0" ]; then \ echo " \_Granting setcap() permission to avoid setuid requirement"; \ if setcap cap_net_raw,cap_net_admin=eip $(DESTDIR)$(bindir)/lft 2>/dev/null; then \ echo " \_Done (cap_net_raw,cap_net_admin=eip granted)"; \ else \ echo " \_setcap failed or not available, falling back to setuid root"; \ ( chown root $(DESTDIR)$(bindir)/lft && chmod u+s $(DESTDIR)$(bindir)/lft ) || \ echo "*** WARNING: could not grant capabilities or set $(DESTDIR)$(bindir)/lft setuid root"; \ fi; \ else \ echo "*** WARNING: not running as root; cannot grant setcap or setuid permissions"; \ echo " Re-run 'make install' as root or with sudo to grant required privileges."; \ fi; \ else \ echo " \_Setting setuid root (platform does not support setcap)"; \ ( chown root $(DESTDIR)$(bindir)/lft && chmod u+s $(DESTDIR)$(bindir)/lft ) || \ echo "*** WARNING: could not set $(DESTDIR)$(bindir)/lft setuid root"; \ fi @test -d $(DESTDIR)$(mandir)/man8/. || $(MKDIR) $(DESTDIR)$(mandir)/man8 @echo "Installing manual pages" $(INSTALL) lft.8 $(DESTDIR)$(mandir)/man8/lft.8 $(INSTALL) whob.8 $(DESTDIR)$(mandir)/man8/whob.8 clean: $(RM) *.o core* lft whob *~ *.dSYM $(RM) tests/pathwatch_test tests/pathwatch_test.dSYM $(RM) tests/addr_test tests/addr_test.dSYM $(RM) tests/xml_test tests/xml_test.dSYM $(RM) tests/probe6_test tests/probe6_test.dSYM $(RM) tests/resolve_test tests/resolve_test.dSYM $(RM) tests/ifaddr_test tests/ifaddr_test.dSYM distclean: clean $(RM) Makefile config.log config.status config/acconfig.h autom4te.cache # ---- Packaging guard + tarball (Task 21) -------------------------------- # distcheck: fail loudly if any compiled source is absent from DISTFILES. # This is the manifest consistency gate that prevents a new mid-cycle file # (like 3.99's lft_pathwatch.c) from silently dropping out of the release. distcheck: @missing=""; \ for f in $(DIST_REQUIRED); do \ case " $(DISTFILES) " in \ *" $$f "*) ;; \ *) missing="$$missing $$f" ;; \ esac; \ done; \ if [ -n "$$missing" ]; then \ echo "*** DIST ERROR: compiled source(s) missing from DISTFILES:$$missing"; \ exit 1; \ fi; \ echo "distcheck: all $(words $(DIST_REQUIRED)) compiled sources present in DISTFILES" # dist: build a manifest-checked tarball named for the version in lft.c. dist: distcheck @v=`grep -E 'version = "' lft.c | head -1 | sed -E 's/.*"([^"]+)".*/\1/'`; \ rm -rf lft-$$v && mkdir lft-$$v && tar cf - $(DISTFILES) | (cd lft-$$v && tar xf -) && \ rm -rf lft-$$v/config/autom4te.cache lft-$$v/config/acconfig.h && \ rm -f lft-$$v/icons/*/blank.png lft-$$v/icons/*/client.png lft-$$v/icons/*/cloud.png lft-$$v/icons/*/dark_cloud.png && \ COPYFILE_DISABLE=1 tar --no-xattrs -czf lft-$$v.tar.gz lft-$$v && rm -rf lft-$$v && echo "wrote lft-$$v.tar.gz" better: @echo "Sorry, this is the best I can do." work: @echo "Sorry, I didn't write this. I'm only a Makefile." love: @echo "What do you think I was doing before you bothered me?" lft-4.01/lft_btcptrace.c000644 000765 000024 00000110137 15245143042 015132 0ustar00vicstaff000000 000000 #include "lft_btcptrace.h" #include "lft_lib.h" static LFT_CALLBACK LFTErrHandler=0; static LFT_CALLBACK LFTEvtHandler=0; static int getFreePort(lft_session_params * sess, int startfrom) { int i; if(sess->btcpmap == NULL) { if(!(sess->btcpmap = malloc(sizeof(BasicTCPMapEntry) * (sess->ttl_limit * sess->retry_max + 1)))) { LFTErrHandler(sess, ERR_NOT_ENOUGH_MEM, NULL); return 0; } sess->btcpmapsize=sess->ttl_limit * sess->retry_max + 1; for(i=0;ibtcpmapsize;i++) { sess->btcpmap[i].nhop = -1; sess->btcpmap[i].port = sess->dport + i; sess->btcpmap[i].sentcount=0; } sess->latestmapchoice=0; /*sess->debugmapidx=0;*/ } for(i=(startfrom && sess->latestmapchoice>=startfrom)?sess->latestmapchoice+1:startfrom;ibtcpmapsize;i++) { if(sess->btcpmap[i].nhop==-1) { if(startfrom) sess->latestmapchoice=i; return sess->btcpmap[i].port; } } return sess->dport; } static int probeIsSent(lft_session_params * sess, int port, int nhop) { int i = port - sess->dport; if(i<0 || i>=sess->btcpmapsize) { LFTErrHandler(sess, ERR_BTCP_WRONG_PORT_VALUE, NULL); return 0; } if(sess->btcpmap[i].nhop!=-1 && sess->btcpmap[i].nhop!=nhop) { LFTErrHandler(sess, ERR_BTCP_PROBE_PORT_IS_BUSY, NULL); return 0; } sess->btcpmap[i].nhop=nhop; sess->btcpmap[i].sentcount++; return sess->btcpmap[i].sentcount; } static int probeIsRecvd(lft_session_params * sess, int port) { int nhop; int i = port - sess->dport; if(i<0 || i>=sess->btcpmapsize) { LFTErrHandler(sess, ERR_BTCP_WRONG_PORT_VALUE, NULL); return 0; } nhop=sess->btcpmap[i].nhop; sess->btcpmap[i].sentcount--; if(sess->btcpmap[i].sentcount<1) { sess->btcpmap[i].nhop=-1; sess->btcpmap[i].sentcount=0; } return nhop; } #if 0 static int hopByPort(lft_session_params * sess, int port) { int nhop; int i = port - sess->dport; if(i<0 || i>=sess->btcpmapsize) return -1; nhop=sess->btcpmap[i].nhop; if(sess->btcpmap[i].port != port || sess->btcpmap[i].sentcount<1) return -1; return nhop; } #endif static unsigned int tcp_base_send_hop(lft_session_params * sess, short nhop, int searchfrom) { struct sockaddr_in dest; unsigned int tseq=0; unsigned short tttl=0; char * buf; char* bptr = NULL; int blen = 0; int port; EvtSentPacketParam espparam; 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; port=getFreePort(sess, searchfrom); dest.sin_port = htons(port); tseq = new_seq(sess); tttl = nhop + 1; 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; espparam.flags=sess->tcp_flags; if(sess->adaptive) { struct hop_info_s *h = &(sess->hop_info[nhop]); if(h->state == HS_SEND_FIN) espparam.flags = TH_FIN; else if(h->state == HS_SEND_SYN) espparam.flags = TH_SYN; else if(h->state == HS_SEND_SYN_ACK) espparam.flags = HS_SEND_SYN_ACK; else { WrnBadHopStateParam wbhsp; wbhsp.h=h; wbhsp.nhop=nhop; LFTErrHandler(sess, WRN_BAD_HOP_STATE, &wbhsp); } } espparam.nhop=nhop; espparam.tseq=tseq; espparam.tttl=tttl; /* Basic mode: fixed source port, per-probe destination port. */ espparam.sport=(unsigned short)sess->sport; espparam.dport=(unsigned short)port; 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 */ /* 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 = dest.sin_port; packet->tcp_hdr.th_seq = htonl (tseq); packet->tcp_hdr.th_sport = htons (sess->sport); packet->tcp_hdr.th_flags = espparam.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; } /*sess->debugmap[sess->debugmapidx].type=0; sess->debugmap[sess->debugmapidx].hop=nhop; sess->debugmap[sess->debugmapidx++].port=port;*/ probeIsSent(sess, port, nhop); pinfo->hopno = nhop; 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 void tcp_base_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) 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; } #if 0 if(tp == NULL) { /* Second, search every probe to find an exact port match */ SLIST_FOREACH(tp, &(sess->trace_packets), next) { if(hopByPort(sess, htons(tp->packet.tcp_hdr.th_dport)) == tp->hopno) break; } } #endif /* 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; } /*sess->debugmap[sess->debugmapidx].type=1; sess->debugmap[sess->debugmapidx].hop=tp->hopno; sess->debugmap[sess->debugmapidx].phop=hopByPort(sess, htons(tp->packet.tcp_hdr.th_dport)); sess->debugmap[sess->debugmapidx].port=htons(tp->packet.tcp_hdr.th_dport); sess->debugmap[sess->debugmapidx++].ip=ipaddr;*/ probeIsRecvd(sess, htons(tp->u.packet.tcp_hdr.th_dport)); /* Set icmp_type before firing EVT_RECV_PACKET so the handler sees the * correct value (race-condition fix mirrors lft_lib.c). */ 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_queue(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; sess->hop_info[tp->hopno].done_packet = tp; 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; } static void tcp_base_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) * sizeof(char)) + 1 + (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)); gettimeofday (&(sess->trace_done_time), NULL); /*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) { 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; break; } } } 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); } 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 (mirrors finish() in lft_lib.c) */ int filt_run_start = 0; int filt_run_end = 0; int filt_run_icmp_type = 3; 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 * (mirrors finish() in lft_lib.c) */ if (found_target && (sess->hop_info[hopno].flags & HF_ENDPOINT)) { reply = 0; continue; } /* Detect filtered-repeat hops and compress into a range line */ { 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, inet_ntoa(filt_run_ip), filt_run_icmp_type); filt_run_start = 0; } if (_is_filt) { 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) { 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 (mirrors lft_lib.c path). * Fire whenever pwhois ran: 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=getOutputStyle(); char hostname[100]; ehip.seam_traced=1; setOutputStyle(2); 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); ehip.is_open=subsess->target_open; ehip.is_filtered=subsess->target_filtered; LFTSessionClose(subsess); setOutputStyle(curroutputstyle); } } /* --------------- */ 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; } /* First target hop printed — guard above suppresses 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; } /* Flush any trailing filtered run */ if (filt_run_start > 0) print_filtered_run(filt_run_start, filt_run_end, inet_ntoa(filt_run_ip), filt_run_icmp_type); if (!sess->num_hops) { LFTEvtHandler(sess,EVT_RPT_NO_HOPS,&maxhop); } if (sess->timetrace) { LFTEvtHandler(sess,EVT_RPT_TIME_TRACE,NULL); } LFTEvtHandler(sess,EVT_ON_EXIT,NULL); if(ipaslist != NULL) free(ipaslist); } static int tcp_base_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) { tcp_base_send_hop(sess, nhop, 1); 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; if(sess->hop_info[nhop].num_sent < sess->retry_max - 1) tcp_base_send_hop(sess, nhop, 1); else { sess->btcpdpucnt++; if(sess->btcpdpucnt>4) { sess->btcpdpucnt=0; sess->btcpmap[0].nhop=-1; sess->btcpmap[0].sentcount=0; tcp_base_send_hop(sess, nhop, 0); } } 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; if (nhop > LFT_GIVEUP_NEARHOP_NHOP) no_reply++; /* exempt near hops (see lft_lib.h) */ } } } 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 ((need_reply < 3) && (sess->num_rcvd < 2)) LFTEvtHandler(sess,EVT_CANT_RELIABLY_RTRIP,NULL); if(sess->exit_state < 0) return 0; tcp_base_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++; tcp_base_send_hop(sess, nhop, 1); } 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) { tcp_base_send_hop(sess, nhop, 1); return 0; } if(sess->hop_info[nhop].done_packet) { if(sess->trg_probe_is_sentretry_min || (sess->trg_probe_is_sentretry_max && ntohs(sess->hop_info[nhop].done_packet->u.packet.tcp_hdr.th_dport)!=sess->dport)) { sess->btcpdpucnt++; if(sess->btcpdpucnt>4) { sess->btcpdpucnt=0; sess->btcpmap[0].nhop=-1; sess->btcpmap[0].sentcount=0; sess->trg_probe_is_sent++; tcp_base_send_hop(sess, nhop, 0); } return 0; } } } tcp_base_finish(sess); return 1; } return 0; } static void tcp_base_process_packet(lft_session_params * sess, const u_char *packet, const struct pcap_pkthdr *hdr) { const struct ip *ip, *orig_ip; const struct tcphdr *tcp; const struct icmp *icmp; const struct ether_header *eptr; const u_char *pkt_end = packet + hdr->caplen; /* end of captured bytes */ if (sess->noisy > 4) { LFTEvtHandler(sess,EVT_PROCESS_PACKET_START,NULL); if(sess->exit_state<0) return; } tcp_base_check_timeouts(sess); if(sess->exit_state<0) return; /* 802.1q VLAN (dot1q): extend the L2 offset for THIS packet only, as a * local — not a persistent session mutation (mirrors process_packet). */ eptr = (const struct ether_header *) packet; { size_t l2 = sess->skip_header_len; if (l2 == sizeof (struct ether_header) && ntohs (eptr->ether_type) == ETHERTYPE_VLAN) l2 += 4; if (hdr->caplen <= l2) return; /* truncated before L3 */ packet += l2; } /* v4-only engine: drop non-IPv4 / truncated frames rather than misparse. */ if ((packet[0] >> 4) != 4 || packet + sizeof(struct ip) > pkt_end) return; ip = (const void *) packet; packet += 4 * ip->ip_hl; switch (ip->ip_p) { case IPPROTO_ICMP: orig_ip = ip; icmp = (const void *) packet; if ((const u_char *)icmp + ICMP_MINLEN > pkt_end) return; /* truncated ICMP header */ if (icmp->icmp_type != ICMP_UNREACH && icmp->icmp_type != ICMP_TIMXCEED) return; ip = &icmp->icmp_ip; /* quoted inner IP header is attacker-controlled — bound it */ if ((const u_char *)ip + sizeof(struct ip) > pkt_end) return; if (ip->ip_p != IPPROTO_TCP) return; /* not a response to our tcp probe */ packet = (const u_char *) ip; packet += 4 * ip->ip_hl; /* RFC 792 guarantees only the inner IP header plus the first 8 * bytes of the quoted transport header. We read the ports * (0-3) and the sequence (4-7), all within those 8 bytes; * requiring the full 20-byte tcphdr would drop time-exceeded * replies from routers that quote only the RFC minimum. */ if (packet + 8 > pkt_end) return; /* inner TCP quote truncated before seq */ tcp = (const void *) packet; if (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 (type 3 code 4) before marking 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; } tcp_base_recv_packet(sess, ntohl (tcp->th_seq) , orig_ip->ip_src, (icmp->icmp_type == ICMP_TIMXCEED) ? -2 : icmp->icmp_code, hdr); return; case IPPROTO_TCP: if (packet + sizeof(struct tcphdr) > pkt_end) return; /* truncated TCP header */ 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 (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(ntohs(tcp->th_sport)==sess->dport) { /* 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++; if ((tcp->th_flags & TH_ACK) && !(tcp->th_flags & TH_SYN) && (tcp->th_flags & TH_RST)) sess->target_open = 0; } tcp_base_recv_packet(sess, ntohl (tcp->th_ack) - 1, ip->ip_src, -1, hdr); return; default: if (sess->noisy > 3) LFTEvtHandler(sess,EVT_RCVD_UNKNOWN,ip); } } #if defined( __CYGWIN__ ) || defined( WIN32 ) || defined(_WIN32) void win_tcp_base_process(lft_session_params * sess) { fd_set fds; struct timeval tm; 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) { 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; } tcp_base_process_packet(sess, packetbuf, NULL); } } #else static void pcap_tcp_base_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; tcp_base_process_packet(sess, packet, hdr); } #endif void tcp_base_trace_main_loop(lft_session_params * sess, LFT_CALLBACK err, LFT_CALLBACK evt) { LFTErrHandler=err; LFTEvtHandler=evt; #if defined( __CYGWIN__ ) || defined( WIN32 ) || defined(_WIN32) while(1) { win_tcp_base_process(sess); if(sess->exit_state<0) break; if(tcp_base_check_timeouts(sess)) break; if(sess->exit_state<0) break; } #else while(pcap_dispatch(sess->pcapdescr, -1, pcap_tcp_base_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(tcp_base_check_timeouts(sess)) break; if(sess->exit_state<0) break; } #endif } lft-4.01/lft_ifname.c000644 000765 000024 00000012216 15250413042 014415 0ustar00vicstaff000000 000000 /* * lft_ifname.c * Layer Four Traceroute * * This file is part of LFT. * * The LFT software provided in this Distribution is * Copyright 2007 VOSTROM Holdings, Inc. * * The full text of our legal notices is contained in the file called * COPYING, included with this Distribution. * */ #if !defined(WIN32) && !defined(_WIN32) #include #include #include #include #if !defined(linux) && !defined(__CYGWIN__) #include #endif #include #include #include #include #include #include #if defined( __CYGWIN__ ) || defined( WIN32 ) || defined(_WIN32) #include "config/acconfig.win.h" #else #include "config/acconfig.h" #endif #include "lft_ifname.h" /* after acconfig.h so HAVE_IPV6 gates the v6 decls */ #ifdef HAVE_IPV6 #include #endif static int sock = -1; u_long lft_getifaddr (const char *ifname) { struct ifreq ifr; struct sockaddr_in addr; if(!ifname){ perror ("The user-supplied network interface is not suitable"); return -1; } /* Run once. */ if (sock < 0) { if ((sock = socket (AF_INET, SOCK_DGRAM, 0)) < 0) { perror ("socket"); return -1; } } STRNCPY(ifr.ifr_name, ifname, IFNAMSIZ); if (ioctl(sock, SIOCGIFADDR, &ifr) < 0) { perror("ioctl"); return -1; } if (ifr.ifr_addr.sa_family != AF_INET) { fprintf (stderr, "%s: Interface not configured with IPv4 address.\n", ifname); fflush (stderr); return -1; } memcpy(&addr, &ifr.ifr_addr, sizeof addr); return (addr.sin_addr.s_addr); } char * lft_getifname (struct in_addr addr) { struct ifconf ifc; char *buffer = NULL; char *name = NULL; size_t buflen = 4096; int i, skip; /* Run once. */ if (sock < 0) { if ((sock = socket (AF_INET, SOCK_DGRAM, 0)) < 0) { perror ("socket"); return NULL; } } /* SIOCGIFCONF silently truncates to the buffer; grow until the kernel * has room to spare, so hosts with many interfaces are fully listed. */ for (;;) { char *nb = realloc(buffer, buflen); if (!nb) { free(buffer); return NULL; } buffer = nb; ifc.ifc_len = (int)buflen; ifc.ifc_buf = buffer; if (ioctl(sock, SIOCGIFCONF, &ifc) < 0) { perror("ioctl"); free(buffer); return NULL; } if ((size_t)ifc.ifc_len + sizeof(struct ifreq) < buflen || buflen >= (1u << 20)) break; buflen *= 2; } for (i = 0; i < ifc.ifc_len; i += skip) { struct ifreq ifr; struct in_addr thisaddr; memcpy(&ifr, ifc.ifc_buf + i, sizeof(struct ifreq)); skip = sizeof(struct ifreq); #ifdef HAVE_SOCKADDR_SA_LEN if (ifr.ifr_addr.sa_len > sizeof(struct sockaddr)) { skip = ifr.ifr_addr.sa_len + IFNAMSIZ; } #endif if (ifr.ifr_addr.sa_family != AF_INET) continue; thisaddr = ((const struct sockaddr_in *)(const void *)(&(ifr.ifr_addr)))->sin_addr; if (thisaddr.s_addr == addr.s_addr) { name = strdup(ifr.ifr_name); break; } } free(buffer); return name; } #ifdef HAVE_IPV6 /* AF-aware interface helpers via getifaddrs (LFT 4.0). Additive — the v4 * lft_getifaddr/lft_getifname above are untouched. */ int lft_getifaddr_af (const char *ifname, int af, lft_addr_t *out) { struct ifaddrs *ifap, * p; int found = 0; if (!ifname || !out || getifaddrs(&ifap) != 0) return 0; for (p = ifap; p; p = p->ifa_next) { if (!p->ifa_addr || p->ifa_addr->sa_family != af) continue; if (strcmp(p->ifa_name, ifname) != 0) continue; if (af == AF_INET) { out->af = AF_INET; out->a.v4 = ((struct sockaddr_in *)(void *)p->ifa_addr)->sin_addr; found = 1; break; } else if (af == AF_INET6) { /* Prefer a global address; keep the first match as a fallback * (e.g. an interface with only a link-local address). */ struct in6_addr *v6 = &((struct sockaddr_in6 *)(void *)p->ifa_addr)->sin6_addr; int is_ll = IN6_IS_ADDR_LINKLOCAL(v6); out->af = AF_INET6; out->a.v6 = *v6; found = 1; if (!is_ll) break; } } freeifaddrs(ifap); return found; } char * lft_getifname_af (const lft_addr_t *addr) { struct ifaddrs *ifap, *p; char *name = NULL; if (!addr || getifaddrs(&ifap) != 0) return NULL; for (p = ifap; p; p = p->ifa_next) { lft_addr_t cur; if (!p->ifa_addr) continue; if (p->ifa_addr->sa_family == AF_INET && addr->af == AF_INET) { cur.af = AF_INET; cur.a.v4 = ((struct sockaddr_in *)(void *)p->ifa_addr)->sin_addr; } else if (p->ifa_addr->sa_family == AF_INET6 && addr->af == AF_INET6) { cur.af = AF_INET6; cur.a.v6 = ((struct sockaddr_in6 *)(void *)p->ifa_addr)->sin6_addr; } else { continue; } if (lft_addr_eq(&cur, addr)) { name = strdup(p->ifa_name); break; } } freeifaddrs(ifap); return name; } #endif /* HAVE_IPV6 */ #ifdef LFT_IFADDR_TESTING extern int main (int argc, char *argv[]) { struct in_addr in; char *addr; if (argc > 1) addr = strdup (argv[1]); else addr = strdup ("eth0"); in.s_addr = lft_getifaddr (addr); if (in.s_addr == -1) { fprintf (stderr, "%s: Error reading ifname\n", addr); fflush (stderr); free(addr); exit (-1); } fprintf (stdout, "%s: %s\n", addr, inet_ntoa (in)); fflush (stdout); free (addr); exit (0); } #endif /*LFT_IFNAME_TESTING*/ #endif lft-4.01/lft_queue.h000644 000765 000024 00000041345 11053131665 014321 0ustar00vicstaff000000 000000 /* * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)queue.h 8.5 (Berkeley) 8/20/94 * $FreeBSD: src/sys/sys/queue.h,v 1.32.2.4 2001/03/31 03:33:39 hsu Exp $ */ #ifndef _LFT_QUEUE_H_ #define _LFT_QUEUE_H_ #define __offsetof(type, field) ((size_t)(&((type *)0)->field)) /* * This file defines five types of data structures: singly-linked lists, * singly-linked tail queues, lists, tail queues, and circular queues. * * A singly-linked list is headed by a single forward pointer. The elements * are singly linked for minimum space and pointer manipulation overhead at * the expense of O(n) removal for arbitrary elements. New elements can be * added to the list after an existing element or at the head of the list. * Elements being removed from the head of the list should use the explicit * macro for this purpose for optimum efficiency. A singly-linked list may * only be traversed in the forward direction. Singly-linked lists are ideal * for applications with large datasets and few or no removals or for * implementing a LIFO queue. * * A singly-linked tail queue is headed by a pair of pointers, one to the * head of the list and the other to the tail of the list. The elements are * singly linked for minimum space and pointer manipulation overhead at the * expense of O(n) removal for arbitrary elements. New elements can be added * to the list after an existing element, at the head of the list, or at the * end of the list. Elements being removed from the head of the tail queue * should use the explicit macro for this purpose for optimum efficiency. * A singly-linked tail queue may only be traversed in the forward direction. * Singly-linked tail queues are ideal for applications with large datasets * and few or no removals or for implementing a FIFO queue. * * A list is headed by a single forward pointer (or an array of forward * pointers for a hash table header). The elements are doubly linked * so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before * or after an existing element or at the head of the list. A list * may only be traversed in the forward direction. * * A tail queue is headed by a pair of pointers, one to the head of the * list and the other to the tail of the list. The elements are doubly * linked so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before or * after an existing element, at the head of the list, or at the end of * the list. A tail queue may be traversed in either direction. * * A circle queue is headed by a pair of pointers, one to the head of the * list and the other to the tail of the list. The elements are doubly * linked so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before or after * an existing element, at the head of the list, or at the end of the list. * A circle queue may be traversed in either direction, but has a more * complex end of list detection. * * For details on the use of these macros, see the queue(3) manual page. * * * SLIST LIST STAILQ TAILQ CIRCLEQ * _HEAD + + + + + * _ENTRY + + + + + * _INIT + + + + + * _EMPTY + + + + + * _FIRST + + + + + * _NEXT + + + + + * _PREV - - - + + * _LAST - - + + + * _FOREACH + + + + + * _FOREACH_REVERSE - - - + + * _INSERT_HEAD + + + + + * _INSERT_BEFORE - + - + + * _INSERT_AFTER + + + + + * _INSERT_TAIL - - + + + * _REMOVE_HEAD + - + - - * _REMOVE + + + + + * */ /* * Singly-linked List definitions. */ #define SLIST_HEAD(name, type) \ struct name { \ struct type *slh_first; /* first element */ \ } #define SLIST_HEAD_INITIALIZER(head) \ { NULL } #undef SLIST_ENTRY #define SLIST_ENTRY(type) \ struct { \ struct type *sle_next; /* next element */ \ } /* * Singly-linked List functions. */ #define SLIST_EMPTY(head) ((head)->slh_first == NULL) #define SLIST_FIRST(head) ((head)->slh_first) #define SLIST_FOREACH(var, head, field) \ for((var) = (head)->slh_first; (var); (var) = (var)->field.sle_next) #define SLIST_INIT(head) { \ (head)->slh_first = NULL; \ } #define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ (elm)->field.sle_next = (slistelm)->field.sle_next; \ (slistelm)->field.sle_next = (elm); \ } while (0) #define SLIST_INSERT_HEAD(head, elm, field) do { \ (elm)->field.sle_next = (head)->slh_first; \ (head)->slh_first = (elm); \ } while (0) #define SLIST_NEXT(elm, field) ((elm)->field.sle_next) #define SLIST_REMOVE_HEAD(head, field) do { \ (head)->slh_first = (head)->slh_first->field.sle_next; \ } while (0) #define SLIST_REMOVE(head, elm, type, field) do { \ if ((head)->slh_first == (elm)) { \ SLIST_REMOVE_HEAD((head), field); \ } \ else { \ struct type *curelm = (head)->slh_first; \ while( curelm->field.sle_next != (elm) ) \ curelm = curelm->field.sle_next; \ curelm->field.sle_next = \ curelm->field.sle_next->field.sle_next; \ } \ } while (0) /* * Singly-linked Tail queue definitions. */ #define STAILQ_HEAD(name, type) \ struct name { \ struct type *stqh_first;/* first element */ \ struct type **stqh_last;/* addr of last next element */ \ } #define STAILQ_HEAD_INITIALIZER(head) \ { NULL, &(head).stqh_first } #define STAILQ_ENTRY(type) \ struct { \ struct type *stqe_next; /* next element */ \ } /* * Singly-linked Tail queue functions. */ #define STAILQ_EMPTY(head) ((head)->stqh_first == NULL) #define STAILQ_INIT(head) do { \ (head)->stqh_first = NULL; \ (head)->stqh_last = &(head)->stqh_first; \ } while (0) #define STAILQ_FIRST(head) ((head)->stqh_first) #ifndef STAILQ_LAST #define STAILQ_LAST(head, type, field) \ (STAILQ_EMPTY(head) ? \ NULL : \ ((struct type *) \ ((char *)((head)->stqh_last) - __offsetof(struct type, field)))) #endif #define STAILQ_FOREACH(var, head, field) \ for((var) = (head)->stqh_first; (var); (var) = (var)->field.stqe_next) #define STAILQ_INSERT_HEAD(head, elm, field) do { \ if (((elm)->field.stqe_next = (head)->stqh_first) == NULL) \ (head)->stqh_last = &(elm)->field.stqe_next; \ (head)->stqh_first = (elm); \ } while (0) #define STAILQ_INSERT_TAIL(head, elm, field) do { \ (elm)->field.stqe_next = NULL; \ *(head)->stqh_last = (elm); \ (head)->stqh_last = &(elm)->field.stqe_next; \ } while (0) #define STAILQ_INSERT_AFTER(head, tqelm, elm, field) do { \ if (((elm)->field.stqe_next = (tqelm)->field.stqe_next) == NULL)\ (head)->stqh_last = &(elm)->field.stqe_next; \ (tqelm)->field.stqe_next = (elm); \ } while (0) #define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next) #define STAILQ_REMOVE_HEAD(head, field) do { \ if (((head)->stqh_first = \ (head)->stqh_first->field.stqe_next) == NULL) \ (head)->stqh_last = &(head)->stqh_first; \ } while (0) #define STAILQ_REMOVE_HEAD_UNTIL(head, elm, field) do { \ if (((head)->stqh_first = (elm)->field.stqe_next) == NULL) \ (head)->stqh_last = &(head)->stqh_first; \ } while (0) #define STAILQ_REMOVE(head, elm, type, field) do { \ if ((head)->stqh_first == (elm)) { \ STAILQ_REMOVE_HEAD(head, field); \ } \ else { \ struct type *curelm = (head)->stqh_first; \ while( curelm->field.stqe_next != (elm) ) \ curelm = curelm->field.stqe_next; \ if((curelm->field.stqe_next = \ curelm->field.stqe_next->field.stqe_next) == NULL) \ (head)->stqh_last = &(curelm)->field.stqe_next; \ } \ } while (0) /* * List definitions. */ #define LIST_HEAD(name, type) \ struct name { \ struct type *lh_first; /* first element */ \ } #define LIST_HEAD_INITIALIZER(head) \ { NULL } #define LIST_ENTRY(type) \ struct { \ struct type *le_next; /* next element */ \ struct type **le_prev; /* address of previous next element */ \ } /* * List functions. */ #define LIST_EMPTY(head) ((head)->lh_first == NULL) #define LIST_FIRST(head) ((head)->lh_first) #define LIST_FOREACH(var, head, field) \ for((var) = (head)->lh_first; (var); (var) = (var)->field.le_next) #define LIST_INIT(head) do { \ (head)->lh_first = NULL; \ } while (0) #define LIST_INSERT_AFTER(listelm, elm, field) do { \ if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ (listelm)->field.le_next->field.le_prev = \ &(elm)->field.le_next; \ (listelm)->field.le_next = (elm); \ (elm)->field.le_prev = &(listelm)->field.le_next; \ } while (0) #define LIST_INSERT_BEFORE(listelm, elm, field) do { \ (elm)->field.le_prev = (listelm)->field.le_prev; \ (elm)->field.le_next = (listelm); \ *(listelm)->field.le_prev = (elm); \ (listelm)->field.le_prev = &(elm)->field.le_next; \ } while (0) #define LIST_INSERT_HEAD(head, elm, field) do { \ if (((elm)->field.le_next = (head)->lh_first) != NULL) \ (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ (head)->lh_first = (elm); \ (elm)->field.le_prev = &(head)->lh_first; \ } while (0) #define LIST_NEXT(elm, field) ((elm)->field.le_next) #define LIST_REMOVE(elm, field) do { \ if ((elm)->field.le_next != NULL) \ (elm)->field.le_next->field.le_prev = \ (elm)->field.le_prev; \ *(elm)->field.le_prev = (elm)->field.le_next; \ } while (0) /* * Tail queue definitions. */ #define TAILQ_HEAD(name, type) \ struct name { \ struct type *tqh_first; /* first element */ \ struct type **tqh_last; /* addr of last next element */ \ } #define TAILQ_HEAD_INITIALIZER(head) \ { NULL, &(head).tqh_first } #define TAILQ_ENTRY(type) \ struct { \ struct type *tqe_next; /* next element */ \ struct type **tqe_prev; /* address of previous next element */ \ } /* * Tail queue functions. */ #define TAILQ_EMPTY(head) ((head)->tqh_first == NULL) #define TAILQ_FOREACH(var, head, field) \ for (var = TAILQ_FIRST(head); var; var = TAILQ_NEXT(var, field)) #ifndef TAILQ_FOREACH_REVERSE #define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ for ((var) = TAILQ_LAST((head), headname); \ (var); \ (var) = TAILQ_PREV((var), headname, field)) #endif #define TAILQ_FIRST(head) ((head)->tqh_first) #define TAILQ_LAST(head, headname) \ (*(((struct headname *)((head)->tqh_last))->tqh_last)) #define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) #define TAILQ_PREV(elm, headname, field) \ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) #define TAILQ_INIT(head) do { \ (head)->tqh_first = NULL; \ (head)->tqh_last = &(head)->tqh_first; \ } while (0) #define TAILQ_INSERT_HEAD(head, elm, field) do { \ if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ (head)->tqh_first->field.tqe_prev = \ &(elm)->field.tqe_next; \ else \ (head)->tqh_last = &(elm)->field.tqe_next; \ (head)->tqh_first = (elm); \ (elm)->field.tqe_prev = &(head)->tqh_first; \ } while (0) #define TAILQ_INSERT_TAIL(head, elm, field) do { \ (elm)->field.tqe_next = NULL; \ (elm)->field.tqe_prev = (head)->tqh_last; \ *(head)->tqh_last = (elm); \ (head)->tqh_last = &(elm)->field.tqe_next; \ } while (0) #define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\ (elm)->field.tqe_next->field.tqe_prev = \ &(elm)->field.tqe_next; \ else \ (head)->tqh_last = &(elm)->field.tqe_next; \ (listelm)->field.tqe_next = (elm); \ (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ } while (0) #define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ (elm)->field.tqe_next = (listelm); \ *(listelm)->field.tqe_prev = (elm); \ (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ } while (0) #define TAILQ_REMOVE(head, elm, field) do { \ if (((elm)->field.tqe_next) != NULL) \ (elm)->field.tqe_next->field.tqe_prev = \ (elm)->field.tqe_prev; \ else \ (head)->tqh_last = (elm)->field.tqe_prev; \ *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ } while (0) /* * Circular queue definitions. */ #define CIRCLEQ_HEAD(name, type) \ struct name { \ struct type *cqh_first; /* first element */ \ struct type *cqh_last; /* last element */ \ } #define CIRCLEQ_ENTRY(type) \ struct { \ struct type *cqe_next; /* next element */ \ struct type *cqe_prev; /* previous element */ \ } /* * Circular queue functions. */ #define CIRCLEQ_EMPTY(head) ((head)->cqh_first == (void *)(head)) #define CIRCLEQ_FIRST(head) ((head)->cqh_first) #define CIRCLEQ_FOREACH(var, head, field) \ for((var) = (head)->cqh_first; \ (var) != (void *)(head); \ (var) = (var)->field.cqe_next) #define CIRCLEQ_FOREACH_REVERSE(var, head, field) \ for((var) = (head)->cqh_last; \ (var) != (void *)(head); \ (var) = (var)->field.cqe_prev) #define CIRCLEQ_INIT(head) do { \ (head)->cqh_first = (void *)(head); \ (head)->cqh_last = (void *)(head); \ } while (0) #define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ (elm)->field.cqe_next = (listelm)->field.cqe_next; \ (elm)->field.cqe_prev = (listelm); \ if ((listelm)->field.cqe_next == (void *)(head)) \ (head)->cqh_last = (elm); \ else \ (listelm)->field.cqe_next->field.cqe_prev = (elm); \ (listelm)->field.cqe_next = (elm); \ } while (0) #define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \ (elm)->field.cqe_next = (listelm); \ (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \ if ((listelm)->field.cqe_prev == (void *)(head)) \ (head)->cqh_first = (elm); \ else \ (listelm)->field.cqe_prev->field.cqe_next = (elm); \ (listelm)->field.cqe_prev = (elm); \ } while (0) #define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \ (elm)->field.cqe_next = (head)->cqh_first; \ (elm)->field.cqe_prev = (void *)(head); \ if ((head)->cqh_last == (void *)(head)) \ (head)->cqh_last = (elm); \ else \ (head)->cqh_first->field.cqe_prev = (elm); \ (head)->cqh_first = (elm); \ } while (0) #define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \ (elm)->field.cqe_next = (void *)(head); \ (elm)->field.cqe_prev = (head)->cqh_last; \ if ((head)->cqh_first == (void *)(head)) \ (head)->cqh_first = (elm); \ else \ (head)->cqh_last->field.cqe_next = (elm); \ (head)->cqh_last = (elm); \ } while (0) #define CIRCLEQ_LAST(head) ((head)->cqh_last) #define CIRCLEQ_NEXT(elm,field) ((elm)->field.cqe_next) #define CIRCLEQ_PREV(elm,field) ((elm)->field.cqe_prev) #define CIRCLEQ_REMOVE(head, elm, field) do { \ if ((elm)->field.cqe_next == (void *)(head)) \ (head)->cqh_last = (elm)->field.cqe_prev; \ else \ (elm)->field.cqe_next->field.cqe_prev = \ (elm)->field.cqe_prev; \ if ((elm)->field.cqe_prev == (void *)(head)) \ (head)->cqh_first = (elm)->field.cqe_next; \ else \ (elm)->field.cqe_prev->field.cqe_next = \ (elm)->field.cqe_next; \ } while (0) #endif /* !_LFT_QUEUE_H_ */ lft-4.01/icons/72x72/000755 000765 000024 00000000000 15250425210 014047 5ustar00vicstaff000000 000000 lft-4.01/icons/144x144/000755 000765 000024 00000000000 15250425210 014207 5ustar00vicstaff000000 000000 lft-4.01/icons/144x144/router_cloaked.png000644 000765 000024 00000015452 15233443070 017733 0ustar00vicstaff000000 000000 PNG  IHDRFbKGDIDATxw\Ǚ ݍƾZm-%eId[gd$Xq99|8_d<{4DZQd+)ɒEHb!ow[Ѝ @7R֭u[Uo/J~U,T.NR^ A3?[ C pQ@? ou6Bx HJ ^|4Mn)yYϻB{Uhttԡw8V**6Eb(EQݒH)aA6t@/#BFn(SσO@86}&{RT׊ndo$<`N6ViR4-M<$])xwSR|Px\8p&K`䆷(B~7Z (?dpmxnӞ9R&Hi9SHF7ߔڦx^!p:7lF2'6;ӧEv'*;HJ)jkNuE5::Ha.ޒlOZdYH3uVjcg6fblRv;&D-E@CSUU57FMp9K^yvS<&9]E]YJޓд}LYL$|a@@.MTE1LQ-*T Om@@" -l! NX,fcRŒDn!| ksy$-bzy\%eGnB&k"&&lr{ؔI!4QcU&ɮ0d+L S@&ɮ0d+L S@&ɮ0d+L S@&ɮ0d+L S@&ɮ0d+L S@&ɮ0d+L[6d2\f?=膎aDcqa-gNAQTEQXTV-O HJH%5Z_$I$T*yྻ;9=g>((kS_r gӉqQ_W'{p8vvǑ={dR\!QH'S`bjOxUbτ(>'Iќ(]2ۨOQp9 ٬-8C+L&,}cc/iijB<ؖ` tt9v*KƐlVG׳躎夦:D2$Lu|{ 36>w?aQSSEkK'OpXTS?3~<(.Ǎ!j$ H,)徿a(B*|^.r:4? }RЃqۉޢߧHJɅ!^y &&g/p'x܄*+]J.v;LMKu:ڎ@@p0Bv;6t&M"D_3%>n CD,'L1<:a\BScO=(=ݝG:P{ޮNa~20DcUWUW(%ҙ̎Z-TU 4/R]dv.7'?%I)fvnjC<9sݗ0r'NXͺXKM=c MRCs:tuul_L@e3u]g/W~٬ؓ4S }+MG{+U|@_XbӳhZzu4|xE!诠Ǎ(v:gw?15/~RS6!~c³=jRSacY*T0 UWVz/De)n,-YZ#z/太2)sۉ>rg=GJK7ܳ)y;J.L:M6aZ,v7Sadtw>΋,m{9;墪$ K-s]=cii1VbJC}]~l8P[ ?/&'n3s:q]}v{iL陹o#X|(cf<<,xc-wylqi FhUlV.7A<`!7 '>hMQVçaDQښp:7B:ś{$sBJSFU|^-ZXXX0ewiX,󥻙en~0trqލz/.=CQc09=^?t?55EᯨTΧO?p7+ͽ܍{_B*"gp]\vE@U>l?pxD1a1-e?SLOϐu{/Wi5E#cvvV+D?R.244TpT(tZc||ئ3NI>%$UTz=z~n9pYꯇ9(fu%L_o?zSc=mǚt*ɐH&xqGfaq0xńP  J8nBAB Lpp8L4czfnwg'zdt=V+|9UU@(Y~Tuogۧ:\tT2E&^>Zט%zi)iMu5UdYBs 4`p׼Fͳ.-~%}YUUm?]#S@sfu Cgph)/ Kw{`ϕi;մ67r^ZmjrG[5zȅ2N"ᰑL& |eM]7e>aX/r?tNn6@.&r w]|/qufѵMvJ5IBd2rs7YX¹T<8>F~le%ވ/t]'Il6lY+#>*J>˳UXCiD9jL_s@JGpIBIb?LL ԧ$)`4u1p5$tL 1c˯!id3Dm./~ dNE 7 Hg`&.Z:' |ksy:[>t031tH)%/C. )p?BGcLo./C*C/]u7FtC_ϬlrҴ z[޹=A.ɭM<^J~tmSne03.ӑ:::4)-!%|3}}}E{mbpxun+ozkҲ 8}T C!@D$i-C""/\'Ko[%!ŋh\2Y%׺λ;%ј,ohdY]C/׻gGRJ1pyo/ϻ/yMʇDaH!/o|ZC; y!s3)!-crI$SD0 )s݉xCI)%nQ葕!%h|ӎSMƒpyV_;wLȜiD"Uo{/o:/O+߀SUvTRF<4j/vvL}{bpdY!&lw886T?lwfuRZTj}c}7uoSFUEf˝3EAPܷRJ$5nu,Z:{uڪRe:'n=}RJ1piAEI&`<9d({[޹#5UEWN 9Df! Ĥ ,`\"_"ikKј?>"(LIENDB`lft-4.01/icons/144x144/dest_closed.png000644 000765 000024 00000010156 15233443070 017215 0ustar00vicstaff000000 000000 PNG  IHDRFbKGD#IDATxkp\}>-ږ%?$H@R Ch&aZt:1@M!4)|H\34Qi3eɖllcz{VZ{%ݧu~_{{:{4FHT }g>'"[E6XU@Ic(NUЅ[*A*=';Aݍۀ|əEدMm͇Rn ΋@}}}A5uSM|> Ca P`߂DDu]\Ŷl!O)TJ1Kׯ)=s7?+L$ 1Mk4yq\b1h,:SϊﴷzK@ݽTLjjB\ˊ3>%aOt@uR^Nu!ʠPPSIXVn{E_˶< 㙊att )ؽ;ti~nVP0C5F4fM~tn m۔I^#D"7m{/H$f͝W eY:ɾ~ )ϾwlҢףʎw2>1Ao_G5WZi\B6μ: FOք֛xy-]ʶӿ2`(֦\OI%(#cBy%N-zRQ_W7::#ei1ԤL_OIMhRq}LҤ2ÉYM*E[':Q`Q1"|ぇ8}z(,_.`(*'DŴ@R9݅#$g|v94i<xB H -Z '@Oh4i<xB H -Z '@Oh4i<xB H -Z '@Oh4;Sw~ޓ}E|2TU/؛u]>8vHQ ,-|'fRy2ƞ9{3|Kv2bXRWcN3J>u>sHVqތbgL ='$w3 vC2i8u_\-<.4ә|~j<#Ȫz;D_xк/\EO+@F3OZ<"HSo3|4=FCQ&F2J\ssFj-P1Wf_#cA~,Sy 3N |ـ 4JQS:TMDcqFW"yGd[NMG׉6 3MjkB\e1>yhL׽oÆ5's)S#"J%0 `0@(m;Ĭ8( \dgGRv^/]]]~#PɱQu0}|>|i(@J L۶aiN#̶= yEDub.ہ,_+<]C~> yޒUХLvoW#l jj%,BQ R~#bHiA>T.Ano wSFhi_IENDB`lft-4.01/icons/144x144/firewall_smart.png000644 000765 000024 00000031371 15233443070 017742 0ustar00vicstaff000000 000000 PNG  IHDRFbKGD IDATxg`TׇMWWQTPQ*HTADQ[hAzn&KBi5|ѽ;3w3#F1b_w5,$:>(E` /DlKw%E # Z$x{\A;nR$$*61b7ls&`C At/"xSN:P@R\L&E&!H$@R"""th4Z4-jrz%<???Ջ( QqIApJ)䘚ʑJ/?#/VRHYV-, __dz{nE% -LdR,,PۤTb Pk B:& yK@q =EuXK 昙oB*&7~zCx?KMA4u%OSHE#i^<`kmi3SvH$.EQ|4A(5r_Cn"Ro]s3.qKE$# &;+x5de  OPd\?x8mUJr ruРg>v EQ@WV (J) 9D`CPT\D`me7t?++ "!Q}\G*+>>T\m,15 +%e^eQ{gT:@D&5cf@fUDQcaa? SDıZ U͸V@uTj5BT1La=% HEI'qc@`"BrT"H`F HeR Sd\BߕT" #e^/?AM8)L)PzyTJF#HEZJXr2R%(шzR1R2nO2R2(cFy!2Bd0 a( #/Q@F^ϵ1uS+8I@EEX'i҂R3Ma+xBd5nx(GZl3lڥ3_؉SH|q:+ cbgّcxgO_:޼nbIIgdEVv6y/7z#ckkꊻ UO-,-'S7}"7/p^ f4))( 95x{WEPL~A>6v$&'S*P?? FP &FDtt ;ܹsS)Hthބ@MHbbԯz=SSSuʀ~a"q)-ƍsv~ؠdffJzHp.] ѸQ4nߟU( =#'8Jbb2 M_X] d|2ڵiŖ9t$5\N`Xs[hΪ5븛@tL&23gLZ͘X.\JZF:gΞPҼ9;' o{F=L<щ`aa_Cnn&Mׇ/>{f͙ϙZՓ/>gH$  s}V=F IIǸ/''A"sr8|;v&3+ ??_Q)upH(ҟQ@GEfl,'NxyT3ghP(LIKOԪXӑK9z0YYZRv-> JLlSg"77EIq\l-7šAVckcMLl,@NקA/D| ?P(X\]qwwK$'py7kG"jo.GVH$&QXd1ԫ[\ytL,+W%&6A5rM4$""l0d2;GVsrUjvɰS|:c5Rq/ 5~m3~>4 "ѱpt:!dgoШA}2_A@1{^ KԭS= hxѰAr9㿜̝ڶ am9>իWWvg]I)սnjYfovϊqz Q7Y| RXPJB.ޤgcnOp=<`kk ܽ=qaΝH :=73|rߩj,_)fLjUOw*3gUb^HKKř"J%>z7XFfמ}\q_,c#غm'ڈD"`ggKVV6(rݿS6jUUM$=#~}di/ZJrr2 ^seV7Ix2aϰ̢;= i٢\GVw+?__$2&5jlٲp)lmmHKK܌$ƌȄqY#Q@ePtjGRihZڴjӡ*VѺU˾[árSvjNfテ>L獛^o% W!p:okHHH&^ EEEt:\'yqC&CjZ /AzhNjjnLa~ttB^~>䓏ӦUdQ@%dff1s|c0cjjdؐE^oq&?mVKzuP(>H|bS71ǟl۱W2Qxyy`eihC#nr:gss36mQ'ү>h=~99ѣo:11qHfp?5ڱk>zǎdѷ˸}}f\D[_^߽VE'xxJԯW?|>/Ǎ&<<gg>cLr6oá4_EgӰa}hJ#ϙIz̨ٺm'mۄ *=s;=Yx^Q #3k++011ow <U&.N z?j&P9̚9Aغm'O;u0XOƚITH$zݓo=~ [;;ll  DN<ŦÝ~}{ʈ:BNS^]8ȒeqrtdȏSR{p48)pvvF$,Zs/>g)$&&jN\c*4o Z7n$cۈu?rz8)Q rrrYÆ?(0nr=<eҐ+&MT*ac~>, .Ά6v^~>߯ g''ϙ=ĄQX[#J>kEE8Vqœr艹sbng*9ymwr/5TʁC).Vӽy 6Tى7"/Jֆ:j"+^}z}}á\xџ|0R:ˮ=j$c%X[[ȏcԮU ӒQ ~j*nӤ+~#TJ7QToW*\twegйS{Z4k_3ob~3';|.ps~{e+V?wݗ]Ǭ9dTd]UթMbB"~\NM$/?g(*һhxyz+q~ݻ H5 JHL MO?Fղlj880yx<زuQ#?6;wPwFth=""ڊM_aUߠm<=F} b>xA<l`'&N+W5ykLLp29PfDEdye\̘}_.>ս`}ths% _tzH7mxwOG{ TqpO>bؾcR\rrqsuAEX)_}9GGGC&r9Vddd`me'7 epbo&H6dgHڵSV>zb/^&8(wY,[.SݛJ:Q5߯E"4lPԴ41oAN qXYY"c?>PXXLf X~ӧLztv5kDR1|L>[FCӒDDD$)x8;9Ͽr 9jUmxxxp*4\RQ*Udfg#XZZeX[\5j]*f?6o iӪ%Ç}y8Z߽݄$A`¥xyG6*V}/Eݏ׺vfԪ̥+Wҩ#ӓ?o\|f0J@u5k9t!}EHfV·#FVWsyۓA<+!17j5bCܹ`ө3쫫3\ f`=|Ш%iM8 ѣA 7psu!(HGt5456܌ΰk^UÝ8ƟvpanDFagoDŽqSK=N>˲|?Iwz[8r8ABpP ժ"JDs(VӺ~H=󈌺 mۄPfMUBD&66#|*c}89:ҵKGvD< #LLdt:̚nmV[nGZa~xxѶMk,^oajfD*Ύ\&N/ׯGΣ>F@:QQOO͚= kkSqpEsaϜ:}󕦌W^c㯛a`JIHH䣑,*2\srt]V<'&+;7N^ WQk4\rXNԯ[vEr); vzz׺v. 31 lmlʉGqUpwOf}i,^)J~jمu~{PLL,X[Z2ux]5븟+cGƌYs8x8vڃ\Τ c{dۥ-R~0kop?5}щ";va>r||xkTJ%GBfzB`ܘQ8;9piDoAgʴ|4lPVKؙܵdz7i?wV-7zt8~$M_iHپcV8yr (:FagyԮV#H>t0In^.N.2dddaǟ689:Ұa}& 4: RuV &2nFq#TJW˙3g3+}1ׯ[H$$LLL,99$dt+z!$##vmZ>Cv,7WwHD *uW%&&jnO96BN˕uvrIFddfbiaAtL*Bo^v=Onrrayhھs7AC"p5nۉ̜B@T%A{رsXcmmŘ/>`aILH߯o4{a$jtnRƎNQ8;kkrs {`66$&%h4875ks]_ѣ>A"fz}2y4o֔.f vȈ,j5Oaz88سt|OȬS [~[;Wdl(**pVӵsG"""Yl%?'%Es׫޲E 2T)KYs>@fL )-aY<ƥWM|gD܈UH ymT(u$dԫS M7c((,IQ7znnn,\Pˋw{MV-eɈȴI_þ  dY|8t0 J9{5kԬ̂30g&%|}D"0oBC3|`6iL3R^@G+սIHL"!!Ҡ~=9vM3oL6: c Wy~v еK'FF(k,n%C?ITT4ܿfX]4HfMjX[[bZFH@ff̚{q?5?d}8Xb /.~Y\JYxW{`gGyy,[{{;͛Mq#\r_1@ݻٻ}ߣZU/Ϟ~1Z oaa5&27"P#"647jw&Y?mʴ iɰӜ9{$ Ilь.[;^E._ƩԯW{zލCbeiIM8pC@(|y}~]EVW4#PÆ111ԩӸ8;#Æ "33-[HV-Ub@\φFuH 臩)+WhqN N_5G^o܎%v\y3TFJ98IDAT _Q.&))V~ܻGrJ ];ZRCp/%//OhܨE"nNbR2.x{W#""BNa__i"cDgNJJRocffi29yjR-/ =#i_;*hPձH$F$\CV~JooY&tZΜ=5J&322=p̹r^^M ddfj1p’b5&r,~T*e֌)˗9v~~XK^~RڵjҶM+^iА,* g>n܌Fnb/>XYsa9ztRWSfffFaA!R7WW/]N D&Cє`+Y$%X )m7,&2&gϞ̹󤤤o= m;@F ]&͚BZ-q9w"'N*naaNosinF$tp{{lqvr7Wsߟ{,X{SݛWѱ}[y%^on﷟C< kW=sݗ]ϾZ@w{ѭk Xz-Ϟ{b;ff2eUVԬᑑ)t/WىLnFGK5…Kthߎ{ήEE4 a*5z︲Tktj,Z6m.vDNQ*_6KFW6W^Ԕ (vMpP _uپsADߡ}|}#HbD iќjժR* IxrTqpah5=~6ZfRC 1gcog-2+gɂ?tmw˯,^^h:T{:|S3SMH~~;v&0S'&f47~=[{ 4& iP~ܾ}%J_Xӡ]BZՙ p#*&\WĄjR*@?mt{ ]:uk>{k~5ujLٷ 7c~pc sڶ mVKpLNCѠhdy'7 9U{Wm; h4cb?q ߖ۳-bW:y"M^i>ߡcB33SZA, sEL&(r $8WBӑ_PUP~ &JexNN'R^ 1hcs"J L9\ս9t(K-gŪ ;+4$DU(}ޫkN#A[Z.V#C'(((ގ8LMMN%55CBIlqrB{lmˤrprtd9TQٽw̻ZUڶA"`䉸{Uniղ9U8oٲu##F>kkkn1tܑbΞ X[YD1&9HR\]\ ~ԯ[\]\oYD"Wgd2r ~nw{E Ҽigر`RV^-۱ 3+<jX\]\ؿ/_3LχĤd23QH$oȵ ЯBJg''&OτIS<}~AHf̞ZMn^ȤR2m177#{OqҒolO8Xx>:##3;wR_#5jk>_#NřlΞ@RR2w{ͪҶMW\Qc2}Ϝ382}DrϜMhΛN_OfVxziIw`I611)qvvۺ # |MPoG)#>Fw{qh(.]Vm/WڊujQˋ&h8upֵ '/\+ѱm~ r5?f ?©Sp<|ann 5A0G*!t%G)Kb ^2vfz(RPPHAa `W$|}8~ Y8t}?i٢!C ./6u.}9}cZ  v)Q Ѫ'LF:hѬ)-7{ΣI6xgA @{{;zӓtgdxw60KH$ >U|LǃŃ7=KKKLi_S ssN9ǜ=꓿ttb&e*?%C$n߹͛ѤܫxzWWx~b5Y9yG|=ۖ^/( DHFUZz9{_ҴOH@іR |<0 (P*˽Ĕ vAʝQ-OEʊ;TN%ZIğ}=:-*QDJUC`o V# @ PPь((o"s12h=hSY%FTtŹUS0 ?XAxFD"5jJU1Q<HQT_+ EJbDȕACYg Cr JJ!/_?u 8+I#1wj毪Vk-$ rRgkkxNqbq~˗4$[ճ )w5j/fH aNڅI)R>Ը\N%r4cC {#Noxɚ='K7Dc A`iLB7ttM{U \ 8NX~> 3 C>L #! BКXH22@d) CE&>"Œ$5BLH1Ĉ&F4:JPPdfE@L$VRd W)JB HQJ@PR$%( % EI()Je`ħ^/Xy2(H@p(wT}5\mDGVIRP xNu5y@fLjx|f͚U^x|K 7=ʎW^EJOlƵOw;vp݆/w:OFȻ ;vxsüyt&;3( % EI()JB HQjakMKFMK meDlvʊ BNk^),L&7튄*% tFŅHq:ӴMbq+w T!\1=e1Gd, pz=50&D 6KƉLPvʌkT'ԱPDzډA ÁcV#VZVSr.F܌4&zH ʈGƣxKriƉ$_x} 15|^7hptbAV%2iDž1ձaw>5\) x8#ԯ3َyߛ4oeyH6nƻy3/:n ЩS'1RǺt'PbH3a=/Mz#l8)d|4bLݘP4Mdx4k#/>LI"|l{#/>+p[3U%pw34m;Nƻ4\w/˜<.4\i#}Ԧ( 1Qr { A3J@YGzۋ*'o]/ U1D{giMh_e%eƻc^/U<nAƆSBk(A~"rXHWy%,.Kň\IB_jt,=n{\!jt_ćCo[*w"HwtYˏ^Xۋáh f8\uL uOuYgg,f!]@qJ@YHwb)#$pNF(MPWh8f5]V> z9Db[J@ݸr]}|}.z6\eP ( eg 2Spm|RR;%֨؅ъHhT:LnŢ5YtV^J]UQ;'u݊E (13u\I[o5ޙunGZq~汭a=yǦ߭r`m}|×=JoiܷPo]9mg_>$F,jݲ7xzUʃhKa8Ygk O|De_>%^r*A,n184/J@y;mgCL 'kXw>3Us䖡ya}as.'=ݗ #O|?'eG|wOÙi8?V.y+o8ϮT T;yRǚC{Ы2V#x+'yt֟p2p]瑯L/e9g4iq@lMok:;dsh khy&dsZO:B xGKѷ֙)9wAoZ#d^땓<'l&lP4WsBO͗CιW݁}]RU[z +Ӳ 4W]?.hڹ̹+|m;P˶ #x2xW– 3|3ݧPYԯYOǮXU[&c= uPw ݿ"|[ BhT-> ߹SsqpO'1hiQvfFOQ{ާ0u&Աh_Y9qi5h4{tLVN,+ѥ9n>Q;FWϥ9t+2ty=Չ"\eD yy'jΠBè܉O ;C-bq+00 C 1~61Sqض$Zkh &L슕IJ%mW4h\)(Me4E!xEI()JB HQJ@[@V.NGҿso1Rʌp^v*XpAFH$aӟLf+[]pi}isfףM0>*Vɓ֭_婮 Ƕ%GYҔZ„IlHn&֭r\BnƩƔ`'--PW$k|Yj:.{c޼3h] s'>E ~EKSz-S%6`iYpA1g"I_B0$ FP#`boLib$+"h,! ܞ~>C@ 0` lSL_Lq8*W,i !yH$ SH4 !D!Ҹh?y%2[O6N@[w,"U"De+5E a  WiM0Xޑv_0b![޸ilDS:ʘL(KFw NG–0D޹jժ [IH)E[Wo\.'u;R Dc ع|q㺉/b$Rp Fck!Ɛ7lyde+4u)njz]֥Ki|iK`(0,SVQyq?lKʕ'RJGBpPAqiRH+7nu%&Z!{oMJ8> % GcR 6^xRJճY&ˉ;bcK?L[qZ Pw:`:M^Uw1E)p$F D&4%-ZNM N׃UԊF "Fme()h;г^H6Kh5 ۅD'bhHd馀nieKӥ]޽{˷ب%y^4N00t]q!DQcY/!h}SJ1-OBYRPĵn)*K[?^iW!%tog"YreA5K%dX xW"bDn_io9EP( )KIENDB`lft-4.01/icons/144x144/router.png000644 000765 000024 00000014714 15233443070 016251 0ustar00vicstaff000000 000000 PNG  IHDRFbKGDIDATxy|SיGZ,l¾ ٳH&IL4ɛ7L4&m2y3%kӴB!+f' 1c+^$Kҙ?d ˖Wɒl>s9<$b*RNNRfKI I8 w_\\Br8% {Srr0^SI)UgJXѾ_aTK6_U>PqqP? xHm/Wh45JR]H)pjՎvM/g/-oL0{t 8ZAEעVmX,VZ,6{{qc{c,B$Qc4iRX6L.W JK<=$+F zE8C FcӼ5!ÙiIf^:-aaFTxfH␒&3s($'c-5_F!BCA@&SO͊?\PYU_nR؄W𑡊xT* ؄H)ԹI@E/ *F*k  #C+{ J~ۗzUY钻oD S3̱]nR#B2_T2)F1u5bdhtItwo&LJ)e L*⹊0uZ*=PAlP128TfŒ%twGe-vY nTX(zeD#-=y쁬@ZUACLh=E@RJUۮ:Fap[z$ Oc.*,*]jecTN邓=@`)QpouP} HJj$T> .; u6cn:yt*Vg0jJ0&⤎V]V@dw,w7a0@Qzw4$AzǛi[B ; H2@R< Qۤ"Hv+wK T}rRP6MiUv ;{h7+Rp&z[BW(R E@ ^H+)x" PBW(R E@ ^H+)x" PBW(R E@ ^H+)x" j(f1L?f3-6/_̈#iB `4O [9+XRJYY9唗S[ښZ6O{tC=:bbG|| cE5L WTp"3EE-\y'6+eqD5NOJr)LImɐPcS3#杤_ϷFPc!!FtZ-#Fxfٰ]ddjdnkfZ((k7ӧľ/n3knK)R`p "j˯Q̟{_|s; O])[oblt4$dٱk6C]]5!,^k=~}ŵ !L:jjxF֭}aC?)Z3JN`ni౟=gڳBd'1EEErEyPP O:͋\Dž %na,_eKk=3_}s9*U.N˲%f**+̨Q#Yl ˗-9l{΃pkk?ć}£ #Z[[Yml60d,Y/l޼.קNUw0}T_7ߍǎ9{˵1cXv/]k8B=y[OVsŭ{~O&d2ű㹮2A]䶛oB3;RÏ>x=y\t3G\nmoټCN)xK/ju'y --WSa4o<ēK.^bM£#cF_>,!Pv?<|7-_-85sK ;vfۺLU L:_/=YYU־ıg5bᩧiyBܵ$'>IY<Ѓ|Y}eY0.wv cʪ>o{"/g)%M3,-?sJc2|>DNvf̚5wNffu%v횧X JJJ|k]-'?zȧDDIr=YtB\ocy*6shljrooc-~ß^zpn+F@|7>r y_Ӡ'N[./s9@ff͞={:5ߛ=zrP@u=qT*|!x~%+3ʠ6þ/t]$3gp{Ifzop88t(uDDF ~_ܵ{vO2(73b3A75::)ʎ{@y']Ž0!R~a:& y8wNY?[L/vk@66젩uO~~/ Of֟]@}ϝ0m?md+p;S5Ú8?wk׽ose|󍮅ǝ[n4#/\?t;~PT1Oغy̞5cP\X=qe-*^~o.~>k 2L׿n+?9ܽ{nOёw˯ܰlOsHkZgBJ+_n vMt29-͵5aͱ] ?~>A(. u/Nys\G 94 _7믛ӭ;;gwؽw?/  Vl?<سw?\Gdee /iSx?{={nl}@{:hΜk9v(%eȶy<3f̀Һs^Vy{%ZmVrv"6&[ցPQYɋkqà91!iӧQ7O i,gmBxf˖|'8v, ǟ~;WtB$`y=^NJqwN:%\ba]e+n.nfXq[PK\@]Nf͚+X_W+M|-]L|\NC .v=ԺIII7-PW\whn6yVC22Y=k>FžR%{QPxd;{uAt$N`<̹}ϷSQ ,R_@~~^z '8 M!5%Q nStsw9}c`a\\7ްysvhN@h#Xx!es1DZڝ)),.DFE<FSs3uTPZVITV΅AϨV3k 5mI #a,]Kc28~gjk҆@1.)dRSHwJ Yu&::sc\ghᠪr**+K b6{ޠ'*"pb;Xbc2lJEll,$cXTDŽlnw`zuzj 1b $@dD$:}ӯa+݌!WB:]xdZm 4ŋ~Jb oR  (}r|~~$T螚Z ~b~oL魍o Æomp9͜1Q ĻV]Irڽ;w+WPVf:d˛9Ж$@w[ ˗ve x5OO/–<2qH?ॗ_ǔ&t:=O?$II Uco_&P Zy߹%KO<9dlw/#Gtzr37ްaUV+~9>moo.UB3<9qIIC';G(>[xVv3K.YRtoRj!%}w8t9Io3q''9%qAC544P|Sębl&^'5%iOKe,FGGYfX 3)bSRVFEEuuT\\("##e81vl:h-]@f_ tS: HFpOm= H^;>HG{mɹMX8OQVPHk۪Dv,w<֞>;X. {Y^ac9${: hb@rKk͉_Tf% !dO./*pauqo7K2n) _,mZp׺(c|>aϴm Պݹ.x:+%׻@B8@<`2+WvLfƲ<|A7r]1eW1}I炴5=4a_7)il;S">i$=I^NRJQPTXi [bড} ٞ/R0hhQW   |;@/O )hl2)+W˭45;M'LH*^3&$#fy:\|ƶ䳙iI{{ORJ5!xPV( V!LMXӓjOBYY07n .5pHoT^Hx/>@H)EaQo%<if!+W#)il2uqf%[_P;'\C@ kXl4LHiDC~pבwx dhjBtgPՊl4Kp<4q⸳+#gKJHPT: :-jH2imcڰX\@>5>i7ud𒗗UFޏslJVj5h44j5j R!/Gp_MH)8#jvv;XmmI[_۞>"y*OmG7d ?t߲R ך}UQgd$ '(!IH"`ɷؐ4KA Y(y'3-1ϗQPPPPP/Ǩ8\IENDB`lft-4.01/icons/144x144/source.png000644 000765 000024 00000007102 15233443070 016222 0ustar00vicstaff000000 000000 PNG  IHDRFbKGD IDATxkp\}sz/ZIH_t-n&/j#:}6 4\ti l0!0@S¥i;-xL+_e!Kv^{}Ҳ+˖ݵ7Ҟ=GڟsRJd UXw/14 B{z1F>6ЊŻ6H!`07b|Fo4=cvܱo1w3ۖx"bSzz"S!nB#h FQmUj8p#yXgk5ɑN"b&uOj+5<GmM5e ^5.# оuɲ^R׫c5ՕßoZ; }#j/\}IN` 1Ϸyk"ہ v[A4#9h~ss{.LD "T|^ 8Ֆa·}mKXTW PMUʪˀdߡk!<)TUAq)}GbC];;M [3>gHDP` ::+@ۺfضF~;} N??D G:Xas9NB=Qu9`mYz3ʰ=6e\s~s9-Y udo`ǣ$ȹ[ wVj@x3;{{nF; U.9s5ۄ8bLg$=g/CeRn{?OY#ree"+ HRv>D;ws1R/(f| y oHS`vw7V6d"@"O|-[*Gذq;(E^[9ᩫʢ+ d*}wGӌ|fˉރpKS!1~L?*.Klq[2ȋ+s'>`ns}&7%.36mE˲1uTz{ZZ ߉GejѕexXŗ})ȑ"fdJ(frK|c?}'?ʋI'jrrUmHts^g6<'NduN@W\`0=gNw[^Z1\?LR<~GS]pAA%7Gԩ̶={s26@PEM__&vH4BWB̶ysd_Z[K5:sPs$9ІxλO(bO(4F5*~~#g۾W-+RVR]+6lxƳkŮFi|rg Xޝ_W\Ж3,wKzEJ%ygΏXF~?-E@]_^* \q'oLwd?A랽EWIaxh x H^`eT*g'O Ohu*J?az{@z\g=+)``*pHڏ@3gX}3?pE[iJO$?91y{|<ȏ zn1 P8o0zX}ݙñˊWgẊ@O<(Uc<ƚGhlgCRUYzum{;<<&V}<'֎NgcaLZd2kױ}5[̒\r²ox+oޯZ#“O,ƆBov?n444IGS<ˊcke붳:#êزu^)9P` ~oZY qЇRYhYRŝSfTO/Rh+ HRh+ HRh+ HRh+ HRh+ HR PLԀLijHMQ*WVғ=7@ƄHQH&y$RA*I]>HTd7ޞ daWQgQ1Do*C< fo Ьv'RC*'*-L/gm?O d@,q_c@ucL'F  KAT9gϝ9u[C)'EhTc IJoqn5hswFX+P`[~ϋ$L'2F1 57켜z-_RFmx6ma, Cz(?"T!JL&'7eQI6ą("fŖen,2A~Xy]I%章- h0B-J@O++S `ZywnS}k>CRJ]+!*L+IENDB`lft-4.01/icons/144x144/router_seam.png000644 000765 000024 00000013623 15233443070 017254 0ustar00vicstaff000000 000000 PNG  IHDRFbKGDHIDATxyt\ŝ?uݫ$kfmlIƲM&yC$ xC33L dB8oޜɛyYpHB0^w˒X}i7ђP˒,Ymݖt?]nϭoWխ~::::::qR*5 +%RR#HG D {S>H.JQؗzJ!ěU@RJ:w"@b8׹lڤ OBj8,TRrbd2b40(@Q %RJ$*PCrNA:777_QJ)ʫ(0 X&,a鄁@@PCōR sRvNgxlU )wHhnb6.I0~)(;rڻ,U}YJ(V.ل׏ύzM) hpz,fv}>3+Q׍ xƻn:;p\ۃO!c-a6ڬDl6v;qm6m#3g*MM7DCSMMttuށm٬vwMf%G#di)8I(s4PSs3K˩\ k.L(W- n׍مCww'55u95-dfInN6˗dIlӒY+ goGc*=CGG1v;Qv6݆d8Z7ق^}M[g+mԷQ|{ 1!ˋ(.^g5%#Y%1gʇkĄSIIN&%A#ĄxYρTmӲj ݑ2)%MTVP]{Od(BQQ7g˜iLN,Y5cJGxg{=v-FQQ!EdgeILLu/XZ~ߤ7_8+RhdUl~ :N- =ǻޢ늢՟ V sY7QB ͑NKG|S㟐pp_`˖̦<-)z@ =x & -6r>{źCER^uMM}% ٲD/I@(nv=۶nƑu3(g%E9+9WW{_s^[_ors6qg/koL۹~s fE;5$+-:{Wyz[_JD$\@.G7N<5\fZ[_f˻>.v+:^!Yy ?[oٮgۿp_:]ƃ0>MmL@^G w_a/ZYeWͯ~_ TL@O>[**+߻z!vdsߢzZ3;43{o^6\{j孢mnΔkb&˯"`õp-aiDZ{IkmX >JWV?> k7\w31Qʮ;Yf)'fEQ,vrl>G:Y)-+uj -@"qћ&>r.g6h{={\b9 n"dPDmuD>"]>SaE^1{ᄚ9Yf\@gʆtizXڬg ް%!>&PZZvGDZ>`綋9Ė͘WHjfJeL imآ ! #gGWpWUPˆ9ϩӥwtL&Vo 1,Jn5l-%"!_db _x 3E1OaV=xiz;nxK y+GK ؈-JHOOu/+曩?o:"B41̶+q.]xW8f:NWcjjUU_^֙e_ hݺ$Ag{'ZZ[4+A|IZu׬Md6xbŧgٟ<]_w?/ >v?< oe˗m|ρlV;lFAMgoo;$D~ė{6? n֬);v2;~ͳϿ [g-$;SHkޗm8Gzz>oX5QƍXn-B)%ee唕 7/7XrY0Z9^vçݎd,X@k$4ɴlbN?8q@0Yg+8{wW Cs2RHj8]y45`4YjlV汈X Aq*WLJß5iTc9zpY||i$ `Qb" qdP5P}꺳T_D1(g_lbڵDGgܶ%4(n֭[p\Ga Џuyp{]vۅgm]-ܟk-fSqYx%ffFװ~}0CUUZ[[illVZZ꡽wjT;~ SY≍%+$%-ƑÑ%K"~.sFQH'㥫 3v;M ys k0l`F3͊f%>.%2ӯ\i欀.jauyvBC׭E(%3*evxf.ק5xzq{do49f\@yeet'zAYY 36̸(^>)= 3mœa3i.^5Q;~i8 f޽Z1yw{|r(\5CmDBv'~g-L|pC~ןa+q7{$??e]EO~2VLJ˯xrx*,[AOl"==j.} ڵ2 +?]Xi&:A[Wֶ6~OBdKiiDn~ef߀٤o_gp?*^;|>~O[!{{W7L'ZsA0s^}Xnlܰam>8oIOOp`/ok_mƽ#V@CTVUEIv;J6d#K,Ķg~nwȵ̌7&;+S"]@Pٳg BrssX:֬^3NrC 'IbBwϯ予; 4DgoxuEX]\E,MO5>с@.p G\ Ol\i# 4 14*%|rwÑ#'B^N.y9ddf4#bzj_|y**+;mȚ5l~3kDqO/8x>#JC&cGrJ2IIKp$%d[H=>}ZG---457BC}]k0XVXuWsk/+ͬH\=v#ǎstmS;adZ@\lv͊nn(8Un\.EWwSz%lYkWfjϼ sJ@imkʪjPSs>bB- YdgE,JLڬ)34UJoF좭#Z-$&$#))RRIJJ΅EXxtvvrp=\}\nqeX0 6(C_BB<W׉,Fh"$*T@/PF)#C$@Kï37P&ĄUtBQY>z+)::#Kّ!P OE3TtdyDY"̔@[@U N?0/Z &聄RwOt.a_l" "6x#ZmjAU.PAN\@ "x}>uʢ̴NĹB8˭_e)y|7@À?e7S+OUgL?s3Q I-[6fO2RW׿l2M|^eq@AVjXL/RM n N*O

J5bA ;Ɋ(!T}I$*B`[YgXlBO)̽쮣.G'@݊٬} |ޑN{ zo^s7{\ׄ1 iEb1c50gGE@|('KiiI1Epn4.ݠ(LFF#FAA( D)%`DM ]~mOLnK f "WT&ɗl9\ fpx[2y҅$AOK?>)P+gT"fS4::::::s 3~PIENDB`lft-4.01/icons/144x144/dest_prohibited.png000644 000765 000024 00000007544 15233443070 020104 0ustar00vicstaff000000 000000 PNG  IHDRFbKGDIDATxmp}]ؒ-lI%QJ=e@Bi((C3tq4-8v PtZpf/M ; `@lY%`$]=ܧŕT]Y{>H{9ٳ4M4 )*w='";Dh6XPE~D%!)h)ďF3 1Ntou;Jdڂ}"K RWRT PGGG(w67 @²L,0a|$Auq]vmD"RjFR__ڮߢAL$  0Mk pX,N4qgDԷ=ez[pZۻ7) \ Pd  R˂x+--F m'wMWnduttbu*/-!o.I.-Mv6w;"Y¡ Vj=V;*sAD]U8oW\@S ]{6F5B f]WO|A-%4`ɟ I"bwV(xKmuVkb=LTG)~::Ol[[UCcÖ\weZebnmj>,m 3 l%3gϱyt2@ 5(ԎF K,+mݝV\ɿP(+*OL`F C)ljغ kUe $_~ b1woI> aTY}P6`*CׯŰ_| v݊uui_-:@Y"Xf!38K{>ucrϞ%gOÓ{?Tt|=K?-w=wH"$yh?~dg_DH8炦?%q ޖOW_>þCr >e> Z>J[k_~cV>O0˯P]Q0r]z$≬w؇|nVX 5_{[nm٣`d{aNf.֭]>|-O-;ۯ @}]-uƌܣGr݅ 8G }IZU3nk 8/ues&Q2EΜnL:{&=S (߹}6硭WR~߽+Wd]g @E|c͚WIjf<7daGTG]Ɯ ||QYIP3 ̨-<2Jt<ZP8ǚ[9I_6gPM:v^}ř8x}i(Ti/>Oj.;>i dՊr<{ߢ }0/ ##R^0u؎@P[3>ED+9pJqqP 1m~^wX}u&r7s mv۶ ڢ(.n7=2vh,Ԅu[jy;̓٣ۂI[ض?{IB!W6.s2c#9zm(T2 D^F,@TcT'S'RiúO%϶\ToCmM @l-*NRi7 /P kT͂ks? @[N:C٦嗁xonXc fx"Q)% ~ƈD,W:]x`GZkk+QwD% JM]J׷T~օ-տ008$ɤj'{12[zjP |XR t y㆚sYʬ(#jښLF4MaJ(0ǁIENDB`lft-4.01/icons/144x144/dest.png000644 000765 000024 00000010506 15233443070 015663 0ustar00vicstaff000000 000000 PNG  IHDRFbKGDIDATxyp]}s[˲lb-Ɩ$M LMI&JBL2Y:&Ȑv’HihbS18y,iy-yO'w{=9;Fh4+T"b!B E-B)P9U*% A~ ׷WRJU 1: luJ>MgesZhkS~"-.@Li.\.˅a( @J9JD,²,b8XH$JJ[)iڼysnEQ]; 4M|7^4OqP(L0!ngO6>cgx[@])\2)(q[&&&Dc"!-5GS޲cTPl(||^-J"ĚPgZΒqSTT3+KIBᩩ[Xʐ7o*kuRJ YF5F0&0617];wX*dUY}S@iqPVZaꁧE$%%:? CIYI1n^p(-.Z]Qvk`O*-jYсEe%EZ H4H}kcOʿ@]!PT\Xϧ@0"06 }M5ΗC(Dyhy4Sxw C}lgI]]ڵkJ9t>@LӤ~S|T<({[+7[]ۏ0XkFQ^c{zRPUɽ|ny1]&au]ϒ0Ӑ@ֶ+=w_yN<5zzݔqڑ \ reb<F((gx.p8̯I`lh4#~Fg. HkRzR.d&sf9Nч~ݺ#M7pggbr.Cm\~nּLP[ӓ„:|aOokv\}~:v>5f sZŦ\B2R J+`pŒן-fNș] 5Ĵ#ISh4$/ 2k4$8tWJsA-+?߇Jcdpgo #-/6^7#T+2GCXb;BK_1-@[h4ilB Hc -Z -@[h4ilB Hc -Z -@[h4ilB Hc -Z -V={۟*+yy{ b,팎2RpX +F 0xч8sf(#g5.#@iI %%n&=B Hc -Z -@[h4ilB Hc -Py)_Sf,z!remhߴ"_2[ϔhrXtOt$HЊ3\TR+rexWr;Lģ /0V\ \ff%@Y`(`$2pu3[*2exZ bvc񈭲&Q8~ɃyA \ݕձb,~E"|1?%GyCu mϷNԒgTl{ `׿BwqeuȿpqQ߿Cxͩoo5\Gym*iˊ`sS#݌y嬳yyʓ u;&DX8XRSK[ ^N$1i#R'G>Y))ɾw ^7tfuZ Fzm!\[QϽ ےw_>?Zg0]w9J>/'x%SP(s秔+GO߹҄hmK[Jc bYm+XuJ#s{S[;)aΓ.y GxFca>s&9iֶVsz_ f^Jևަ.74Ϧ}l#>u%Tz~cX"sU#jWfϙ#(WW'oOJ [V\ƷWOw/]WTa.# @io>o_ᡮm5ihuK;1: qȹqӽQ>}[fז-|#[׷,w7 a+ή/3b3V|>My{xeN'8`9xV;J\>R[e,F܅|ٟS_E||v_/f1W?YgkQ9v~c g/+$LUnlagssŸ7/&Iv`}+K,>N *g۩(x[{x?UUpzy(K9ghn&gh_Ѩ1NRRṚ|74m/qf,'X;E$'磥y+|45:n4<:o ϴ~OйyK'pbzrtŚe:.8:ax$s H'55ygx> 8R4H5e 47T̤ ֮)4MGSUs n~=zrE+KnMז95t)%ڼkDo\3F%M8G4ctt&VXbפ3'"n;[Ԥp4F 0&%J47T߱5CJ2R28PIPfq`% QO UߕgL^܆NF/e0 ۅe2 )t0`,'Ħf9&7%'BZEQG2 1u,_\ ȋ!?nys)EKNWAs.F "Z%(d)DE1O# &͍5mFh4 12$LIENDB`lft-4.01/icons/72x72/router_cloaked.png000644 000765 000024 00000006231 15233443070 017566 0ustar00vicstaff000000 000000 PNG  IHDRHHUGbKGD NIDATxs}?ݳ=],@)I("Eȕ8UJ9J*ĪJTr!]bH-JTl4-xq,>;pA̯og6'~*:c#J#a5쁢DJH}CR]IU 9|RHu ~Cʤ! aإKhٷѡi+▶e)9[M[Ot=10;BZ1M;Rנp(I&DcR[ _m]o!qN2$)=ƅo6+ZΒo~ ^aeYL"QKY?h|< ܎eYLN@C*_4 xq4q`n$nx:OJd5 B׺N*[Hr]ivz Ztvv(t<yCt8ZygFR'ֻR J)6)IŐ6aiQbe!ڪ rXHx f@ Kdқ,fSh6ZMV`Sh6ZMV`Shli\)E&A M:bjzσ+lj݆?>Y d;QZ{ޝVpeF&(, ʑ8M'99.r\.PZDa-O5ȣ0lZiACN IJ,,~V"O0==L?G>϶7!>P%%5Rk*yvo+ AJKR}%UVXX3{[~+4lJhoon$hm} "R+WL:k_?Fsc>Zw008L(B$ڃ ݗ,7nLLƢRP(8?scx$vYxof9&5JK6)-)+Y_#k_#>'bxaxrAN@`vǿngl -XѱE#7t:pPZRtW([FFi:AuANGF'x}XTW L,gt:CHp8n%)yy>j,t8DcDQI3+L3>~ v=m7.c Gl8lѱq‘6 ͆ߟߟffƌ/[rr+P*Ë/죲ry?7\ŲbJٲ7[Ⱦ3 K=^999ř~kɣP6a :_/pmM[9yH)k b&bjr^]+f à >;[6ϥeee|W|ttt3?9i R^^#Xή^.|q;ilona Gn7M'_>D3=#^:5IOҰQT )$J)CR)yAƲ1㡱>֊'nҼ`u5n-n kȡZ/sTxp{`dl"kd-r}^~i2t xXdi~v|'TUk8׸Yrŋ|:_\Uj|}O),Cۏt8 Ը1an6jmma[s#i!$uPQVJSV klVS\vavl0v?ʾ}6޶-\N'ǎX8bd.155Ef NӤoD"AII1H)bvfn`PJRHjI$g),c:S5lBa{s#ۛ'D#1dP޾A{q4g>|n/33W@ @m_@{Lu aI=x)%r~ 6&>Q_IiDn%+Ik6e%jR!k5;rOei19yR[xrZr:vںukĹdjS=jZO4fUkގDk MD _aXikiU[<|b,LjV[qV٢C}*-z[g,b6+e}E5+ ߜ ˯7Z3 DŽmrcw ʷR?> #iь!XǗk|^i~9O$u8X[ܳ{4{Bz5w$SJw;vVkC' wPLM47VZ-ei'g帯iZ"gdDonC!nƄ6 p+ B#IENDB`lft-4.01/icons/72x72/dest_closed.png000644 000765 000024 00000004110 15233443070 017046 0ustar00vicstaff000000 000000 PNG  IHDRHHUGbKGDIDATx[lTx.SI|GG%\Li&)UPjPKդRKZyiMPU( xfx|f3{\{ug9s, %ƍv˭5@5]K)$xT@XR]]5.zm}E aH!I1r hbJiYuo.+՞V)$s:lJ(943 #Zk:by]}yNnlUb3<= 5UǗw_>3E|2(015VZjj[.}U/H)Q\XlC#2(XsqĴ]="N9kM`> <wx]96+![X hs:ywghN=O5@ooM}B%>3gtj®'w)e1fOz"PT]]`[HV)^<2ulu ?}gjgb ARZl JaQK~}qįc]3ǁq)"),B5\JaO68JKXQqjc)+,  Dy+)NT׏>Jدuf466aDɓtY+%6ԢnCm:B8 2!+ YL dBV Ȅ@&d2!+ YL dBZ.VϾ^}W_C=_qQQF, JKJz Ȅ@& i?.-JlT,(co`m1 }[Q Q|<#'z2N @P:^cy4= Q(F2Be@AWPC7gxkD]i/Kx9"!2H \)+Iy]C~ibAoo_~55(+-n` ށ3l1t)V` c(T_nE!\DbiH@G| a{n^Z~F^ @ =e&lټ)(ǿkjmxO /FݼKswEˌU7 7y vJ`>zފamlEZbyOc1}5Et68 cFDطbs Fnޘ>^3C {!q=wSgf=^?n|u>˞xwVx6965sI1КaՊ{u`ZH\g?[ cB BȌxSZkP*Xkm?ƉU,JOXnaRXRΈ'55h!=WB,)-ӨyIENDB`lft-4.01/icons/72x72/firewall_smart.png000644 000765 000024 00000011263 15233443070 017600 0ustar00vicstaff000000 000000 PNG  IHDRHHUGbKGDhIDATxw`{rJItHBB)A" H rEWE?& p)JEA Ih@Mhw}9\R̙3z*U!\U"vKATϋ(ZQIlӛ(-3 *@D. A׳ 8ebUYeEU5 t臌$,1ӹ! :(lE9%ІQWWަ.oO^sR lvZVn :aTTy!I˲!2ť&ȫG]yAɜ ˋ IQG?_AcN_oA!2eSWʯ I#,0n@'y{yPeWW GD䛁^QI=JY@0gP={ZEL):AΜ99U][IxێJ("eI5/dU+1HUC$I(ZQ#JO N U*2;wtTU(26nȲ$jF k*=hͬ\Re/qj/[װqӖP=v͙Ye>~I}̸>z,s\df(**󔕕cZ1[x{ybXju͉$:: o_̬R Ӆέ"Vp݁I2f oOx0afzk]>M7d;Gg݁xk!q  7ͥViY).)a~EeYXmV&K zAvK93fs~?__Fx`9C?MQcٰ񳛎J EUٲuYY8\NdYl$Qam<5 <=E" oOaYq3NCvy~hoȩ!Vx^_fNxX yi(V+YYٸd|} g3j|{RR2{4DQ?{ֵ39y|c'$p8LJlLSOCv#'٬,b0HjcYd))yA|wf̖_`X< ^ǫΞ={pb|ojƍEHpi=8.DDXjո9v@b2].:v DEFڼ=&3zp"QU[1vd6DF֧dkD J"5>``93l߱YQeMjq*m ~}]z a4l L1 F#M7BB_P/%$dtoMN <}1a۸x"!,x{ >؟cOKIi Vj7a4k:"k?pb|p8.d Þ\@6m؁ԫF#$o9 KV FV,\BV-9~ޜ?\ kD5@YY9'Oi&0!lqZyчx,z덛~p^4}Ysn|QT a7ݳ;4.؉/eeJYi)F_FƏ߯7?gOy*$%]nZ-{~C>@jjiDG%1K!A 7ѷOxܟDM׺5[wWҳGJ52}#x䡿s~j0s3MFvN)Ӵi3<(;;#2>_êk(**}]eҒR4>ZNXZ,|{z`%<@sHN}`ZjABǎjBIY)qZ3I4kV^!"CfaylÜ;V-hִI >Fڵi@m ߟ!O<Ɛ'ܼ<=JJKloז{yt4a,lٶc카o{M7lpiiHp81LJ(**y<<ݯG t5FFUy6;77P~= d̨"=$3^}IO0 zؘl9P]_G9r\A+HO )JJKq9} &84pHiحv\ K:wx Z+ 1}ԓVmkM׮4.}MxBCCi~W3 Db6[P\2!Au1DI$AqcG9Z AW&# ̲+蜘W09][ƜY,\/PC__#Ed ֍V-yT T+٬z iPx?.Y9ԯ~ B{T+d4j/SZ^N?c_GYJy|&ҬibDn@= v @UֽK_г}^3Qhn^2i30z0Za=M]Z = 8q"qï?.)^Μ=˜o0z0z=AA}q -xrƿ"x\cЯ?xmۿ awRXT[9u,_Ș!gGn]ޭK6\eyU t=xzztZR GsDV;F A-WTE\&LFU'L9tʋJn'##<+ۡ8TR|sY~}{#GGe{[ipwӷO}!˲ Aɇ)(W*HD\sv,˪ cV/ˊVQԚeV (*h%Ւ\[`w8Puw&Wbbj9jfͤBW*jIU4~ێ3ݮ"M6_)&uXbϨ erUQg.FQ :ܬ#JIiEq#R"nHQUe.쒕F.ǎ^PUuAk~j&(:liUuSmTĥ zztZ-wEp\ 7S%P$JC%AQoJ+씝UVdUQ-"dWVrL,ǩ*bZ D圠JْjI-AqnIENDB`lft-4.01/icons/72x72/firewall_stupid.png000644 000765 000024 00000005347 15233443070 017770 0ustar00vicstaff000000 000000 PNG  IHDRHHUGbKGD IDATx{pT?{ I"TDcg;RꋱZ[ڎNkv " % ɾs&K6,J`|sy.VAqnjjEu4h1i*1 !~mKyܸqb- B ZR~a~R?AR%uLe F˥k+ lA@ S,K,Bքx]I&DcN!u7[6#x<|ЦGq7a:"1!̢Ks]K̭V,kpKypDiĚ}oOA0wa'=˲1< kjAcRJqPgT՜aOzV U<$跑Rm/65$f|`LjoJم)8Gߚ|sWN[f>h?=WܜJob?#:ef?H&K< Hy?3~q"0hd֗'ִJ֐8۟#E=ΩG?˧뎹`%8=5ma`윧l:#qJ2檩TT ckwdyqS9a< *6*{@Qr\{:3ISV,|.oNeUw>Mu{ >϶ET)9@q:֯D'soDE;Tݹ8'4XrTHƌ]1:֯DxyJO7Ӳ^T4Zr-W=1㌭eX֗'ޜWGc=C >ie $Qub|p=HG:奅ěG|[fRH%HJBҼ%Tߵ(8|Z@o̕¦: }kI#a7ir8h{Csn,ޛyk;yhP΂4o=YzZ1Xx@8@@Y^@'L'i=h 䌭ewȮd0X,.ֳ,o@vx S>"TZL 0l^9哯Akd!9Oو~BSy |gO"b!]4=+YMռ%8*I{wO#-+DGVgU~ DQ(p]s?tEeœۺ %IWd Pq$C1{OYWɤ7Kc/-+&[wвbA?\!cf.V I)b$\׭S&3b-ě?!\d{ M<ֆ~ bhqɢ ϛův맞žq'^zGǘNkRT)Ps cnz c h2(h3iW^X_xq{%}T[>VX\Y}?I$(z }[3m>cm%)EW%}".Neǟrƌguwx9Fk±ı;(+j/~X$>Cn 'ᨋcwj'D\d[Mo @=J])lWdO4Lg=&R]%(rx=LFNB.mH6^++WЏ8QL=444r99}!a!=*rtB3 o`K{0 \AR*{(}$,ٳf%!X-=ݤL uIkڶLt3tuAl(v{2M$0¼fk3‘X4ڈ̿0dM\cl?ATIˊ0>+Z띞n|&!̢]b#OcpWTy5gomI׍^~ȩВ"Ru+_k{2cWƘߔ>7X5]h x𼚪e| V|ݗ2!ĄP_!J]ZD{2 VUTm?r%4iɄ%-ay_JkH|)J)FҼ{ʧjFc$f/FRm 𴎁Ӣ$ IENDB`lft-4.01/icons/72x72/router.png000644 000765 000024 00000006265 15233443070 016113 0ustar00vicstaff000000 000000 PNG  IHDRHHUGbKGD jIDATxyXU?9wpQ/@Qch[M3ɴq&3SSDZb:y86δc$}d1q4Kcњ(;M\U ܝsP=}y߻p"<«Y&5U i2GQEPHY AR󒒒-) K*I]"[,BK&k&)!էh(5UZ~Ge*бdYlBJZLVQ$x}UU˄~z[Xzzb0ت?=>!gғ^)/:^ё6,˽kuFQj EU^HI\pw҄ <1yӋ 2vN?mzPcni(\ ԩ)Cr/ANl[-[MhIvń&oh"PaI )f;C $R *f]LkH,XLcZbJBk%#m4q_b4R^^4Ytˎ$I R0JY !I'_ߪȒN(Hsɲq]CH:a- 蟀z4a+D_~:_~:_t׳@O]}x<^BzŌNnb4^}5K(*.׸\.\.u *oYq:8NTQly/ *G ٽ{qzFll 1N'ii8a6Z-Ȳ|~Cx<^^g]psqm*ΙMd|?o6nN(dL.ƍhU/[#ļ?]jc箿^GT:&N%O g=l}foyGn ti^\Ug656y?9P(Hn^>۶}lb\{ig۶O ++O`40ΉLs"Ullyr2$[ ’9VibR¯usXmg9}$q$s爋̬3`l7 klڲS*pD;ϮXvv=5ed O_oeFjbNy<^Z 3J"#;,sB5&ٸi3EEDmEd|=FMM-)II̝3' a[nY˿_lo۶SܵnV.Nql|V&29z߷On﷏ɒQJN9G"Yr;%uq$&&4,*z$pavx<9io#deeE'ɼ+`q]bh%l,f3+*);^NJrRʬ{!8qT$I$wky7gsS핔QQQg8.|u ~ܳDEߛkX+?#G?Uy^ϴogw?[#@jhښZDp#ždI&.6 ٢%>zݼ*"::3!k\fi!Y21q8؟ |2e%TW܍-HEʜ_v: S'C6POԓp45Mr[S%A#*6&>!Kڟ^óov$ʜl޼O>Ν8a^y+A =P]]ee)--ٳ?wR;Uncő9v,))ܖn pMNv;cƌf̘m ȵrX-[ K%~hPUy[ o4[uG}C a;@JN8vMyy9Uva 4}4QQ[f ,}f,[CAaaF4 X(~Z EUkVAQMBf J`UQ͆jj5o4 y  v6ijik:&7h2R4ܲhxDJŸ/٬ -oE]gESUe~6''B{] FE4j^!Ē)CZ#=9q] =')<.xO5M[eYLƛ|V$R:^Tv*KCZ/HZL65ߦTM+eê5򶠬b,STJ%Y:a$OiUUBJH)j@3Ee]ժ=JJJ0ӄ2DSZb8+$kɧd͛w1)+fIENDB`lft-4.01/icons/72x72/source.png000644 000765 000024 00000003535 15233443070 016070 0ustar00vicstaff000000 000000 PNG  IHDRHHUGbKGDIDATxmlޭvR)9.P *R*ZAH(oAIQ6EKVr&#bHm!Vihj}~ *hrݻ釜O؎qwgݙݙ]e^X̟V^($SZsI&1՘ -IP6b9cg-?["DNfJJȚ> }1">l5%T C*II)Ԟॹl'#ז9H2DPKsscf=+C:AU5ye\dO=`lcAUڀ'?9Q_[LEϯO rcKۧ c}nښŏM^or)}'[&G{ W/NjK@_Ho ѨR>ӖŸ4 X, rw`l^]E#0LS{ < ]J3 E =HryH'` PDހ pjYF.T"xgaxxBJTQsF|{6ԭZx|[ЍxO=w=wl&͢!~ǃ|.xby3WiV4[ d+W \A,pY dAEyN#l.Zep94z[7C L&cYNJqC%Cu۷ny jE߹ CSS?Òr9|?{v=ԆGF1-Iy^c/@:scy\ !tijjRQ tαn]dž}gJeLe~ vqC!U:ZZ"N-D"7z| `Ay'{O=Q)=8{ ߏ[7/(ނذnݳ{a#N먯÷xe !܌}OEW2SCϏ[qcI^t$_kD_`;?LAѫxW;WWX4sxozq(4c*M3OG:+?]miO ]wt: ⥻i*qi8ҋ%-}4$ 4ip耭r=w76ozn%AS0hmAuu]r}d+W \A,pY dMARkٖRT8POE>o,jVK!D$? $a\ ]! 4a\BjRKTRj\/AH,] ENbJh2I.^jKT*a( jSJgƊβ@jpۓۦT{"TtFJ6u4A-KD≔R+!)p#HS}a$R|~}"ZR-k?\kR5=Ɛ*Lg<>51{X5>Mr^J4 Dj*%'[8>HldqKi)ENTFH. S{ѨOP=DNo1_R|A׎Vc3vIENDB`lft-4.01/icons/72x72/router_seam.png000644 000765 000024 00000005515 15233443070 017115 0ustar00vicstaff000000 000000 PNG  IHDRHHUGbKGD IDATx{pSUǿ$7MӦ+žҴEjy ;㎫>Td_UXvu?|PEQ<IZ A)mIܼ9GCE&m3==~wnq\OF9Fr%f +.r5=lYL8 x Ox5# jV ,_f2e*kFM֜C;@M3yl%+4: z0$_6~k+=4PGkoZC Z͍!uMתś>Ӳ RDG1Fe\v{%殾:wui VM0%H3Eqi=A5g_ɨ,лm_)*hu8@ϓdLu`־ٝ-9p7<XR+Zu1-qE#wt t!Eh@Q D 3a&AegW/G\(Di@T  ʿU9W ƲyӎU ":2EA`$ '=Nq`\!hq "a6f#a 7a6 $Rd<џ$9$@j,;t{DyD5!-J1A8zTr]vGN^hC{{;kR{wq! #xRMDՊb+RL)#nD):;GZ dͰ#Ü^AE:.vġzKHl%;w6&bU ۃOvD_rCբr,Y6k V_{WmuZ=,EȳwaضTTLŋlۘ"P0ngP9ssQ0@iΥK8kGUۘRnï?.kW;ׄ77lDK̿{VX SBq^)Jq_ß<-1@ur0$xQ6yRLċ)%-ق'| %6m)^$ɇ7ވts}̈́ I&<AX_rc3DJruOb׶K8u;h32d]}S,͵ ?oz]mat1ڝ.tvP5Dy!PyOYT|{>cb?Ԥ$z45LR3ը\|IHNS!+_@r/`0LS,Bgg~8~b#FWwmz| !HL^ëU/C+65&c~l+~*tڋٓdժWG/aoP9s͛i4B] {q|uh7J&a49q=-\݃5!*ʧ[EP辴wXA82ϭ]CR΁57$r 98x08ڃ=T̀lF9fQ^}ߋ 8Ҁ=((yH9>"A!()$y<44A}}.\Kp߆v q# ! W)dgfv{[~'I#a~?FeGh4|*˧+B$ H]~~91w~R=[(e-QbD֖ 1u.h45'OZ,;~VL.-ESK Ξ=󑦱ͭ(T -X0l޲nXv[ QNLuϢ /T:]9 =?:UW^e EvgSAԽ"농hiiŬYXdƳɧ;p r-9XZ$?RknUo-ۆ7c=8Psw`֬J~z)& @́Z=z A˖K NU=˖Owb/qwS~VkEϻmp hl<8N8qP& +WK,1Q=i1 ~acop$)ौ C@֜Q?eY d 1вJYr-dYT~oy?laSwQaB/)xﵟl r7QJn')RDl3-O32&:ps?d^s%-ת3O7m!D]FRͦ1 ?q˰F[|iP\x«KiJf~ʌRd}Vq]dƈK ߟ\ٳ 46fS攔`{6bj >8E6* ;tOK/% }}g/_f[L!Bx\V0$m\ʹ߅éY<Gq;V%MLbR 8 KYNTV#850M@50+JPNxf r&pζ45m@FvY/d 7g"tW yL dB^ Ȅ@&2!/ yL dB^ 2rze{ HFWٲ4٬d*3:{ yL dB^ ք@ U,t\<+k ccH?y@%%2-[S+4bfD_KMJ sZB@AJ#~$Kzh߽+pvvRXrI3+$\ԅUWP DH> V^A7,ednZEeEZQ^n=_yH62" ޻d"@B }L(/N$>C)dD֖'}P{m zz8t(rq(`<MsbعZ} <}N&1u0Q\wDَƜ 9ӋҎ'^s۴%І ϞXew/ G͖PՒ}n5})5GA})"C!.at[QgA3OŋyyTWWrPWX-;`|ke))5#{6?GssS!Lg044 G`eǎ5#Π0SAT2l}dY!?Z֔*8ȝ.) 0-& *ms&ӓЁWJ6@lѲppb9L TˊSkFόe Ė-YOX SrA~?ЅX<¬W.F,d2όDZQptB3;aV&u7Dc5H4cVi:$ɈR*٭2J)OFVs2 O!13''[7ϻt5Z>2:^4JpR7UX8"x#"U6x|b2[7\?'hK]Ttq2h͈ӫTj.)Z^)pq)$IBȜxRZkP (Xk?ƉrX*WX<ʤ6wjj=zXH9cqDIENDB`lft-4.01/icons/72x72/dest.png000644 000765 000024 00000004201 15233443070 015516 0ustar00vicstaff000000 000000 PNG  IHDRHHUGbKGD6IDATx{lSǿڎ88 y`BGP;M0:!Vդ .VնjݺicҦӮ!uEVUb вM@'h'༡NHbǾ9<`klyy{|2b׭mJA A%A4B0(T\Z5&Am;{Tef6"aթCxHsa)w1tw c0!a9,3(1t)!'~!D7!rwueh^m[W>Bosvժ(UMOȗ+KD*{Ygn1V~p1:sT;ޝջ.]r1:zzݝ9r9;:>I Ϧ/AD8M0ݓr6Hvx~jNȱ`(Pݷ}6mNak_LӾ<|>J4UÞpDKB{&ZAR3FpYJbQP*Ĉw$qP "!I603MgW7m\IhQ=<($WV*SNCFA:dCFA:dCFA:$ać'Οb޷y ;wl/B"k=;IhA& dN99H 2taQR{\RVT=()r HPc/F$m H@YnǠ7DيbK6ʰ!{$} $0Jqo~7c"ʭxqE9+!aJt?>-#8ObgM|R0h hn?݅}- ӣi$ y||/ڧG=8a!vmtvvWYY"WiHj(T1pI FDAǁV9';Ͻ ;x$EPCF4oLX< eF nŦ\.O^⽁?PK9hD `X\{ ߔ/} oPa4i4 lTl v.OW}􇿞gDvZ35ؿQy院߀PsXȉT` K{o |r{_@IIq"BSOk6ř^\t"rBH_W B=!j+Y6T[VU[c#$4 2 >5{sw]횄(𛕍x̹<Q6ܖJz<"A !`",ǥ[8=ڋ~uXjĒFgs?F\R>`FsPExό튺eؐ31#Ba2pap*@ΨZ>zW3iDv2o}/?}aNP䘔-Tn.*AdǚϦ-Q$DM8}n9d@g~AՕ/MtGJ;c*0?/Qwt ORxQf)k9vղ8r7UE*s ol!v[EQ>)BH6Z{X'ݷQ4C213,-NJ C<$\p)P@880 IxE!MM2L"y0{!/,׳SFIENDB`lft-4.01/tests/ifaddr_test.c000644 000765 000024 00000011347 15250425146 015757 0ustar00vicstaff000000 000000 /* ifaddr_test.c — unit tests for the AF-aware getifaddrs helpers (LFT 4.0). cc -Wall -Wextra -DHAVE_IPV6 -o tests/ifaddr_test \ tests/ifaddr_test.c lft_ifname.c lft_addr.c ./tests/ifaddr_test (exit 0 = pass) No root (getifaddrs is unprivileged). Asserts loopback is discoverable by name->addr and addr->name in both families. The v6 loopback (::1) check is skipped if the host has no v6 loopback configured. */ #include #include #include #include #include #include #include #include #include "../config/acconfig.h" /* pick up HAVE_IPV6 for this TU */ #include "../lft_addr.h" #include "../lft_ifname.h" #ifndef HAVE_IPV6 int main(void) { printf("== lft_ifname AF-aware unit tests ==\n"); printf(" (HAVE_IPV6 not defined on this platform — skipped)\n"); printf("\n0 checks, 0 failures — PASS\n"); return 0; } #else static int checks = 0, failures = 0; #define CHECK(cond) do { \ checks++; \ if (!(cond)) { \ failures++; \ printf(" FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ } \ } while (0) #define SCENARIO(name) printf(" %s\n", name) /* find the loopback interface name by probing common names for a v4 addr */ static const char *find_loopback(void) { static const char *cands[] = { "lo0", "lo", "lo1", NULL }; lft_addr_t a; int i; for (i = 0; cands[i]; i++) if (lft_getifaddr_af(cands[i], AF_INET, &a)) return cands[i]; return NULL; } int main(void) { const char *lo; lft_addr_t a; char buf[64]; printf("== lft_ifname AF-aware unit tests ==\n"); SCENARIO("1 loopback interface is discoverable (v4)"); lo = find_loopback(); CHECK(lo != NULL); if (!lo) { printf("\n%d checks, %d failures — FAIL (no loopback?)\n", checks, failures); return 1; } CHECK(lft_getifaddr_af(lo, AF_INET, &a) == 1); CHECK(a.af == AF_INET); lft_ntop(&a, buf, sizeof buf); CHECK(strcmp(buf, "127.0.0.1") == 0); SCENARIO("2 addr->name round-trips (v4 loopback)"); { char *nm = lft_getifname_af(&a); CHECK(nm != NULL && strcmp(nm, lo) == 0); } SCENARIO("3 v6 loopback ::1 (skipped if host has no v6 loopback)"); if (lft_getifaddr_af(lo, AF_INET6, &a)) { CHECK(a.af == AF_INET6); char *nm = lft_getifname_af(&a); CHECK(nm != NULL); } else { printf(" (no v6 loopback on %s — skipped)\n", lo); } SCENARIO("4 unknown interface / family returns not-found"); CHECK(lft_getifaddr_af("definitely_not_an_iface_9", AF_INET, &a) == 0); SCENARIO("5 legacy lft_getifname finds every v4 address (no ifconf truncation)"); { struct ifaddrs *ifap, *p; int n = 0; CHECK(getifaddrs(&ifap) == 0); for (p = ifap; p; p = p->ifa_next) { char *nm; if (!p->ifa_addr || p->ifa_addr->sa_family != AF_INET) continue; nm = lft_getifname(((struct sockaddr_in *)(void *)p->ifa_addr)->sin_addr); if (!nm) printf(" %s not found by lft_getifname\n", p->ifa_name); CHECK(nm != NULL); free(nm); n++; } freeifaddrs(ifap); CHECK(n > 0); } SCENARIO("6 kernel egress address maps to an interface (default-route check)"); { /* connect() on a UDP socket picks the route without sending anything */ int sd = socket(AF_INET, SOCK_DGRAM, 0); struct sockaddr_in dst, src; socklen_t sl = sizeof src; memset(&dst, 0, sizeof dst); dst.sin_family = AF_INET; dst.sin_port = htons(53); inet_pton(AF_INET, "192.0.2.1", &dst.sin_addr); if (sd >= 0 && connect(sd, (struct sockaddr *)&dst, sizeof dst) == 0 && getsockname(sd, (struct sockaddr *)&src, &sl) == 0) { char *nm = lft_getifname(src.sin_addr); char sb[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &src.sin_addr, sb, sizeof sb); printf(" egress %s -> %s\n", sb, nm ? nm : "(not found)"); CHECK(nm != NULL); free(nm); } else { printf(" (no IPv4 route — skipped)\n"); } if (sd >= 0) close(sd); } printf("\n%d checks, %d failures — %s\n", checks, failures, failures ? "FAIL" : "PASS"); return failures ? 1 : 0; } #endif /* HAVE_IPV6 */ lft-4.01/tests/resolve_test.c000644 000765 000024 00000006176 15247771366 016227 0ustar00vicstaff000000 000000 /* resolve_test.c — unit tests for lft_resolve_af (LFT 4.0). cc -Wall -Wextra -o tests/resolve_test tests/resolve_test.c lft_addr.c ./tests/resolve_test (exit 0 = pass) Numeric-literal resolution only (offline; no DNS, works regardless of the host's own v6 configuration). Hostname/AAAA cases are covered live in the plan's Tier 3 to keep the unit tier network-free. */ #include #include #include #include "../lft_addr.h" static int checks = 0, failures = 0; #define CHECK(cond) do { \ checks++; \ if (!(cond)) { \ failures++; \ printf(" FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ } \ } while (0) #define SCENARIO(name) printf(" %s\n", name) static void t_numeric_family(void) { struct sockaddr_storage ss; int af; SCENARIO("1 numeric literal resolves to the right family"); CHECK(lft_resolve_af("192.0.2.1", AF_UNSPEC, &ss, &af) == 1 && af == AF_INET); CHECK(lft_resolve_af("2001:db8::1", AF_UNSPEC, &ss, &af) == 1 && af == AF_INET6); CHECK(lft_resolve_af("::1", AF_UNSPEC, &ss, &af) == 1 && af == AF_INET6); } static void t_force_af(void) { struct sockaddr_storage ss; int af; SCENARIO("2 force_af rejects a mismatched literal"); CHECK(lft_resolve_af("192.0.2.1", AF_INET6, &ss, &af) == 0); /* v4 lit, forced v6 */ CHECK(lft_resolve_af("2001:db8::1", AF_INET, &ss, &af) == 0); /* v6 lit, forced v4 */ /* matching force succeeds */ CHECK(lft_resolve_af("192.0.2.1", AF_INET, &ss, &af) == 1 && af == AF_INET); CHECK(lft_resolve_af("2001:db8::1", AF_INET6, &ss, &af) == 1 && af == AF_INET6); } static void t_storage_correct(void) { struct sockaddr_storage ss; int af; char buf[64]; SCENARIO("3 filled storage round-trips to the same address"); CHECK(lft_resolve_af("2001:db8::1", AF_UNSPEC, &ss, &af) == 1); CHECK(ss.ss_family == AF_INET6); struct sockaddr_in6 *s6 = (struct sockaddr_in6 *)&ss; inet_ntop(AF_INET6, &s6->sin6_addr, buf, sizeof buf); CHECK(strcmp(buf, "2001:db8::1") == 0); CHECK(lft_resolve_af("192.0.2.1", AF_UNSPEC, &ss, &af) == 1); CHECK(ss.ss_family == AF_INET); struct sockaddr_in *s4 = (struct sockaddr_in *)&ss; inet_ntop(AF_INET, &s4->sin_addr, buf, sizeof buf); CHECK(strcmp(buf, "192.0.2.1") == 0); } static void t_reject(void) { struct sockaddr_storage ss; int af; SCENARIO("4 empty/NULL input rejected"); CHECK(lft_resolve_af("", AF_UNSPEC, &ss, &af) == 0); CHECK(lft_resolve_af(NULL, AF_UNSPEC, &ss, &af) == 0); } int main(void) { printf("== lft_resolve_af unit tests ==\n"); t_numeric_family(); t_force_af(); t_storage_correct(); t_reject(); printf("\n%d checks, %d failures — %s\n", checks, failures, failures ? "FAIL" : "PASS"); return failures ? 1 : 0; } lft-4.01/tests/xml_test.c000644 000765 000024 00000006310 15247771366 015336 0ustar00vicstaff000000 000000 /* xml_test.c — unit test driver for lft_xml_escape (LFT 4.0). cc -Wall -Wextra -o tests/xml_test tests/xml_test.c lft_xml.c ./tests/xml_test (exit 0 = pass) */ #include #include #include "../lft_xml.h" static int checks = 0, failures = 0; #define CHECK(cond) do { \ checks++; \ if (!(cond)) { \ failures++; \ printf(" FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ } \ } while (0) #define SCENARIO(name) printf(" %s\n", name) #define EQ(in, want) do { \ char b[256]; \ lft_xml_escape((in), b, sizeof b); \ CHECK(strcmp(b, (want)) == 0); \ } while (0) static void t_each_metachar(void) { SCENARIO("1 each metacharacter maps to its entity"); EQ("a&b", "a&b"); EQ("", "<x>"); EQ("\"q\"", ""q""); EQ("'a'", "'a'"); } static void t_plain_passthrough(void) { SCENARIO("2 plain text and empty pass through unchanged"); EQ("just text 123", "just text 123"); EQ("", ""); } static void t_ptr_injection(void) { SCENARIO("3 hostile PTR-style string is fully neutralized"); EQ("evil\">d\"e'f", "a&b<c>d"e'f"); } static void t_null_safe(void) { char b[16]; SCENARIO("4 NULL input yields empty string"); lft_xml_escape(NULL, b, sizeof b); CHECK(b[0] == '\0'); } static void t_no_partial_entity(void) { char b[8]; SCENARIO("5 truncation never splits an entity"); /* "&" needs 5 bytes (&) + NUL. With outsz=8, "a" (1) + "&" (5) * = 6 + NUL = 7 fits; a second "&" would need 5 more -> must stop. */ lft_xml_escape("a&&", b, sizeof b); CHECK(strcmp(b, "a&") == 0); /* second & dropped, not partial */ CHECK(strlen(b) < sizeof b); /* Tiny buffer that cannot even hold one entity: emit nothing but NUL. */ char t[3]; lft_xml_escape("&", t, sizeof t); CHECK(t[0] == '\0'); /* no "&a" partial */ /* Plain chars fill up to the boundary and stop cleanly. */ char s[4]; lft_xml_escape("abcdef", s, sizeof s); CHECK(strcmp(s, "abc") == 0); } static void t_always_terminated(void) { char b[5]; SCENARIO("6 output is always NUL-terminated within bounds"); lft_xml_escape("xxxxxxxx", b, sizeof b); CHECK(b[sizeof b - 1] == '\0'); CHECK(strlen(b) <= sizeof b - 1); } int main(void) { printf("== lft_xml unit tests ==\n"); t_each_metachar(); t_plain_passthrough(); t_ptr_injection(); t_null_safe(); t_no_partial_entity(); t_always_terminated(); printf("\n%d checks, %d failures — %s\n", checks, failures, failures ? "FAIL" : "PASS"); return failures ? 1 : 0; } lft-4.01/tests/addr_test.c000644 000765 000024 00000013643 15247771366 015457 0ustar00vicstaff000000 000000 /* addr_test.c — unit test driver for lft_addr (LFT 4.0 IPv6 seam). Standalone, no root, no network: cc -Wall -Wextra -o tests/addr_test tests/addr_test.c lft_addr.c ./tests/addr_test (exit 0 = pass) Tier-1 coverage: parse (v4/v6/shorthand/reject), canonical + expanded ntop, cross-family equality, v4-mapped normalization, and the v4/v6 checksum pseudo-headers (RFC 2460/8200 sec 8.1). */ #include #include #include #include "../lft_addr.h" static int checks = 0, failures = 0; #define CHECK(cond) do { \ checks++; \ if (!(cond)) { \ failures++; \ printf(" FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ } \ } while (0) #define SCENARIO(name) printf(" %s\n", name) static lft_addr_t P(const char *s) { lft_addr_t a; memset(&a, 0, sizeof a); lft_addr_parse(s, &a); return a; } static void t_parse_family(void) { lft_addr_t a; SCENARIO("1 parse selects the right address family"); CHECK(lft_addr_parse("192.0.2.1", &a) == 1 && a.af == AF_INET); CHECK(lft_addr_parse("2001:db8::1", &a) == 1 && a.af == AF_INET6); CHECK(lft_addr_parse("::1", &a) == 1 && a.af == AF_INET6); CHECK(lft_addr_parse("::", &a) == 1 && a.af == AF_INET6); CHECK(lft_addr_parse("fe80::1", &a) == 1 && a.af == AF_INET6); } static void t_parse_reject(void) { lft_addr_t a; SCENARIO("2 parse rejects non-addresses"); CHECK(lft_addr_parse("nonsense", &a) == 0); CHECK(lft_addr_parse("", &a) == 0); CHECK(lft_addr_parse("2001:db8::/32", &a) == 0); /* no CIDR here */ CHECK(lft_addr_parse("192.0.2.1/24", &a) == 0); CHECK(lft_addr_parse("999.0.0.1", &a) == 0); } static void t_ntop_canonical(void) { char buf[INET6_ADDRSTRLEN]; SCENARIO("3 ntop produces RFC 5952 canonical shorthand"); CHECK(strcmp(lft_ntop(&(lft_addr_t){0}, buf, 0), buf) == 0 || 1); /* no crash on sz 0 */ lft_addr_t a = P("2001:0db8:0000:0000:0000:0000:0000:0001"); CHECK(strcmp(lft_ntop(&a, buf, sizeof buf), "2001:db8::1") == 0); lft_addr_t b = P("192.0.2.1"); CHECK(strcmp(lft_ntop(&b, buf, sizeof buf), "192.0.2.1") == 0); lft_addr_t c = P("::1"); CHECK(strcmp(lft_ntop(&c, buf, sizeof buf), "::1") == 0); } static void t_ntop_expanded(void) { char buf[INET6_ADDRSTRLEN]; SCENARIO("4 ntop_fmt expand renders full 8-group v6"); lft_addr_t a = P("2001:db8::1"); CHECK(strcmp(lft_ntop_fmt(&a, buf, sizeof buf, 1), "2001:0db8:0000:0000:0000:0000:0000:0001") == 0); /* expand is a no-op for v4 */ lft_addr_t b = P("192.0.2.1"); CHECK(strcmp(lft_ntop_fmt(&b, buf, sizeof buf, 1), "192.0.2.1") == 0); /* expand off == canonical */ CHECK(strcmp(lft_ntop_fmt(&a, buf, sizeof buf, 0), "2001:db8::1") == 0); } static void t_v4mapped(void) { char buf[INET6_ADDRSTRLEN]; SCENARIO("5 v4-mapped v6 normalizes to v4"); lft_addr_t a; CHECK(lft_addr_parse("::ffff:192.0.2.1", &a) == 1); CHECK(a.af == AF_INET); CHECK(strcmp(lft_ntop(&a, buf, sizeof buf), "192.0.2.1") == 0); } static void t_eq(void) { SCENARIO("6 addr_eq: same v6 different text equal; cross-family never"); lft_addr_t x = P("2001:0db8::0001"); lft_addr_t y = P("2001:db8::1"); CHECK(lft_addr_eq(&x, &y) == 1); lft_addr_t v4 = P("192.0.2.1"); lft_addr_t v6 = P("::ffff:c000:201"); /* != 192.0.2.1 after normalize? it IS */ (void)v6; lft_addr_t other4 = P("192.0.2.2"); CHECK(lft_addr_eq(&v4, &other4) == 0); CHECK(lft_addr_eq(&v4, &x) == 0); /* v4 vs v6 never equal */ } static void t_is_v6(void) { SCENARIO("7 is_v6 classifies family"); lft_addr_t a = P("2001:db8::1"); lft_addr_t b = P("192.0.2.1"); CHECK(lft_addr_is_v6(&a) == 1); CHECK(lft_addr_is_v6(&b) == 0); } static void t_pseudo_v4(void) { unsigned char out[64]; SCENARIO("8 v4 pseudo-header is 12 bytes, proto+len placed"); lft_addr_t s = P("192.0.2.1"), d = P("198.51.100.2"); memset(out, 0xAA, sizeof out); size_t n = lft_pack_pseudo_header(&s, &d, 6 /*TCP*/, 20, out); CHECK(n == 12); /* bytes 0-3 src, 4-7 dst, 8 zero, 9 proto, 10-11 length (network order) */ CHECK(memcmp(out, &s.a.v4, 4) == 0); CHECK(memcmp(out + 4, &d.a.v4, 4) == 0); CHECK(out[8] == 0); CHECK(out[9] == 6); CHECK(out[10] == 0 && out[11] == 20); } static void t_pseudo_v6(void) { unsigned char out[64]; SCENARIO("9 v6 pseudo-header is 40 bytes per RFC 2460 sec 8.1"); lft_addr_t s = P("2001:db8::1"), d = P("2001:db8::2"); memset(out, 0xAA, sizeof out); size_t n = lft_pack_pseudo_header(&s, &d, 58 /*ICMPv6*/, 8, out); CHECK(n == 40); CHECK(memcmp(out, &s.a.v6, 16) == 0); /* src */ CHECK(memcmp(out + 16, &d.a.v6, 16) == 0); /* dst */ /* upper-layer length: 4 bytes network order at 32..35 */ CHECK(out[32] == 0 && out[33] == 0 && out[34] == 0 && out[35] == 8); /* 3 zero bytes then next-header */ CHECK(out[36] == 0 && out[37] == 0 && out[38] == 0); CHECK(out[39] == 58); } static void t_pseudo_mismatch(void) { unsigned char out[64]; SCENARIO("10 pseudo-header rejects mixed families"); lft_addr_t s = P("192.0.2.1"), d = P("2001:db8::1"); CHECK(lft_pack_pseudo_header(&s, &d, 6, 20, out) == 0); } int main(void) { printf("== lft_addr unit tests ==\n"); t_parse_family(); t_parse_reject(); t_ntop_canonical(); t_ntop_expanded(); t_v4mapped(); t_eq(); t_is_v6(); t_pseudo_v4(); t_pseudo_v6(); t_pseudo_mismatch(); printf("\n%d checks, %d failures — %s\n", checks, failures, failures ? "FAIL" : "PASS"); return failures ? 1 : 0; } lft-4.01/tests/probe6_test.c000644 000765 000024 00000010426 15247771366 015736 0ustar00vicstaff000000 000000 /* probe6_test.c — unit tests for the pure IPv6 probe helpers (LFT 4.0). cc -Wall -Wextra -o tests/probe6_test tests/probe6_test.c \ lft_probe6.c lft_addr.c ./tests/probe6_test (exit 0 = pass) No root, no network. Covers L3 version dispatch, ICMPv6 classification, and the v6 transport checksum (via its defining ones-complement invariant). */ #include #include #include #include "../lft_probe6.h" #include "../lft_addr.h" static int checks = 0, failures = 0; #define CHECK(cond) do { \ checks++; \ if (!(cond)) { \ failures++; \ printf(" FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ } \ } while (0) #define SCENARIO(name) printf(" %s\n", name) static void t_l3_dispatch(void) { unsigned char v4[20], v6[40], trunc6[39]; SCENARIO("1 L3 dispatch reads version nibble + min length"); memset(v4, 0, sizeof v4); v4[0] = 0x45; /* v4, IHL 5 */ memset(v6, 0, sizeof v6); v6[0] = 0x60; /* v6 */ memset(trunc6, 0, sizeof trunc6); trunc6[0] = 0x60; CHECK(lft_l3_dispatch(v4, sizeof v4) == LFT_L3_V4); CHECK(lft_l3_dispatch(v6, sizeof v6) == LFT_L3_V6); CHECK(lft_l3_dispatch(trunc6, sizeof trunc6) == LFT_L3_DROP); /* 39 < 40 */ CHECK(lft_l3_dispatch(v4, 19) == LFT_L3_DROP); /* 19 < 20 */ unsigned char garbage[40]; memset(garbage, 0, sizeof garbage); garbage[0] = 0x30; CHECK(lft_l3_dispatch(garbage, sizeof garbage) == LFT_L3_DROP);/* version 3 */ CHECK(lft_l3_dispatch(v6, 0) == LFT_L3_DROP); } static void t_icmp6_classify(void) { SCENARIO("2 ICMPv6 type/code classification"); CHECK(lft_icmp6_classify(3, 0) == LFT_ICMP6_TIME_EXCEEDED); CHECK(lft_icmp6_classify(2, 0) == LFT_ICMP6_TOO_BIG); CHECK(lft_icmp6_classify(1, 3) == LFT_ICMP6_UNREACH); CHECK(lft_icmp6_classify(1, 0) == LFT_ICMP6_UNREACH); CHECK(lft_icmp6_classify(129, 0) == LFT_ICMP6_ECHO_REPLY); CHECK(lft_icmp6_classify(128, 0) == LFT_ICMP6_OTHER); /* echo request */ CHECK(lft_icmp6_classify(135, 0) == LFT_ICMP6_OTHER); /* neighbor solicit */ } static void t_cksum6_invariant(void) { /* Defining property of the Internet checksum: summing a datagram that * already carries its correct checksum yields 0. We compute the checksum * over an ICMPv6 echo-request with a zeroed checksum field, write it back * (big-endian, matching the accumulator), and re-run — expecting 0. */ SCENARIO("3 v6 checksum satisfies the ones-complement invariant"); lft_addr_t src, dst; lft_addr_parse("2001:db8::1", &src); lft_addr_parse("2001:db8::2", &dst); unsigned char icmp6[16]; memset(icmp6, 0, sizeof icmp6); icmp6[0] = 128; /* echo request */ icmp6[1] = 0; /* code */ icmp6[2] = 0; icmp6[3] = 0; /* checksum field = 0 */ icmp6[4] = 0x12; icmp6[5] = 0x34; /* id */ icmp6[6] = 0x00; icmp6[7] = 0x01; /* seq */ memcpy(icmp6 + 8, "lftping!", 8); /* 8 bytes payload */ uint16_t c = lft_cksum6(&src, &dst, 58, icmp6, sizeof icmp6); CHECK(c != 0); /* a real checksum, not the stub's 0 */ /* place c back (big-endian) and re-sum -> must be 0 */ icmp6[2] = (unsigned char)(c >> 8); icmp6[3] = (unsigned char)(c & 0xff); uint16_t c2 = lft_cksum6(&src, &dst, 58, icmp6, sizeof icmp6); CHECK(c2 == 0); /* odd-length payload path also holds the invariant */ unsigned char odd[9]; memset(odd, 0, sizeof odd); odd[0] = 128; uint16_t oc = lft_cksum6(&src, &dst, 58, odd, sizeof odd); odd[2] = (unsigned char)(oc >> 8); odd[3] = (unsigned char)(oc & 0xff); CHECK(lft_cksum6(&src, &dst, 58, odd, sizeof odd) == 0); } int main(void) { printf("== lft_probe6 unit tests ==\n"); t_l3_dispatch(); t_icmp6_classify(); t_cksum6_invariant(); printf("\n%d checks, %d failures — %s\n", checks, failures, failures ? "FAIL" : "PASS"); return failures ? 1 : 0; } lft-4.01/tests/pathwatch_test.c000644 000765 000024 00000022510 15227414246 016506 0ustar00vicstaff000000 000000 /* pathwatch_test.c — unit test driver for the J1 path-change heuristic. Standalone, no root, no network, no ncurses: cc -Wall -Wextra -o tests/pathwatch_test tests/pathwatch_test.c lft_pathwatch.c ./tests/pathwatch_test (exit 0 = pass) Scenarios mirror the design doc test plan (Tier 1), one function each. */ #include #include #include "../lft_pathwatch.h" static int checks = 0, failures = 0; #define CHECK(cond) do { \ checks++; \ if (!(cond)) { \ failures++; \ printf(" FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ } \ } while (0) #define SCENARIO(name) printf(" %s\n", name) /* Convenience: fresh state with confirmed IP preloaded and noted. */ static void setup(watch_pathwatch_t *pw, char *conf, const char *ip) { memset(pw, 0, sizeof(*pw)); conf[0] = '\0'; if (ip) { snprintf(conf, PW_IPLEN, "%s", ip); pw_note_ip(pw, ip); } } static int in_set(const watch_pathwatch_t *pw, const char *ip) { int i; for (i = 0; i < pw->n_known; i++) if (strcmp(pw->known_ips[i], ip) == 0) return 1; return 0; } /* 1. Learning: unarmed observations are discovery; sets populate quietly. */ static void t01_learning(void) { watch_pathwatch_t pw; char conf[PW_IPLEN]; SCENARIO("01 learning phase = discovery"); setup(&pw, conf, NULL); CHECK(pw_observe(&pw, conf, "10.0.0.1", 0, NULL) == PW_DISCOVERY); CHECK(strcmp(conf, "10.0.0.1") == 0); /* pre-arm change is still discovery, not pending/alert */ CHECK(pw_observe(&pw, conf, "10.0.0.2", 0, NULL) == PW_DISCOVERY); CHECK(strcmp(conf, "10.0.0.2") == 0); CHECK(in_set(&pw, "10.0.0.1")); CHECK(in_set(&pw, "10.0.0.2")); CHECK(pw.pending_ip[0] == '\0'); } /* 2. Arming latch truth table. */ static void t02_arming(void) { SCENARIO("02 arming latch"); CHECK(pw_update_armed(0, 0, 1) == 0); CHECK(pw_update_armed(0, 2, 7) == 0); CHECK(pw_update_armed(0, 3, 3) == 1); /* stable path arms */ CHECK(pw_update_armed(0, 0, 8) == 1); /* cycle-8 fallback */ CHECK(pw_update_armed(1, 0, 2) == 1); /* never disarms */ } /* 3. Stable path: same IP forever, no pending. */ static void t03_stable(void) { watch_pathwatch_t pw; char conf[PW_IPLEN]; int i; SCENARIO("03 stable path"); setup(&pw, conf, "192.0.2.1"); for (i = 0; i < 5; i++) CHECK(pw_observe(&pw, conf, "192.0.2.1", 1, NULL) == PW_STABLE); CHECK(strcmp(conf, "192.0.2.1") == 0); } /* 4. One-cycle flap: X, Y, X -> PENDING then FLAP; confirmed never moves. */ static void t04_flap(void) { watch_pathwatch_t pw; char conf[PW_IPLEN], was[PW_IPLEN] = ""; SCENARIO("04 one-cycle flap"); setup(&pw, conf, "192.0.2.1"); CHECK(pw_observe(&pw, conf, "192.0.2.9", 1, was) == PW_PENDING); CHECK(strcmp(conf, "192.0.2.1") == 0); /* not accepted yet */ CHECK(pw_observe(&pw, conf, "192.0.2.1", 1, was) == PW_FLAP); CHECK(strcmp(was, "192.0.2.9") == 0); /* flap_was reports Y */ CHECK(strcmp(conf, "192.0.2.1") == 0); CHECK(pw.pending_ip[0] == '\0'); /* pending cleared */ } /* 5. Genuine reroute: X, Y, Y -> PENDING then CONFIRMED; set reset to {Y}. */ static void t05_reroute(void) { watch_pathwatch_t pw; char conf[PW_IPLEN]; SCENARIO("05 genuine reroute"); setup(&pw, conf, "192.0.2.1"); CHECK(pw_observe(&pw, conf, "192.0.2.9", 1, NULL) == PW_PENDING); CHECK(pw_observe(&pw, conf, "192.0.2.9", 1, NULL) == PW_CONFIRMED); CHECK(strcmp(conf, "192.0.2.9") == 0); CHECK(pw.n_known == 1); /* set reset ... */ CHECK(in_set(&pw, "192.0.2.9")); /* ... to {Y} */ CHECK(pw.pending_ip[0] == '\0'); } /* 6. Route restore: after confirmed change, old IP alerts again. */ static void t06_route_restore(void) { watch_pathwatch_t pw; char conf[PW_IPLEN]; SCENARIO("06 route restore alerts (set was reset)"); setup(&pw, conf, "192.0.2.1"); pw_observe(&pw, conf, "192.0.2.9", 1, NULL); pw_observe(&pw, conf, "192.0.2.9", 1, NULL); /* confirmed Y */ CHECK(pw_observe(&pw, conf, "192.0.2.1", 1, NULL) == PW_PENDING); CHECK(pw_observe(&pw, conf, "192.0.2.1", 1, NULL) == PW_CONFIRMED); CHECK(strcmp(conf, "192.0.2.1") == 0); } /* 7. ECMP alternation: PENDING, FLAP, then ECMP_SHIFT on every transition. */ static void t07_ecmp_alternation(void) { watch_pathwatch_t pw; char conf[PW_IPLEN]; int i; SCENARIO("07 ECMP alternation"); setup(&pw, conf, "203.0.113.1"); CHECK(pw_observe(&pw, conf, "203.0.113.2", 1, NULL) == PW_PENDING); CHECK(pw_observe(&pw, conf, "203.0.113.1", 1, NULL) == PW_FLAP); /* .2 was noted at first sighting -> membership now classifies it */ CHECK(pw_observe(&pw, conf, "203.0.113.2", 1, NULL) == PW_ECMP_SHIFT); CHECK(strcmp(conf, "203.0.113.2") == 0); CHECK(pw_observe(&pw, conf, "203.0.113.1", 1, NULL) == PW_ECMP_SHIFT); /* counter drives log suppression in the caller */ CHECK(pw.ecmp_logged == 2); for (i = 0; i < 4; i++) { pw_observe(&pw, conf, "203.0.113.2", 1, NULL); pw_observe(&pw, conf, "203.0.113.1", 1, NULL); } CHECK(pw.ecmp_logged == 10); CHECK(pw.ecmp_logged > PW_ECMP_LOG_MAX); } /* 8. alt_hosts feeding: pre-noted IP shifts immediately, no hysteresis. */ static void t08_alt_hosts(void) { watch_pathwatch_t pw; char conf[PW_IPLEN]; SCENARIO("08 alt_hosts pre-noted -> immediate ECMP shift"); setup(&pw, conf, "203.0.113.1"); pw_note_ip(&pw, "203.0.113.2"); /* seen as alt in-cycle */ CHECK(pw_observe(&pw, conf, "203.0.113.2", 1, NULL) == PW_ECMP_SHIFT); CHECK(strcmp(conf, "203.0.113.2") == 0); } /* 9. Cloaked gaps: pending survives '*'; '*' alone never alerts. */ static void t09_cloaked(void) { watch_pathwatch_t pw; char conf[PW_IPLEN]; SCENARIO("09 cloaked-gap hysteresis"); setup(&pw, conf, "192.0.2.1"); CHECK(pw_observe(&pw, conf, "192.0.2.9", 1, NULL) == PW_PENDING); CHECK(pw_observe(&pw, conf, "*", 1, NULL) == PW_IGNORE); CHECK(strcmp(pw.pending_ip, "192.0.2.9") == 0); /* preserved */ CHECK(pw.pending_cycles == 1); /* not advanced */ CHECK(pw_observe(&pw, conf, "192.0.2.9", 1, NULL) == PW_CONFIRMED); /* '*' <-> IP transitions alone: no alerts either direction */ setup(&pw, conf, "192.0.2.1"); CHECK(pw_observe(&pw, conf, "*", 1, NULL) == PW_IGNORE); CHECK(pw_observe(&pw, conf, "", 1, NULL) == PW_IGNORE); CHECK(pw_observe(&pw, conf, "192.0.2.1", 1, NULL) == PW_STABLE); } /* 10. Third-IP churn: X, Y, Z, Z -> pending rebinds, Z confirms. */ static void t10_churn(void) { watch_pathwatch_t pw; char conf[PW_IPLEN]; SCENARIO("10 third-IP churn"); setup(&pw, conf, "192.0.2.1"); CHECK(pw_observe(&pw, conf, "192.0.2.8", 1, NULL) == PW_PENDING); CHECK(pw_observe(&pw, conf, "192.0.2.9", 1, NULL) == PW_PENDING); CHECK(strcmp(pw.pending_ip, "192.0.2.9") == 0); /* rebound to Z */ CHECK(pw_observe(&pw, conf, "192.0.2.9", 1, NULL) == PW_CONFIRMED); CHECK(strcmp(conf, "192.0.2.9") == 0); CHECK(pw.n_known == 1); /* Y evicted by reset */ } /* 11. Set overflow: caps at PW_KNOWN_MAX; hysteresis still confirms. */ static void t11_overflow(void) { watch_pathwatch_t pw; char conf[PW_IPLEN]; char ip[PW_IPLEN]; int i; SCENARIO("11 known-set overflow"); setup(&pw, conf, "10.9.9.1"); for (i = 0; i < 10; i++) { snprintf(ip, sizeof(ip), "10.1.1.%d", i + 1); pw_note_ip(&pw, ip); } CHECK(pw.n_known == PW_KNOWN_MAX); pw_note_ip(&pw, "10.1.1.1"); /* dup: no change */ CHECK(pw.n_known == PW_KNOWN_MAX); /* 9th-IP change still alerts through hysteresis */ CHECK(pw_observe(&pw, conf, "10.2.2.2", 1, NULL) == PW_PENDING); CHECK(pw_observe(&pw, conf, "10.2.2.2", 1, NULL) == PW_CONFIRMED); CHECK(strcmp(conf, "10.2.2.2") == 0); } /* 12. Reset clears everything; new-slot discovery works even when armed. */ static void t12_reset_and_new_slot(void) { watch_pathwatch_t pw; char conf[PW_IPLEN]; SCENARIO("12 reset + armed new-slot discovery"); setup(&pw, conf, "192.0.2.1"); pw_observe(&pw, conf, "192.0.2.9", 1, NULL); pw_reset(&pw); CHECK(pw.n_known == 0); CHECK(pw.pending_ip[0] == '\0'); CHECK(pw.pending_cycles == 0); CHECK(pw.ecmp_logged == 0); /* TTL expansion: empty confirmed slot is discovery even when armed */ conf[0] = '\0'; CHECK(pw_observe(&pw, conf, "198.51.100.7", 1, NULL) == PW_DISCOVERY); CHECK(strcmp(conf, "198.51.100.7") == 0); CHECK(in_set(&pw, "198.51.100.7")); } int main(void) { printf("pathwatch unit tests:\n"); t01_learning(); t02_arming(); t03_stable(); t04_flap(); t05_reroute(); t06_route_restore(); t07_ecmp_alternation(); t08_alt_hosts(); t09_cloaked(); t10_churn(); t11_overflow(); t12_reset_and_new_slot(); printf("%d checks, %d failures — %s\n", checks, failures, failures ? "FAIL" : "PASS"); return failures ? 1 : 0; } lft-4.01/include/net/000755 000765 000024 00000000000 15250425210 014354 5ustar00vicstaff000000 000000 lft-4.01/include/netinet/000755 000765 000024 00000000000 15250425210 015234 5ustar00vicstaff000000 000000 lft-4.01/include/sys/000755 000765 000024 00000000000 15250425210 014404 5ustar00vicstaff000000 000000 lft-4.01/include/win32/000755 000765 000024 00000000000 15250425210 014530 5ustar00vicstaff000000 000000 lft-4.01/include/libpcap/000755 000765 000024 00000000000 15250425210 015200 5ustar00vicstaff000000 000000 lft-4.01/include/libpcap/bittypes.h000755 000765 000024 00000007445 11053131665 017236 0ustar00vicstaff000000 000000 /* * Copyright (C) 1999 WIDE Project. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the project nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef _BITTYPES_H #define _BITTYPES_H #ifndef HAVE_U_INT8_T #if SIZEOF_CHAR == 1 typedef unsigned char u_int8_t; typedef signed char int8_t; #elif SIZEOF_INT == 1 typedef unsigned int u_int8_t; typedef signed int int8_t; #else /* XXX */ #error "there's no appropriate type for u_int8_t" #endif #define HAVE_U_INT8_T 1 #define HAVE_INT8_T 1 #endif /* HAVE_U_INT8_T */ #ifndef HAVE_U_INT16_T #if SIZEOF_SHORT == 2 typedef unsigned short u_int16_t; typedef signed short int16_t; #elif SIZEOF_INT == 2 typedef unsigned int u_int16_t; typedef signed int int16_t; #elif SIZEOF_CHAR == 2 typedef unsigned char u_int16_t; typedef signed char int16_t; #else /* XXX */ #error "there's no appropriate type for u_int16_t" #endif #define HAVE_U_INT16_T 1 #define HAVE_INT16_T 1 #endif /* HAVE_U_INT16_T */ #ifndef HAVE_U_INT32_T #if SIZEOF_INT == 4 typedef unsigned int u_int32_t; typedef signed int int32_t; #elif SIZEOF_LONG == 4 typedef unsigned long u_int32_t; typedef signed long int32_t; #elif SIZEOF_SHORT == 4 typedef unsigned short u_int32_t; typedef signed short int32_t; #else /* XXX */ #error "there's no appropriate type for u_int32_t" #endif #define HAVE_U_INT32_T 1 #define HAVE_INT32_T 1 #endif /* HAVE_U_INT32_T */ #ifndef HAVE_U_INT64_T #if SIZEOF_LONG_LONG == 8 typedef unsigned long long u_int64_t; #elif defined(_MSC_EXTENSIONS) typedef unsigned _int64 u_int64_t; #elif SIZEOF_INT == 8 typedef unsigned int u_int64_t; #elif SIZEOF_LONG == 8 typedef unsigned long u_int64_t; #elif SIZEOF_SHORT == 8 typedef unsigned short u_int64_t; #else /* XXX */ #error "there's no appropriate type for u_int64_t" #endif #endif /* HAVE_U_INT64_T */ #ifndef PRId64 #ifdef _MSC_EXTENSIONS #define PRId64 "I64d" #else /* _MSC_EXTENSIONS */ #define PRId64 "lld" #endif /* _MSC_EXTENSIONS */ #endif /* PRId64 */ #ifndef PRIo64 #ifdef _MSC_EXTENSIONS #define PRIo64 "I64o" #else /* _MSC_EXTENSIONS */ #define PRIo64 "llo" #endif /* _MSC_EXTENSIONS */ #endif /* PRIo64 */ #ifndef PRIx64 #ifdef _MSC_EXTENSIONS #define PRIx64 "I64x" #else /* _MSC_EXTENSIONS */ #define PRIx64 "llx" #endif /* _MSC_EXTENSIONS */ #endif /* PRIx64 */ #ifndef PRIu64 #ifdef _MSC_EXTENSIONS #define PRIu64 "I64u" #else /* _MSC_EXTENSIONS */ #define PRIu64 "llu" #endif /* _MSC_EXTENSIONS */ #endif /* PRIu64 */ #endif /* _BITTYPES_H */ lft-4.01/include/libpcap/Gnuc.h000755 000765 000024 00000001417 11053131665 016260 0ustar00vicstaff000000 000000 /* @(#) $Header: /tcpdump/master/libpcap/Win32/Include/Gnuc.h,v 1.1 2002/08/01 08:33:05 risso Exp $ (LBL) */ /* Define __P() macro, if necessary */ #ifndef __P #if __STDC__ #define __P(protos) protos #else #define __P(protos) () #endif #endif /* inline foo */ #ifndef __cplusplus #ifdef __GNUC__ #define inline __inline #else #define inline #endif #endif /* * Handle new and old "dead" routine prototypes * * For example: * * __dead void foo(void) __attribute__((volatile)); * */ #ifdef __GNUC__ #ifndef __dead #define __dead volatile #endif #if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5) #ifndef __attribute__ #define __attribute__(args) #endif #endif #else #ifndef __dead #define __dead #endif #ifndef __attribute__ #define __attribute__(args) #endif #endif lft-4.01/include/win32/wingettimeofday.c000755 000765 000024 00000004461 11053131665 020110 0ustar00vicstaff000000 000000 #if defined(WIN32) || defined(_WIN32) #include "wingettimeofday.h" #include #ifndef __GNUC__ #define EPOCHFILETIME (116444736000000000i64) #else #define EPOCHFILETIME (116444736000000000LL) #endif int __gettimeofday(struct timeval* tv, void * tz) { struct _timeb currSysTime; _ftime(&currSysTime); tv->tv_sec = currSysTime.time; tv->tv_usec = currSysTime.millitm * 1000; return 0; } LARGE_INTEGER getFILETIMEoffset() { SYSTEMTIME s; FILETIME f; LARGE_INTEGER t; s.wYear = 1970; s.wMonth = 1; s.wDay = 1; s.wHour = 0; s.wMinute = 0; s.wSecond = 0; s.wMilliseconds = 0; SystemTimeToFileTime(&s, &f); t.QuadPart = f.dwHighDateTime; t.QuadPart <<= 32; t.QuadPart |= f.dwLowDateTime; return (t); } int gettimeofday(struct timeval *tv, void * tz) { LARGE_INTEGER t; FILETIME f; double microseconds; static LARGE_INTEGER offset; static LARGE_INTEGER base; static double frequencyToMicroseconds; static int initialized = 0; static BOOL usePerformanceCounter = 0; if(!initialized) { LARGE_INTEGER performanceFrequency; initialized = 1; usePerformanceCounter = QueryPerformanceFrequency(&performanceFrequency); if(usePerformanceCounter) { LARGE_INTEGER tmpoffs; QueryPerformanceCounter(&offset); frequencyToMicroseconds = (double)performanceFrequency.QuadPart / 1000000.; tmpoffs = getFILETIMEoffset(); GetSystemTimeAsFileTime(&f); base.QuadPart = f.dwHighDateTime; base.QuadPart <<= 32; base.QuadPart |= f.dwLowDateTime; base.QuadPart -= tmpoffs.QuadPart; microseconds = (double)base.QuadPart / 10; base.QuadPart = microseconds; tv->tv_sec = base.QuadPart / 1000000; tv->tv_usec = base.QuadPart % 1000000; } else { offset = getFILETIMEoffset(); frequencyToMicroseconds = 10.; base.QuadPart=0i64; } } if(usePerformanceCounter) QueryPerformanceCounter(&t); else { GetSystemTimeAsFileTime(&f); t.QuadPart = f.dwHighDateTime; t.QuadPart <<= 32; t.QuadPart |= f.dwLowDateTime; } t.QuadPart -= offset.QuadPart; microseconds = (double)t.QuadPart / frequencyToMicroseconds; t.QuadPart = microseconds + base.QuadPart; tv->tv_sec = t.QuadPart / 1000000; tv->tv_usec = t.QuadPart % 1000000; return (0); } #endif lft-4.01/include/win32/wingetopt.h000755 000765 000024 00000001711 15165341545 016741 0ustar00vicstaff000000 000000 #ifndef WINGETOPT_H #define WINGETOPT_H #ifdef __cplusplus extern "C" { #endif /* Globals shared between getopt() and getopt_long() */ extern char *optarg; extern int optind; extern int opterr; extern int optopt; /* has_arg values for struct option */ #define no_argument 0 #define required_argument 1 #define optional_argument 2 /* Long-option descriptor passed to getopt_long() */ struct option { const char *name; /* long option name (without "--") */ int has_arg; /* no_argument / required_argument / optional_argument */ int *flag; /* if non-NULL, set *flag = val and return 0 */ int val; /* value to return (or store in *flag) */ }; int getopt(int argc, char *argv[], const char *optstring); int getopt_long(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex); #ifdef __cplusplus } #endif #endif /* WINGETOPT_H */ lft-4.01/include/win32/wingettimeofday.h000755 000765 000024 00000000322 11053131665 020105 0ustar00vicstaff000000 000000 #ifndef WINGETTIMEOFDAY_H #define WINGETTIMEOFDAY_H #include #ifdef __cplusplus extern "C"{ #endif int gettimeofday(struct timeval* tp, void* tzp); #ifdef __cplusplus } #endif #endif lft-4.01/include/win32/wingetopt.c000755 000765 000024 00000012673 15165341545 016745 0ustar00vicstaff000000 000000 #if defined(WIN32) || defined(_WIN32) #include "wingetopt.h" #include #include /* Option-parsing state shared across calls */ char *optarg = NULL; int optind = 0; /* 0 = not yet started; first call advances to 1 */ int opterr = 1; /* print errors to stderr when non-zero */ int optopt = 0; /* holds the unknown option char on '?' return */ /* ------------------------------------------------------------------------- * getopt() — POSIX short-option parser * ------------------------------------------------------------------------- */ int getopt(int argc, char *argv[], const char *optstring) { static char *next = NULL; char c; const char *cp; /* optind == 0 is the GNU reset convention */ if (optind == 0) { optind = 1; next = NULL; } optarg = NULL; /* Advance to next argv element when current one is exhausted */ if (next == NULL || *next == '\0') { if (optind >= argc || argv[optind][0] != '-' || argv[optind][1] == '\0') return -1; if (strcmp(argv[optind], "--") == 0) { optind++; return -1; } next = argv[optind] + 1; /* skip the leading '-' */ optind++; } c = *next++; cp = strchr(optstring, c); if (cp == NULL || c == ':') { optopt = c; if (opterr && optstring[0] != ':') fprintf(stderr, "lft: invalid option -- '%c'\n", c); return '?'; } if (cp[1] == ':') { /* option requires an argument */ if (*next != '\0') { optarg = next; next = NULL; } else if (optind < argc) { optarg = argv[optind++]; } else { optopt = c; if (optstring[0] == ':') return ':'; if (opterr) fprintf(stderr, "lft: option requires an argument -- '%c'\n", c); return '?'; } } return c; } /* ------------------------------------------------------------------------- * getopt_long() — GNU-style long-option parser * * Long options take the form --name or --name=value. * When a match is found: * - if longopts[i].flag != NULL → *flag = val, return 0 * - if longopts[i].flag == NULL → return val * Short options are handled by delegating to getopt(). * ------------------------------------------------------------------------- */ int getopt_long(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex) { /* optind == 0: reset (GNU extension) */ if (optind == 0) optind = 1; /* Is the current token a long option? */ if (optind < argc && argv[optind][0] == '-' && argv[optind][1] == '-' && argv[optind][2] != '\0') { const char *token = argv[optind] + 2; /* skip "--" */ const char *eq = strchr(token, '='); size_t namelen = eq ? (size_t)(eq - token) : strlen(token); int i; for (i = 0; longopts[i].name != NULL; i++) { if (strncmp(token, longopts[i].name, namelen) == 0 && longopts[i].name[namelen] == '\0') { if (longindex) *longindex = i; optind++; /* consume the --name token */ optarg = NULL; switch (longopts[i].has_arg) { case required_argument: if (eq) { optarg = (char *)(eq + 1); } else if (optind < argc) { optarg = argv[optind++]; } else { /* required argument missing */ optopt = longopts[i].val; if (optstring[0] == ':') return ':'; if (opterr) fprintf(stderr, "lft: option '--%s' requires an argument\n", longopts[i].name); return '?'; } break; case optional_argument: optarg = eq ? (char *)(eq + 1) : NULL; break; case no_argument: default: if (eq) { /* argument supplied to a no-argument option */ optopt = longopts[i].val; if (opterr) fprintf(stderr, "lft: option '--%s' doesn't allow an argument\n", longopts[i].name); return '?'; } break; } if (longopts[i].flag != NULL) { *longopts[i].flag = longopts[i].val; return 0; } return longopts[i].val; } } /* No match — unknown long option */ if (opterr) fprintf(stderr, "lft: unrecognized option '--%.*s'\n", (int)namelen, token); optind++; return '?'; } /* "--" end-of-options sentinel */ if (optind < argc && strcmp(argv[optind], "--") == 0) { optind++; return -1; } /* Not a long option — delegate to short-option parser */ return getopt(argc, (char **)argv, optstring); } #endif /* WIN32 */ lft-4.01/include/win32/winlft_ifname.c000755 000765 000024 00000052302 11053131665 017530 0ustar00vicstaff000000 000000 #if defined(WIN32) || defined(_WIN32) #include #include #include #include #include #include #include #include #include "../../lft_ifname.h" typedef enum { KWV_UNKNOWN, //while unknown (before first call of this module) KWV_VISTA, //Vista KWV_2K, //98/ME, NTSP4, W2K and XP KWV_NT4, //NT4 with SP<4 KWV_95 //Win95 }KNOWN_WIN_VERSION; static KNOWN_WIN_VERSION VerifyWindowsVersion() { static KNOWN_WIN_VERSION WinVersion=KWV_UNKNOWN; OSVERSIONINFOEX osvi; BOOL bOsVersionInfoEx; if(WinVersion!=KWV_UNKNOWN) return WinVersion; // Try calling GetVersionEx using the OSVERSIONINFOEX structure. // If that fails, try using the OSVERSIONINFO structure. ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX)); osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); if(!(bOsVersionInfoEx = GetVersionEx((OSVERSIONINFO *) &osvi))) { osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); if(!GetVersionEx( (OSVERSIONINFO *) &osvi)) return KWV_UNKNOWN; } switch(osvi.dwPlatformId) { case VER_PLATFORM_WIN32_NT: //Test for the Windows NT product family if(osvi.dwMajorVersion<=4) //NT4 { if(osvi.wServicePackMajor<4) WinVersion=KWV_NT4; else WinVersion=KWV_2K; } else { if(osvi.dwMajorVersion>5) WinVersion=KWV_VISTA; else WinVersion=KWV_2K; //2K and XP } break; case VER_PLATFORM_WIN32_WINDOWS: //Test for the Windows Me/98/95 default: WinVersion=KWV_95; break; } return WinVersion; } static u_long lft_getifaddr_95(const char *argifname) { HKEY key; char ethname[5]="eth/"; char pppname[5]="ppp/"; int i; FILETIME update; LONG res; DWORD size; u_long ret; char ifname[256]; if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"Enum\\Network\\MSTCP", 0, KEY_READ, &key) != ERROR_SUCCESS) return -1; size = sizeof(ifname); for(i=0;(res = RegEnumKeyEx(key, i, (LPTSTR)ifname, &size, 0, 0, 0, &update))!=ERROR_NO_MORE_ITEMS;i++) { HKEY ifkey, subkey; DWORD dsize,ipsize,npsize,asize; char driver[256], classname[256], netname[256]; char adapter[256], ip[256], np[256]; if(res != ERROR_SUCCESS || RegOpenKeyEx(key, (LPCTSTR)ifname, 0, KEY_READ, &ifkey) != ERROR_SUCCESS) continue; dsize = sizeof(driver); if(RegQueryValueEx(ifkey, L"Driver", 0, NULL, (unsigned char *)driver, &dsize) != ERROR_SUCCESS) { RegCloseKey(ifkey); continue; } strcpy(classname, "System\\CurrentControlSet\\Services\\Class\\"); strcat(classname, driver); if((res = RegOpenKeyEx(HKEY_LOCAL_MACHINE, (LPCTSTR)classname, 0, KEY_READ, &subkey)) != ERROR_SUCCESS) { RegCloseKey(ifkey); continue; } ipsize=sizeof(ip); npsize=sizeof(np); if(RegQueryValueEx(subkey, L"IPAddress", 0, NULL, (unsigned char *) ip, &ipsize) == ERROR_SUCCESS) { ret=inet_addr(ip); RegCloseKey (subkey); strcpy(netname, "System\\CurrentControlSet\\Services\\Class\\Net\\"); strcat(netname, ifname); if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, (LPCTSTR)netname, 0, KEY_READ, &subkey) != ERROR_SUCCESS) { RegCloseKey(ifkey); continue; } asize=sizeof(adapter); if( RegQueryValueEx (subkey, L"AdapterName", 0, NULL, (unsigned char *) adapter, &asize) == ERROR_SUCCESS && !strcmp(adapter, "MS$PPP")) { pppname[3]++; } else { ethname[3]++; } RegCloseKey(subkey); RegCloseKey(ifkey); if(!strcmp(pppname,argifname) || !strcmp(ethname,argifname)) break; else ret=-1; } } RegCloseKey(key); return ret; } static char * lft_getifname_95(struct in_addr addr) { HKEY key; char ethname[5]="eth/"; char pppname[5]="ppp/"; int i; FILETIME update; LONG res; DWORD size; u_long ret; int isethaddr,isfound; char ifname[256]; if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"Enum\\Network\\MSTCP", 0, KEY_READ, &key) != ERROR_SUCCESS) return NULL; size = sizeof(ifname); for(i=0;(res = RegEnumKeyEx(key, i, (TCHAR *)ifname, &size, 0, 0, 0, &update))!=ERROR_NO_MORE_ITEMS;i++) { HKEY ifkey, subkey; DWORD dsize,ipsize,npsize,asize; char driver[256], classname[256], netname[256]; char adapter[256], ip[256], np[256]; if(res != ERROR_SUCCESS || RegOpenKeyEx(key, (LPCTSTR)ifname, 0, KEY_READ, &ifkey) != ERROR_SUCCESS) continue; dsize = sizeof(driver); if(RegQueryValueEx(ifkey, L"Driver", 0, NULL, (unsigned char *)driver, &dsize) != ERROR_SUCCESS) { RegCloseKey(ifkey); continue; } strcpy(classname, "System\\CurrentControlSet\\Services\\Class\\"); strcat(classname, driver); if((res = RegOpenKeyEx(HKEY_LOCAL_MACHINE, (LPCTSTR)classname, 0, KEY_READ, &subkey)) != ERROR_SUCCESS) { RegCloseKey(ifkey); continue; } ipsize=sizeof(ip); npsize=sizeof(np); if(RegQueryValueEx(subkey, L"IPAddress", 0, NULL, (unsigned char *) ip, &ipsize) == ERROR_SUCCESS) { ret=inet_addr(ip); RegCloseKey (subkey); strcpy(netname, "System\\CurrentControlSet\\Services\\Class\\Net\\"); strcat(netname, ifname); if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, (LPCTSTR)netname, 0, KEY_READ, &subkey) != ERROR_SUCCESS) { RegCloseKey(ifkey); continue; } asize=sizeof(adapter); if( RegQueryValueEx (subkey, L"AdapterName", 0, NULL, (unsigned char *) adapter, &asize) == ERROR_SUCCESS && !strcmp(adapter, "MS$PPP")) { pppname[3]++; isethaddr=0; } else { ethname[3]++; isethaddr=1; } RegCloseKey(subkey); RegCloseKey(ifkey); if(ret==addr.s_addr) { isfound=1; break; } else isfound=0; } } RegCloseKey(key); if(!isfound) return NULL; if(isethaddr) return strdup(ethname); return strdup(pppname); } static u_long lft_getifaddr_NT4(const char *ifname) { HKEY key; char devname[256]; struct sockaddr_in *sa = NULL; struct sockaddr *so = NULL; DWORD size; int cnt = 1,isfound; u_long ret; char *binding = (char *)0; if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Linkage", 0, KEY_READ, &key) == ERROR_SUCCESS) { if(RegQueryValueEx(key, L"Bind", NULL, NULL, NULL, &size) == ERROR_SUCCESS) { binding = (char *)_alloca(size); if(RegQueryValueEx (key, L"Bind", NULL, NULL, (unsigned char *)binding, &size) != ERROR_SUCCESS) binding = NULL; } RegCloseKey(key); } if(binding) { char *bp, eth[2] = "/"; int ipsize; char cardkey[256], ipaddress[256]; for(bp = binding; *bp; bp+=strlen(bp)+1) { bp += strlen("\\Device\\"); strcpy(cardkey, "SYSTEM\\CurrentControlSet\\Services\\"); strcat(cardkey, bp); strcat(cardkey, "\\Parameters\\Tcpip"); if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, (LPCTSTR)cardkey, 0, KEY_READ, &key) != ERROR_SUCCESS) continue; ipsize=256; if(RegQueryValueEx(key, L"IPAddress", NULL, NULL, (unsigned char *) ipaddress, &ipsize) == ERROR_SUCCESS) { char *ip; for(ip = ipaddress; *ip; ip += strlen(ip)+1) { if(!strncmp(bp, "NdisWan", 7)) { strcpy(devname, "ppp"); strcat(devname, bp + 7); } else { eth[0]++; strcpy(devname, "eth"); strcat(devname, eth); } ret=inet_addr(ipaddress); if(!ret) { ipsize=256; if(RegQueryValueEx (key, L"DhcpIPAddress", NULL, NULL, (unsigned char *) ipaddress, &ipsize) == ERROR_SUCCESS) { ret=inet_addr(ipaddress); } } if(!strcmp(devname,ifname)) { isfound=1; break; } else isfound=0; } } RegCloseKey(key); if(isfound) return ret; } } return -1; } static char * lft_getifname_NT4(struct in_addr addr) { HKEY key; char devname[256]; struct sockaddr_in *sa = NULL; struct sockaddr *so = NULL; DWORD size; int cnt = 1,isfound; u_long ret; char *binding = (char *)0; if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Linkage", 0, KEY_READ, &key) == ERROR_SUCCESS) { if(RegQueryValueEx(key, L"Bind", NULL, NULL, NULL, &size) == ERROR_SUCCESS) { binding = (char *)_alloca(size); if(RegQueryValueEx (key, L"Bind", NULL, NULL, (unsigned char *)binding, &size) != ERROR_SUCCESS) binding = NULL; } RegCloseKey(key); } if(binding) { char *bp, eth[2] = "/"; int ipsize; char cardkey[256]; char ipaddress[256]; for(bp = binding; *bp; bp+=strlen(bp)+1) { bp += strlen("\\Device\\"); strcpy(cardkey, "SYSTEM\\CurrentControlSet\\Services\\"); strcat(cardkey, bp); strcat(cardkey, "\\Parameters\\Tcpip"); if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, (LPCTSTR)cardkey, 0, KEY_READ, &key) != ERROR_SUCCESS) continue; ipsize=256; if(RegQueryValueEx(key, L"IPAddress", NULL, NULL, (unsigned char *) ipaddress, &ipsize) == ERROR_SUCCESS) { char *ip; for(ip = ipaddress; *ip; ip += strlen(ip)+1) { if(!strncmp(bp, "NdisWan", 7)) { strcpy(devname, "ppp"); strcat(devname, bp + 7); } else { eth[0]++; strcpy(devname, "eth"); strcat(devname, eth); } ret=inet_addr(ipaddress); if(!ret) { ipsize=256; if(RegQueryValueEx (key, L"DhcpIPAddress", NULL, NULL, (unsigned char *) ipaddress, &ipsize) == ERROR_SUCCESS) { ret=inet_addr(ipaddress); } } if(addr.s_addr==ret) { isfound=1; break; } else isfound=0; } } RegCloseKey(key); if(isfound) return strdup(devname); } } return NULL; } static u_long lft_getifaddr_2K(const char *ifname) { int cnt = 0; int ethId = 0, pppId = 0, slpId = 0, tokId = 0; DWORD ip_cnt; DWORD siz_ip_table = 0; PMIB_IPADDRTABLE ipt; PMIB_IFROW ifrow; char devname[256]; typedef struct { DWORD ifIndex; size_t count; unsigned int enumerated; // for eth0:1 unsigned int classId; // for eth0, tok0 ... } ifcount_t; ifcount_t *iflist, *ifEntry; if(GetIpAddrTable (NULL, &siz_ip_table, TRUE) == ERROR_INSUFFICIENT_BUFFER) { ifrow = (PMIB_IFROW)_alloca(sizeof(MIB_IFROW)); ipt = (PMIB_IPADDRTABLE)_alloca(siz_ip_table); } if(GetIpAddrTable (ipt, &siz_ip_table, TRUE) != NO_ERROR) return -1; iflist = (ifcount_t *) alloca(sizeof(ifcount_t)*(ipt->dwNumEntries + 1)); memset(iflist, 0, sizeof (ifcount_t) * (ipt->dwNumEntries + 1)); for(ip_cnt = 0; ip_cnt < ipt->dwNumEntries; ++ip_cnt) { ifEntry = iflist; /* search for matching entry (and stop at first free entry) */ while(ifEntry->count != 0) { if(ifEntry->ifIndex == ipt->table[ip_cnt].dwIndex) break; ifEntry++; } if(ifEntry->count == 0) { ifEntry->count = 1; ifEntry->ifIndex = ipt->table[ip_cnt].dwIndex; } else { ifEntry->count++; } } // reset the last element. This is just the stopper for the loop. iflist[ipt->dwNumEntries].count = 0; for(ip_cnt = 0; ip_cnt < ipt->dwNumEntries; ip_cnt++) { ifcount_t *ifEntry = iflist; memset(ifrow, 0, sizeof(MIB_IFROW)); ifrow->dwIndex = ipt->table[ip_cnt].dwIndex; if(GetIfEntry(ifrow) != NO_ERROR) continue; /* search for matching entry (and stop at first free entry) */ while(ifEntry->count != 0) { if(ifEntry->ifIndex == ipt->table[ip_cnt].dwIndex) break; ifEntry++; } /* Setup the interface name */ switch(ifrow->dwType) { case MIB_IF_TYPE_TOKENRING: if(ifEntry->enumerated == 0) { ifEntry->classId = tokId++; sprintf(devname, "tok%u", ifEntry->classId); } else { sprintf(devname, "tok%u:%u", ifEntry->classId, ifEntry->enumerated - 1); } ifEntry->enumerated++; break; #ifdef IF_TYPE_IEEE80211 case IF_TYPE_IEEE80211: #endif case MIB_IF_TYPE_ETHERNET: if(ifEntry->enumerated == 0) { ifEntry->classId = ethId++; sprintf(devname, "eth%u", ifEntry->classId); } else { sprintf(devname, "eth%u:%u", ifEntry->classId, ifEntry->enumerated - 1); } ifEntry->enumerated++; break; case MIB_IF_TYPE_PPP: if(ifEntry->enumerated == 0) { ifEntry->classId = pppId++; sprintf(devname, "ppp%u", ifEntry->classId); } else { sprintf(devname, "ppp%u:%u", ifEntry->classId, ifEntry->enumerated - 1); } ifEntry->enumerated++; break; case MIB_IF_TYPE_SLIP: if(ifEntry->enumerated == 0) { ifEntry->classId = slpId++; sprintf(devname, "slp%u", ifEntry->classId); } else { sprintf(devname, "slp%u:%u", ifEntry->classId, ifEntry->enumerated - 1); } ifEntry->enumerated++; break; case MIB_IF_TYPE_LOOPBACK: strcpy(devname, "lo"); break; default: continue; } if(!strcmp(devname,ifname)) { return ipt->table[ip_cnt].dwAddr; } } return -1; } static char * lft_getifname_2K(struct in_addr addr) { int cnt = 0; int ethId = 0, pppId = 0, slpId = 0, tokId = 0; DWORD ip_cnt; DWORD siz_ip_table = 0; PMIB_IPADDRTABLE ipt; PMIB_IFROW ifrow; static char devname[256]; static struct in_addr savedaddr; static int FirstTime=1; typedef struct { DWORD ifIndex; size_t count; unsigned int enumerated; // for eth0:1 unsigned int classId; // for eth0, tok0 ... } ifcount_t; ifcount_t *iflist, *ifEntry; if(!FirstTime && savedaddr.s_addr==addr.s_addr) { return strdup(devname); } savedaddr.s_addr=addr.s_addr; if(GetIpAddrTable (NULL, &siz_ip_table, TRUE) == ERROR_INSUFFICIENT_BUFFER) { ifrow = (PMIB_IFROW)_alloca(sizeof(MIB_IFROW)); ipt = (PMIB_IPADDRTABLE)_alloca(siz_ip_table); } if(GetIpAddrTable (ipt, &siz_ip_table, TRUE) != NO_ERROR) return NULL; iflist = (ifcount_t *) alloca(sizeof(ifcount_t)*(ipt->dwNumEntries + 1)); memset(iflist, 0, sizeof (ifcount_t) * (ipt->dwNumEntries + 1)); for(ip_cnt = 0; ip_cnt < ipt->dwNumEntries; ++ip_cnt) { ifEntry = iflist; /* search for matching entry (and stop at first free entry) */ while(ifEntry->count != 0) { if(ifEntry->ifIndex == ipt->table[ip_cnt].dwIndex) break; ifEntry++; } if(ifEntry->count == 0) { ifEntry->count = 1; ifEntry->ifIndex = ipt->table[ip_cnt].dwIndex; } else { ifEntry->count++; } } // reset the last element. This is just the stopper for the loop. iflist[ipt->dwNumEntries].count = 0; for(ip_cnt = 0; ip_cnt < ipt->dwNumEntries; ip_cnt++) { ifcount_t *ifEntry = iflist; memset(ifrow, 0, sizeof(MIB_IFROW)); ifrow->dwIndex = ipt->table[ip_cnt].dwIndex; if(GetIfEntry(ifrow) != NO_ERROR) continue; /* search for matching entry (and stop at first free entry) */ while(ifEntry->count != 0) { if(ifEntry->ifIndex == ipt->table[ip_cnt].dwIndex) break; ifEntry++; } /* Setup the interface name */ switch(ifrow->dwType) { case MIB_IF_TYPE_TOKENRING: if(ifEntry->enumerated == 0) { ifEntry->classId = tokId++; sprintf(devname, "tok%u", ifEntry->classId); } else { sprintf(devname, "tok%u:%u", ifEntry->classId, ifEntry->enumerated - 1); } ifEntry->enumerated++; break; #ifdef IF_TYPE_IEEE80211 case IF_TYPE_IEEE80211: #endif case MIB_IF_TYPE_ETHERNET: if(ifEntry->enumerated == 0) { ifEntry->classId = ethId++; sprintf(devname, "eth%u", ifEntry->classId); } else { sprintf(devname, "eth%u:%u", ifEntry->classId, ifEntry->enumerated - 1); } ifEntry->enumerated++; break; case MIB_IF_TYPE_PPP: if(ifEntry->enumerated == 0) { ifEntry->classId = pppId++; sprintf(devname, "ppp%u", ifEntry->classId); } else { sprintf(devname, "ppp%u:%u", ifEntry->classId, ifEntry->enumerated - 1); } ifEntry->enumerated++; break; case MIB_IF_TYPE_SLIP: if(ifEntry->enumerated == 0) { ifEntry->classId = slpId++; sprintf(devname, "slp%u", ifEntry->classId); } else { sprintf(devname, "slp%u:%u", ifEntry->classId, ifEntry->enumerated - 1); } ifEntry->enumerated++; break; case MIB_IF_TYPE_LOOPBACK: strcpy(devname, "lo"); break; default: continue; } if(addr.s_addr==ipt->table[ip_cnt].dwAddr) { FirstTime=0; return strdup(devname); } } return NULL; } u_long lft_getifaddr (const char *ifname) { switch(VerifyWindowsVersion()) { case KWV_95: return lft_getifaddr_95(ifname); case KWV_2K: case KWV_VISTA: return lft_getifaddr_2K(ifname); case KWV_NT4: return lft_getifaddr_NT4(ifname); } return -1; } char * lft_getifname (struct in_addr addr) { static char ifname[256]; static struct in_addr savedaddr; static int FirstTime=1; char * ret; if(!FirstTime && savedaddr.s_addr==addr.s_addr) { return strdup(ifname); } switch(VerifyWindowsVersion()) { case KWV_95: ret=lft_getifname_95(addr); break; case KWV_2K: case KWV_VISTA: ret=lft_getifname_2K(addr); break; case KWV_NT4: ret=lft_getifname_NT4(addr); break; } savedaddr.s_addr=addr.s_addr; FirstTime=0; strncpy(ifname,ret,255); return ret; } #ifdef LFT_IFADDR_TESTING extern int main (int argc, char *argv[]) { struct in_addr in; char *addr; if (argc > 1) addr = strdup (argv[1]); else addr = strdup ("eth0"); in.s_addr = lft_getifaddr (addr); if (in.s_addr == -1) { fprintf (stderr, "%s: Error reading ifname\n", addr); fflush (stderr); free(addr); exit (-1); } fprintf (stdout, "%s: %s\n", addr, inet_ntoa (in)); fflush (stdout); free (addr); exit (0); } #endif /*LFT_IFNAME_TESTING*/ #endif lft-4.01/include/sys/mbuf.h000755 000765 000024 00000000105 11053131665 015512 0ustar00vicstaff000000 000000 #ifndef MLEN #define MLEN 128 /* needed for slcompress.h */ #endif lft-4.01/include/netinet/tcp.h000755 000765 000024 00000007154 11053131665 016212 0ustar00vicstaff000000 000000 /* * Copyright (c) 1982, 1986, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)tcp.h 8.1 (Berkeley) 6/10/93 */ typedef u_long tcp_seq; /* * TCP header. * Per RFC 793, September, 1981. */ struct tcphdr { u_short th_sport; /* source port */ u_short th_dport; /* destination port */ tcp_seq th_seq; /* sequence number */ tcp_seq th_ack; /* acknowledgement number */ #if BYTE_ORDER == LITTLE_ENDIAN u_char th_x2:4, /* (unused) */ th_off:4; /* data offset */ #endif #if BYTE_ORDER == BIG_ENDIAN u_char th_off:4, /* data offset */ th_x2:4; /* (unused) */ #endif u_char th_flags; #define TH_FIN 0x01 #define TH_SYN 0x02 #define TH_RST 0x04 #define TH_PUSH 0x08 #define TH_ACK 0x10 #define TH_URG 0x20 u_short th_win; /* window */ u_short th_sum; /* checksum */ u_short th_urp; /* urgent pointer */ }; #define TCPOPT_EOL 0 #define TCPOPT_NOP 1 #define TCPOPT_MAXSEG 2 #define TCPOLEN_MAXSEG 4 #define TCPOPT_WINDOW 3 #define TCPOLEN_WINDOW 3 #define TCPOPT_SACK_PERMITTED 4 /* Experimental */ #define TCPOLEN_SACK_PERMITTED 2 #define TCPOPT_SACK 5 /* Experimental */ #define TCPOPT_TIMESTAMP 8 #define TCPOLEN_TIMESTAMP 10 #define TCPOLEN_TSTAMP_APPA (TCPOLEN_TIMESTAMP+2) /* appendix A */ #define TCPOPT_TSTAMP_HDR \ (TCPOPT_NOP<<24|TCPOPT_NOP<<16|TCPOPT_TIMESTAMP<<8|TCPOLEN_TIMESTAMP) /* * Default maximum segment size for TCP. * With an IP MSS of 576, this is 536, * but 512 is probably more convenient. * This should be defined as MIN(512, IP_MSS - sizeof (struct tcpiphdr)). */ #define TCP_MSS 512 #define TCP_MAXWIN 65535 /* largest value for (unscaled) window */ #define TCP_MAX_WINSHIFT 14 /* maximum window shift */ /* * User-settable options (used with setsockopt). */ #ifndef TCP_NODELAY #define TCP_NODELAY 0x01 /* don't delay send to coalesce packets */ #endif #ifndef TCP_MAXSEG #define TCP_MAXSEG 0x02 /* set maximum segment size */ #endif lft-4.01/include/netinet/ip_icmp.h000755 000765 000024 00000014530 11053131665 017040 0ustar00vicstaff000000 000000 /* * Copyright (c) 1982, 1986, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)ip_icmp.h 8.1 (Berkeley) 6/10/93 */ /* * Interface Control Message Protocol Definitions. * Per RFC 792, September 1981. */ /* * Structure of an icmp header. */ struct icmp { u_char icmp_type; /* type of message, see below */ u_char icmp_code; /* type sub code */ u_short icmp_cksum; /* ones complement cksum of struct */ union { u_char ih_pptr; /* ICMP_PARAMPROB */ struct in_addr ih_gwaddr; /* ICMP_REDIRECT */ struct ih_idseq { n_short icd_id; n_short icd_seq; } ih_idseq; int ih_void; /* ICMP_UNREACH_NEEDFRAG -- Path MTU Discovery (RFC1191) */ struct ih_pmtu { n_short ipm_void; n_short ipm_nextmtu; } ih_pmtu; } icmp_hun; #define icmp_pptr icmp_hun.ih_pptr #define icmp_gwaddr icmp_hun.ih_gwaddr #define icmp_id icmp_hun.ih_idseq.icd_id #define icmp_seq icmp_hun.ih_idseq.icd_seq #define icmp_void icmp_hun.ih_void #define icmp_pmvoid icmp_hun.ih_pmtu.ipm_void #define icmp_nextmtu icmp_hun.ih_pmtu.ipm_nextmtu union { struct id_ts { n_time its_otime; n_time its_rtime; n_time its_ttime; } id_ts; struct id_ip { struct ip idi_ip; /* options and then 64 bits of data */ } id_ip; u_long id_mask; char id_data[1]; } icmp_dun; #define icmp_otime icmp_dun.id_ts.its_otime #define icmp_rtime icmp_dun.id_ts.its_rtime #define icmp_ttime icmp_dun.id_ts.its_ttime #define icmp_ip icmp_dun.id_ip.idi_ip #define icmp_mask icmp_dun.id_mask #define icmp_data icmp_dun.id_data }; /* * Lower bounds on packet lengths for various types. * For the error advice packets must first insure that the * packet is large enought to contain the returned ip header. * Only then can we do the check to see if 64 bits of packet * data have been returned, since we need to check the returned * ip header length. */ #define ICMP_MINLEN 8 /* abs minimum */ #define ICMP_TSLEN (8 + 3 * sizeof (n_time)) /* timestamp */ #define ICMP_MASKLEN 12 /* address mask */ #define ICMP_ADVLENMIN (8 + sizeof (struct ip) + 8) /* min */ #define ICMP_ADVLEN(p) (8 + ((p)->icmp_ip.ip_hl << 2) + 8) /* N.B.: must separately check that ip_hl >= 5 */ /* * Definition of type and code field values. */ #define ICMP_ECHOREPLY 0 /* echo reply */ #define ICMP_UNREACH 3 /* dest unreachable, codes: */ #define ICMP_UNREACH_NET 0 /* bad net */ #define ICMP_UNREACH_HOST 1 /* bad host */ #define ICMP_UNREACH_PROTOCOL 2 /* bad protocol */ #define ICMP_UNREACH_PORT 3 /* bad port */ #define ICMP_UNREACH_NEEDFRAG 4 /* IP_DF caused drop */ #define ICMP_UNREACH_SRCFAIL 5 /* src route failed */ #define ICMP_UNREACH_NET_UNKNOWN 6 /* unknown net */ #define ICMP_UNREACH_HOST_UNKNOWN 7 /* unknown host */ #define ICMP_UNREACH_ISOLATED 8 /* src host isolated */ #define ICMP_UNREACH_NET_PROHIB 9 /* prohibited access */ #define ICMP_UNREACH_HOST_PROHIB 10 /* ditto */ #define ICMP_UNREACH_TOSNET 11 /* bad tos for net */ #define ICMP_UNREACH_TOSHOST 12 /* bad tos for host */ #define ICMP_SOURCEQUENCH 4 /* packet lost, slow down */ #define ICMP_REDIRECT 5 /* shorter route, codes: */ #define ICMP_REDIRECT_NET 0 /* for network */ #define ICMP_REDIRECT_HOST 1 /* for host */ #define ICMP_REDIRECT_TOSNET 2 /* for tos and net */ #define ICMP_REDIRECT_TOSHOST 3 /* for tos and host */ #define ICMP_ECHO 8 /* echo service */ #define ICMP_ROUTERADVERT 9 /* router advertisement */ #define ICMP_ROUTERSOLICIT 10 /* router solicitation */ #define ICMP_TIMXCEED 11 /* time exceeded, code: */ #define ICMP_TIMXCEED_INTRANS 0 /* ttl==0 in transit */ #define ICMP_TIMXCEED_REASS 1 /* ttl==0 in reass */ #define ICMP_PARAMPROB 12 /* ip header bad */ #define ICMP_PARAMPROB_OPTABSENT 1 /* req. opt. absent */ #define ICMP_TSTAMP 13 /* timestamp request */ #define ICMP_TSTAMPREPLY 14 /* timestamp reply */ #define ICMP_IREQ 15 /* information request */ #define ICMP_IREQREPLY 16 /* information reply */ #define ICMP_MASKREQ 17 /* address mask request */ #define ICMP_MASKREPLY 18 /* address mask reply */ #define ICMP_MAXTYPE 18 #define ICMP_INFOTYPE(type) \ ((type) == ICMP_ECHOREPLY || (type) == ICMP_ECHO || \ (type) == ICMP_ROUTERADVERT || (type) == ICMP_ROUTERSOLICIT || \ (type) == ICMP_TSTAMP || (type) == ICMP_TSTAMPREPLY || \ (type) == ICMP_IREQ || (type) == ICMP_IREQREPLY || \ (type) == ICMP_MASKREQ || (type) == ICMP_MASKREPLY) #ifdef KERNEL void icmp_error __P((struct mbuf *, int, int, n_long, struct ifnet *)); void icmp_input __P((struct mbuf *, int)); void icmp_reflect __P((struct mbuf *)); void icmp_send __P((struct mbuf *, struct mbuf *)); int icmp_sysctl __P((int *, u_int, void *, size_t *, void *, size_t)); #endif lft-4.01/include/netinet/tcpip.h000755 000765 000024 00000004734 11053131665 016544 0ustar00vicstaff000000 000000 /* * Copyright (c) 1982, 1986, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)tcpip.h 8.1 (Berkeley) 6/10/93 */ /* * Tcp+ip header, after ip options removed. */ struct tcpiphdr { struct ipovly ti_i; /* overlaid ip structure */ struct tcphdr ti_t; /* tcp header */ }; #define ti_next ti_i.ih_next #define ti_prev ti_i.ih_prev #define ti_x1 ti_i.ih_x1 #define ti_pr ti_i.ih_pr #define ti_len ti_i.ih_len #define ti_src ti_i.ih_src #define ti_dst ti_i.ih_dst #define ti_sport ti_t.th_sport #define ti_dport ti_t.th_dport #define ti_seq ti_t.th_seq #define ti_ack ti_t.th_ack #define ti_x2 ti_t.th_x2 #define ti_off ti_t.th_off #define ti_flags ti_t.th_flags #define ti_win ti_t.th_win #define ti_sum ti_t.th_sum #define ti_urp ti_t.th_urp lft-4.01/include/netinet/tcp_var.h000755 000765 000024 00000027663 11053131665 017071 0ustar00vicstaff000000 000000 /* * Copyright (c) 1982, 1986, 1993, 1994, 1995 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)tcp_var.h 8.4 (Berkeley) 5/24/95 */ /* * Kernel variables for tcp. */ /* * Tcp control block, one per tcp; fields: */ struct tcpcb { struct tcpiphdr *seg_next; /* sequencing queue */ struct tcpiphdr *seg_prev; short t_state; /* state of this connection */ short t_timer[TCPT_NTIMERS]; /* tcp timers */ short t_rxtshift; /* log(2) of rexmt exp. backoff */ short t_rxtcur; /* current retransmit value */ short t_dupacks; /* consecutive dup acks recd */ u_short t_maxseg; /* maximum segment size */ char t_force; /* 1 if forcing out a byte */ u_short t_flags; #define TF_ACKNOW 0x0001 /* ack peer immediately */ #define TF_DELACK 0x0002 /* ack, but try to delay it */ #define TF_NODELAY 0x0004 /* don't delay packets to coalesce */ #define TF_NOOPT 0x0008 /* don't use tcp options */ #define TF_SENTFIN 0x0010 /* have sent FIN */ #define TF_REQ_SCALE 0x0020 /* have/will request window scaling */ #define TF_RCVD_SCALE 0x0040 /* other side has requested scaling */ #define TF_REQ_TSTMP 0x0080 /* have/will request timestamps */ #define TF_RCVD_TSTMP 0x0100 /* a timestamp was received in SYN */ #define TF_SACK_PERMIT 0x0200 /* other side said I could SACK */ struct tcpiphdr *t_template; /* skeletal packet for transmit */ struct inpcb *t_inpcb; /* back pointer to internet pcb */ /* * The following fields are used as in the protocol specification. * See RFC783, Dec. 1981, page 21. */ /* send sequence variables */ tcp_seq snd_una; /* send unacknowledged */ tcp_seq snd_nxt; /* send next */ tcp_seq snd_up; /* send urgent pointer */ tcp_seq snd_wl1; /* window update seg seq number */ tcp_seq snd_wl2; /* window update seg ack number */ tcp_seq iss; /* initial send sequence number */ u_long snd_wnd; /* send window */ /* receive sequence variables */ u_long rcv_wnd; /* receive window */ tcp_seq rcv_nxt; /* receive next */ tcp_seq rcv_up; /* receive urgent pointer */ tcp_seq irs; /* initial receive sequence number */ /* * Additional variables for this implementation. */ /* receive variables */ tcp_seq rcv_adv; /* advertised window */ /* retransmit variables */ tcp_seq snd_max; /* highest sequence number sent; * used to recognize retransmits */ /* congestion control (for slow start, source quench, retransmit after loss) */ u_long snd_cwnd; /* congestion-controlled window */ u_long snd_ssthresh; /* snd_cwnd size threshhold for * for slow start exponential to * linear switch */ /* * transmit timing stuff. See below for scale of srtt and rttvar. * "Variance" is actually smoothed difference. */ u_short t_idle; /* inactivity time */ short t_rtt; /* round trip time */ tcp_seq t_rtseq; /* sequence number being timed */ short t_srtt; /* smoothed round-trip time */ short t_rttvar; /* variance in round-trip time */ u_short t_rttmin; /* minimum rtt allowed */ u_long max_sndwnd; /* largest window peer has offered */ /* out-of-band data */ char t_oobflags; /* have some */ char t_iobc; /* input character */ #define TCPOOB_HAVEDATA 0x01 #define TCPOOB_HADDATA 0x02 short t_softerror; /* possible error not yet reported */ /* RFC 1323 variables */ u_char snd_scale; /* window scaling for send window */ u_char rcv_scale; /* window scaling for recv window */ u_char request_r_scale; /* pending window scaling */ u_char requested_s_scale; u_long ts_recent; /* timestamp echo data */ u_long ts_recent_age; /* when last updated */ tcp_seq last_ack_sent; /* TUBA stuff */ caddr_t t_tuba_pcb; /* next level down pcb for TCP over z */ }; #define intotcpcb(ip) ((struct tcpcb *)(ip)->inp_ppcb) #define sototcpcb(so) (intotcpcb(sotoinpcb(so))) /* * The smoothed round-trip time and estimated variance * are stored as fixed point numbers scaled by the values below. * For convenience, these scales are also used in smoothing the average * (smoothed = (1/scale)sample + ((scale-1)/scale)smoothed). * With these scales, srtt has 3 bits to the right of the binary point, * and thus an "ALPHA" of 0.875. rttvar has 2 bits to the right of the * binary point, and is smoothed with an ALPHA of 0.75. */ #define TCP_RTT_SCALE 8 /* multiplier for srtt; 3 bits frac. */ #define TCP_RTT_SHIFT 3 /* shift for srtt; 3 bits frac. */ #define TCP_RTTVAR_SCALE 4 /* multiplier for rttvar; 2 bits */ #define TCP_RTTVAR_SHIFT 2 /* multiplier for rttvar; 2 bits */ /* * The initial retransmission should happen at rtt + 4 * rttvar. * Because of the way we do the smoothing, srtt and rttvar * will each average +1/2 tick of bias. When we compute * the retransmit timer, we want 1/2 tick of rounding and * 1 extra tick because of +-1/2 tick uncertainty in the * firing of the timer. The bias will give us exactly the * 1.5 tick we need. But, because the bias is * statistical, we have to test that we don't drop below * the minimum feasible timer (which is 2 ticks). * This macro assumes that the value of TCP_RTTVAR_SCALE * is the same as the multiplier for rttvar. */ #define TCP_REXMTVAL(tp) \ (((tp)->t_srtt >> TCP_RTT_SHIFT) + (tp)->t_rttvar) /* XXX * We want to avoid doing m_pullup on incoming packets but that * means avoiding dtom on the tcp reassembly code. That in turn means * keeping an mbuf pointer in the reassembly queue (since we might * have a cluster). As a quick hack, the source & destination * port numbers (which are no longer needed once we've located the * tcpcb) are overlayed with an mbuf pointer. */ #define REASS_MBUF(ti) (*(struct mbuf **)&((ti)->ti_t)) /* * TCP statistics. * Many of these should be kept per connection, * but that's inconvenient at the moment. */ struct tcpstat { u_long tcps_connattempt; /* connections initiated */ u_long tcps_accepts; /* connections accepted */ u_long tcps_connects; /* connections established */ u_long tcps_drops; /* connections dropped */ u_long tcps_conndrops; /* embryonic connections dropped */ u_long tcps_closed; /* conn. closed (includes drops) */ u_long tcps_segstimed; /* segs where we tried to get rtt */ u_long tcps_rttupdated; /* times we succeeded */ u_long tcps_delack; /* delayed acks sent */ u_long tcps_timeoutdrop; /* conn. dropped in rxmt timeout */ u_long tcps_rexmttimeo; /* retransmit timeouts */ u_long tcps_persisttimeo; /* persist timeouts */ u_long tcps_keeptimeo; /* keepalive timeouts */ u_long tcps_keepprobe; /* keepalive probes sent */ u_long tcps_keepdrops; /* connections dropped in keepalive */ u_long tcps_sndtotal; /* total packets sent */ u_long tcps_sndpack; /* data packets sent */ u_long tcps_sndbyte; /* data bytes sent */ u_long tcps_sndrexmitpack; /* data packets retransmitted */ u_long tcps_sndrexmitbyte; /* data bytes retransmitted */ u_long tcps_sndacks; /* ack-only packets sent */ u_long tcps_sndprobe; /* window probes sent */ u_long tcps_sndurg; /* packets sent with URG only */ u_long tcps_sndwinup; /* window update-only packets sent */ u_long tcps_sndctrl; /* control (SYN|FIN|RST) packets sent */ u_long tcps_rcvtotal; /* total packets received */ u_long tcps_rcvpack; /* packets received in sequence */ u_long tcps_rcvbyte; /* bytes received in sequence */ u_long tcps_rcvbadsum; /* packets received with ccksum errs */ u_long tcps_rcvbadoff; /* packets received with bad offset */ u_long tcps_rcvshort; /* packets received too short */ u_long tcps_rcvduppack; /* duplicate-only packets received */ u_long tcps_rcvdupbyte; /* duplicate-only bytes received */ u_long tcps_rcvpartduppack; /* packets with some duplicate data */ u_long tcps_rcvpartdupbyte; /* dup. bytes in part-dup. packets */ u_long tcps_rcvoopack; /* out-of-order packets received */ u_long tcps_rcvoobyte; /* out-of-order bytes received */ u_long tcps_rcvpackafterwin; /* packets with data after window */ u_long tcps_rcvbyteafterwin; /* bytes rcvd after window */ u_long tcps_rcvafterclose; /* packets rcvd after "close" */ u_long tcps_rcvwinprobe; /* rcvd window probe packets */ u_long tcps_rcvdupack; /* rcvd duplicate acks */ u_long tcps_rcvacktoomuch; /* rcvd acks for unsent data */ u_long tcps_rcvackpack; /* rcvd ack packets */ u_long tcps_rcvackbyte; /* bytes acked by rcvd acks */ u_long tcps_rcvwinupd; /* rcvd window update packets */ u_long tcps_pawsdrop; /* segments dropped due to PAWS */ u_long tcps_predack; /* times hdr predict ok for acks */ u_long tcps_preddat; /* times hdr predict ok for data pkts */ u_long tcps_pcbcachemiss; u_long tcps_persistdrop; /* timeout in persist state */ u_long tcps_badsyn; /* bogus SYN, e.g. premature ACK */ }; #ifdef KERNEL struct inpcb tcb; /* head of queue of active tcpcb's */ struct tcpstat tcpstat; /* tcp statistics */ u_long tcp_now; /* for RFC 1323 timestamps */ int tcp_attach __P((struct socket *)); void tcp_canceltimers __P((struct tcpcb *)); struct tcpcb * tcp_close __P((struct tcpcb *)); void tcp_ctlinput __P((int, struct sockaddr *, struct ip *)); int tcp_ctloutput __P((int, struct socket *, int, int, struct mbuf **)); struct tcpcb * tcp_disconnect __P((struct tcpcb *)); struct tcpcb * tcp_drop __P((struct tcpcb *, int)); void tcp_dooptions __P((struct tcpcb *, u_char *, int, struct tcpiphdr *, int *, u_long *, u_long *)); void tcp_drain __P((void)); void tcp_fasttimo __P((void)); void tcp_init __P((void)); void tcp_input __P((struct mbuf *, int)); int tcp_mss __P((struct tcpcb *, u_int)); struct tcpcb * tcp_newtcpcb __P((struct inpcb *)); void tcp_notify __P((struct inpcb *, int)); int tcp_output __P((struct tcpcb *)); void tcp_pulloutofband __P((struct socket *, struct tcpiphdr *, struct mbuf *)); void tcp_quench __P((struct inpcb *, int)); int tcp_reass __P((struct tcpcb *, struct tcpiphdr *, struct mbuf *)); void tcp_respond __P((struct tcpcb *, struct tcpiphdr *, struct mbuf *, u_long, u_long, int)); void tcp_setpersist __P((struct tcpcb *)); void tcp_slowtimo __P((void)); struct tcpiphdr * tcp_template __P((struct tcpcb *)); struct tcpcb * tcp_timers __P((struct tcpcb *, int)); void tcp_trace __P((int, int, struct tcpcb *, struct tcpiphdr *, int)); struct tcpcb * tcp_usrclosed __P((struct tcpcb *)); int tcp_usrreq __P((struct socket *, int, struct mbuf *, struct mbuf *, struct mbuf *)); void tcp_xmit_timer __P((struct tcpcb *, int)); #endif lft-4.01/include/netinet/if_ether.h000755 000765 000024 00000006352 11053131665 017210 0ustar00vicstaff000000 000000 /* * Copyright (c) 1982, 1986, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)if_ether.h 8.3 (Berkeley) 5/2/95 */ /* * Ethernet address - 6 octets */ struct ether_addr { u_char ether_addr_octet[6]; }; /* * Structure of a 10Mb/s Ethernet header. */ struct ether_header { u_char ether_dhost[6]; u_char ether_shost[6]; u_short ether_type; }; #define ETHERTYPE_PUP 0x0200 /* PUP protocol */ #define ETHERTYPE_IP 0x0800 /* IP protocol */ #define ETHERTYPE_ARP 0x0806 /* Addr. resolution protocol */ #define ETHERTYPE_REVARP 0x8035 /* reverse Addr. resolution protocol */ /* * The ETHERTYPE_NTRAILER packet types starting at ETHERTYPE_TRAIL have * (type-ETHERTYPE_TRAIL)*512 bytes of data followed * by an ETHER type (as given above) and then the (variable-length) header. */ #define ETHERTYPE_TRAIL 0x1000 /* Trailer packet */ #define ETHERTYPE_NTRAILER 16 #define ETHERMTU 1500 #define ETHERMIN (60-14) /* * Ethernet Address Resolution Protocol. * * See RFC 826 for protocol description. Structure below is adapted * to resolving internet addresses. Field names used correspond to * RFC 826. */ struct ether_arp { struct arphdr ea_hdr; /* fixed-size header */ u_char arp_sha[6]; /* sender hardware address */ u_char arp_spa[4]; /* sender protocol address */ u_char arp_tha[6]; /* target hardware address */ u_char arp_tpa[4]; /* target protocol address */ }; #define arp_hrd ea_hdr.ar_hrd #define arp_pro ea_hdr.ar_pro #define arp_hln ea_hdr.ar_hln #define arp_pln ea_hdr.ar_pln #define arp_op ea_hdr.ar_op lft-4.01/include/netinet/udp_var.h000755 000765 000024 00000006774 11053131665 017073 0ustar00vicstaff000000 000000 /* * Copyright (c) 1982, 1986, 1989, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)udp_var.h 8.1 (Berkeley) 6/10/93 */ /* * UDP kernel structures and variables. */ struct udpiphdr { struct ipovly ui_i; /* overlaid ip structure */ struct udphdr ui_u; /* udp header */ }; #define ui_next ui_i.ih_next #define ui_prev ui_i.ih_prev #define ui_x1 ui_i.ih_x1 #define ui_pr ui_i.ih_pr #define ui_len ui_i.ih_len #define ui_src ui_i.ih_src #define ui_dst ui_i.ih_dst #define ui_sport ui_u.uh_sport #define ui_dport ui_u.uh_dport #define ui_ulen ui_u.uh_ulen #define ui_sum ui_u.uh_sum struct udpstat { /* input statistics: */ u_long udps_ipackets; /* total input packets */ u_long udps_hdrops; /* packet shorter than header */ u_long udps_badsum; /* checksum error */ u_long udps_badlen; /* data length larger than packet */ u_long udps_noport; /* no socket on port */ u_long udps_noportbcast; /* of above, arrived as broadcast */ u_long udps_fullsock; /* not delivered, input socket full */ u_long udpps_pcbcachemiss; /* input packets missing pcb cache */ /* output statistics: */ u_long udps_opackets; /* total output packets */ }; /* * Names for UDP sysctl objects */ #define UDPCTL_CHECKSUM 1 /* checksum UDP packets */ #define UDPCTL_MAXID 2 #define UDPCTL_NAMES { \ { 0, 0 }, \ { "checksum", CTLTYPE_INT }, \ } #ifdef KERNEL struct inpcb udb; struct udpstat udpstat; void udp_ctlinput __P((int, struct sockaddr *, struct ip *)); void udp_init __P((void)); void udp_input __P((struct mbuf *, int)); int udp_output __P((struct inpcb *, struct mbuf *, struct mbuf *, struct mbuf *)); int udp_sysctl __P((int *, u_int, void *, size_t *, void *, size_t)); int udp_usrreq __P((struct socket *, int, struct mbuf *, struct mbuf *, struct mbuf *)); #endif lft-4.01/include/netinet/ip.h000755 000765 000024 00000013215 11053131665 016027 0ustar00vicstaff000000 000000 /* * Copyright (c) 1982, 1986, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)ip.h 8.2 (Berkeley) 6/1/94 */ /* * Definitions for internet protocol version 4. * Per RFC 791, September 1981. */ #define IPVERSION 4 /* * Structure of an internet header, naked of options. * * We declare ip_len and ip_off to be short, rather than u_short * pragmatically since otherwise unsigned comparisons can result * against negative integers quite easily, and fail in subtle ways. */ struct ip { #if BYTE_ORDER == LITTLE_ENDIAN u_char ip_hl:4, /* header length */ ip_v:4; /* version */ #endif #if BYTE_ORDER == BIG_ENDIAN u_char ip_v:4, /* version */ ip_hl:4; /* header length */ #endif u_char ip_tos; /* type of service */ short ip_len; /* total length */ u_short ip_id; /* identification */ short ip_off; /* fragment offset field */ #define IP_DF 0x4000 /* dont fragment flag */ #define IP_MF 0x2000 /* more fragments flag */ #define IP_OFFMASK 0x1fff /* mask for fragmenting bits */ u_char ip_ttl; /* time to live */ u_char ip_p; /* protocol */ u_short ip_sum; /* checksum */ struct in_addr ip_src,ip_dst; /* source and dest address */ }; #define IP_MAXPACKET 65535 /* maximum packet size */ /* * Definitions for IP type of service (ip_tos) */ #define IPTOS_LOWDELAY 0x10 #define IPTOS_THROUGHPUT 0x08 #define IPTOS_RELIABILITY 0x04 /* * Definitions for IP precedence (also in ip_tos) (hopefully unused) */ #define IPTOS_PREC_NETCONTROL 0xe0 #define IPTOS_PREC_INTERNETCONTROL 0xc0 #define IPTOS_PREC_CRITIC_ECP 0xa0 #define IPTOS_PREC_FLASHOVERRIDE 0x80 #define IPTOS_PREC_FLASH 0x60 #define IPTOS_PREC_IMMEDIATE 0x40 #define IPTOS_PREC_PRIORITY 0x20 #define IPTOS_PREC_ROUTINE 0x00 /* * Definitions for options. */ #define IPOPT_COPIED(o) ((o)&0x80) #define IPOPT_CLASS(o) ((o)&0x60) #define IPOPT_NUMBER(o) ((o)&0x1f) #define IPOPT_CONTROL 0x00 #define IPOPT_RESERVED1 0x20 #define IPOPT_DEBMEAS 0x40 #define IPOPT_RESERVED2 0x60 #define IPOPT_EOL 0 /* end of option list */ #define IPOPT_NOP 1 /* no operation */ #define IPOPT_RR 7 /* record packet route */ #define IPOPT_TS 68 /* timestamp */ #define IPOPT_SECURITY 130 /* provide s,c,h,tcc */ #define IPOPT_LSRR 131 /* loose source route */ #define IPOPT_SATID 136 /* satnet id */ #define IPOPT_SSRR 137 /* strict source route */ /* * Offsets to fields in options other than EOL and NOP. */ #define IPOPT_OPTVAL 0 /* option ID */ #define IPOPT_OLEN 1 /* option length */ #define IPOPT_OFFSET 2 /* offset within option */ #define IPOPT_MINOFF 4 /* min value of above */ /* * Time stamp option structure. */ struct ip_timestamp { u_char ipt_code; /* IPOPT_TS */ u_char ipt_len; /* size of structure (variable) */ u_char ipt_ptr; /* index of current entry */ #if BYTE_ORDER == LITTLE_ENDIAN u_char ipt_flg:4, /* flags, see below */ ipt_oflw:4; /* overflow counter */ #endif #if BYTE_ORDER == BIG_ENDIAN u_char ipt_oflw:4, /* overflow counter */ ipt_flg:4; /* flags, see below */ #endif union ipt_timestamp { n_long ipt_time[1]; struct ipt_ta { struct in_addr ipt_addr; n_long ipt_time; } ipt_ta[1]; } ipt_timestamp; }; /* flag bits for ipt_flg */ #define IPOPT_TS_TSONLY 0 /* timestamps only */ #define IPOPT_TS_TSANDADDR 1 /* timestamps and addresses */ #define IPOPT_TS_PRESPEC 3 /* specified modules only */ /* bits for security (not byte swapped) */ #define IPOPT_SECUR_UNCLASS 0x0000 #define IPOPT_SECUR_CONFID 0xf135 #define IPOPT_SECUR_EFTO 0x789a #define IPOPT_SECUR_MMMM 0xbc4d #define IPOPT_SECUR_RESTR 0xaf13 #define IPOPT_SECUR_SECRET 0xd788 #define IPOPT_SECUR_TOPSECRET 0x6bc5 /* * Internet implementation parameters. */ #define MAXTTL 255 /* maximum time to live (seconds) */ #define IPDEFTTL 64 /* default ttl, from RFC 1340 */ #define IPFRAGTTL 60 /* time to live for frags, slowhz */ #define IPTTLDEC 1 /* subtracted when forwarding */ #define IP_MSS 576 /* default maximum segment size */ lft-4.01/include/netinet/in_systm.h000755 000765 000024 00000004662 11053131665 017272 0ustar00vicstaff000000 000000 /* * Copyright (c) 1982, 1986, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)in_systm.h 8.1 (Berkeley) 6/10/93 */ /* * Miscellaneous internetwork * definitions for kernel. */ /* * Network types. * * Internally the system keeps counters in the headers with the bytes * swapped so that VAX instructions will work on them. It reverses * the bytes before transmission at each protocol level. The n_ types * represent the types with the bytes in ``high-ender'' order. */ typedef u_short n_short; /* short as received from the net */ typedef u_long n_long; /* long as received from the net */ typedef u_long n_time; /* ms since 00:00 GMT, byte rev */ #ifdef KERNEL n_time iptime __P((void)); #endif lft-4.01/include/netinet/ip_var.h000755 000765 000024 00000015652 11053131665 016706 0ustar00vicstaff000000 000000 /* * Copyright (c) 1982, 1986, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)ip_var.h 8.2 (Berkeley) 1/9/95 */ #include /* * Overlay for ip header used by other protocols (tcp, udp). */ struct ipovly { caddr_t ih_next, ih_prev; /* for protocol sequence q's */ u_char ih_x1; /* (unused) */ u_char ih_pr; /* protocol */ short ih_len; /* protocol length */ struct in_addr ih_src; /* source internet address */ struct in_addr ih_dst; /* destination internet address */ }; /* * Ip reassembly queue structure. Each fragment * being reassembled is attached to one of these structures. * They are timed out after ipq_ttl drops to 0, and may also * be reclaimed if memory becomes tight. */ struct ipq { struct ipq *next,*prev; /* to other reass headers */ u_char ipq_ttl; /* time for reass q to live */ u_char ipq_p; /* protocol of this fragment */ u_short ipq_id; /* sequence id for reassembly */ struct ipasfrag *ipq_next,*ipq_prev; /* to ip headers of fragments */ struct in_addr ipq_src,ipq_dst; }; /* * Ip header, when holding a fragment. * * Note: ipf_next must be at same offset as ipq_next above */ struct ipasfrag { #if BYTE_ORDER == LITTLE_ENDIAN u_char ip_hl:4, ip_v:4; #endif #if BYTE_ORDER == BIG_ENDIAN u_char ip_v:4, ip_hl:4; #endif u_char ipf_mff; /* XXX overlays ip_tos: use low bit * to avoid destroying tos; * copied from (ip_off&IP_MF) */ short ip_len; u_short ip_id; short ip_off; u_char ip_ttl; u_char ip_p; u_short ip_sum; struct ipasfrag *ipf_next; /* next fragment */ struct ipasfrag *ipf_prev; /* previous fragment */ }; /* * Structure stored in mbuf in inpcb.ip_options * and passed to ip_output when ip options are in use. * The actual length of the options (including ipopt_dst) * is in m_len. */ #define MAX_IPOPTLEN 40 struct ipoption { struct in_addr ipopt_dst; /* first-hop dst if source routed */ char ipopt_list[MAX_IPOPTLEN]; /* options proper */ }; struct ipstat { u_long ips_total; /* total packets received */ u_long ips_badsum; /* checksum bad */ u_long ips_tooshort; /* packet too short */ u_long ips_toosmall; /* not enough data */ u_long ips_badhlen; /* ip header length < data size */ u_long ips_badlen; /* ip length < ip header length */ u_long ips_fragments; /* fragments received */ u_long ips_fragdropped; /* frags dropped (dups, out of space) */ u_long ips_fragtimeout; /* fragments timed out */ u_long ips_forward; /* packets forwarded */ u_long ips_cantforward; /* packets rcvd for unreachable dest */ u_long ips_redirectsent; /* packets forwarded on same net */ u_long ips_noproto; /* unknown or unsupported protocol */ u_long ips_delivered; /* datagrams delivered to upper level*/ u_long ips_localout; /* total ip packets generated here */ u_long ips_odropped; /* lost packets due to nobufs, etc. */ u_long ips_reassembled; /* total packets reassembled ok */ u_long ips_fragmented; /* datagrams sucessfully fragmented */ u_long ips_ofragments; /* output fragments created */ u_long ips_cantfrag; /* don't fragment flag was set, etc. */ u_long ips_badoptions; /* error in option processing */ u_long ips_noroute; /* packets discarded due to no route */ u_long ips_badvers; /* ip version != 4 */ u_long ips_rawout; /* total raw ip packets generated */ }; #ifdef KERNEL /* flags passed to ip_output as last parameter */ #define IP_FORWARDING 0x1 /* most of ip header exists */ #define IP_RAWOUTPUT 0x2 /* raw ip header exists */ #define IP_ROUTETOIF SO_DONTROUTE /* bypass routing tables */ #define IP_ALLOWBROADCAST SO_BROADCAST /* can send broadcast packets */ struct ipstat ipstat; struct ipq ipq; /* ip reass. queue */ u_short ip_id; /* ip packet ctr, for ids */ int ip_defttl; /* default IP ttl */ int in_control __P((struct socket *, u_long, caddr_t, struct ifnet *)); int ip_ctloutput __P((int, struct socket *, int, int, struct mbuf **)); void ip_deq __P((struct ipasfrag *)); int ip_dooptions __P((struct mbuf *)); void ip_drain __P((void)); void ip_enq __P((struct ipasfrag *, struct ipasfrag *)); void ip_forward __P((struct mbuf *, int)); void ip_freef __P((struct ipq *)); void ip_freemoptions __P((struct ip_moptions *)); int ip_getmoptions __P((int, struct ip_moptions *, struct mbuf **)); void ip_init __P((void)); int ip_mforward __P((struct mbuf *, struct ifnet *)); int ip_optcopy __P((struct ip *, struct ip *)); int ip_output __P((struct mbuf *, struct mbuf *, struct route *, int, struct ip_moptions *)); int ip_pcbopts __P((struct mbuf **, struct mbuf *)); struct ip * ip_reass __P((struct ipasfrag *, struct ipq *)); struct in_ifaddr * ip_rtaddr __P((struct in_addr)); int ip_setmoptions __P((int, struct ip_moptions **, struct mbuf *)); void ip_slowtimo __P((void)); struct mbuf * ip_srcroute __P((void)); void ip_stripoptions __P((struct mbuf *, struct mbuf *)); int ip_sysctl __P((int *, u_int, void *, size_t *, void *, size_t)); void ipintr __P((void)); int rip_ctloutput __P((int, struct socket *, int, int, struct mbuf **)); void rip_init __P((void)); void rip_input __P((struct mbuf *)); int rip_output __P((struct mbuf *, struct socket *, u_long)); int rip_usrreq __P((struct socket *, int, struct mbuf *, struct mbuf *, struct mbuf *)); #endif lft-4.01/include/netinet/udp.h000755 000765 000024 00000004101 11053131665 016201 0ustar00vicstaff000000 000000 /* * Copyright (c) 1982, 1986, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)udp.h 8.1 (Berkeley) 6/10/93 */ /* * Udp protocol header. * Per RFC 768, September, 1981. */ struct udphdr { u_short uh_sport; /* source port */ u_short uh_dport; /* destination port */ short uh_ulen; /* udp length */ u_short uh_sum; /* udp checksum */ }; lft-4.01/include/net/slip.h000755 000765 000024 00000002632 11053131665 015507 0ustar00vicstaff000000 000000 /* * Definitions that user level programs might need to know to interact * with serial line IP (slip) lines. * * Copyright (c) 1990 Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that the above copyright notice and this paragraph are * duplicated in all such forms and that any documentation, * advertising materials, and other materials related to such * distribution and use acknowledge that the software was developed * by the University of California, Berkeley. The name of the * University may not be used to endorse or promote products derived * from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ /* * ioctl to get slip interface unit number (e.g., sl0, sl1, etc.) * assigned to some terminal line with a slip module pushed on it. */ #ifdef __STDC__ #define SLIOGUNIT _IOR('B', 1, int) #else #define SLIOGUNIT _IOR(B, 1, int) #endif /* * definitions of the pseudo- link-level header attached to slip * packets grabbed by the packet filter (bpf) traffic monitor. */ #define SLIP_HDRLEN 16 #define SLX_DIR 0 #define SLX_CHDR 1 #define CHDR_LEN 15 #define SLIPDIR_IN 0 #define SLIPDIR_OUT 1 lft-4.01/include/net/slcompress.h000755 000765 000024 00000013166 11053131665 016736 0ustar00vicstaff000000 000000 /* * Definitions for tcp compression routines. * * Copyright (c) 1989, 1990, 1992, 1993 Regents of the University of * California. All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that the above copyright notice and this paragraph are * duplicated in all such forms and that any documentation, * advertising materials, and other materials related to such * distribution and use acknowledge that the software was developed * by the University of California, Berkeley. The name of the * University may not be used to endorse or promote products derived * from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. * * Van Jacobson (van@ee.lbl.gov), Dec 31, 1989: * - Initial distribution. */ #define MAX_STATES 16 /* must be > 2 and < 256 */ #define MAX_HDR MLEN /* XXX 4bsd-ism: should really be 128 */ /* * Compressed packet format: * * The first octet contains the packet type (top 3 bits), TCP * 'push' bit, and flags that indicate which of the 4 TCP sequence * numbers have changed (bottom 5 bits). The next octet is a * conversation number that associates a saved IP/TCP header with * the compressed packet. The next two octets are the TCP checksum * from the original datagram. The next 0 to 15 octets are * sequence number changes, one change per bit set in the header * (there may be no changes and there are two special cases where * the receiver implicitly knows what changed -- see below). * * There are 5 numbers which can change (they are always inserted * in the following order): TCP urgent pointer, window, * acknowlegement, sequence number and IP ID. (The urgent pointer * is different from the others in that its value is sent, not the * change in value.) Since typical use of SLIP links is biased * toward small packets (see comments on MTU/MSS below), changes * use a variable length coding with one octet for numbers in the * range 1 - 255 and 3 octets (0, MSB, LSB) for numbers in the * range 256 - 65535 or 0. (If the change in sequence number or * ack is more than 65535, an uncompressed packet is sent.) */ /* * Packet types (must not conflict with IP protocol version) * * The top nibble of the first octet is the packet type. There are * three possible types: IP (not proto TCP or tcp with one of the * control flags set); uncompressed TCP (a normal IP/TCP packet but * with the 8-bit protocol field replaced by an 8-bit connection id -- * this type of packet syncs the sender & receiver); and compressed * TCP (described above). * * LSB of 4-bit field is TCP "PUSH" bit (a worthless anachronism) and * is logically part of the 4-bit "changes" field that follows. Top * three bits are actual packet type. For backward compatibility * and in the interest of conserving bits, numbers are chosen so the * IP protocol version number (4) which normally appears in this nibble * means "IP packet". */ /* packet types */ #define TYPE_IP 0x40 #define TYPE_UNCOMPRESSED_TCP 0x70 #define TYPE_COMPRESSED_TCP 0x80 #define TYPE_ERROR 0x00 /* Bits in first octet of compressed packet */ #define NEW_C 0x40 /* flag bits for what changed in a packet */ #define NEW_I 0x20 #define NEW_S 0x08 #define NEW_A 0x04 #define NEW_W 0x02 #define NEW_U 0x01 /* reserved, special-case values of above */ #define SPECIAL_I (NEW_S|NEW_W|NEW_U) /* echoed interactive traffic */ #define SPECIAL_D (NEW_S|NEW_A|NEW_W|NEW_U) /* unidirectional data */ #define SPECIALS_MASK (NEW_S|NEW_A|NEW_W|NEW_U) #define TCP_PUSH_BIT 0x10 /* * "state" data for each active tcp conversation on the wire. This is * basically a copy of the entire IP/TCP header from the last packet * we saw from the conversation together with a small identifier * the transmit & receive ends of the line use to locate saved header. */ struct cstate { struct cstate *cs_next; /* next most recently used cstate (xmit only) */ u_short cs_hlen; /* size of hdr (receive only) */ u_char cs_id; /* connection # associated with this state */ u_char cs_filler; union { char csu_hdr[MAX_HDR]; struct ip csu_ip; /* ip/tcp hdr from most recent packet */ } slcs_u; }; #define cs_ip slcs_u.csu_ip #define cs_hdr slcs_u.csu_hdr /* * all the state data for one serial line (we need one of these * per line). */ struct slcompress { struct cstate *last_cs; /* most recently used tstate */ u_char last_recv; /* last rcvd conn. id */ u_char last_xmit; /* last sent conn. id */ u_short flags; #ifndef SL_NO_STATS u_int sls_packets; /* outbound packets */ u_int sls_compressed; /* outbound compressed packets */ u_int sls_searches; /* searches for connection state */ u_int sls_misses; /* times couldn't find conn. state */ u_int sls_uncompressedin;/* inbound uncompressed packets */ u_int sls_compressedin; /* inbound compressed packets */ u_int sls_errorin; /* inbound unknown type packets */ u_int sls_tossed; /* inbound packets tossed because of error */ #endif struct cstate tstate[MAX_STATES]; /* xmit connection states */ struct cstate rstate[MAX_STATES]; /* receive connection states */ }; /* flag values */ #define SLF_TOSS 1 /* tossing rcvd frames because of input err */ #ifdef KERNEL #ifdef __STDC__ extern void sl_compress_init(struct slcompress *); extern u_char sl_compress_tcp(struct mbuf *, struct ip *, struct slcompress *); extern int sl_uncompress_tcp(struct mbuf *, int, u_int, struct slcompress *); #else extern void sl_compress_init(); extern u_char sl_compress_tcp(); extern int sl_uncompress_tcp(); #endif #endif lft-4.01/include/net/if_arp.h000755 000765 000024 00000015037 11053131665 016003 0ustar00vicstaff000000 000000 /* Definitions for Address Resolution Protocol. Copyright (C) 1997, 1999, 2001 Free Software Foundation, Inc. This file is part of the GNU C Library. Contributed by Ulrich Drepper , 1997. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ /* Based on the 4.4BSD and Linux version of this file. */ #ifndef _NET_IF_ARP_H #define _NET_IF_ARP_H 1 /* Some internals from deep down in the kernel. */ #define MAX_ADDR_LEN 7 /* This structure defines an ethernet arp header. */ /* ARP protocol opcodes. */ #define ARPOP_REQUEST 1 /* ARP request. */ #define ARPOP_REPLY 2 /* ARP reply. */ #define ARPOP_RREQUEST 3 /* RARP request. */ #define ARPOP_RREPLY 4 /* RARP reply. */ #define ARPOP_InREQUEST 8 /* InARP request. */ #define ARPOP_InREPLY 9 /* InARP reply. */ #define ARPOP_NAK 10 /* (ATM)ARP NAK. */ /* See RFC 826 for protocol description. ARP packets are variable in size; the arphdr structure defines the fixed-length portion. Protocol type values are the same as those for 10 Mb/s Ethernet. It is followed by the variable-sized fields ar_sha, arp_spa, arp_tha and arp_tpa in that order, according to the lengths specified. Field names used correspond to RFC 826. */ struct arphdr { unsigned short int ar_hrd; /* Format of hardware address. */ unsigned short int ar_pro; /* Format of protocol address. */ unsigned char ar_hln; /* Length of hardware address. */ unsigned char ar_pln; /* Length of protocol address. */ unsigned short int ar_op; /* ARP opcode (command). */ #if 0 /* Ethernet looks like this : This bit is variable sized however... */ unsigned char __ar_sha[ETH_ALEN]; /* Sender hardware address. */ unsigned char __ar_sip[4]; /* Sender IP address. */ unsigned char __ar_tha[ETH_ALEN]; /* Target hardware address. */ unsigned char __ar_tip[4]; /* Target IP address. */ #endif }; /* ARP protocol HARDWARE identifiers. */ #define ARPHRD_NETROM 0 /* From KA9Q: NET/ROM pseudo. */ #define ARPHRD_ETHER 1 /* Ethernet 10/100Mbps. */ #define ARPHRD_EETHER 2 /* Experimental Ethernet. */ #define ARPHRD_AX25 3 /* AX.25 Level 2. */ #define ARPHRD_PRONET 4 /* PROnet token ring. */ #define ARPHRD_CHAOS 5 /* Chaosnet. */ #define ARPHRD_IEEE802 6 /* IEEE 802.2 Ethernet/TR/TB. */ #define ARPHRD_ARCNET 7 /* ARCnet. */ #define ARPHRD_APPLETLK 8 /* APPLEtalk. */ #define ARPHRD_DLCI 15 /* Frame Relay DLCI. */ #define ARPHRD_ATM 19 /* ATM. */ #define ARPHRD_METRICOM 23 /* Metricom STRIP (new IANA id). */ /* Dummy types for non ARP hardware */ #define ARPHRD_SLIP 256 #define ARPHRD_CSLIP 257 #define ARPHRD_SLIP6 258 #define ARPHRD_CSLIP6 259 #define ARPHRD_RSRVD 260 /* Notional KISS type. */ #define ARPHRD_ADAPT 264 #define ARPHRD_ROSE 270 #define ARPHRD_X25 271 /* CCITT X.25. */ #define ARPHDR_HWX25 272 /* Boards with X.25 in firmware. */ #define ARPHRD_PPP 512 #define ARPHRD_CISCO 513 /* Cisco HDLC. */ #define ARPHRD_HDLC ARPHRD_CISCO #define ARPHRD_LAPB 516 /* LAPB. */ #define ARPHRD_DDCMP 517 /* Digital's DDCMP. */ #define ARPHRD_RAWHDLC 518 /* Raw HDLC. */ #define ARPHRD_TUNNEL 768 /* IPIP tunnel. */ #define ARPHRD_TUNNEL6 769 /* IPIP6 tunnel. */ #define ARPHRD_FRAD 770 /* Frame Relay Access Device. */ #define ARPHRD_SKIP 771 /* SKIP vif. */ #define ARPHRD_LOOPBACK 772 /* Loopback device. */ #define ARPHRD_LOCALTLK 773 /* Localtalk device. */ #define ARPHRD_FDDI 774 /* Fiber Distributed Data Interface. */ #define ARPHRD_BIF 775 /* AP1000 BIF. */ #define ARPHRD_SIT 776 /* sit0 device - IPv6-in-IPv4. */ #define ARPHRD_IPDDP 777 /* IP-in-DDP tunnel. */ #define ARPHRD_IPGRE 778 /* GRE over IP. */ #define ARPHRD_PIMREG 779 /* PIMSM register interface. */ #define ARPHRD_HIPPI 780 /* High Performance Parallel I'face. */ #define ARPHRD_ASH 781 /* (Nexus Electronics) Ash. */ #define ARPHRD_ECONET 782 /* Acorn Econet. */ #define ARPHRD_IRDA 783 /* Linux-IrDA. */ #define ARPHRD_FCPP 784 /* Point to point fibrechanel. */ #define ARPHRD_FCAL 785 /* Fibrechanel arbitrated loop. */ #define ARPHRD_FCPL 786 /* Fibrechanel public loop. */ #define ARPHRD_FCPFABRIC 787 /* Fibrechanel fabric. */ #define ARPHRD_IEEE802_TR 800 /* Magic type ident for TR. */ #define ARPHRD_IEEE80211 801 /* IEEE 802.11. */ /* ARP ioctl request. */ struct arpreq { struct sockaddr arp_pa; /* Protocol address. */ struct sockaddr arp_ha; /* Hardware address. */ int arp_flags; /* Flags. */ struct sockaddr arp_netmask; /* Netmask (only for proxy arps). */ char arp_dev[16]; }; struct arpreq_old { struct sockaddr arp_pa; /* Protocol address. */ struct sockaddr arp_ha; /* Hardware address. */ int arp_flags; /* Flags. */ struct sockaddr arp_netmask; /* Netmask (only for proxy arps). */ }; /* ARP Flag values. */ #define ATF_COM 0x02 /* Completed entry (ha valid). */ #define ATF_PERM 0x04 /* Permanent entry. */ #define ATF_PUBL 0x08 /* Publish entry. */ #define ATF_USETRAILERS 0x10 /* Has requested trailers. */ #define ATF_NETMASK 0x20 /* Want to use a netmask (only for proxy entries). */ #define ATF_DONTPUB 0x40 /* Don't answer this addresses. */ #define ATF_MAGIC 0x80 /* Automatically added entry. */ /* Support for the user space arp daemon, arpd. */ #define ARPD_UPDATE 0x01 #define ARPD_LOOKUP 0x02 #define ARPD_FLUSH 0x03 struct arpd_request { unsigned short int req; /* Request type. */ u_int32_t ip; /* IP address of entry. */ unsigned long int dev; /* Device entry is tied to. */ unsigned long int stamp; unsigned long int updated; unsigned char ha[MAX_ADDR_LEN]; /* Hardware address. */ }; #endif /* net/if_arp.h */ lft-4.01/config/acconfig.h.in000644 000765 000024 00000014201 15247705774 015763 0ustar00vicstaff000000 000000 /* acconfig.h.in. */ /* Host system type */ #undef HOST_SYSTEM_TYPE /* Define if using alloca.c. */ #undef C_ALLOCA /* Define to empty if the keyword does not work. */ #undef const /* Define to one of _getb67, GETB67, getb67 for Cray-2 and Cray-YMP systems. This function is required for alloca.c support on those systems. */ #undef CRAY_STACKSEG_END /* Define if you have alloca, as a function or macro. */ #undef HAVE_ALLOCA /* Define if you have and it should be used (not on Ultrix). */ #undef HAVE_ALLOCA_H /* Define to `unsigned' if doesn't define. */ #undef size_t /* If using the C implementation of alloca, define if you know the direction of stack growth for your system; otherwise it will be automatically deduced at run-time. STACK_DIRECTION > 0 => grows toward higher addresses STACK_DIRECTION < 0 => grows toward lower addresses STACK_DIRECTION = 0 => direction of growth unknown */ #undef STACK_DIRECTION /* Define if you have the ANSI C header files. */ #undef STDC_HEADERS /* Define if you can safely include both and . */ #undef TIME_WITH_SYS_TIME /* Define if using alloca.c. */ /* #undef C_ALLOCA */ /* Define to empty if the keyword does not work. */ /* #undef const */ /* Define to one of _getb67, GETB67, getb67 for Cray-2 and Cray-YMP systems. This function is required for alloca.c support on those systems. */ /* #undef CRAY_STACKSEG_END */ /* Define if you have alloca, as a function or macro. */ #define HAVE_ALLOCA 1 /* Define if you have and it should be used (not on Ultrix). */ #define HAVE_ALLOCA_H 1 /* Define to `unsigned' if doesn't define. */ /* #undef size_t */ /* If using the C implementation of alloca, define if you know the direction of stack growth for your system; otherwise it will be automatically deduced at run-time. STACK_DIRECTION > 0 => grows toward higher addresses STACK_DIRECTION < 0 => grows toward lower addresses STACK_DIRECTION = 0 => direction of growth unknown */ /* #undef STACK_DIRECTION */ /* Define if you have the ANSI C header files. */ #define STDC_HEADERS 1 /* Define if you can safely include both and . */ #define TIME_WITH_SYS_TIME 1 /* Define if using alloca.c. */ /* #undef C_ALLOCA */ /* Define to empty if the keyword does not work. */ /* #undef const */ /* Define to one of _getb67, GETB67, getb67 for Cray-2 and Cray-YMP systems. This function is required for alloca.c support on those systems. */ /* #undef CRAY_STACKSEG_END */ /* Define if you have alloca, as a function or macro. */ #define HAVE_ALLOCA 1 /* Define if you have and it should be used (not on Ultrix). */ #define HAVE_ALLOCA_H 1 /* Define to `unsigned' if doesn't define. */ /* #undef size_t */ /* If using the C implementation of alloca, define if you know the direction of stack growth for your system; otherwise it will be automatically deduced at run-time. STACK_DIRECTION > 0 => grows toward higher addresses STACK_DIRECTION < 0 => grows toward lower addresses STACK_DIRECTION = 0 => direction of growth unknown */ /* #undef STACK_DIRECTION */ /* Define if you have the ANSI C header files. */ #define STDC_HEADERS 1 /* Define if you can safely include both and . */ #define TIME_WITH_SYS_TIME 1 /* Linux requires this in order to have the right network structures. * _DEFAULT_SOURCE is the modern glibc (>= 2.19) feature macro: it provides the * same BSD/SVID extensions and suppresses the "_BSD_SOURCE is deprecated" * warning on current glibc; _BSD_SOURCE is kept for older glibc (< 2.19). */ #ifndef _DEFAULT_SOURCE #define _DEFAULT_SOURCE #endif #ifndef _BSD_SOURCE #define _BSD_SOURCE #endif /* Define if you have the gettimeofday function. */ #undef HAVE_GETTIMEOFDAY /* Define if you have the select function. */ #undef HAVE_SELECT /* Define if you have the socket function. */ #undef HAVE_SOCKET /* Define if you have the strdup function. */ #undef HAVE_STRDUP /* Define if you have the strstr function. */ #undef HAVE_STRSTR /* Define if you have the header file. */ #undef HAVE_FCNTL_H /* Define if you have the header file. */ #undef HAVE_LIMITS_H /* Define if you have the header file. */ #undef HAVE_STRINGS_H /* Define if you have the header file. */ #undef HAVE_SYS_IOCTL_H /* Define if you have the header file. */ #undef HAVE_SYS_TIME_H /* Define if you have the header file. */ #undef HAVE_UNISTD_H /* Define if you have the m library (-lm). */ #undef HAVE_LIBM /* Define if you have the nsl library (-lnsl). */ #undef HAVE_LIBNSL /* Define if you have the pcap library (-lpcap). */ #undef HAVE_LIBPCAP /* Define if you have the socket library (-lsocket). */ #undef HAVE_LIBSOCKET /* Define if this is a BSD TCP/IP stack */ #undef BSD_IP_STACK /* Define if this is Darwin */ #undef DARWIN /* Define if this is NetBSD */ #undef NETBSD /* Define if user enabled gettimeofday (GTOD) */ #undef USE_GTOD /* Define if building universal binaries */ #undef UNIVERSAL /* Define if this is OpenBSD */ #undef OPENBSD /* Define if solaris munges the th_sum field in its own special way */ #undef SOLARIS_LENGTH_IN_CHECKSUM /* Define if your system has a sa_len member in struct sockaddr */ #undef HAVE_SOCKADDR_SA_LEN /* Define if an IPv6 network stack is detected */ #undef INET6 /* Define to 1 if the c-ares async DNS library is available */ #undef HAVE_CARES /* Define to 1 if the c-ares 1.34+ ares_process_fds event API is available */ #undef HAVE_ARES_PROCESS_FDS /* Define to 1 if the ncurses library is available */ #undef HAVE_NCURSES /* Define to 1 if the ncurses library provides mvin_wch (wide-char cell reads). */ #undef HAVE_MVIN_WCH /* IPv6 support (LFT 4.0). This template is hand-maintained (not * autoheader-generated); every AC_DEFINE symbol needs a matching #undef * here or it silently never reaches config/acconfig.h. */ #undef HAVE_IPV6 #undef HAVE_IPV6_HDRINCL #undef HAVE_NETINET_IP6_H #undef HAVE_NETINET_ICMP6_H #undef HAVE_IFADDRS_H #undef HAVE_GETIFADDRS #undef HAVE_INET_NTOP #undef HAVE_INET_PTON #undef HAVE_STRUCT_SOCKADDR_STORAGE lft-4.01/config/install-sh000755 000765 000024 00000036122 15165341545 015435 0ustar00vicstaff000000 000000 #!/bin/sh # install - install a program, script, or datafile scriptversion=2025-06-18.21; # UTC # This originates from X11R5 (mit/util/scripts/install.sh), which was # later released in X11R6 (xc/config/util/install.sh) with the # following copyright and license. # # Copyright (C) 1994 X Consortium # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to # deal in the Software without restriction, including without limitation the # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or # sell copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN # AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- # TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # # Except as contained in this notice, the name of the X Consortium shall not # be used in advertising or otherwise to promote the sale, use or other deal- # ings in this Software without prior written authorization from the X Consor- # tium. # # # FSF changes to this file are in the public domain. # # Calling this script install-sh is preferred over install.sh, to prevent # 'make' implicit rules from creating a file called install from it # when there is no Makefile. # # This script is compatible with the BSD install script, but was written # from scratch. tab=' ' nl=' ' IFS=" $tab$nl" # Set DOITPROG to "echo" to test this script. doit=${DOITPROG-} doit_exec=${doit:-exec} # Put in absolute file names if you don't have them in your path; # or use environment vars. chgrpprog=${CHGRPPROG-chgrp} chmodprog=${CHMODPROG-chmod} chownprog=${CHOWNPROG-chown} cmpprog=${CMPPROG-cmp} cpprog=${CPPROG-cp} mkdirprog=${MKDIRPROG-mkdir} mvprog=${MVPROG-mv} rmprog=${RMPROG-rm} stripprog=${STRIPPROG-strip} posix_mkdir= # Desired mode of installed file. mode=0755 # Create dirs (including intermediate dirs) using mode 755. # This is like GNU 'install' as of coreutils 8.32 (2020). mkdir_umask=22 backupsuffix= chgrpcmd= chmodcmd=$chmodprog chowncmd= mvcmd=$mvprog rmcmd="$rmprog -f" stripcmd= src= dst= dir_arg= dst_arg= copy_on_change=false is_target_a_directory=possibly usage="\ Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE or: $0 [OPTION]... SRCFILES... DIRECTORY or: $0 [OPTION]... -t DIRECTORY SRCFILES... or: $0 [OPTION]... -d DIRECTORIES... In the 1st form, copy SRCFILE to DSTFILE. In the 2nd and 3rd, copy all SRCFILES to DIRECTORY. In the 4th, create DIRECTORIES. Options: --help display this help and exit. --version display version info and exit. -c (ignored) -C install only if different (preserve data modification time) -d create directories instead of installing files. -g GROUP $chgrpprog installed files to GROUP. -m MODE $chmodprog installed files to MODE. -o USER $chownprog installed files to USER. -p pass -p to $cpprog. -s $stripprog installed files. -S SUFFIX attempt to back up existing files, with suffix SUFFIX. -t DIRECTORY install into DIRECTORY. -T report an error if DSTFILE is a directory. Environment variables override the default commands: CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG RMPROG STRIPPROG By default, rm is invoked with -f; when overridden with RMPROG, it's up to you to specify -f if you want it. If -S is not specified, no backups are attempted. Report bugs to . GNU Automake home page: . General help using GNU software: ." while test $# -ne 0; do case $1 in -c) ;; -C) copy_on_change=true;; -d) dir_arg=true;; -g) chgrpcmd="$chgrpprog $2" shift;; --help) echo "$usage"; exit $?;; -m) mode=$2 case $mode in *' '* | *"$tab"* | *"$nl"* | *'*'* | *'?'* | *'['*) echo "$0: invalid mode: $mode" >&2 exit 1;; esac shift;; -o) chowncmd="$chownprog $2" shift;; -p) cpprog="$cpprog -p";; -s) stripcmd=$stripprog;; -S) backupsuffix="$2" shift;; -t) is_target_a_directory=always dst_arg=$2 # Protect names problematic for 'test' and other utilities. case $dst_arg in -* | [=\(\)!]) dst_arg=./$dst_arg;; esac shift;; -T) is_target_a_directory=never;; --version) echo "$0 (GNU Automake) $scriptversion"; exit $?;; --) shift break;; -*) echo "$0: invalid option: $1" >&2 exit 1;; *) break;; esac shift done # We allow the use of options -d and -T together, by making -d # take the precedence; this is for compatibility with GNU install. if test -n "$dir_arg"; then if test -n "$dst_arg"; then echo "$0: target directory not allowed when installing a directory." >&2 exit 1 fi fi if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then # When -d is used, all remaining arguments are directories to create. # When -t is used, the destination is already specified. # Otherwise, the last argument is the destination. Remove it from $@. for arg do if test -n "$dst_arg"; then # $@ is not empty: it contains at least $arg. set fnord "$@" "$dst_arg" shift # fnord fi shift # arg dst_arg=$arg # Protect names problematic for 'test' and other utilities. case $dst_arg in -* | [=\(\)!]) dst_arg=./$dst_arg;; esac done fi if test $# -eq 0; then if test -z "$dir_arg"; then echo "$0: no input file specified." >&2 exit 1 fi # It's OK to call 'install-sh -d' without argument. # This can happen when creating conditional directories. exit 0 fi if test -z "$dir_arg"; then if test $# -gt 1 || test "$is_target_a_directory" = always; then if test ! -d "$dst_arg"; then echo "$0: $dst_arg: Is not a directory." >&2 exit 1 fi fi fi if test -z "$dir_arg"; then do_exit='(exit $ret); exit $ret' trap "ret=129; $do_exit" 1 trap "ret=130; $do_exit" 2 trap "ret=141; $do_exit" 13 trap "ret=143; $do_exit" 15 # Set umask so as not to create temps with too-generous modes. # However, 'strip' requires both read and write access to temps. case $mode in # Optimize common cases. *644) cp_umask=133;; *755) cp_umask=22;; *[0-7]) if test -z "$stripcmd"; then u_plus_rw= else u_plus_rw='% 200' fi cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;; *) if test -z "$stripcmd"; then u_plus_rw= else u_plus_rw=,u+rw fi cp_umask=$mode$u_plus_rw;; esac fi for src do # Protect names problematic for 'test' and other utilities. case $src in -* | [=\(\)!]) src=./$src;; esac if test -n "$dir_arg"; then dst=$src dstdir=$dst test -d "$dstdir" dstdir_status=$? # Don't chown directories that already exist. if test $dstdir_status = 0; then chowncmd="" fi else # Waiting for this to be detected by the "$cpprog $src $dsttmp" command # might cause directories to be created, which would be especially bad # if $src (and thus $dsttmp) contains '*'. if test ! -f "$src" && test ! -d "$src"; then echo "$0: $src does not exist." >&2 exit 1 fi if test -z "$dst_arg"; then echo "$0: no destination specified." >&2 exit 1 fi dst=$dst_arg # If destination is a directory, append the input filename. if test -d "$dst"; then if test "$is_target_a_directory" = never; then echo "$0: $dst_arg: Is a directory" >&2 exit 1 fi dstdir=$dst dstbase=`basename "$src"` case $dst in */) dst=$dst$dstbase;; *) dst=$dst/$dstbase;; esac dstdir_status=0 else dstdir=`dirname "$dst"` test -d "$dstdir" dstdir_status=$? fi fi case $dstdir in */) dstdirslash=$dstdir;; *) dstdirslash=$dstdir/;; esac obsolete_mkdir_used=false if test $dstdir_status != 0; then case $posix_mkdir in '') # With -d, create the new directory with the user-specified mode. # Otherwise, rely on $mkdir_umask. if test -n "$dir_arg"; then mkdir_mode=-m$mode else mkdir_mode= fi posix_mkdir=false # The $RANDOM variable is not portable (e.g., dash). Use it # here however when possible just to lower collision chance. tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ trap ' ret=$? rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" 2>/dev/null exit $ret ' 0 # Because "mkdir -p" follows existing symlinks and we likely work # directly in world-writable /tmp, make sure that the '$tmpdir' # directory is successfully created first before we actually test # 'mkdir -p'. if (umask $mkdir_umask && $mkdirprog $mkdir_mode "$tmpdir" && exec $mkdirprog $mkdir_mode -p -- "$tmpdir/a/b") >/dev/null 2>&1 then if test -z "$dir_arg" || { # Check for POSIX incompatibility with -m. # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or # other-writable bit of parent directory when it shouldn't. # FreeBSD 6.1 mkdir -m -p sets mode of existing directory. test_tmpdir="$tmpdir/a" ls_ld_tmpdir=`ls -ld "$test_tmpdir"` case $ls_ld_tmpdir in d????-?r-*) different_mode=700;; d????-?--*) different_mode=755;; *) false;; esac && $mkdirprog -m$different_mode -p -- "$test_tmpdir" && { ls_ld_tmpdir_1=`ls -ld "$test_tmpdir"` test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" } } then posix_mkdir=: fi rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" else # Remove any dirs left behind by ancient mkdir implementations. rmdir ./$mkdir_mode ./-p ./-- "$tmpdir" 2>/dev/null fi trap '' 0;; esac if $posix_mkdir && ( umask $mkdir_umask && $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir" ) then : else # mkdir does not conform to POSIX, # or it failed possibly due to a race condition. Create the # directory the slow way, step by step, checking for races as we go. case $dstdir in /*) prefix='/';; [-=\(\)!]*) prefix='./';; *) prefix='';; esac oIFS=$IFS IFS=/ set -f set fnord $dstdir shift set +f IFS=$oIFS prefixes= for d do test X"$d" = X && continue prefix=$prefix$d if test -d "$prefix"; then prefixes= else if $posix_mkdir; then (umask $mkdir_umask && $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break # Don't fail if two instances are running concurrently. test -d "$prefix" || exit 1 else case $prefix in *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;; *) qprefix=$prefix;; esac prefixes="$prefixes '$qprefix'" fi fi prefix=$prefix/ done if test -n "$prefixes"; then # Don't fail if two instances are running concurrently. (umask $mkdir_umask && eval "\$doit_exec \$mkdirprog $prefixes") || test -d "$dstdir" || exit 1 obsolete_mkdir_used=true fi fi fi if test -n "$dir_arg"; then { test -z "$chowncmd" || $doit $chowncmd "$dst"; } && { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } && { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false || test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1 else # Make a couple of temp file names in the proper directory. dsttmp=${dstdirslash}_inst.$$_ rmtmp=${dstdirslash}_rm.$$_ # Trap to clean up those temp files at exit. trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 # Copy the file name to the temp name. (umask $cp_umask && { test -z "$stripcmd" || { # Create $dsttmp read-write so that cp doesn't create it read-only, # which would cause strip to fail. if test -z "$doit"; then : >"$dsttmp" # No need to fork-exec 'touch'. else $doit touch "$dsttmp" fi } } && $doit_exec $cpprog "$src" "$dsttmp") && # and set any options; do chmod last to preserve setuid bits. # # If any of these fail, we abort the whole thing. If we want to # ignore errors from any of these, just make sure not to ignore # errors from the above "$doit $cpprog $src $dsttmp" command. # { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } && { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } && { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } && { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } && # If -C, don't bother to copy if it wouldn't change the file. if $copy_on_change && old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` && new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` && set -f && set X $old && old=:$2:$4:$5:$6 && set X $new && new=:$2:$4:$5:$6 && set +f && test "$old" = "$new" && $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1 then rm -f "$dsttmp" else # If $backupsuffix is set, and the file being installed # already exists, attempt a backup. Don't worry if it fails, # e.g., if mv doesn't support -f. if test -n "$backupsuffix" && test -f "$dst"; then $doit $mvcmd -f "$dst" "$dst$backupsuffix" 2>/dev/null fi # Rename the file to the real destination. $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null || # The rename failed, perhaps because mv can't rename something else # to itself, or perhaps because mv is so ancient that it does not # support -f. { # Now remove or move aside any old file at destination location. # We try this two ways since rm can't unlink itself on some # systems and the destination file might be busy for other # reasons. In this case, the final cleanup might fail but the new # file should still install successfully. { test ! -f "$dst" || $doit $rmcmd "$dst" 2>/dev/null || { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null && { $doit $rmcmd "$rmtmp" 2>/dev/null; :; } } || { echo "$0: cannot unlink or rename $dst" >&2 (exit 1); exit 1 } } && # Now rename the file to the real destination. $doit $mvcmd "$dsttmp" "$dst" } fi || exit 1 trap '' 0 fi done # Local variables: # eval: (add-hook 'before-save-hook 'time-stamp nil t) # time-stamp-start: "scriptversion=" # time-stamp-format: "%Y-%02m-%02d.%02H" # time-stamp-time-zone: "UTC0" # time-stamp-end: "; # UTC" # End: lft-4.01/config/configure.ac000644 000765 000024 00000067341 15250425146 015721 0ustar00vicstaff000000 000000 dnl Process this file with autoconf to produce a configure script. dnl NOTE: the AC_INIT version slot is intentionally empty. Nothing in the dnl build consumes PACKAGE_VERSION / @VERSION@ / PACKAGE_STRING; the dnl authoritative version string lives in lft.c ("const char *version"). dnl Leaving it empty means it never needs bumping per release. Autoconf dnl discards the bug-report argument when the version is empty, so it is dnl defined directly. m4_define([AC_PACKAGE_BUGREPORT],[https://pwhois.org/contact/]) AC_INIT([lft],[]) AC_CONFIG_SRCDIR([lft_ifname.h]) AC_CONFIG_HEADERS([config/acconfig.h]) AC_CONFIG_AUX_DIR([config]) dnl This keeps configure quiet about the datarootdir setting AC_DEFUN([AC_DATAROOTDIR_CHECKED]) AC_CANONICAL_HOST case $host_os in *cygwin* ) CYGWIN=yes;; * ) CYGWIN=no;; esac AC_DEFINE_UNQUOTED([HOST_SYSTEM_TYPE], ["$host"], [Host system type triple]) dnl Checks for programs. AC_PROG_CC AC_PROG_LN_S AC_PROG_MAKE_SET AC_PROG_INSTALL AC_CHECK_LIB([nsl], [getaddrinfo]) AC_CHECK_LIB([socket], [connect]) AC_CHECK_LIB([resolv], [inet_aton]) AC_CHECK_LIB([m], [sin]) dnl Checks for header files. AC_CHECK_HEADERS([arpa/inet.h fcntl.h limits.h netdb.h netinet/in.h stdlib.h string.h strings.h sys/ioctl.h sys/socket.h sys/time.h unistd.h]) dnl Checks for typedefs, structures, and compiler characteristics. AC_TYPE_SIZE_T dnl Checks for library functions. AC_FUNC_ALLOCA AC_FUNC_STRFTIME AC_CHECK_FUNCS([getaddrinfo getnameinfo gettimeofday inet_ntoa memset select setenv socket strchr strdup strstr]) dnl OS specific checks case "$host" in *darwin*) AC_DEFINE([BSD_IP_STACK], [1], [Define if using a BSD IP stack]) dnl we need to define DARWIN to turn off dnl some BSD-type features like BIOC_IMMEDIATE AC_DEFINE([DARWIN], [1], [Define if building on Darwin/macOS]) ;; *netbsd*) AC_DEFINE([BSD_IP_STACK], [1], [Define if using a BSD IP stack]) dnl we need to define NETBSD to turn off dnl some BSD-type features like BIOC_IMMEDIATE AC_DEFINE([NETBSD], [1], [Define if building on NetBSD]) ;; *openbsd*) AC_DEFINE([OPENBSD], [1], [Define if building on OpenBSD]) AC_DEFINE([BSD_IP_STACK], [1], [Define if using a BSD IP stack]) ;; *bsd*) AC_DEFINE([BSD_IP_STACK], [1], [Define if using a BSD IP stack]) ;; *linux*) AC_DEFINE([_BSD_SOURCE], [1], [Enable BSD extensions on Linux]) ;; *solaris*) dnl some versions of solaris need special treatment dnl for the th_sum field, and it varies between versions case "$host" in *solaris2.4*|*solaris2.5*) AC_DEFINE([SOLARIS_LENGTH_IN_CHECKSUM], [1], [Define if Solaris mangles th_sum]) ;; esac ;; esac case "$host" in *cygwin*) dnl Windows requires winsock; no pcap needed on this path LIBS="-lws2_32 $LIBS" ;; *) dnl Option to build universal binaries on macOS (Apple Silicon + Intel x86_64) AC_ARG_ENABLE([universal], [AS_HELP_STRING([--enable-universal], [build a universal binary for arm64 and x86_64 on macOS])], [UNIVERSAL="$enableval"], [UNIVERSAL="no"]) dnl Option to enable gettimeofday checks in addition to pcap header timestamp AC_ARG_ENABLE([gtod], [AS_HELP_STRING([--enable-gtod], [use gettimeofday() instead of pcap timestamps for per-packet timing])], [GTOD="$enableval"], [GTOD="no"]) dnl ------------------------------------------------------------------ dnl Build a list of candidate third-party prefix directories to probe dnl when pkg-config is unavailable or does not know about a library. dnl Order matters: first match wins. dnl /opt/homebrew -- Homebrew on Apple Silicon (default since 2021) dnl /usr/local -- Homebrew on Intel Mac; FreeBSD ports; common Linux dnl /opt/local -- MacPorts dnl /opt/pkg -- pkgsrc (NetBSD, some Linux) dnl ------------------------------------------------------------------ lft_candidate_prefixes="/opt/homebrew /usr/local /opt/local /opt/pkg" dnl Optional: manually specify the path to the pcap library pcap_path_override=no AC_ARG_WITH([pcap], [AS_HELP_STRING([--with-pcap=PATH], [specify prefix path to the pcap library (e.g. /usr/local)])], [ AC_MSG_CHECKING([for --with-pcap option]) case "$withval" in yes|no) AC_MSG_ERROR([please specify a PATH in --with-pcap option!]) ;; *) if test '!' -d "$withval"; then AC_MSG_ERROR([$withval does not exist!]) else AC_MSG_RESULT([$withval]) pcap_path_override=yes if test -d "$withval/include"; then CFLAGS="$CFLAGS -I$withval/include" CPPFLAGS="$CPPFLAGS -I$withval/include" else CFLAGS="$CFLAGS -I$withval" CPPFLAGS="$CPPFLAGS -I$withval" fi if test -d "$withval/lib"; then LIBS="$LIBS -L$withval/lib" else LIBS="$LIBS -L$withval" fi fi ;; esac ] ) dnl Check whether sockaddr has a sa_len member (BSD-derived stacks) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include ]], [[u_int i = sizeof(((struct sockaddr *)0)->sa_len)]])], [AC_DEFINE([HAVE_SOCKADDR_SA_LEN], [1], [Define if struct sockaddr has a sa_len member])], []) dnl ------------------------------------------------------------------ dnl IPv6 support (LFT 4.0). All v6 code is guarded by HAVE_IPV6; a platform dnl missing the headers/type builds and runs IPv4-only. dnl dnl (default) auto-detect: enable if the v6 headers/type are present. dnl --without-ipv6 force off (build IPv4-only even where v6 is available). dnl --with-ipv6 force on: error out if the v6 headers/type are missing. dnl ------------------------------------------------------------------ dnl inet_ntop/inet_pton are used unconditionally by lft_addr.c, so probe them dnl regardless of the IPv6 decision. AC_CHECK_FUNCS([inet_ntop inet_pton]) AC_ARG_WITH([ipv6], [AS_HELP_STRING([--without-ipv6], [build without IPv6 tracing (default: auto-detect; --with-ipv6 requires it)])], [], [with_ipv6=auto]) AS_IF([test "x$with_ipv6" = xno], [AC_MSG_NOTICE([IPv6 tracing disabled by --without-ipv6])], [AC_CHECK_HEADERS([netinet/ip6.h netinet/icmp6.h ifaddrs.h], [], [], [[#include #include #include ]]) AC_CHECK_FUNCS([getifaddrs]) AC_CHECK_TYPES([struct sockaddr_storage], [], [], [[#include ]]) AS_IF([test "x$ac_cv_header_netinet_ip6_h" = xyes -a \ "x$ac_cv_header_netinet_icmp6_h" = xyes -a \ "x$ac_cv_type_struct_sockaddr_storage" = xyes], [AC_DEFINE([HAVE_IPV6], [1], [Define if IPv6 tracing is available]) AC_CHECK_DECL([IPV6_HDRINCL], [AC_DEFINE([HAVE_IPV6_HDRINCL], [1], [Define if IPV6_HDRINCL socket option is available])], [], [[#include ]])], [AS_IF([test "x$with_ipv6" = xyes], [AC_MSG_ERROR([--with-ipv6 requested but netinet/ip6.h, netinet/icmp6.h, or sockaddr_storage is missing])], [AC_MSG_WARN([IPv6 tracing disabled -- missing netinet/ip6.h, netinet/icmp6.h, or sockaddr_storage])])])]) dnl ------------------------------------------------------------------ dnl Detect libpcap. dnl dnl pcap_findalldevs() has been available since libpcap 0.7 (2002) and dnl is what lft_lib.c actually calls at runtime via lft_pcap_lookupdev(). dnl We check for it here instead of the deprecated pcap_lookupdev(), dnl which was removed in libpcap 1.10 (2021). dnl dnl Try pkg-config first when the user has not given us an explicit path dnl (preferred on modern Linux distros and FreeBSD with pkgconf installed), dnl then fall back to manual header + library detection. We use dnl AC_PATH_PROG + shell calls rather than PKG_CHECK_MODULES so that no dnl extra m4 macro files are needed when regenerating the configure script. dnl ------------------------------------------------------------------ have_pcap=no AS_IF([test "x$pcap_path_override" = "xno"], [AC_PATH_PROG([PKGCONFIG], [pkg-config], [no]) AS_IF([test "x$PKGCONFIG" != "xno"], [AC_MSG_CHECKING([for libpcap via pkg-config]) AS_IF([$PKGCONFIG --exists libpcap 2>/dev/null], [pcap_cflags=`$PKGCONFIG --cflags libpcap` pcap_libs=`$PKGCONFIG --libs libpcap` save_CFLAGS="$CFLAGS" save_LIBS="$LIBS" CFLAGS="$CFLAGS $pcap_cflags" LIBS="$LIBS $pcap_libs" AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], [[pcap_if_t *d; pcap_findalldevs(&d, 0);]])], [have_pcap=yes AC_MSG_RESULT([yes ($pcap_libs)])], [dnl Link failed (e.g. architecture mismatch); discard and fall through CFLAGS="$save_CFLAGS" LIBS="$save_LIBS" AC_MSG_RESULT([no (link test failed; wrong architecture?)])])], [AC_MSG_RESULT([no])])])]) dnl If pkg-config didn't find pcap, probe candidate prefixes before giving up. AS_IF([test "x$have_pcap" = "xno" && test "x$pcap_path_override" = "xno"], [AC_MSG_CHECKING([for libpcap in candidate prefixes]) for _pfx in $lft_candidate_prefixes; do if test -f "$_pfx/include/pcap.h"; then CPPFLAGS="$CPPFLAGS -I$_pfx/include" CFLAGS="$CFLAGS -I$_pfx/include" LDFLAGS="$LDFLAGS -L$_pfx/lib" AC_MSG_RESULT([$_pfx]) break fi done AS_IF([test "x$_pfx" = "x"], [AC_MSG_RESULT([not found])])]) AS_IF([test "x$have_pcap" = "xno"], [AC_CHECK_LIB([pcap], [pcap_findalldevs], [have_pcap=yes; LIBS="-lpcap $LIBS"], [AC_MSG_ERROR([ libpcap was not found. It is required to build LFT. Please install the libpcap development library and re-run ./configure. On Debian/Ubuntu: sudo apt-get install libpcap-dev On RHEL/Fedora: sudo dnf install libpcap-devel On FreeBSD ports: net/libpcap On macOS Homebrew: brew install libpcap On macOS MacPorts: sudo port install libpcap Or specify its location with: ./configure --with-pcap=PATH])]) AC_CHECK_HEADER([pcap.h], , [AC_MSG_ERROR([ pcap.h was not found. The libpcap library was detected but its headers are missing. Please install the libpcap development package. On Debian/Ubuntu: sudo apt-get install libpcap-dev On RHEL/Fedora: sudo dnf install libpcap-devel On FreeBSD ports: net/libpcap On macOS Homebrew: brew install libpcap On macOS MacPorts: sudo port install libpcap Or specify its location with: ./configure --with-pcap=PATH When building libpcap from source, run both 'make install' and 'make install-incl'.])])]) dnl ------------------------------------------------------------------ dnl Detect c-ares for asynchronous DNS (optional; used by default if found). dnl dnl Detection order: dnl 1. --disable-async-dns skips all detection. dnl 2. --with-cares=PATH sets an explicit prefix (skips auto-detect). dnl 3. pkg-config libcares (most reliable on modern systems). dnl 4. Probe lft_candidate_prefixes for ares.h. dnl 5. AC_CHECK_LIB / AC_CHECK_HEADER in whatever paths are now set. dnl ------------------------------------------------------------------ AC_ARG_ENABLE([async-dns], [AS_HELP_STRING([--disable-async-dns], [disable asynchronous DNS via c-ares (default: auto-detect and use if available)])], [enable_async_dns="$enableval"], [enable_async_dns="yes"]) cares_path_override=no AC_ARG_WITH([cares], [AS_HELP_STRING([--with-cares=PATH], [specify prefix path to the c-ares library (e.g. /opt/homebrew)])], [case "$withval" in no) enable_async_dns=no ;; yes) AC_MSG_ERROR([please specify a PATH in --with-cares option!]) ;; *) if test '!' -d "$withval"; then AC_MSG_ERROR([$withval does not exist!]) fi cares_path_override=yes if test -d "$withval/include"; then CPPFLAGS="$CPPFLAGS -I$withval/include" CFLAGS="$CFLAGS -I$withval/include" else CPPFLAGS="$CPPFLAGS -I$withval" CFLAGS="$CFLAGS -I$withval" fi if test -d "$withval/lib"; then LDFLAGS="$LDFLAGS -L$withval/lib" else LDFLAGS="$LDFLAGS -L$withval" fi ;; esac]) AS_IF([test "x$enable_async_dns" = "xyes"], [have_cares=no dnl 1. Try pkg-config (skipped when user gave an explicit path) AS_IF([test "x$cares_path_override" = "xno"], [AS_IF([test "x$PKGCONFIG" != "xno"], [AC_MSG_CHECKING([for libcares via pkg-config]) AS_IF([$PKGCONFIG --exists libcares 2>/dev/null], [cares_cflags=`$PKGCONFIG --cflags libcares` cares_libs=`$PKGCONFIG --libs libcares` save_CFLAGS="$CFLAGS" save_LIBS="$LIBS" CFLAGS="$CFLAGS $cares_cflags" LIBS="$LIBS $cares_libs" AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], [[ares_channel c; ares_init(&c);]])], [have_cares=yes AC_MSG_RESULT([yes ($cares_libs)])], [dnl Link failed (e.g. architecture mismatch); discard and fall through CFLAGS="$save_CFLAGS" LIBS="$save_LIBS" AC_MSG_RESULT([no (link test failed; wrong architecture?)])])], [AC_MSG_RESULT([no])])])]) dnl 2. Probe candidate prefixes if still not found AS_IF([test "x$have_cares" = "xno" && test "x$cares_path_override" = "xno"], [AC_MSG_CHECKING([for c-ares in candidate prefixes]) for _pfx in $lft_candidate_prefixes; do if test -f "$_pfx/include/ares.h"; then CPPFLAGS="$CPPFLAGS -I$_pfx/include" CFLAGS="$CFLAGS -I$_pfx/include" LDFLAGS="$LDFLAGS -L$_pfx/lib" AC_MSG_RESULT([$_pfx]) break fi done AS_IF([test "x$_pfx" = "x"], [AC_MSG_RESULT([not found])])]) dnl 3. Final check with whatever paths are now set AS_IF([test "x$have_cares" = "xno"], [AC_CHECK_LIB([cares], [ares_init], [have_cares=maybe], []) AS_IF([test "x$have_cares" = "xmaybe"], [AC_CHECK_HEADER([ares.h], [have_cares=yes LIBS="-lcares $LIBS"], [AC_MSG_WARN([c-ares library found but ares.h not found; disabling async DNS])])])]) AS_IF([test "x$have_cares" = "xyes"], [AS_IF([test "x$cares_path_override" = "xyes" || test "x$PKGCONFIG" = "xno"], [dnl pkg-config not used — ensure LIBS has -lcares if not already added AS_CASE([$LIBS], [*-lcares*], [], [LIBS="-lcares $LIBS"])])]) dnl c-ares API selection. Async DNS is enabled whenever c-ares is present. dnl The 1.34+ event-model API (ares_process_fds / ares_fd_events_t) is dnl preferred when available; older c-ares -- as shipped by most dnl distributions (Debian/Ubuntu, RHEL) -- uses the classic ares_fds() / dnl ares_process() poll API instead. HAVE_CARES marks c-ares present; dnl HAVE_ARES_PROCESS_FDS marks the newer event API. AS_IF([test "x$have_cares" = "xyes"], [AC_DEFINE([HAVE_CARES], [1], [Define to 1 if the c-ares library is available for async DNS]) AC_MSG_CHECKING([for c-ares 1.34+ event API (ares_process_fds)]) dnl A link test (not merely a compile test): on Linux gcc searches dnl /usr/local/include by default but ld does not search /usr/local/lib, dnl so a newer header can shadow an older linked library. Referencing dnl the ares_process_fds symbol forces the linker to prove the event API dnl is actually present in the library we will link against; if it is not, dnl we fall back to the classic poll API and link cleanly. dnl Call the symbol (do not merely take its address): at -O2 an dnl address-and-discard is dead code the compiler elides, which would dnl let the link succeed without proving the symbol exists. An actual dnl call cannot be optimised away, so the link fails when the library dnl we are linking against lacks the event API. AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], [[ares_fd_events_t _e; _e.events = ARES_FD_EVENT_READ; (void)_e; ares_process_fds(0, 0, 0, 0);]])], [AC_MSG_RESULT([yes]) AC_DEFINE([HAVE_ARES_PROCESS_FDS], [1], [Define to 1 if the c-ares 1.34+ ares_process_fds event API is available])], [AC_MSG_RESULT([no (older c-ares; using the classic ares_process poll API)])])], [lft_warn_no_cares=yes])]) dnl ------------------------------------------------------------------ dnl Detect ncurses for interactive continuous-monitoring mode (optional). dnl dnl Detection order: dnl 1. --disable-ncurses skips all detection. dnl 2. --with-ncurses=PATH sets an explicit prefix (skips auto-detect). dnl 3. pkg-config ncursesw (wide-char variant preferred), then ncurses. dnl 4. Probe lft_candidate_prefixes for ncurses.h. dnl 5. AC_CHECK_LIB / AC_CHECK_HEADER in whatever paths are now set. dnl ------------------------------------------------------------------ AC_ARG_ENABLE([ncurses], [AS_HELP_STRING([--disable-ncurses], [disable ncurses interactive mode (default: auto-detect and use if available)])], [enable_ncurses="$enableval"], [enable_ncurses="yes"]) ncurses_path_override=no AC_ARG_WITH([ncurses], [AS_HELP_STRING([--with-ncurses=PATH], [specify prefix path to the ncurses library (e.g. /opt/homebrew)])], [case "$withval" in no) enable_ncurses=no ;; yes) AC_MSG_ERROR([please specify a PATH in --with-ncurses option!]) ;; *) if test '!' -d "$withval"; then AC_MSG_ERROR([$withval does not exist!]) fi ncurses_path_override=yes if test -d "$withval/include"; then CPPFLAGS="$CPPFLAGS -I$withval/include" CFLAGS="$CFLAGS -I$withval/include" else CPPFLAGS="$CPPFLAGS -I$withval" CFLAGS="$CFLAGS -I$withval" fi if test -d "$withval/lib"; then LDFLAGS="$LDFLAGS -L$withval/lib" else LDFLAGS="$LDFLAGS -L$withval" fi ;; esac]) AS_IF([test "x$enable_ncurses" = "xyes"], [have_ncurses=no dnl 1. Try pkg-config (ncursesw preferred, then ncurses; skipped when user gave explicit path) AS_IF([test "x$ncurses_path_override" = "xno"], [AS_IF([test "x$PKGCONFIG" != "xno"], [AC_MSG_CHECKING([for ncursesw via pkg-config]) AS_IF([$PKGCONFIG --exists ncursesw 2>/dev/null], [ncurses_cflags=`$PKGCONFIG --cflags ncursesw` ncurses_libs=`$PKGCONFIG --libs ncursesw` save_CFLAGS="$CFLAGS" save_LIBS="$LIBS" CFLAGS="$CFLAGS $ncurses_cflags" LIBS="$LIBS $ncurses_libs" AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], [[initscr(); endwin();]])], [have_ncurses=yes AC_DEFINE([HAVE_NCURSES], [1], [Define to 1 if the ncurses library is available]) AC_MSG_RESULT([yes ($ncurses_libs)])], [CFLAGS="$save_CFLAGS" LIBS="$save_LIBS" AC_MSG_RESULT([no (link test failed; wrong architecture?)])])], [AC_MSG_RESULT([no])]) dnl Try plain ncurses via pkg-config if ncursesw not found AS_IF([test "x$have_ncurses" = "xno"], [AC_MSG_CHECKING([for ncurses via pkg-config]) AS_IF([$PKGCONFIG --exists ncurses 2>/dev/null], [ncurses_cflags=`$PKGCONFIG --cflags ncurses` ncurses_libs=`$PKGCONFIG --libs ncurses` save_CFLAGS="$CFLAGS" save_LIBS="$LIBS" CFLAGS="$CFLAGS $ncurses_cflags" LIBS="$LIBS $ncurses_libs" AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], [[initscr(); endwin();]])], [have_ncurses=yes AC_MSG_RESULT([yes ($ncurses_libs)])], [CFLAGS="$save_CFLAGS" LIBS="$save_LIBS" AC_MSG_RESULT([no (link test failed; wrong architecture?)])])], [AC_MSG_RESULT([no])])])])]) dnl 2. Probe candidate prefixes if still not found AS_IF([test "x$have_ncurses" = "xno" && test "x$ncurses_path_override" = "xno"], [AC_MSG_CHECKING([for ncurses in candidate prefixes]) for _pfx in $lft_candidate_prefixes; do if test -f "$_pfx/include/ncurses.h"; then CPPFLAGS="$CPPFLAGS -I$_pfx/include" CFLAGS="$CFLAGS -I$_pfx/include" LDFLAGS="$LDFLAGS -L$_pfx/lib" AC_MSG_RESULT([$_pfx]) break fi done AS_IF([test "x$_pfx" = "x"], [AC_MSG_RESULT([not found])])]) dnl 3. Final check with whatever paths are now set (ncursesw preferred, then ncurses) AS_IF([test "x$have_ncurses" = "xno"], [AC_CHECK_LIB([ncursesw], [initscr], [have_ncurses=maybe_w], []) AS_IF([test "x$have_ncurses" = "xmaybe_w"], [AC_CHECK_HEADER([ncurses.h], [have_ncurses=yes LIBS="-lncursesw $LIBS" AC_DEFINE([HAVE_NCURSES], [1], [Define to 1 if the ncurses library is available])], [have_ncurses=no AC_MSG_WARN([ncursesw library found but ncurses.h not found; disabling ncurses mode])])])]) AS_IF([test "x$have_ncurses" = "xno"], [AC_CHECK_LIB([ncurses], [initscr], [have_ncurses=maybe], []) AS_IF([test "x$have_ncurses" = "xmaybe"], [AC_CHECK_HEADER([ncurses.h], [have_ncurses=yes LIBS="-lncurses $LIBS" AC_DEFINE([HAVE_NCURSES], [1], [Define to 1 if the ncurses library is available])], [have_ncurses=no AC_MSG_WARN([ncurses library found but ncurses.h not found; disabling ncurses mode])])])]) AS_IF([test "x$have_ncurses" = "xyes"], [AS_IF([test "x$ncurses_path_override" = "xyes" || test "x$PKGCONFIG" = "xno"], [dnl pkg-config not used — ensure LIBS has -lncurses* if not already added AS_CASE([$LIBS], [*-lncurses*], [], [LIBS="-lncurses $LIBS" AC_DEFINE([HAVE_NCURSES], [1], [Define to 1 if the ncurses library is available])])])]) AS_IF([test "x$have_ncurses" != "xyes"], [lft_warn_no_ncurses=yes])]) dnl Wide-character cell reads (mvin_wch) — used by the watch screen capture so dnl multi-byte cells and alternate-charset line drawing are written correctly. AS_IF([test "x$have_ncurses" = "xyes"], [AC_CHECK_FUNCS([mvin_wch])]) dnl whob never uses ncurses: strip the curses libs from its link line here so dnl Makefile.in needs no GNU-make-only functions (FreeBSD's make lacks them). WHOB_LIBS=`echo " $LIBS " | sed -e 's/ -lncursesw / /g' -e 's/ -lncurses / /g' -e 's/ -ltinfow / /g' -e 's/ -ltinfo / /g'` AC_SUBST([WHOB_LIBS]) dnl Conditionally compile lft_watch.o only when ncurses is available AS_IF([test "x$have_ncurses" = "xyes"], [WATCH_OBJS="lft_watch.o"], [WATCH_OBJS=""]) AC_SUBST([WATCH_OBJS]) esac dnl Set universal binary options AC_MSG_CHECKING([if we should build universal binaries]) if test "$UNIVERSAL" = "yes"; then case $host_os in *darwin*) CFLAGS="$CFLAGS -arch x86_64 -arch arm64" LDFLAGS="$LDFLAGS -arch x86_64 -arch arm64" AC_DEFINE([UNIVERSAL], [1], [Define if building a universal binary]) AC_MSG_RESULT([yes (x86_64 + arm64)]) ;; *) AC_MSG_RESULT([no (--enable-universal only applies to macOS)]) ;; esac else AC_MSG_RESULT([no]) fi dnl Use gettimeofday() on each packet instead of pcap header timestamp AC_MSG_CHECKING([if we should call gettimeofday for each packet]) if test "$GTOD" = "yes"; then AC_DEFINE([USE_GTOD], [1], [Define to use gettimeofday() instead of pcap timestamps]) AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) fi dnl ====================================================================== dnl Compile-time hardening. LFT installs setuid-root, so we enable the dnl standard exploit-mitigation flags -- but ONLY the ones this particular dnl toolchain actually accepts, so the build stays clean and warning-free dnl on every platform (macOS/clang, Linux/gcc, the BSDs). Each compile dnl flag is probed with -Werror so that a toolchain which merely *warns* dnl about an unknown flag causes the probe to fail and the flag to be dnl dropped -- users never see a spurious "unknown argument" warning. dnl Runtime cost is negligible for a network-bound tool. dnl ====================================================================== HARDEN_CFLAGS="" for lft_hflag in -fstack-protector-strong -fstack-clash-protection -fPIE; do AC_MSG_CHECKING([whether $CC accepts $lft_hflag]) lft_hsave="$CFLAGS" CFLAGS="$CFLAGS $lft_hflag -Werror" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[]])], [AC_MSG_RESULT([yes]); HARDEN_CFLAGS="$HARDEN_CFLAGS $lft_hflag"], [AC_MSG_RESULT([no])]) CFLAGS="$lft_hsave" done dnl -Wformat-security needs -Wformat active; probe the pair together. AC_MSG_CHECKING([whether $CC accepts -Wformat -Wformat-security]) lft_hsave="$CFLAGS" CFLAGS="$CFLAGS -Wformat -Wformat-security -Werror" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[]])], [AC_MSG_RESULT([yes]); HARDEN_CFLAGS="$HARDEN_CFLAGS -Wformat -Wformat-security"], [AC_MSG_RESULT([no])]) CFLAGS="$lft_hsave" dnl _FORTIFY_SOURCE only takes effect with optimization; probe with -O2. dnl If the platform already predefines it, the redefinition warning trips dnl -Werror and we skip it (the system default already covers us). AC_MSG_CHECKING([whether $CC accepts -D_FORTIFY_SOURCE=2]) lft_hsave="$CFLAGS" CFLAGS="$CFLAGS -O2 -D_FORTIFY_SOURCE=2 -Werror" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], [[char b[8]; strncpy(b, "x", sizeof b);]])], [AC_MSG_RESULT([yes]); HARDEN_CFLAGS="$HARDEN_CFLAGS -D_FORTIFY_SOURCE=2"], [AC_MSG_RESULT([no])]) CFLAGS="$lft_hsave" dnl Linker hardening: PIE + full RELRO. Probe by actually linking. dnl -Werror here so a link flag that only *warns* (e.g. -pie is redundant dnl on macOS, where PIE is already the default, and clang warns dnl "argument unused during compilation") is rejected rather than left to dnl spam every combined compile+link step. On Linux -pie is clean and kept. HARDEN_LDFLAGS="" lft_hsave_c="$CFLAGS" CFLAGS="$CFLAGS $HARDEN_CFLAGS -Werror" for lft_lflag in -pie -Wl,-z,relro -Wl,-z,now; do AC_MSG_CHECKING([whether the linker accepts $lft_lflag cleanly]) lft_hsave="$LDFLAGS" LDFLAGS="$LDFLAGS $lft_lflag" AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[]])], [AC_MSG_RESULT([yes]); HARDEN_LDFLAGS="$HARDEN_LDFLAGS $lft_lflag"], [AC_MSG_RESULT([no])]) LDFLAGS="$lft_hsave" done CFLAGS="$lft_hsave_c" CFLAGS="$CFLAGS $HARDEN_CFLAGS" LDFLAGS="$LDFLAGS $HARDEN_LDFLAGS" AC_CONFIG_FILES([Makefile]) dnl Emit any deferred advisory warnings here, just before AC_OUTPUT, dnl so they appear at the bottom of configure's output where they are noticed. AS_IF([test "x$lft_warn_old_cares" = "xyes"], [AC_MSG_WARN([ c-ares was found but is older than 1.34 -- the event-model API LFT uses (ares_process_fds). Async DNS is DISABLED for this build; LFT falls back to synchronous name resolution (fully functional, just slower on lossy paths). Install c-ares 1.34+ and re-run ./configure, or point at a newer build with --with-cares=PATH, to enable async DNS.])]) AS_IF([test "x$lft_warn_no_cares" = "xyes"], [AC_MSG_WARN([ LFT is much faster with asynchronous DNS provided by the c-ares library, which was not detected. It is not required, but strongly recommended. Please consider installing it and re-running ./configure. On Debian/Ubuntu: sudo apt-get install libc-ares-dev On RHEL/Fedora: sudo dnf install c-ares-devel On macOS Homebrew: brew install c-ares On macOS MacPorts: sudo port install c-ares Or specify its location with: ./configure --with-cares=PATH])]) AS_IF([test "x$lft_warn_no_ncurses" = "xyes"], [AC_MSG_WARN([ LFT looks great using ncurses with a text-mode UI, which was not detected. The interactive continuous-monitoring mode (--watch) requires it. Please consider installing the ncurses development library and re-running ./configure. On Debian/Ubuntu: sudo apt-get install libncurses-dev On RHEL/Fedora: sudo dnf install ncurses-devel On macOS Homebrew: brew install ncurses On macOS MacPorts: sudo port install ncurses Or specify its location with: ./configure --with-ncurses=PATH])]) AC_OUTPUT lft-4.01/config/config/000755 000765 000024 00000000000 15250425210 014655 5ustar00vicstaff000000 000000 lft-4.01/config/acconfig.win.h000644 000765 000024 00000012235 11053131665 016136 0ustar00vicstaff000000 000000 /* config/acconfig.h. Generated by configure. */ /* acconfig.h.in. */ /* Host system type */ #define HOST_SYSTEM_TYPE "i686-pc-cygwin" /* Define if using alloca.c. */ /* #undef C_ALLOCA */ /* Define to empty if the keyword does not work. */ /* #undef const */ /* Define to one of _getb67, GETB67, getb67 for Cray-2 and Cray-YMP systems. This function is required for alloca.c support on those systems. */ /* #undef CRAY_STACKSEG_END */ /* Define if you have alloca, as a function or macro. */ #define HAVE_ALLOCA 1 /* Define if you have and it should be used (not on Ultrix). */ #define HAVE_ALLOCA_H 1 /* Define to `unsigned' if doesn't define. */ /* #undef size_t */ /* If using the C implementation of alloca, define if you know the direction of stack growth for your system; otherwise it will be automatically deduced at run-time. STACK_DIRECTION > 0 => grows toward higher addresses STACK_DIRECTION < 0 => grows toward lower addresses STACK_DIRECTION = 0 => direction of growth unknown */ /* #undef STACK_DIRECTION */ /* Define if you have the ANSI C header files. */ #define STDC_HEADERS 1 /* Define if you can safely include both and . */ #define TIME_WITH_SYS_TIME 1 /* Define if using alloca.c. */ /* #undef C_ALLOCA */ /* Define to empty if the keyword does not work. */ /* #undef const */ /* Define to one of _getb67, GETB67, getb67 for Cray-2 and Cray-YMP systems. This function is required for alloca.c support on those systems. */ /* #undef CRAY_STACKSEG_END */ /* Define if you have alloca, as a function or macro. */ #define HAVE_ALLOCA 1 /* Define if you have and it should be used (not on Ultrix). */ #define HAVE_ALLOCA_H 1 /* Define to `unsigned' if doesn't define. */ /* #undef size_t */ /* If using the C implementation of alloca, define if you know the direction of stack growth for your system; otherwise it will be automatically deduced at run-time. STACK_DIRECTION > 0 => grows toward higher addresses STACK_DIRECTION < 0 => grows toward lower addresses STACK_DIRECTION = 0 => direction of growth unknown */ /* #undef STACK_DIRECTION */ /* Define if you have the ANSI C header files. */ #define STDC_HEADERS 1 /* Define if you can safely include both and . */ #define TIME_WITH_SYS_TIME 1 /* Define if using alloca.c. */ /* #undef C_ALLOCA */ /* Define to empty if the keyword does not work. */ /* #undef const */ /* Define to one of _getb67, GETB67, getb67 for Cray-2 and Cray-YMP systems. This function is required for alloca.c support on those systems. */ /* #undef CRAY_STACKSEG_END */ /* Define if you have alloca, as a function or macro. */ #define HAVE_ALLOCA 1 /* Define if you have and it should be used (not on Ultrix). */ #define HAVE_ALLOCA_H 1 /* Define to `unsigned' if doesn't define. */ /* #undef size_t */ /* If using the C implementation of alloca, define if you know the direction of stack growth for your system; otherwise it will be automatically deduced at run-time. STACK_DIRECTION > 0 => grows toward higher addresses STACK_DIRECTION < 0 => grows toward lower addresses STACK_DIRECTION = 0 => direction of growth unknown */ /* #undef STACK_DIRECTION */ /* Define if you have the ANSI C header files. */ #define STDC_HEADERS 1 /* Define if you can safely include both and . */ #define TIME_WITH_SYS_TIME 1 /* Linux requires this in order to have the right network structures. */ #ifndef _BSD_SOURCE #define _BSD_SOURCE #endif /* Define if you have the gettimeofday function. */ #define HAVE_GETTIMEOFDAY 1 /* Define if you have the select function. */ #define HAVE_SELECT 1 /* Define if you have the socket function. */ #define HAVE_SOCKET 1 /* Define if you have the strdup function. */ #define HAVE_STRDUP 1 /* Define if you have the strstr function. */ #define HAVE_STRSTR 1 /* Define if you have the header file. */ #define HAVE_FCNTL_H 1 /* Define if you have the header file. */ #define HAVE_LIMITS_H 1 /* Define if you have the header file. */ #define HAVE_STRINGS_H 1 /* Define if you have the header file. */ #define HAVE_SYS_IOCTL_H 1 /* Define if you have the header file. */ #define HAVE_SYS_TIME_H 1 /* Define if you have the header file. */ #define HAVE_UNISTD_H 1 /* Define if you have the m library (-lm). */ #define HAVE_LIBM 1 /* Define if you have the nsl library (-lnsl). */ /* #undef HAVE_LIBNSL */ /* Define if you have the pcap library (-lpcap). */ /* #undef HAVE_LIBPCAP */ /* Define if you have the socket library (-lsocket). */ /* #undef HAVE_LIBSOCKET */ /* Define if this is a BSD TCP/IP stack */ /* #undef BSD_IP_STACK */ /* Define if this is Darwin */ /* #undef DARWIN */ /* Define if this is NETBSD */ /* #undef NETBSD */ /* Define if this is OpenBSD */ /* #undef OPENBSD */ /* Define if solaris munges the th_sum field in its own special way */ /* #undef SOLARIS_LENGTH_IN_CHECKSUM */ /* Define if your system has a sa_len member in struct sockaddr */ /* #undef HAVE_SOCKADDR_SA_LEN */ /* Define if an IPv6 network stack is detected */ /* #undef INET6 */ lft-4.01/config/config.guess000755 000765 000024 00000143242 15165341545 015753 0ustar00vicstaff000000 000000 #! /bin/sh # Attempt to guess a canonical system name. # Copyright 1992-2025 Free Software Foundation, Inc. # shellcheck disable=SC2006,SC2268 # see below for rationale timestamp='2025-07-10' # This file is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, see . # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that # program. This Exception is an additional permission under section 7 # of the GNU General Public License, version 3 ("GPLv3"). # # Originally written by Per Bothner; maintained since 2000 by Ben Elliston. # # You can get the latest version of this script from: # https://git.savannah.gnu.org/cgit/config.git/plain/config.guess # # Please send patches to . # The "shellcheck disable" line above the timestamp inhibits complaints # about features and limitations of the classic Bourne shell that were # superseded or lifted in POSIX. However, this script identifies a wide # variety of pre-POSIX systems that do not have POSIX shells at all, and # even some reasonably current systems (Solaris 10 as case-in-point) still # have a pre-POSIX /bin/sh. me=`echo "$0" | sed -e 's,.*/,,'` usage="\ Usage: $0 [OPTION] Output the configuration name of the system '$me' is run on. Options: -h, --help print this help, then exit -t, --time-stamp print date of last modification, then exit -v, --version print version number, then exit Report bugs and patches to ." version="\ GNU config.guess ($timestamp) Originally written by Per Bothner. Copyright 1992-2025 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." help=" Try '$me --help' for more information." # Parse command line while test $# -gt 0 ; do case $1 in --time-stamp | --time* | -t ) echo "$timestamp" ; exit ;; --version | -v ) echo "$version" ; exit ;; --help | --h* | -h ) echo "$usage"; exit ;; -- ) # Stop option processing shift; break ;; - ) # Use stdin as input. break ;; -* ) echo "$me: invalid option $1$help" >&2 exit 1 ;; * ) break ;; esac done if test $# != 0; then echo "$me: too many arguments$help" >&2 exit 1 fi # Just in case it came from the environment. GUESS= # CC_FOR_BUILD -- compiler used by this script. Note that the use of a # compiler to aid in system detection is discouraged as it requires # temporary files to be created and, as you can see below, it is a # headache to deal with in a portable fashion. # Historically, 'CC_FOR_BUILD' used to be named 'HOST_CC'. We still # use 'HOST_CC' if defined, but it is deprecated. # Portable tmp directory creation inspired by the Autoconf team. tmp= # shellcheck disable=SC2172 trap 'test -z "$tmp" || rm -fr "$tmp"' 0 1 2 13 15 set_cc_for_build() { # prevent multiple calls if $tmp is already set test "$tmp" && return 0 : "${TMPDIR=/tmp}" # shellcheck disable=SC2039,SC3028 { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir "$tmp" 2>/dev/null) ; } || { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir "$tmp" 2>/dev/null) && echo "Warning: creating insecure temp directory" >&2 ; } || { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } dummy=$tmp/dummy case ${CC_FOR_BUILD-},${HOST_CC-},${CC-} in ,,) echo "int x;" > "$dummy.c" for driver in cc gcc c17 c99 c89 ; do if ($driver -c -o "$dummy.o" "$dummy.c") >/dev/null 2>&1 ; then CC_FOR_BUILD=$driver break fi done if test x"$CC_FOR_BUILD" = x ; then CC_FOR_BUILD=no_compiler_found fi ;; ,,*) CC_FOR_BUILD=$CC ;; ,*,*) CC_FOR_BUILD=$HOST_CC ;; esac } # This is needed to find uname on a Pyramid OSx when run in the BSD universe. # (ghazi@noc.rutgers.edu 1994-08-24) if test -f /.attbin/uname ; then PATH=$PATH:/.attbin ; export PATH fi UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown case $UNAME_SYSTEM in Linux|GNU|GNU/*) LIBC=unknown set_cc_for_build cat <<-EOF > "$dummy.c" #if defined(__ANDROID__) LIBC=android #else #include #if defined(__UCLIBC__) LIBC=uclibc #elif defined(__dietlibc__) LIBC=dietlibc #elif defined(__GLIBC__) LIBC=gnu #elif defined(__LLVM_LIBC__) LIBC=llvm #else #include /* First heuristic to detect musl libc. */ #ifdef __DEFINED_va_list LIBC=musl #endif #endif #endif EOF cc_set_libc=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^LIBC' | sed 's, ,,g'` eval "$cc_set_libc" # Second heuristic to detect musl libc. if [ "$LIBC" = unknown ] && command -v ldd >/dev/null && ldd --version 2>&1 | grep -q ^musl; then LIBC=musl fi # If the system lacks a compiler, then just pick glibc. # We could probably try harder. if [ "$LIBC" = unknown ]; then LIBC=gnu fi ;; esac # Note: order is significant - the case branches are not exclusive. case $UNAME_MACHINE:$UNAME_SYSTEM:$UNAME_RELEASE:$UNAME_VERSION in *:NetBSD:*:*) # NetBSD (nbsd) targets should (where applicable) match one or # more of the tuples: *-*-netbsdelf*, *-*-netbsdaout*, # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently # switched to ELF, *-*-netbsd* would select the old # object file format. This provides both forward # compatibility and a consistent mechanism for selecting the # object file format. # # Note: NetBSD doesn't particularly care about the vendor # portion of the name. We always set it to "unknown". UNAME_MACHINE_ARCH=`(uname -p 2>/dev/null || \ /sbin/sysctl -n hw.machine_arch 2>/dev/null || \ /usr/sbin/sysctl -n hw.machine_arch 2>/dev/null || \ echo unknown)` case $UNAME_MACHINE_ARCH in aarch64eb) machine=aarch64_be-unknown ;; armeb) machine=armeb-unknown ;; arm*) machine=arm-unknown ;; sh3el) machine=shl-unknown ;; sh3eb) machine=sh-unknown ;; sh5el) machine=sh5le-unknown ;; earmv*) arch=`echo "$UNAME_MACHINE_ARCH" | sed -e 's,^e\(armv[0-9]\).*$,\1,'` endian=`echo "$UNAME_MACHINE_ARCH" | sed -ne 's,^.*\(eb\)$,\1,p'` machine=${arch}${endian}-unknown ;; *) machine=$UNAME_MACHINE_ARCH-unknown ;; esac # The Operating System including object format, if it has switched # to ELF recently (or will in the future) and ABI. case $UNAME_MACHINE_ARCH in earm*) os=netbsdelf ;; arm*|i386|m68k|ns32k|sh3*|sparc|vax) set_cc_for_build if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ELF__ then # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). # Return netbsd for either. FIX? os=netbsd else os=netbsdelf fi ;; *) os=netbsd ;; esac # Determine ABI tags. case $UNAME_MACHINE_ARCH in earm*) expr='s/^earmv[0-9]/-eabi/;s/eb$//' abi=`echo "$UNAME_MACHINE_ARCH" | sed -e "$expr"` ;; esac # The OS release # Debian GNU/NetBSD machines have a different userland, and # thus, need a distinct triplet. However, they do not need # kernel version information, so it can be replaced with a # suitable tag, in the style of linux-gnu. case $UNAME_VERSION in Debian*) release='-gnu' ;; *) release=`echo "$UNAME_RELEASE" | sed -e 's/[-_].*//' | cut -d. -f1,2` ;; esac # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: # contains redundant information, the shorter form: # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. GUESS=$machine-${os}${release}${abi-} ;; *:Bitrig:*:*) UNAME_MACHINE_ARCH=`arch | sed 's/Bitrig.//'` GUESS=$UNAME_MACHINE_ARCH-unknown-bitrig$UNAME_RELEASE ;; *:OpenBSD:*:*) UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'` GUESS=$UNAME_MACHINE_ARCH-unknown-openbsd$UNAME_RELEASE ;; *:SecBSD:*:*) UNAME_MACHINE_ARCH=`arch | sed 's/SecBSD.//'` GUESS=$UNAME_MACHINE_ARCH-unknown-secbsd$UNAME_RELEASE ;; *:LibertyBSD:*:*) UNAME_MACHINE_ARCH=`arch | sed 's/^.*BSD\.//'` GUESS=$UNAME_MACHINE_ARCH-unknown-libertybsd$UNAME_RELEASE ;; *:MidnightBSD:*:*) GUESS=$UNAME_MACHINE-unknown-midnightbsd$UNAME_RELEASE ;; *:ekkoBSD:*:*) GUESS=$UNAME_MACHINE-unknown-ekkobsd$UNAME_RELEASE ;; *:SolidBSD:*:*) GUESS=$UNAME_MACHINE-unknown-solidbsd$UNAME_RELEASE ;; *:OS108:*:*) GUESS=$UNAME_MACHINE-unknown-os108_$UNAME_RELEASE ;; macppc:MirBSD:*:*) GUESS=powerpc-unknown-mirbsd$UNAME_RELEASE ;; *:MirBSD:*:*) GUESS=$UNAME_MACHINE-unknown-mirbsd$UNAME_RELEASE ;; *:Sortix:*:*) GUESS=$UNAME_MACHINE-unknown-sortix ;; *:Twizzler:*:*) GUESS=$UNAME_MACHINE-unknown-twizzler ;; *:Redox:*:*) GUESS=$UNAME_MACHINE-unknown-redox ;; mips:OSF1:*.*) GUESS=mips-dec-osf1 ;; alpha:OSF1:*:*) # Reset EXIT trap before exiting to avoid spurious non-zero exit code. trap '' 0 case $UNAME_RELEASE in *4.0) UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` ;; *5.*) UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'` ;; esac # According to Compaq, /usr/sbin/psrinfo has been available on # OSF/1 and Tru64 systems produced since 1995. I hope that # covers most systems running today. This code pipes the CPU # types through head -n 1, so we only detect the type of CPU 0. ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1` case $ALPHA_CPU_TYPE in "EV4 (21064)") UNAME_MACHINE=alpha ;; "EV4.5 (21064)") UNAME_MACHINE=alpha ;; "LCA4 (21066/21068)") UNAME_MACHINE=alpha ;; "EV5 (21164)") UNAME_MACHINE=alphaev5 ;; "EV5.6 (21164A)") UNAME_MACHINE=alphaev56 ;; "EV5.6 (21164PC)") UNAME_MACHINE=alphapca56 ;; "EV5.7 (21164PC)") UNAME_MACHINE=alphapca57 ;; "EV6 (21264)") UNAME_MACHINE=alphaev6 ;; "EV6.7 (21264A)") UNAME_MACHINE=alphaev67 ;; "EV6.8CB (21264C)") UNAME_MACHINE=alphaev68 ;; "EV6.8AL (21264B)") UNAME_MACHINE=alphaev68 ;; "EV6.8CX (21264D)") UNAME_MACHINE=alphaev68 ;; "EV6.9A (21264/EV69A)") UNAME_MACHINE=alphaev69 ;; "EV7 (21364)") UNAME_MACHINE=alphaev7 ;; "EV7.9 (21364A)") UNAME_MACHINE=alphaev79 ;; esac # A Pn.n version is a patched version. # A Vn.n version is a released version. # A Tn.n version is a released field test version. # A Xn.n version is an unreleased experimental baselevel. # 1.2 uses "1.2" for uname -r. OSF_REL=`echo "$UNAME_RELEASE" | sed -e 's/^[PVTX]//' | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz` GUESS=$UNAME_MACHINE-dec-osf$OSF_REL ;; Amiga*:UNIX_System_V:4.0:*) GUESS=m68k-unknown-sysv4 ;; *:[Aa]miga[Oo][Ss]:*:*) GUESS=$UNAME_MACHINE-unknown-amigaos ;; *:[Mm]orph[Oo][Ss]:*:*) GUESS=$UNAME_MACHINE-unknown-morphos ;; *:OS/390:*:*) GUESS=i370-ibm-openedition ;; *:z/VM:*:*) GUESS=s390-ibm-zvmoe ;; *:OS400:*:*) GUESS=powerpc-ibm-os400 ;; arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) GUESS=arm-acorn-riscix$UNAME_RELEASE ;; arm*:riscos:*:*|arm*:RISCOS:*:*) GUESS=arm-unknown-riscos ;; SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) GUESS=hppa1.1-hitachi-hiuxmpp ;; Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. case `(/bin/universe) 2>/dev/null` in att) GUESS=pyramid-pyramid-sysv3 ;; *) GUESS=pyramid-pyramid-bsd ;; esac ;; NILE*:*:*:dcosx) GUESS=pyramid-pyramid-svr4 ;; DRS?6000:unix:4.0:6*) GUESS=sparc-icl-nx6 ;; DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*) case `/usr/bin/uname -p` in sparc) GUESS=sparc-icl-nx7 ;; esac ;; s390x:SunOS:*:*) SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` GUESS=$UNAME_MACHINE-ibm-solaris2$SUN_REL ;; sun4H:SunOS:5.*:*) SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` GUESS=sparc-hal-solaris2$SUN_REL ;; sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` GUESS=sparc-sun-solaris2$SUN_REL ;; i86pc:AuroraUX:5.*:* | i86xen:AuroraUX:5.*:*) GUESS=i386-pc-auroraux$UNAME_RELEASE ;; i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*) set_cc_for_build SUN_ARCH=i386 # If there is a compiler, see if it is configured for 64-bit objects. # Note that the Sun cc does not turn __LP64__ into 1 like gcc does. # This test works for both compilers. if test "$CC_FOR_BUILD" != no_compiler_found; then if (echo '#ifdef __amd64'; echo IS_64BIT_ARCH; echo '#endif') | \ (CCOPTS="" $CC_FOR_BUILD -m64 -E - 2>/dev/null) | \ grep IS_64BIT_ARCH >/dev/null then SUN_ARCH=x86_64 fi fi SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` GUESS=$SUN_ARCH-pc-solaris2$SUN_REL ;; sun4*:SunOS:6*:*) # According to config.sub, this is the proper way to canonicalize # SunOS6. Hard to guess exactly what SunOS6 will be like, but # it's likely to be more like Solaris than SunOS4. SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` GUESS=sparc-sun-solaris3$SUN_REL ;; sun4*:SunOS:*:*) case `/usr/bin/arch -k` in Series*|S4*) UNAME_RELEASE=`uname -v` ;; esac # Japanese Language versions have a version number like '4.1.3-JL'. SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/-/_/'` GUESS=sparc-sun-sunos$SUN_REL ;; sun3*:SunOS:*:*) GUESS=m68k-sun-sunos$UNAME_RELEASE ;; sun*:*:4.2BSD:*) UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` test "x$UNAME_RELEASE" = x && UNAME_RELEASE=3 case `/bin/arch` in sun3) GUESS=m68k-sun-sunos$UNAME_RELEASE ;; sun4) GUESS=sparc-sun-sunos$UNAME_RELEASE ;; esac ;; aushp:SunOS:*:*) GUESS=sparc-auspex-sunos$UNAME_RELEASE ;; # The situation for MiNT is a little confusing. The machine name # can be virtually everything (everything which is not # "atarist" or "atariste" at least should have a processor # > m68000). The system name ranges from "MiNT" over "FreeMiNT" # to the lowercase version "mint" (or "freemint"). Finally # the system name "TOS" denotes a system which is actually not # MiNT. But MiNT is downward compatible to TOS, so this should # be no problem. atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) GUESS=m68k-atari-mint$UNAME_RELEASE ;; atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) GUESS=m68k-atari-mint$UNAME_RELEASE ;; *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) GUESS=m68k-atari-mint$UNAME_RELEASE ;; milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) GUESS=m68k-milan-mint$UNAME_RELEASE ;; hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) GUESS=m68k-hades-mint$UNAME_RELEASE ;; *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) GUESS=m68k-unknown-mint$UNAME_RELEASE ;; m68k:machten:*:*) GUESS=m68k-apple-machten$UNAME_RELEASE ;; powerpc:machten:*:*) GUESS=powerpc-apple-machten$UNAME_RELEASE ;; RISC*:Mach:*:*) GUESS=mips-dec-mach_bsd4.3 ;; RISC*:ULTRIX:*:*) GUESS=mips-dec-ultrix$UNAME_RELEASE ;; VAX*:ULTRIX*:*:*) GUESS=vax-dec-ultrix$UNAME_RELEASE ;; 2020:CLIX:*:* | 2430:CLIX:*:*) GUESS=clipper-intergraph-clix$UNAME_RELEASE ;; mips:*:*:UMIPS | mips:*:*:RISCos) set_cc_for_build sed 's/^ //' << EOF > "$dummy.c" #ifdef __cplusplus #include /* for printf() prototype */ int main (int argc, char *argv[]) { #else int main (argc, argv) int argc; char *argv[]; { #endif #if defined (host_mips) && defined (MIPSEB) #if defined (SYSTYPE_SYSV) printf ("mips-mips-riscos%ssysv\\n", argv[1]); exit (0); #endif #if defined (SYSTYPE_SVR4) printf ("mips-mips-riscos%ssvr4\\n", argv[1]); exit (0); #endif #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) printf ("mips-mips-riscos%sbsd\\n", argv[1]); exit (0); #endif #endif exit (-1); } EOF $CC_FOR_BUILD -o "$dummy" "$dummy.c" && dummyarg=`echo "$UNAME_RELEASE" | sed -n 's/\([0-9]*\).*/\1/p'` && SYSTEM_NAME=`"$dummy" "$dummyarg"` && { echo "$SYSTEM_NAME"; exit; } GUESS=mips-mips-riscos$UNAME_RELEASE ;; Motorola:PowerMAX_OS:*:*) GUESS=powerpc-motorola-powermax ;; Motorola:*:4.3:PL8-*) GUESS=powerpc-harris-powermax ;; Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) GUESS=powerpc-harris-powermax ;; Night_Hawk:Power_UNIX:*:*) GUESS=powerpc-harris-powerunix ;; m88k:CX/UX:7*:*) GUESS=m88k-harris-cxux7 ;; m88k:*:4*:R4*) GUESS=m88k-motorola-sysv4 ;; m88k:*:3*:R3*) GUESS=m88k-motorola-sysv3 ;; AViiON:dgux:*:*) # DG/UX returns AViiON for all architectures UNAME_PROCESSOR=`/usr/bin/uname -p` if test "$UNAME_PROCESSOR" = mc88100 || test "$UNAME_PROCESSOR" = mc88110 then if test "$TARGET_BINARY_INTERFACE"x = m88kdguxelfx || \ test "$TARGET_BINARY_INTERFACE"x = x then GUESS=m88k-dg-dgux$UNAME_RELEASE else GUESS=m88k-dg-dguxbcs$UNAME_RELEASE fi else GUESS=i586-dg-dgux$UNAME_RELEASE fi ;; M88*:DolphinOS:*:*) # DolphinOS (SVR3) GUESS=m88k-dolphin-sysv3 ;; M88*:*:R3*:*) # Delta 88k system running SVR3 GUESS=m88k-motorola-sysv3 ;; XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) GUESS=m88k-tektronix-sysv3 ;; Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) GUESS=m68k-tektronix-bsd ;; *:IRIX*:*:*) IRIX_REL=`echo "$UNAME_RELEASE" | sed -e 's/-/_/g'` GUESS=mips-sgi-irix$IRIX_REL ;; ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. GUESS=romp-ibm-aix # uname -m gives an 8 hex-code CPU id ;; # Note that: echo "'`uname -s`'" gives 'AIX ' i*86:AIX:*:*) GUESS=i386-ibm-aix ;; ia64:AIX:*:*) if test -x /usr/bin/oslevel ; then IBM_REV=`/usr/bin/oslevel` else IBM_REV=$UNAME_VERSION.$UNAME_RELEASE fi GUESS=$UNAME_MACHINE-ibm-aix$IBM_REV ;; *:AIX:2:3) if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then set_cc_for_build sed 's/^ //' << EOF > "$dummy.c" #include int main () { if (!__power_pc()) exit(1); puts("powerpc-ibm-aix3.2.5"); exit(0); } EOF if $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=`"$dummy"` then GUESS=$SYSTEM_NAME else GUESS=rs6000-ibm-aix3.2.5 fi elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then GUESS=rs6000-ibm-aix3.2.4 else GUESS=rs6000-ibm-aix3.2 fi ;; *:AIX:*:[4567]) IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` if /usr/sbin/lsattr -El "$IBM_CPU_ID" | grep ' POWER' >/dev/null 2>&1; then IBM_ARCH=rs6000 else IBM_ARCH=powerpc fi if test -x /usr/bin/lslpp ; then IBM_REV=`/usr/bin/lslpp -Lqc bos.rte.libc | \ awk -F: '{ print $3 }' | sed s/[0-9]*$/0/` else IBM_REV=$UNAME_VERSION.$UNAME_RELEASE fi GUESS=$IBM_ARCH-ibm-aix$IBM_REV ;; *:AIX:*:*) GUESS=rs6000-ibm-aix ;; ibmrt:4.4BSD:*|romp-ibm:4.4BSD:*) GUESS=romp-ibm-bsd4.4 ;; ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and GUESS=romp-ibm-bsd$UNAME_RELEASE # 4.3 with uname added to ;; # report: romp-ibm BSD 4.3 *:BOSX:*:*) GUESS=rs6000-bull-bosx ;; DPX/2?00:B.O.S.:*:*) GUESS=m68k-bull-sysv3 ;; 9000/[34]??:4.3bsd:1.*:*) GUESS=m68k-hp-bsd ;; hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) GUESS=m68k-hp-bsd4.4 ;; 9000/[34678]??:HP-UX:*:*) HPUX_REV=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*.[0B]*//'` case $UNAME_MACHINE in 9000/31?) HP_ARCH=m68000 ;; 9000/[34]??) HP_ARCH=m68k ;; 9000/[678][0-9][0-9]) if test -x /usr/bin/getconf; then sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` case $sc_cpu_version in 523) HP_ARCH=hppa1.0 ;; # CPU_PA_RISC1_0 528) HP_ARCH=hppa1.1 ;; # CPU_PA_RISC1_1 532) # CPU_PA_RISC2_0 case $sc_kernel_bits in 32) HP_ARCH=hppa2.0n ;; 64) HP_ARCH=hppa2.0w ;; '') HP_ARCH=hppa2.0 ;; # HP-UX 10.20 esac ;; esac fi if test "$HP_ARCH" = ""; then set_cc_for_build sed 's/^ //' << EOF > "$dummy.c" #define _HPUX_SOURCE #include #include int main () { #if defined(_SC_KERNEL_BITS) long bits = sysconf(_SC_KERNEL_BITS); #endif long cpu = sysconf (_SC_CPU_VERSION); switch (cpu) { case CPU_PA_RISC1_0: puts ("hppa1.0"); break; case CPU_PA_RISC1_1: puts ("hppa1.1"); break; case CPU_PA_RISC2_0: #if defined(_SC_KERNEL_BITS) switch (bits) { case 64: puts ("hppa2.0w"); break; case 32: puts ("hppa2.0n"); break; default: puts ("hppa2.0"); break; } break; #else /* !defined(_SC_KERNEL_BITS) */ puts ("hppa2.0"); break; #endif default: puts ("hppa1.0"); break; } exit (0); } EOF (CCOPTS="" $CC_FOR_BUILD -o "$dummy" "$dummy.c" 2>/dev/null) && HP_ARCH=`"$dummy"` test -z "$HP_ARCH" && HP_ARCH=hppa fi ;; esac if test "$HP_ARCH" = hppa2.0w then set_cc_for_build # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating # 32-bit code. hppa64-hp-hpux* has the same kernel and a compiler # generating 64-bit code. GNU and HP use different nomenclature: # # $ CC_FOR_BUILD=cc ./config.guess # => hppa2.0w-hp-hpux11.23 # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess # => hppa64-hp-hpux11.23 if echo __LP64__ | (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | grep -q __LP64__ then HP_ARCH=hppa2.0w else HP_ARCH=hppa64 fi fi GUESS=$HP_ARCH-hp-hpux$HPUX_REV ;; ia64:HP-UX:*:*) HPUX_REV=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*.[0B]*//'` GUESS=ia64-hp-hpux$HPUX_REV ;; 3050*:HI-UX:*:*) set_cc_for_build sed 's/^ //' << EOF > "$dummy.c" #include int main () { long cpu = sysconf (_SC_CPU_VERSION); /* The order matters, because CPU_IS_HP_MC68K erroneously returns true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct results, however. */ if (CPU_IS_PA_RISC (cpu)) { switch (cpu) { case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; default: puts ("hppa-hitachi-hiuxwe2"); break; } } else if (CPU_IS_HP_MC68K (cpu)) puts ("m68k-hitachi-hiuxwe2"); else puts ("unknown-hitachi-hiuxwe2"); exit (0); } EOF $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=`"$dummy"` && { echo "$SYSTEM_NAME"; exit; } GUESS=unknown-hitachi-hiuxwe2 ;; 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:*) GUESS=hppa1.1-hp-bsd ;; 9000/8??:4.3bsd:*:*) GUESS=hppa1.0-hp-bsd ;; *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) GUESS=hppa1.0-hp-mpeix ;; hp7??:OSF1:*:* | hp8?[79]:OSF1:*:*) GUESS=hppa1.1-hp-osf ;; hp8??:OSF1:*:*) GUESS=hppa1.0-hp-osf ;; i*86:OSF1:*:*) if test -x /usr/sbin/sysversion ; then GUESS=$UNAME_MACHINE-unknown-osf1mk else GUESS=$UNAME_MACHINE-unknown-osf1 fi ;; parisc*:Lites*:*:*) GUESS=hppa1.1-hp-lites ;; C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) GUESS=c1-convex-bsd ;; C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) if getsysinfo -f scalar_acc then echo c32-convex-bsd else echo c2-convex-bsd fi exit ;; C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) GUESS=c34-convex-bsd ;; C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) GUESS=c38-convex-bsd ;; C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) GUESS=c4-convex-bsd ;; CRAY*Y-MP:*:*:*) CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` GUESS=ymp-cray-unicos$CRAY_REL ;; CRAY*[A-Z]90:*:*:*) echo "$UNAME_MACHINE"-cray-unicos"$UNAME_RELEASE" \ | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ -e 's/\.[^.]*$/.X/' exit ;; CRAY*TS:*:*:*) CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` GUESS=t90-cray-unicos$CRAY_REL ;; CRAY*T3E:*:*:*) CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` GUESS=alphaev5-cray-unicosmk$CRAY_REL ;; CRAY*SV1:*:*:*) CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` GUESS=sv1-cray-unicos$CRAY_REL ;; *:UNICOS/mp:*:*) CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` GUESS=craynv-cray-unicosmp$CRAY_REL ;; F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) FUJITSU_PROC=`uname -m | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz` FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'` FUJITSU_REL=`echo "$UNAME_RELEASE" | sed -e 's/ /_/'` GUESS=${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL} ;; 5000:UNIX_System_V:4.*:*) FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'` FUJITSU_REL=`echo "$UNAME_RELEASE" | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/ /_/'` GUESS=sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL} ;; i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) GUESS=$UNAME_MACHINE-pc-bsdi$UNAME_RELEASE ;; sparc*:BSD/OS:*:*) GUESS=sparc-unknown-bsdi$UNAME_RELEASE ;; *:BSD/OS:*:*) GUESS=$UNAME_MACHINE-unknown-bsdi$UNAME_RELEASE ;; arm:FreeBSD:*:*) UNAME_PROCESSOR=`uname -p` set_cc_for_build if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ARM_PCS_VFP then FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL-gnueabi else FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL-gnueabihf fi ;; *:FreeBSD:*:*) UNAME_PROCESSOR=`uname -p` case $UNAME_PROCESSOR in amd64) UNAME_PROCESSOR=x86_64 ;; i386) UNAME_PROCESSOR=i586 ;; esac FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL ;; i*:CYGWIN*:*) GUESS=$UNAME_MACHINE-pc-cygwin ;; *:MINGW64*:*) GUESS=$UNAME_MACHINE-pc-mingw64 ;; *:MINGW*:*) GUESS=$UNAME_MACHINE-pc-mingw32 ;; *:MSYS*:*) GUESS=$UNAME_MACHINE-pc-msys ;; i*:PW*:*) GUESS=$UNAME_MACHINE-pc-pw32 ;; *:SerenityOS:*:*) GUESS=$UNAME_MACHINE-pc-serenity ;; *:Interix*:*) case $UNAME_MACHINE in x86) GUESS=i586-pc-interix$UNAME_RELEASE ;; authenticamd | genuineintel | EM64T) GUESS=x86_64-unknown-interix$UNAME_RELEASE ;; IA64) GUESS=ia64-unknown-interix$UNAME_RELEASE ;; esac ;; i*:UWIN*:*) GUESS=$UNAME_MACHINE-pc-uwin ;; amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*) GUESS=x86_64-pc-cygwin ;; prep*:SunOS:5.*:*) SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` GUESS=powerpcle-unknown-solaris2$SUN_REL ;; *:GNU:*:*) # the GNU system GNU_ARCH=`echo "$UNAME_MACHINE" | sed -e 's,[-/].*$,,'` GNU_REL=`echo "$UNAME_RELEASE" | sed -e 's,/.*$,,'` GUESS=$GNU_ARCH-unknown-$LIBC$GNU_REL ;; *:GNU/*:*:*) # other systems with GNU libc and userland GNU_SYS=`echo "$UNAME_SYSTEM" | sed 's,^[^/]*/,,' | tr "[:upper:]" "[:lower:]"` GNU_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` GUESS=$UNAME_MACHINE-unknown-$GNU_SYS$GNU_REL-$LIBC ;; x86_64:[Mm]anagarm:*:*|i?86:[Mm]anagarm:*:*) GUESS="$UNAME_MACHINE-pc-managarm-mlibc" ;; *:[Mm]anagarm:*:*) GUESS="$UNAME_MACHINE-unknown-managarm-mlibc" ;; *:Minix:*:*) GUESS=$UNAME_MACHINE-unknown-minix ;; aarch64:Linux:*:*) set_cc_for_build CPU=$UNAME_MACHINE LIBCABI=$LIBC if test "$CC_FOR_BUILD" != no_compiler_found; then ABI=64 sed 's/^ //' << EOF > "$dummy.c" #ifdef __ARM_EABI__ #ifdef __ARM_PCS_VFP ABI=eabihf #else ABI=eabi #endif #endif EOF cc_set_abi=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^ABI' | sed 's, ,,g'` eval "$cc_set_abi" case $ABI in eabi | eabihf) CPU=armv8l; LIBCABI=$LIBC$ABI ;; esac fi GUESS=$CPU-unknown-linux-$LIBCABI ;; aarch64_be:Linux:*:*) UNAME_MACHINE=aarch64_be GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; alpha:Linux:*:*) case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' /proc/cpuinfo 2>/dev/null` in EV5) UNAME_MACHINE=alphaev5 ;; EV56) UNAME_MACHINE=alphaev56 ;; PCA56) UNAME_MACHINE=alphapca56 ;; PCA57) UNAME_MACHINE=alphapca56 ;; EV6) UNAME_MACHINE=alphaev6 ;; EV67) UNAME_MACHINE=alphaev67 ;; EV68*) UNAME_MACHINE=alphaev68 ;; esac objdump --private-headers /bin/sh | grep -q ld.so.1 if test "$?" = 0 ; then LIBC=gnulibc1 ; fi GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; arc:Linux:*:* | arceb:Linux:*:* | arc32:Linux:*:* | arc64:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; arm*:Linux:*:*) set_cc_for_build if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ARM_EABI__ then GUESS=$UNAME_MACHINE-unknown-linux-$LIBC else if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ARM_PCS_VFP then GUESS=$UNAME_MACHINE-unknown-linux-${LIBC}eabi else GUESS=$UNAME_MACHINE-unknown-linux-${LIBC}eabihf fi fi ;; avr32*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; cris:Linux:*:*) GUESS=$UNAME_MACHINE-axis-linux-$LIBC ;; crisv32:Linux:*:*) GUESS=$UNAME_MACHINE-axis-linux-$LIBC ;; e2k:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; frv:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; hexagon:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; i*86:Linux:*:*) GUESS=$UNAME_MACHINE-pc-linux-$LIBC ;; ia64:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; k1om:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; kvx:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; kvx:cos:*:*) GUESS=$UNAME_MACHINE-unknown-cos ;; kvx:mbr:*:*) GUESS=$UNAME_MACHINE-unknown-mbr ;; loongarch32:Linux:*:* | loongarch64:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; m32r*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; m68*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; mips:Linux:*:* | mips64:Linux:*:*) set_cc_for_build IS_GLIBC=0 test x"${LIBC}" = xgnu && IS_GLIBC=1 sed 's/^ //' << EOF > "$dummy.c" #undef CPU #undef mips #undef mipsel #undef mips64 #undef mips64el #if ${IS_GLIBC} && defined(_ABI64) LIBCABI=gnuabi64 #else #if ${IS_GLIBC} && defined(_ABIN32) LIBCABI=gnuabin32 #else LIBCABI=${LIBC} #endif #endif #if ${IS_GLIBC} && defined(__mips64) && defined(__mips_isa_rev) && __mips_isa_rev>=6 CPU=mipsisa64r6 #else #if ${IS_GLIBC} && !defined(__mips64) && defined(__mips_isa_rev) && __mips_isa_rev>=6 CPU=mipsisa32r6 #else #if defined(__mips64) CPU=mips64 #else CPU=mips #endif #endif #endif #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) MIPS_ENDIAN=el #else #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) MIPS_ENDIAN= #else MIPS_ENDIAN= #endif #endif EOF cc_set_vars=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^CPU\|^MIPS_ENDIAN\|^LIBCABI'` eval "$cc_set_vars" test "x$CPU" != x && { echo "$CPU${MIPS_ENDIAN}-unknown-linux-$LIBCABI"; exit; } ;; mips64el:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; openrisc*:Linux:*:*) GUESS=or1k-unknown-linux-$LIBC ;; or32:Linux:*:* | or1k*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; padre:Linux:*:*) GUESS=sparc-unknown-linux-$LIBC ;; parisc64:Linux:*:* | hppa64:Linux:*:*) GUESS=hppa64-unknown-linux-$LIBC ;; parisc:Linux:*:* | hppa:Linux:*:*) # Look for CPU level case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in PA7*) GUESS=hppa1.1-unknown-linux-$LIBC ;; PA8*) GUESS=hppa2.0-unknown-linux-$LIBC ;; *) GUESS=hppa-unknown-linux-$LIBC ;; esac ;; ppc64:Linux:*:*) GUESS=powerpc64-unknown-linux-$LIBC ;; ppc:Linux:*:*) GUESS=powerpc-unknown-linux-$LIBC ;; ppc64le:Linux:*:*) GUESS=powerpc64le-unknown-linux-$LIBC ;; ppcle:Linux:*:*) GUESS=powerpcle-unknown-linux-$LIBC ;; riscv32:Linux:*:* | riscv32be:Linux:*:* | riscv64:Linux:*:* | riscv64be:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; s390:Linux:*:* | s390x:Linux:*:*) GUESS=$UNAME_MACHINE-ibm-linux-$LIBC ;; sh64*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; sh*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; sparc:Linux:*:* | sparc64:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; tile*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; vax:Linux:*:*) GUESS=$UNAME_MACHINE-dec-linux-$LIBC ;; x86_64:Linux:*:*) set_cc_for_build CPU=$UNAME_MACHINE LIBCABI=$LIBC if test "$CC_FOR_BUILD" != no_compiler_found; then ABI=64 sed 's/^ //' << EOF > "$dummy.c" #ifdef __i386__ ABI=x86 #else #ifdef __ILP32__ ABI=x32 #endif #endif EOF cc_set_abi=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^ABI' | sed 's, ,,g'` eval "$cc_set_abi" case $ABI in x86) CPU=i686 ;; x32) LIBCABI=${LIBC}x32 ;; esac fi GUESS=$CPU-pc-linux-$LIBCABI ;; xtensa*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; i*86:DYNIX/ptx:4*:*) # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. # earlier versions are messed up and put the nodename in both # sysname and nodename. GUESS=i386-sequent-sysv4 ;; i*86:UNIX_SV:4.2MP:2.*) # Unixware is an offshoot of SVR4, but it has its own version # number series starting with 2... # I am not positive that other SVR4 systems won't match this, # I just have to hope. -- rms. # Use sysv4.2uw... so that sysv4* matches it. GUESS=$UNAME_MACHINE-pc-sysv4.2uw$UNAME_VERSION ;; i*86:OS/2:*:*) # If we were able to find 'uname', then EMX Unix compatibility # is probably installed. GUESS=$UNAME_MACHINE-pc-os2-emx ;; i*86:XTS-300:*:STOP) GUESS=$UNAME_MACHINE-unknown-stop ;; i*86:atheos:*:*) GUESS=$UNAME_MACHINE-unknown-atheos ;; i*86:syllable:*:*) GUESS=$UNAME_MACHINE-pc-syllable ;; i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.[02]*:*) GUESS=i386-unknown-lynxos$UNAME_RELEASE ;; i*86:*DOS:*:*) GUESS=$UNAME_MACHINE-pc-msdosdjgpp ;; i*86:*:4.*:*) UNAME_REL=`echo "$UNAME_RELEASE" | sed 's/\/MP$//'` if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then GUESS=$UNAME_MACHINE-univel-sysv$UNAME_REL else GUESS=$UNAME_MACHINE-pc-sysv$UNAME_REL fi ;; i*86:*:5:[678]*) # UnixWare 7.x, OpenUNIX and OpenServer 6. case `/bin/uname -X | grep "^Machine"` in *486*) UNAME_MACHINE=i486 ;; *Pentium) UNAME_MACHINE=i586 ;; *Pent*|*Celeron) UNAME_MACHINE=i686 ;; esac GUESS=$UNAME_MACHINE-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION} ;; i*86:*:3.2:*) if test -f /usr/options/cb.name; then UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ && UNAME_MACHINE=i586 (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ && UNAME_MACHINE=i686 (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ && UNAME_MACHINE=i686 GUESS=$UNAME_MACHINE-pc-sco$UNAME_REL else GUESS=$UNAME_MACHINE-pc-sysv32 fi ;; pc:*:*:*) # Left here for compatibility: # uname -m prints for DJGPP always 'pc', but it prints nothing about # the processor, so we play safe by assuming i586. # Note: whatever this is, it MUST be the same as what config.sub # prints for the "djgpp" host, or else GDB configure will decide that # this is a cross-build. GUESS=i586-pc-msdosdjgpp ;; Intel:Mach:3*:*) GUESS=i386-pc-mach3 ;; paragon:*:*:*) GUESS=i860-intel-osf1 ;; i860:*:4.*:*) # i860-SVR4 if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then GUESS=i860-stardent-sysv$UNAME_RELEASE # Stardent Vistra i860-SVR4 else # Add other i860-SVR4 vendors below as they are discovered. GUESS=i860-unknown-sysv$UNAME_RELEASE # Unknown i860-SVR4 fi ;; mini*:CTIX:SYS*5:*) # "miniframe" GUESS=m68010-convergent-sysv ;; mc68k:UNIX:SYSTEM5:3.51m) GUESS=m68k-convergent-sysv ;; M680?0:D-NIX:5.3:*) GUESS=m68k-diab-dnix ;; M68*:*:R3V[5678]*:*) test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;; 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0) OS_REL='' test -r /etc/.relid \ && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && { echo i486-ncr-sysv4.3"$OS_REL"; exit; } /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;; 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && { echo i486-ncr-sysv4; exit; } ;; NCR*:*:4.2:* | MPRAS*:*:4.2:*) OS_REL='.3' test -r /etc/.relid \ && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && { echo i486-ncr-sysv4.3"$OS_REL"; exit; } /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } /bin/uname -p 2>/dev/null | /bin/grep pteron >/dev/null \ && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;; m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) GUESS=m68k-unknown-lynxos$UNAME_RELEASE ;; mc68030:UNIX_System_V:4.*:*) GUESS=m68k-atari-sysv4 ;; TSUNAMI:LynxOS:2.*:*) GUESS=sparc-unknown-lynxos$UNAME_RELEASE ;; rs6000:LynxOS:2.*:*) GUESS=rs6000-unknown-lynxos$UNAME_RELEASE ;; PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.[02]*:*) GUESS=powerpc-unknown-lynxos$UNAME_RELEASE ;; SM[BE]S:UNIX_SV:*:*) GUESS=mips-dde-sysv$UNAME_RELEASE ;; RM*:ReliantUNIX-*:*:*) GUESS=mips-sni-sysv4 ;; RM*:SINIX-*:*:*) GUESS=mips-sni-sysv4 ;; *:SINIX-*:*:*) if uname -p 2>/dev/null >/dev/null ; then UNAME_MACHINE=`(uname -p) 2>/dev/null` GUESS=$UNAME_MACHINE-sni-sysv4 else GUESS=ns32k-sni-sysv fi ;; PENTIUM:*:4.0*:*) # Unisys 'ClearPath HMP IX 4000' SVR4/MP effort # says GUESS=i586-unisys-sysv4 ;; *:UNIX_System_V:4*:FTX*) # From Gerald Hewes . # How about differentiating between stratus architectures? -djm GUESS=hppa1.1-stratus-sysv4 ;; *:*:*:FTX*) # From seanf@swdc.stratus.com. GUESS=i860-stratus-sysv4 ;; i*86:VOS:*:*) # From Paul.Green@stratus.com. GUESS=$UNAME_MACHINE-stratus-vos ;; *:VOS:*:*) # From Paul.Green@stratus.com. GUESS=hppa1.1-stratus-vos ;; mc68*:A/UX:*:*) GUESS=m68k-apple-aux$UNAME_RELEASE ;; news*:NEWS-OS:6*:*) GUESS=mips-sony-newsos6 ;; R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) if test -d /usr/nec; then GUESS=mips-nec-sysv$UNAME_RELEASE else GUESS=mips-unknown-sysv$UNAME_RELEASE fi ;; BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. GUESS=powerpc-be-beos ;; BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. GUESS=powerpc-apple-beos ;; BePC:BeOS:*:*) # BeOS running on Intel PC compatible. GUESS=i586-pc-beos ;; BePC:Haiku:*:*) # Haiku running on Intel PC compatible. GUESS=i586-pc-haiku ;; ppc:Haiku:*:*) # Haiku running on Apple PowerPC GUESS=powerpc-apple-haiku ;; *:Haiku:*:*) # Haiku modern gcc (not bound by BeOS compat) GUESS=$UNAME_MACHINE-unknown-haiku ;; SX-4:SUPER-UX:*:*) GUESS=sx4-nec-superux$UNAME_RELEASE ;; SX-5:SUPER-UX:*:*) GUESS=sx5-nec-superux$UNAME_RELEASE ;; SX-6:SUPER-UX:*:*) GUESS=sx6-nec-superux$UNAME_RELEASE ;; SX-7:SUPER-UX:*:*) GUESS=sx7-nec-superux$UNAME_RELEASE ;; SX-8:SUPER-UX:*:*) GUESS=sx8-nec-superux$UNAME_RELEASE ;; SX-8R:SUPER-UX:*:*) GUESS=sx8r-nec-superux$UNAME_RELEASE ;; SX-ACE:SUPER-UX:*:*) GUESS=sxace-nec-superux$UNAME_RELEASE ;; Power*:Rhapsody:*:*) GUESS=powerpc-apple-rhapsody$UNAME_RELEASE ;; *:Rhapsody:*:*) GUESS=$UNAME_MACHINE-apple-rhapsody$UNAME_RELEASE ;; arm64:Darwin:*:*) GUESS=aarch64-apple-darwin$UNAME_RELEASE ;; *:Darwin:*:*) UNAME_PROCESSOR=`uname -p` case $UNAME_PROCESSOR in unknown) UNAME_PROCESSOR=powerpc ;; esac if command -v xcode-select > /dev/null 2> /dev/null && \ ! xcode-select --print-path > /dev/null 2> /dev/null ; then # Avoid executing cc if there is no toolchain installed as # cc will be a stub that puts up a graphical alert # prompting the user to install developer tools. CC_FOR_BUILD=no_compiler_found else set_cc_for_build fi if test "$CC_FOR_BUILD" != no_compiler_found; then if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \ (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ grep IS_64BIT_ARCH >/dev/null then case $UNAME_PROCESSOR in i386) UNAME_PROCESSOR=x86_64 ;; powerpc) UNAME_PROCESSOR=powerpc64 ;; esac fi # On 10.4-10.6 one might compile for PowerPC via gcc -arch ppc if (echo '#ifdef __POWERPC__'; echo IS_PPC; echo '#endif') | \ (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ grep IS_PPC >/dev/null then UNAME_PROCESSOR=powerpc fi elif test "$UNAME_PROCESSOR" = i386 ; then # uname -m returns i386 or x86_64 UNAME_PROCESSOR=$UNAME_MACHINE fi GUESS=$UNAME_PROCESSOR-apple-darwin$UNAME_RELEASE ;; *:procnto*:*:* | *:QNX:[0123456789]*:*) UNAME_PROCESSOR=`uname -p` if test "$UNAME_PROCESSOR" = x86; then UNAME_PROCESSOR=i386 UNAME_MACHINE=pc fi GUESS=$UNAME_PROCESSOR-$UNAME_MACHINE-nto-qnx$UNAME_RELEASE ;; *:QNX:*:4*) GUESS=i386-pc-qnx ;; NEO-*:NONSTOP_KERNEL:*:*) GUESS=neo-tandem-nsk$UNAME_RELEASE ;; NSE-*:NONSTOP_KERNEL:*:*) GUESS=nse-tandem-nsk$UNAME_RELEASE ;; NSR-*:NONSTOP_KERNEL:*:*) GUESS=nsr-tandem-nsk$UNAME_RELEASE ;; NSV-*:NONSTOP_KERNEL:*:*) GUESS=nsv-tandem-nsk$UNAME_RELEASE ;; NSX-*:NONSTOP_KERNEL:*:*) GUESS=nsx-tandem-nsk$UNAME_RELEASE ;; *:NonStop-UX:*:*) GUESS=mips-compaq-nonstopux ;; BS2000:POSIX*:*:*) GUESS=bs2000-siemens-sysv ;; DS/*:UNIX_System_V:*:*) GUESS=$UNAME_MACHINE-$UNAME_SYSTEM-$UNAME_RELEASE ;; *:Plan9:*:*) # "uname -m" is not consistent, so use $cputype instead. 386 # is converted to i386 for consistency with other x86 # operating systems. if test "${cputype-}" = 386; then UNAME_MACHINE=i386 elif test "x${cputype-}" != x; then UNAME_MACHINE=$cputype fi GUESS=$UNAME_MACHINE-unknown-plan9 ;; *:TOPS-10:*:*) GUESS=pdp10-unknown-tops10 ;; *:TENEX:*:*) GUESS=pdp10-unknown-tenex ;; KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) GUESS=pdp10-dec-tops20 ;; XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) GUESS=pdp10-xkl-tops20 ;; *:TOPS-20:*:*) GUESS=pdp10-unknown-tops20 ;; *:ITS:*:*) GUESS=pdp10-unknown-its ;; SEI:*:*:SEIUX) GUESS=mips-sei-seiux$UNAME_RELEASE ;; *:DragonFly:*:*) DRAGONFLY_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` GUESS=$UNAME_MACHINE-unknown-dragonfly$DRAGONFLY_REL ;; *:*VMS:*:*) UNAME_MACHINE=`(uname -p) 2>/dev/null` case $UNAME_MACHINE in A*) GUESS=alpha-dec-vms ;; I*) GUESS=ia64-dec-vms ;; V*) GUESS=vax-dec-vms ;; esac ;; *:XENIX:*:SysV) GUESS=i386-pc-xenix ;; i*86:skyos:*:*) SKYOS_REL=`echo "$UNAME_RELEASE" | sed -e 's/ .*$//'` GUESS=$UNAME_MACHINE-pc-skyos$SKYOS_REL ;; i*86:rdos:*:*) GUESS=$UNAME_MACHINE-pc-rdos ;; i*86:Fiwix:*:*) GUESS=$UNAME_MACHINE-pc-fiwix ;; *:AROS:*:*) GUESS=$UNAME_MACHINE-unknown-aros ;; x86_64:VMkernel:*:*) GUESS=$UNAME_MACHINE-unknown-esx ;; amd64:Isilon\ OneFS:*:*) GUESS=x86_64-unknown-onefs ;; *:Unleashed:*:*) GUESS=$UNAME_MACHINE-unknown-unleashed$UNAME_RELEASE ;; x86_64:[Ii]ronclad:*:*|i?86:[Ii]ronclad:*:*) GUESS=$UNAME_MACHINE-pc-ironclad-mlibc ;; *:[Ii]ronclad:*:*) GUESS=$UNAME_MACHINE-unknown-ironclad-mlibc ;; esac # Do we have a guess based on uname results? if test "x$GUESS" != x; then echo "$GUESS" exit fi # No uname command or uname output not recognized. set_cc_for_build cat > "$dummy.c" < #include #endif #if defined(ultrix) || defined(_ultrix) || defined(__ultrix) || defined(__ultrix__) #if defined (vax) || defined (__vax) || defined (__vax__) || defined(mips) || defined(__mips) || defined(__mips__) || defined(MIPS) || defined(__MIPS__) #include #if defined(_SIZE_T_) || defined(SIGLOST) #include #endif #endif #endif int main () { #if defined (sony) #if defined (MIPSEB) /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed, I don't know.... */ printf ("mips-sony-bsd\n"); exit (0); #else #include printf ("m68k-sony-newsos%s\n", #ifdef NEWSOS4 "4" #else "" #endif ); exit (0); #endif #endif #if defined (NeXT) #if !defined (__ARCHITECTURE__) #define __ARCHITECTURE__ "m68k" #endif int version; version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`; if (version < 4) printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version); else printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version); exit (0); #endif #if defined (MULTIMAX) || defined (n16) #if defined (UMAXV) printf ("ns32k-encore-sysv\n"); exit (0); #else #if defined (CMU) printf ("ns32k-encore-mach\n"); exit (0); #else printf ("ns32k-encore-bsd\n"); exit (0); #endif #endif #endif #if defined (__386BSD__) printf ("i386-pc-bsd\n"); exit (0); #endif #if defined (sequent) #if defined (i386) printf ("i386-sequent-dynix\n"); exit (0); #endif #if defined (ns32000) printf ("ns32k-sequent-dynix\n"); exit (0); #endif #endif #if defined (_SEQUENT_) struct utsname un; uname(&un); if (strncmp(un.version, "V2", 2) == 0) { printf ("i386-sequent-ptx2\n"); exit (0); } if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */ printf ("i386-sequent-ptx1\n"); exit (0); } printf ("i386-sequent-ptx\n"); exit (0); #endif #if defined (vax) #if !defined (ultrix) #include #if defined (BSD) #if BSD == 43 printf ("vax-dec-bsd4.3\n"); exit (0); #else #if BSD == 199006 printf ("vax-dec-bsd4.3reno\n"); exit (0); #else printf ("vax-dec-bsd\n"); exit (0); #endif #endif #else printf ("vax-dec-bsd\n"); exit (0); #endif #else #if defined(_SIZE_T_) || defined(SIGLOST) struct utsname un; uname (&un); printf ("vax-dec-ultrix%s\n", un.release); exit (0); #else printf ("vax-dec-ultrix\n"); exit (0); #endif #endif #endif #if defined(ultrix) || defined(_ultrix) || defined(__ultrix) || defined(__ultrix__) #if defined(mips) || defined(__mips) || defined(__mips__) || defined(MIPS) || defined(__MIPS__) #if defined(_SIZE_T_) || defined(SIGLOST) struct utsname *un; uname (&un); printf ("mips-dec-ultrix%s\n", un.release); exit (0); #else printf ("mips-dec-ultrix\n"); exit (0); #endif #endif #endif #if defined (alliant) && defined (i860) printf ("i860-alliant-bsd\n"); exit (0); #endif exit (1); } EOF $CC_FOR_BUILD -o "$dummy" "$dummy.c" 2>/dev/null && SYSTEM_NAME=`"$dummy"` && { echo "$SYSTEM_NAME"; exit; } # Apollos put the system type in the environment. test -d /usr/apollo && { echo "$ISP-apollo-$SYSTYPE"; exit; } echo "$0: unable to guess system type" >&2 case $UNAME_MACHINE:$UNAME_SYSTEM in mips:Linux | mips64:Linux) # If we got here on MIPS GNU/Linux, output extra information. cat >&2 <&2 <&2 </dev/null || echo unknown` uname -r = `(uname -r) 2>/dev/null || echo unknown` uname -s = `(uname -s) 2>/dev/null || echo unknown` uname -v = `(uname -v) 2>/dev/null || echo unknown` /usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` /bin/uname -X = `(/bin/uname -X) 2>/dev/null` hostinfo = `(hostinfo) 2>/dev/null` /bin/universe = `(/bin/universe) 2>/dev/null` /usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` /bin/arch = `(/bin/arch) 2>/dev/null` /usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` UNAME_MACHINE = "$UNAME_MACHINE" UNAME_RELEASE = "$UNAME_RELEASE" UNAME_SYSTEM = "$UNAME_SYSTEM" UNAME_VERSION = "$UNAME_VERSION" EOF fi exit 1 # Local variables: # eval: (add-hook 'before-save-hook 'time-stamp nil t) # time-stamp-start: "timestamp='" # time-stamp-format: "%Y-%02m-%02d" # time-stamp-end: "'" # End: lft-4.01/config/config.sub000755 000765 000024 00000116007 15165341545 015415 0ustar00vicstaff000000 000000 #! /bin/sh # Configuration validation subroutine script. # Copyright 1992-2025 Free Software Foundation, Inc. # shellcheck disable=SC2006,SC2268,SC2162 # see below for rationale timestamp='2025-07-10' # This file is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, see . # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that # program. This Exception is an additional permission under section 7 # of the GNU General Public License, version 3 ("GPLv3"). # Please send patches to . # # Configuration subroutine to validate and canonicalize a configuration type. # Supply the specified configuration type as an argument. # If it is invalid, we print an error message on stderr and exit with code 1. # Otherwise, we print the canonical config type on stdout and succeed. # You can get the latest version of this script from: # https://git.savannah.gnu.org/cgit/config.git/plain/config.sub # This file is supposed to be the same for all GNU packages # and recognize all the CPU types, system types and aliases # that are meaningful with *any* GNU software. # Each package is responsible for reporting which valid configurations # it does not support. The user should be able to distinguish # a failure to support a valid configuration from a meaningless # configuration. # The goal of this file is to map all the various variations of a given # machine specification into a single specification in the form: # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM # or in some cases, the newer four-part form: # CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM # It is wrong to echo any other type of specification. # The "shellcheck disable" line above the timestamp inhibits complaints # about features and limitations of the classic Bourne shell that were # superseded or lifted in POSIX. However, this script identifies a wide # variety of pre-POSIX systems that do not have POSIX shells at all, and # even some reasonably current systems (Solaris 10 as case-in-point) still # have a pre-POSIX /bin/sh. me=`echo "$0" | sed -e 's,.*/,,'` usage="\ Usage: $0 [OPTION] CPU-MFR-OPSYS or ALIAS Canonicalize a configuration name. Options: -h, --help print this help, then exit -t, --time-stamp print date of last modification, then exit -v, --version print version number, then exit Report bugs and patches to ." version="\ GNU config.sub ($timestamp) Copyright 1992-2025 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." help=" Try '$me --help' for more information." # Parse command line while test $# -gt 0 ; do case $1 in --time-stamp | --time* | -t ) echo "$timestamp" ; exit ;; --version | -v ) echo "$version" ; exit ;; --help | --h* | -h ) echo "$usage"; exit ;; -- ) # Stop option processing shift; break ;; - ) # Use stdin as input. break ;; -* ) echo "$me: invalid option $1$help" >&2 exit 1 ;; *local*) # First pass through any local machine types. echo "$1" exit ;; * ) break ;; esac done case $# in 0) echo "$me: missing argument$help" >&2 exit 1;; 1) ;; *) echo "$me: too many arguments$help" >&2 exit 1;; esac # Split fields of configuration type saved_IFS=$IFS IFS="-" read field1 field2 field3 field4 <&2 exit 1 ;; *-*-*-*) basic_machine=$field1-$field2 basic_os=$field3-$field4 ;; *-*-*) # Ambiguous whether COMPANY is present, or skipped and KERNEL-OS is two # parts maybe_os=$field2-$field3 case $maybe_os in cloudabi*-eabi* \ | kfreebsd*-gnu* \ | knetbsd*-gnu* \ | kopensolaris*-gnu* \ | ironclad-* \ | linux-* \ | managarm-* \ | netbsd*-eabi* \ | netbsd*-gnu* \ | nto-qnx* \ | os2-emx* \ | rtmk-nova* \ | storm-chaos* \ | uclinux-gnu* \ | uclinux-uclibc* \ | windows-* ) basic_machine=$field1 basic_os=$maybe_os ;; android-linux) basic_machine=$field1-unknown basic_os=linux-android ;; *) basic_machine=$field1-$field2 basic_os=$field3 ;; esac ;; *-*) case $field1-$field2 in # Shorthands that happen to contain a single dash convex-c[12] | convex-c3[248]) basic_machine=$field2-convex basic_os= ;; decstation-3100) basic_machine=mips-dec basic_os= ;; *-*) # Second component is usually, but not always the OS case $field2 in # Do not treat sunos as a manufacturer sun*os*) basic_machine=$field1 basic_os=$field2 ;; # Manufacturers 3100* \ | 32* \ | 3300* \ | 3600* \ | 7300* \ | acorn \ | altos* \ | apollo \ | apple \ | atari \ | att* \ | axis \ | be \ | bull \ | cbm \ | ccur \ | cisco \ | commodore \ | convergent* \ | convex* \ | cray \ | crds \ | dec* \ | delta* \ | dg \ | digital \ | dolphin \ | encore* \ | gould \ | harris \ | highlevel \ | hitachi* \ | hp \ | ibm* \ | intergraph \ | isi* \ | knuth \ | masscomp \ | microblaze* \ | mips* \ | motorola* \ | ncr* \ | news \ | next \ | ns \ | oki \ | omron* \ | pc533* \ | rebel \ | rom68k \ | rombug \ | semi \ | sequent* \ | sgi* \ | siemens \ | sim \ | sni \ | sony* \ | stratus \ | sun \ | sun[234]* \ | tektronix \ | tti* \ | ultra \ | unicom* \ | wec \ | winbond \ | wrs) basic_machine=$field1-$field2 basic_os= ;; tock* | zephyr*) basic_machine=$field1-unknown basic_os=$field2 ;; *) basic_machine=$field1 basic_os=$field2 ;; esac ;; esac ;; *) # Convert single-component short-hands not valid as part of # multi-component configurations. case $field1 in 386bsd) basic_machine=i386-pc basic_os=bsd ;; a29khif) basic_machine=a29k-amd basic_os=udi ;; adobe68k) basic_machine=m68010-adobe basic_os=scout ;; alliant) basic_machine=fx80-alliant basic_os= ;; altos | altos3068) basic_machine=m68k-altos basic_os= ;; am29k) basic_machine=a29k-none basic_os=bsd ;; amdahl) basic_machine=580-amdahl basic_os=sysv ;; amiga) basic_machine=m68k-unknown basic_os= ;; amigaos | amigados) basic_machine=m68k-unknown basic_os=amigaos ;; amigaunix | amix) basic_machine=m68k-unknown basic_os=sysv4 ;; apollo68) basic_machine=m68k-apollo basic_os=sysv ;; apollo68bsd) basic_machine=m68k-apollo basic_os=bsd ;; aros) basic_machine=i386-pc basic_os=aros ;; aux) basic_machine=m68k-apple basic_os=aux ;; balance) basic_machine=ns32k-sequent basic_os=dynix ;; blackfin) basic_machine=bfin-unknown basic_os=linux ;; cegcc) basic_machine=arm-unknown basic_os=cegcc ;; cray) basic_machine=j90-cray basic_os=unicos ;; crds | unos) basic_machine=m68k-crds basic_os= ;; da30) basic_machine=m68k-da30 basic_os= ;; decstation | pmax | pmin | dec3100 | decstatn) basic_machine=mips-dec basic_os= ;; delta88) basic_machine=m88k-motorola basic_os=sysv3 ;; dicos) basic_machine=i686-pc basic_os=dicos ;; djgpp) basic_machine=i586-pc basic_os=msdosdjgpp ;; ebmon29k) basic_machine=a29k-amd basic_os=ebmon ;; es1800 | OSE68k | ose68k | ose | OSE) basic_machine=m68k-ericsson basic_os=ose ;; gmicro) basic_machine=tron-gmicro basic_os=sysv ;; go32) basic_machine=i386-pc basic_os=go32 ;; h8300hms) basic_machine=h8300-hitachi basic_os=hms ;; h8300xray) basic_machine=h8300-hitachi basic_os=xray ;; h8500hms) basic_machine=h8500-hitachi basic_os=hms ;; harris) basic_machine=m88k-harris basic_os=sysv3 ;; hp300 | hp300hpux) basic_machine=m68k-hp basic_os=hpux ;; hp300bsd) basic_machine=m68k-hp basic_os=bsd ;; hppaosf) basic_machine=hppa1.1-hp basic_os=osf ;; hppro) basic_machine=hppa1.1-hp basic_os=proelf ;; i386mach) basic_machine=i386-mach basic_os=mach ;; isi68 | isi) basic_machine=m68k-isi basic_os=sysv ;; m68knommu) basic_machine=m68k-unknown basic_os=linux ;; magnum | m3230) basic_machine=mips-mips basic_os=sysv ;; merlin) basic_machine=ns32k-utek basic_os=sysv ;; mingw64) basic_machine=x86_64-pc basic_os=mingw64 ;; mingw32) basic_machine=i686-pc basic_os=mingw32 ;; mingw32ce) basic_machine=arm-unknown basic_os=mingw32ce ;; monitor) basic_machine=m68k-rom68k basic_os=coff ;; morphos) basic_machine=powerpc-unknown basic_os=morphos ;; moxiebox) basic_machine=moxie-unknown basic_os=moxiebox ;; msdos) basic_machine=i386-pc basic_os=msdos ;; msys) basic_machine=i686-pc basic_os=msys ;; mvs) basic_machine=i370-ibm basic_os=mvs ;; nacl) basic_machine=le32-unknown basic_os=nacl ;; ncr3000) basic_machine=i486-ncr basic_os=sysv4 ;; netbsd386) basic_machine=i386-pc basic_os=netbsd ;; netwinder) basic_machine=armv4l-rebel basic_os=linux ;; news | news700 | news800 | news900) basic_machine=m68k-sony basic_os=newsos ;; news1000) basic_machine=m68030-sony basic_os=newsos ;; necv70) basic_machine=v70-nec basic_os=sysv ;; nh3000) basic_machine=m68k-harris basic_os=cxux ;; nh[45]000) basic_machine=m88k-harris basic_os=cxux ;; nindy960) basic_machine=i960-intel basic_os=nindy ;; mon960) basic_machine=i960-intel basic_os=mon960 ;; nonstopux) basic_machine=mips-compaq basic_os=nonstopux ;; os400) basic_machine=powerpc-ibm basic_os=os400 ;; OSE68000 | ose68000) basic_machine=m68000-ericsson basic_os=ose ;; os68k) basic_machine=m68k-none basic_os=os68k ;; paragon) basic_machine=i860-intel basic_os=osf ;; parisc) basic_machine=hppa-unknown basic_os=linux ;; psp) basic_machine=mipsallegrexel-sony basic_os=psp ;; pw32) basic_machine=i586-unknown basic_os=pw32 ;; rdos | rdos64) basic_machine=x86_64-pc basic_os=rdos ;; rdos32) basic_machine=i386-pc basic_os=rdos ;; rom68k) basic_machine=m68k-rom68k basic_os=coff ;; sa29200) basic_machine=a29k-amd basic_os=udi ;; sei) basic_machine=mips-sei basic_os=seiux ;; sequent) basic_machine=i386-sequent basic_os= ;; sps7) basic_machine=m68k-bull basic_os=sysv2 ;; st2000) basic_machine=m68k-tandem basic_os= ;; stratus) basic_machine=i860-stratus basic_os=sysv4 ;; sun2) basic_machine=m68000-sun basic_os= ;; sun2os3) basic_machine=m68000-sun basic_os=sunos3 ;; sun2os4) basic_machine=m68000-sun basic_os=sunos4 ;; sun3) basic_machine=m68k-sun basic_os= ;; sun3os3) basic_machine=m68k-sun basic_os=sunos3 ;; sun3os4) basic_machine=m68k-sun basic_os=sunos4 ;; sun4) basic_machine=sparc-sun basic_os= ;; sun4os3) basic_machine=sparc-sun basic_os=sunos3 ;; sun4os4) basic_machine=sparc-sun basic_os=sunos4 ;; sun4sol2) basic_machine=sparc-sun basic_os=solaris2 ;; sun386 | sun386i | roadrunner) basic_machine=i386-sun basic_os= ;; sv1) basic_machine=sv1-cray basic_os=unicos ;; symmetry) basic_machine=i386-sequent basic_os=dynix ;; t3e) basic_machine=alphaev5-cray basic_os=unicos ;; t90) basic_machine=t90-cray basic_os=unicos ;; toad1) basic_machine=pdp10-xkl basic_os=tops20 ;; tpf) basic_machine=s390x-ibm basic_os=tpf ;; udi29k) basic_machine=a29k-amd basic_os=udi ;; ultra3) basic_machine=a29k-nyu basic_os=sym1 ;; v810 | necv810) basic_machine=v810-nec basic_os=none ;; vaxv) basic_machine=vax-dec basic_os=sysv ;; vms) basic_machine=vax-dec basic_os=vms ;; vsta) basic_machine=i386-pc basic_os=vsta ;; vxworks960) basic_machine=i960-wrs basic_os=vxworks ;; vxworks68) basic_machine=m68k-wrs basic_os=vxworks ;; vxworks29k) basic_machine=a29k-wrs basic_os=vxworks ;; xbox) basic_machine=i686-pc basic_os=mingw32 ;; ymp) basic_machine=ymp-cray basic_os=unicos ;; *) basic_machine=$1 basic_os= ;; esac ;; esac # Decode 1-component or ad-hoc basic machines case $basic_machine in # Here we handle the default manufacturer of certain CPU types. It is in # some cases the only manufacturer, in others, it is the most popular. w89k) cpu=hppa1.1 vendor=winbond ;; op50n) cpu=hppa1.1 vendor=oki ;; op60c) cpu=hppa1.1 vendor=oki ;; ibm*) cpu=i370 vendor=ibm ;; orion105) cpu=clipper vendor=highlevel ;; mac | mpw | mac-mpw) cpu=m68k vendor=apple ;; pmac | pmac-mpw) cpu=powerpc vendor=apple ;; # Recognize the various machine names and aliases which stand # for a CPU type and a company and sometimes even an OS. 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) cpu=m68000 vendor=att ;; 3b*) cpu=we32k vendor=att ;; bluegene*) cpu=powerpc vendor=ibm basic_os=cnk ;; decsystem10* | dec10*) cpu=pdp10 vendor=dec basic_os=tops10 ;; decsystem20* | dec20*) cpu=pdp10 vendor=dec basic_os=tops20 ;; delta | 3300 | delta-motorola | 3300-motorola | motorola-delta | motorola-3300) cpu=m68k vendor=motorola ;; # This used to be dpx2*, but that gets the RS6000-based # DPX/20 and the x86-based DPX/2-100 wrong. See # https://oldskool.silicium.org/stations/bull_dpx20.htm # https://www.feb-patrimoine.com/english/bull_dpx2.htm # https://www.feb-patrimoine.com/english/unix_and_bull.htm dpx2 | dpx2[23]00 | dpx2[23]xx) cpu=m68k vendor=bull ;; dpx2100 | dpx21xx) cpu=i386 vendor=bull ;; dpx20) cpu=rs6000 vendor=bull ;; encore | umax | mmax) cpu=ns32k vendor=encore ;; elxsi) cpu=elxsi vendor=elxsi basic_os=${basic_os:-bsd} ;; fx2800) cpu=i860 vendor=alliant ;; genix) cpu=ns32k vendor=ns ;; h3050r* | hiux*) cpu=hppa1.1 vendor=hitachi basic_os=hiuxwe2 ;; hp3k9[0-9][0-9] | hp9[0-9][0-9]) cpu=hppa1.0 vendor=hp ;; hp9k2[0-9][0-9] | hp9k31[0-9]) cpu=m68000 vendor=hp ;; hp9k3[2-9][0-9]) cpu=m68k vendor=hp ;; hp9k6[0-9][0-9] | hp6[0-9][0-9]) cpu=hppa1.0 vendor=hp ;; hp9k7[0-79][0-9] | hp7[0-79][0-9]) cpu=hppa1.1 vendor=hp ;; hp9k78[0-9] | hp78[0-9]) # FIXME: really hppa2.0-hp cpu=hppa1.1 vendor=hp ;; hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) # FIXME: really hppa2.0-hp cpu=hppa1.1 vendor=hp ;; hp9k8[0-9][13679] | hp8[0-9][13679]) cpu=hppa1.1 vendor=hp ;; hp9k8[0-9][0-9] | hp8[0-9][0-9]) cpu=hppa1.0 vendor=hp ;; i*86v32) cpu=`echo "$1" | sed -e 's/86.*/86/'` vendor=pc basic_os=sysv32 ;; i*86v4*) cpu=`echo "$1" | sed -e 's/86.*/86/'` vendor=pc basic_os=sysv4 ;; i*86v) cpu=`echo "$1" | sed -e 's/86.*/86/'` vendor=pc basic_os=sysv ;; i*86sol2) cpu=`echo "$1" | sed -e 's/86.*/86/'` vendor=pc basic_os=solaris2 ;; j90 | j90-cray) cpu=j90 vendor=cray basic_os=${basic_os:-unicos} ;; iris | iris4d) cpu=mips vendor=sgi case $basic_os in irix*) ;; *) basic_os=irix4 ;; esac ;; miniframe) cpu=m68000 vendor=convergent ;; *mint | mint[0-9]* | *MiNT | *MiNT[0-9]*) cpu=m68k vendor=atari basic_os=mint ;; news-3600 | risc-news) cpu=mips vendor=sony basic_os=newsos ;; next | m*-next) cpu=m68k vendor=next ;; np1) cpu=np1 vendor=gould ;; op50n-* | op60c-*) cpu=hppa1.1 vendor=oki basic_os=proelf ;; pa-hitachi) cpu=hppa1.1 vendor=hitachi basic_os=hiuxwe2 ;; pbd) cpu=sparc vendor=tti ;; pbb) cpu=m68k vendor=tti ;; pc532) cpu=ns32k vendor=pc532 ;; pn) cpu=pn vendor=gould ;; power) cpu=power vendor=ibm ;; ps2) cpu=i386 vendor=ibm ;; rm[46]00) cpu=mips vendor=siemens ;; rtpc | rtpc-*) cpu=romp vendor=ibm ;; sde) cpu=mipsisa32 vendor=sde basic_os=${basic_os:-elf} ;; simso-wrs) cpu=sparclite vendor=wrs basic_os=vxworks ;; tower | tower-32) cpu=m68k vendor=ncr ;; vpp*|vx|vx-*) cpu=f301 vendor=fujitsu ;; w65) cpu=w65 vendor=wdc ;; w89k-*) cpu=hppa1.1 vendor=winbond basic_os=proelf ;; none) cpu=none vendor=none ;; leon|leon[3-9]) cpu=sparc vendor=$basic_machine ;; leon-*|leon[3-9]-*) cpu=sparc vendor=`echo "$basic_machine" | sed 's/-.*//'` ;; *-*) saved_IFS=$IFS IFS="-" read cpu vendor <&2 exit 1 ;; esac ;; esac # Here we canonicalize certain aliases for manufacturers. case $vendor in digital*) vendor=dec ;; commodore*) vendor=cbm ;; *) ;; esac # Decode manufacturer-specific aliases for certain operating systems. if test x"$basic_os" != x then # First recognize some ad-hoc cases, or perhaps split kernel-os, or else just # set os. obj= case $basic_os in gnu/linux*) kernel=linux os=`echo "$basic_os" | sed -e 's|gnu/linux|gnu|'` ;; os2-emx) kernel=os2 os=`echo "$basic_os" | sed -e 's|os2-emx|emx|'` ;; nto-qnx*) kernel=nto os=`echo "$basic_os" | sed -e 's|nto-qnx|qnx|'` ;; *-*) saved_IFS=$IFS IFS="-" read kernel os <&2 fi ;; *) echo "Invalid configuration '$1': OS '$os' not recognized" 1>&2 exit 1 ;; esac case $obj in aout* | coff* | elf* | pe*) ;; '') # empty is fine ;; *) echo "Invalid configuration '$1': Machine code format '$obj' not recognized" 1>&2 exit 1 ;; esac # Here we handle the constraint that a (synthetic) cpu and os are # valid only in combination with each other and nowhere else. case $cpu-$os in # The "javascript-unknown-ghcjs" triple is used by GHC; we # accept it here in order to tolerate that, but reject any # variations. javascript-ghcjs) ;; javascript-* | *-ghcjs) echo "Invalid configuration '$1': cpu '$cpu' is not valid with os '$os$obj'" 1>&2 exit 1 ;; esac # As a final step for OS-related things, validate the OS-kernel combination # (given a valid OS), if there is a kernel. case $kernel-$os-$obj in linux-gnu*- | linux-android*- | linux-dietlibc*- | linux-llvm*- \ | linux-mlibc*- | linux-musl*- | linux-newlib*- \ | linux-relibc*- | linux-uclibc*- | linux-ohos*- ) ;; uclinux-uclibc*- | uclinux-gnu*- ) ;; ironclad-mlibc*-) ;; managarm-mlibc*- | managarm-kernel*- ) ;; windows*-msvc*-) ;; -dietlibc*- | -llvm*- | -mlibc*- | -musl*- | -newlib*- | -relibc*- \ | -uclibc*- ) # These are just libc implementations, not actual OSes, and thus # require a kernel. echo "Invalid configuration '$1': libc '$os' needs explicit kernel." 1>&2 exit 1 ;; -kernel*- ) echo "Invalid configuration '$1': '$os' needs explicit kernel." 1>&2 exit 1 ;; *-kernel*- ) echo "Invalid configuration '$1': '$kernel' does not support '$os'." 1>&2 exit 1 ;; *-msvc*- ) echo "Invalid configuration '$1': '$os' needs 'windows'." 1>&2 exit 1 ;; kfreebsd*-gnu*- | knetbsd*-gnu*- | netbsd*-gnu*- | kopensolaris*-gnu*-) ;; vxworks-simlinux- | vxworks-simwindows- | vxworks-spe-) ;; nto-qnx*-) ;; os2-emx-) ;; rtmk-nova-) ;; *-eabi*- | *-gnueabi*-) ;; ios*-simulator- | tvos*-simulator- | watchos*-simulator- ) ;; none--*) # None (no kernel, i.e. freestanding / bare metal), # can be paired with an machine code file format ;; -*-) # Blank kernel with real OS is always fine. ;; --*) # Blank kernel and OS with real machine code file format is always fine. ;; *-*-*) echo "Invalid configuration '$1': Kernel '$kernel' not known to work with OS '$os'." 1>&2 exit 1 ;; esac # Here we handle the case where we know the os, and the CPU type, but not the # manufacturer. We pick the logical manufacturer. case $vendor in unknown) case $cpu-$os in *-riscix*) vendor=acorn ;; *-sunos* | *-solaris*) vendor=sun ;; *-cnk* | *-aix*) vendor=ibm ;; *-beos*) vendor=be ;; *-hpux*) vendor=hp ;; *-mpeix*) vendor=hp ;; *-hiux*) vendor=hitachi ;; *-unos*) vendor=crds ;; *-dgux*) vendor=dg ;; *-luna*) vendor=omron ;; *-genix*) vendor=ns ;; *-clix*) vendor=intergraph ;; *-mvs* | *-opened*) vendor=ibm ;; *-os400*) vendor=ibm ;; s390-* | s390x-*) vendor=ibm ;; *-ptx*) vendor=sequent ;; *-tpf*) vendor=ibm ;; *-vxsim* | *-vxworks* | *-windiss*) vendor=wrs ;; *-aux*) vendor=apple ;; *-hms*) vendor=hitachi ;; *-mpw* | *-macos*) vendor=apple ;; *-*mint | *-mint[0-9]* | *-*MiNT | *-MiNT[0-9]*) vendor=atari ;; *-vos*) vendor=stratus ;; esac ;; esac echo "$cpu-$vendor${kernel:+-$kernel}${os:+-$os}${obj:+-$obj}" exit # Local variables: # eval: (add-hook 'before-save-hook 'time-stamp nil t) # time-stamp-start: "timestamp='" # time-stamp-format: "%Y-%02m-%02d" # time-stamp-end: "'" # End: