diff --git a/Target.cc b/Target.cc index 121dbc00a..044a525ce 100644 --- a/Target.cc +++ b/Target.cc @@ -116,6 +116,7 @@ void Target::Initialize() { FPR = NULL; osscan_flag = OS_NOTPERF; weird_responses = flags = 0; + traceroute_probespec.type = PS_NONE; memset(&to, 0, sizeof(to)); memset(&targetsock, 0, sizeof(targetsock)); memset(&sourcesock, 0, sizeof(sourcesock)); @@ -228,7 +229,7 @@ void Target::setTargetSockAddr(struct sockaddr_storage *ss, size_t ss_len) { } // Returns IPv4 host address or {0} if unavailable. -struct in_addr Target::v4host() { +struct in_addr Target::v4host() const { const struct in_addr *addy = v4hostip(); struct in_addr in; if (addy) return *addy; diff --git a/Target.h b/Target.h index 086b99376..dafca338c 100644 --- a/Target.h +++ b/Target.h @@ -106,6 +106,8 @@ #include "tcpip.h" #include "scan_engine.h" +#include + #ifndef INET6_ADDRSTRLEN #define INET6_ADDRSTRLEN 46 #endif @@ -131,6 +133,22 @@ struct host_timeout_nfo { time_t host_start, host_end; /* The absolute start and end for this host */ }; +struct TracerouteHop { + struct sockaddr_storage tag; + bool timedout; + std::string name; + struct sockaddr_storage addr; + int ttl; + float rtt; /* In milliseconds. */ + + int display_name(char *buf, size_t len) { + if (name.empty()) + return Snprintf(buf, len, "%s", inet_ntop_ez(&addr, sizeof(addr))); + else + return Snprintf(buf, len, "%s (%s)", name.c_str(), inet_ntop_ez(&addr, sizeof(addr))); + } +}; + class Target { public: /* For now ... a lot of the data members should be made private */ Target(); @@ -148,7 +166,7 @@ class Target { to sockaddr_storage */ void setTargetSockAddr(struct sockaddr_storage *ss, size_t ss_len); // Returns IPv4 target host address or {0} if unavailable. - struct in_addr v4host(); + struct in_addr v4host() const; const struct in_addr *v4hostip() const; /* The source address used to reach the target */ int SourceSockAddr(struct sockaddr_storage *ss, size_t *ss_len); @@ -158,7 +176,7 @@ class Target { struct in_addr v4source() const; const struct in_addr *v4sourceip() const; /* The IPv4 or IPv6 literal string for the target host */ - const char *targetipstr() { return targetipstring; } + const char *targetipstr() const { return targetipstring; } /* Give the name from the last setHostName() call, which should be the name obtained from reverse-resolution (PTR query) of the IP (v4 or v6). If the name has not been set, or was set to NULL, an empty @@ -262,6 +280,9 @@ class Target { char *hostname; // Null if unable to resolve or unset char * targetname; // The name of the target host given on the commmand line if it is a named host + struct probespec traceroute_probespec; + std::list traceroute_hops; + #ifndef NOLUA ScriptResults scriptResults; #endif diff --git a/global_structures.h b/global_structures.h index e0d831c0a..6a2648832 100644 --- a/global_structures.h +++ b/global_structures.h @@ -236,6 +236,6 @@ struct scan_lists { int prot_count; }; -typedef enum { STYPE_UNKNOWN, HOST_DISCOVERY, ACK_SCAN, SYN_SCAN, FIN_SCAN, XMAS_SCAN, UDP_SCAN, CONNECT_SCAN, NULL_SCAN, WINDOW_SCAN, SCTP_INIT_SCAN, SCTP_COOKIE_ECHO_SCAN, RPC_SCAN, MAIMON_SCAN, IPPROT_SCAN, PING_SCAN, PING_SCAN_ARP, IDLE_SCAN, BOUNCE_SCAN, SERVICE_SCAN, OS_SCAN, SCRIPT_SCAN, TRACEROUTE, REF_TRACEROUTE}stype; +typedef enum { STYPE_UNKNOWN, HOST_DISCOVERY, ACK_SCAN, SYN_SCAN, FIN_SCAN, XMAS_SCAN, UDP_SCAN, CONNECT_SCAN, NULL_SCAN, WINDOW_SCAN, SCTP_INIT_SCAN, SCTP_COOKIE_ECHO_SCAN, RPC_SCAN, MAIMON_SCAN, IPPROT_SCAN, PING_SCAN, PING_SCAN_ARP, IDLE_SCAN, BOUNCE_SCAN, SERVICE_SCAN, OS_SCAN, SCRIPT_SCAN, TRACEROUTE}stype; #endif /*GLOBAL_STRUCTURES_H */ diff --git a/nmap.cc b/nmap.cc index 2c6cef558..95e855818 100644 --- a/nmap.cc +++ b/nmap.cc @@ -569,7 +569,6 @@ int nmap_main(int argc, char *argv[]) { char *endptr = NULL; struct scan_lists ports = { 0 }; TargetGroup *exclude_group = NULL; - Traceroute *troute = NULL; char myname[MAXHOSTNAMELEN + 1]; #if (defined(IN_ADDR_DEEPSTRUCT) || defined( SOLARIS)) /* Note that struct in_addr in solaris is 3 levels deep just to store an @@ -1811,9 +1810,7 @@ int nmap_main(int argc, char *argv[]) { if(o.traceroute && o.noportscan) { /* Assume that all targets in a group use the same device */ vector::iterator it; - troute = new Traceroute(Targets[0]->deviceName(), Targets[0]->ifType(), &ports); - troute->trace(Targets); - troute->resolveHops(); + traceroute(Targets); /* print ping traceroutes, making sure the reference * trace is first */ @@ -1823,7 +1820,7 @@ int nmap_main(int argc, char *argv[]) { log_write(LOG_XML, ""); write_host_status(currenths, o.resolve_all); printmacinfo(currenths); - troute->outputTarget(currenths); + printtraceroute(currenths); log_write(LOG_XML, "\n"); log_write(LOG_PLAIN,"\n"); } @@ -1834,8 +1831,6 @@ int nmap_main(int argc, char *argv[]) { } o.numhosts_scanning = 0; log_flush_all(); - delete troute; - troute = NULL; continue; } @@ -1906,11 +1901,8 @@ int nmap_main(int argc, char *argv[]) { if (o.osscan == OS_SCAN_DEFAULT) os_scan2(Targets); - if(o.traceroute) { - troute = new Traceroute(Targets[0]->deviceName(), Targets[0]->ifType(), &ports); - troute->trace(Targets); - troute->resolveHops(); - } + if(o.traceroute) + traceroute(Targets); if (o.servicescan || o.rpcscan) { /* This scantype must be after any TCP or UDP scans since it @@ -1949,8 +1941,8 @@ int nmap_main(int argc, char *argv[]) { #endif } - if(troute) - troute->outputTarget(currenths); + if(o.traceroute) + printtraceroute(currenths); if (o.debugging) log_write(LOG_STDOUT, "Final times for host: srtt: %d rttvar: %d to: %d\n", @@ -1973,9 +1965,6 @@ int nmap_main(int argc, char *argv[]) { o.numhosts_scanning = 0; } while(!o.max_ips_to_scan || o.max_ips_to_scan > o.numhosts_scanned); - if(troute) - delete troute; - delete hstate; if (exclude_group) delete[] exclude_group; @@ -2027,6 +2016,7 @@ void nmap_free_mem() { close_nse(); free(o.scriptargs); #endif + traceroute_hop_cache_clear(); } /* Reads in a (normal or machine format) Nmap log file and gathers enough @@ -2646,7 +2636,6 @@ const char *scantype2str(stype scantype) { case OS_SCAN: return "OS Scan"; break; case SCRIPT_SCAN: return "Script Scan"; break; case TRACEROUTE: return "Traceroute" ; break; - case REF_TRACEROUTE: return "Reference Traceroute"; break; default: assert(0); break; } diff --git a/nmap_dns.h b/nmap_dns.h index 6d5f8f9aa..71310f10f 100644 --- a/nmap_dns.h +++ b/nmap_dns.h @@ -88,6 +88,8 @@ class Target; +#include "nbase.h" + #include #include diff --git a/output.cc b/output.cc index 942d9f522..6b4b59542 100644 --- a/output.cc +++ b/output.cc @@ -1897,6 +1897,162 @@ void printhostscriptresults(Target *currenths) { } #endif +/* Print a table with traceroute hops. */ +static void printtraceroute_normal(Target *currenths) { + static const int HOP_COL = 0, RTT_COL = 1, HOST_COL = 2; + NmapOutputTable Tbl(currenths->traceroute_hops.size() + 1, 3); + struct probespec probe; + list ::iterator it; + int row; + + /* No trace, must be localhost. */ + if (currenths->traceroute_hops.size() == 0) + return; + + /* Print header. */ + log_write(LOG_PLAIN, "\n"); + probe = currenths->traceroute_probespec; + if (probe.type == PS_TCP) { + log_write(LOG_PLAIN, "TRACEROUTE (using port %d/%s)\n", + probe.pd.tcp.dport, proto2ascii(probe.proto)); + } else if (probe.type == PS_UDP) { + log_write(LOG_PLAIN, "TRACEROUTE (using port %d/%s)\n", + probe.pd.udp.dport, proto2ascii(probe.proto)); + } else if (probe.type == PS_SCTP) { + log_write(LOG_PLAIN, "TRACEROUTE (using port %d/%s)\n", + probe.pd.sctp.dport, proto2ascii(probe.proto)); + } else if (probe.type == PS_ICMP || probe.type == PS_PROTO) { + struct protoent *proto = nmap_getprotbynum(htons(probe.proto)); + log_write(LOG_PLAIN, "TRACEROUTE (using proto %d/%s)\n", + probe.proto, proto ? proto->p_name : "unknown"); + } + + row = 0; + Tbl.addItem(row, HOP_COL, false, "HOP"); + Tbl.addItem(row, RTT_COL, false, "RTT"); + Tbl.addItem(row, HOST_COL, false, "ADDRESS"); + row++; + + it = currenths->traceroute_hops.begin(); + + if (!o.debugging) { + /* Consolidate shared hops. */ + TracerouteHop *shared_hop = NULL; + struct sockaddr_storage addr; + size_t sslen; + + sslen = sizeof(addr); + currenths->TargetSockAddr(&addr, &sslen); + while (it != currenths->traceroute_hops.end() + && sockaddr_storage_cmp(&it->tag, &addr) != 0) { + shared_hop = &*it; + it++; + } + + if (shared_hop != NULL) { + Tbl.addItem(row, HOP_COL, false, "-"); + if (shared_hop->ttl == 1) { + Tbl.addItemFormatted(row, RTT_COL, true, "Hop 1 is the same as for %s", + inet_ntop_ez(&shared_hop->tag, sizeof(shared_hop->tag))); + } else if (shared_hop->ttl > 1) { + Tbl.addItemFormatted(row, RTT_COL, true, + "Hops 1-%d are the same as for %s", shared_hop->ttl, + inet_ntop_ez(&shared_hop->tag, sizeof(shared_hop->tag))); + } + row++; + } + } + + while (it != currenths->traceroute_hops.end()) { + Tbl.addItemFormatted(row, HOP_COL, false, "%d", it->ttl); + if (it->timedout) { + if (o.debugging) { + Tbl.addItem(row, RTT_COL, false, "..."); + it++; + } else { + /* The beginning and end of timeout consolidation. */ + int begin_ttl, end_ttl; + begin_ttl = end_ttl = it->ttl; + for (; it != currenths->traceroute_hops.end() && it->timedout; it++) + end_ttl = it->ttl; + if (begin_ttl == end_ttl) + Tbl.addItem(row, RTT_COL, false, "..."); + else + Tbl.addItemFormatted(row, RTT_COL, false, "... %d", end_ttl); + } + row++; + } else { + char namebuf[256]; + /* Normal hop output. */ + + it->display_name(namebuf, sizeof(namebuf)); + Tbl.addItemFormatted(row, RTT_COL, false, "%.2f ms", it->rtt); + Tbl.addItemFormatted(row, HOST_COL, false, "%s", namebuf); + row++; + it++; + } + } + + log_write(LOG_PLAIN, "%s", Tbl.printableTable(NULL)); + + log_flush(LOG_PLAIN); +} + +static void printtraceroute_xml(Target *currenths) { + struct probespec probe; + list ::iterator it; + + /* No trace, must be localhost. */ + if (currenths->traceroute_hops.size() == 0) + return; + + /* XML traceroute header */ + log_write(LOG_XML, "traceroute_probespec; + if (probe.type == PS_TCP) { + log_write(LOG_XML, "port=\"%d\" ", probe.pd.tcp.dport); + } else if (probe.type == PS_UDP) { + log_write(LOG_XML, "port=\"%d\" ", probe.pd.udp.dport); + } else if (probe.type == PS_SCTP) { + log_write(LOG_XML, "port=\"%d\" ", probe.pd.sctp.dport); + } else if (probe.type == PS_ICMP || probe.type == PS_PROTO) { + struct protoent *proto = nmap_getprotbynum(htons(probe.proto)); + if (proto == NULL) + log_write(LOG_XML, "proto=\"%d\"", probe.proto); + else + log_write(LOG_XML, "proto=\"%s\"", proto->p_name); + } + log_write(LOG_XML, ">\n"); + + for (it = currenths->traceroute_hops.begin(); + it != currenths->traceroute_hops.end(); + it++) { + if (it->timedout) + continue; + log_write(LOG_XML, "ttl, it->rtt, inet_ntop_ez(&it->addr, sizeof(it->addr))); + if (!it->name.empty()) + log_write(LOG_XML, " host=\"%s\"", it->name.c_str()); + log_write(LOG_XML, "/>\n"); + } + + /* + if (tg->getState() == G_DEAD_TTL) + log_write(LOG_XML, "\n"); + else if (!tg->gotReply || (tp && (tp->ipreplysrc.s_addr != tg->ipdst))) + log_write(LOG_XML, "\n", inet_ntoa(tp->ipdst)); + */ + + /* traceroute XML footer */ + log_write(LOG_XML, "\n"); + log_flush(LOG_XML); +} + +void printtraceroute(Target *currenths) { + printtraceroute_normal(currenths); + printtraceroute_xml(currenths); +} + /* Prints a status message while the program is running */ void printStatusMessage() { // Pre-computations diff --git a/output.h b/output.h index b1142b8c7..352f62c63 100644 --- a/output.h +++ b/output.h @@ -194,6 +194,9 @@ void printserviceinfooutput(Target *currenths); void printhostscriptresults(Target *currenths); +/* Print a table with traceroute hops. */ +void printtraceroute(Target *currenths); + /* Print a detailed list of Nmap interfaces and routes to normal/skiddy/stdout output */ int print_iflist(void); diff --git a/tcpip.cc b/tcpip.cc index a2ff7aeb4..5c4e9f4dc 100644 --- a/tcpip.cc +++ b/tcpip.cc @@ -1149,7 +1149,7 @@ void eth_close_cached() { // fill ip header. no error check. // This function is also changing what's needed from host to network order. -static inline int fill_ip_raw(struct ip *ip, int packetlen, u8 *ipopt, +static inline int fill_ip_raw(struct ip *ip, int packetlen, const u8 *ipopt, int ipoptlen, int ip_tos, int ip_id, int ip_off, int ip_ttl, int ip_p, const struct in_addr *ip_src, @@ -1221,9 +1221,9 @@ int send_tcp_raw_decoys(int sd, struct eth_nfo *eth, packetlen, which must be a valid int pointer. */ u8 *build_tcp_raw(const struct in_addr *source, const struct in_addr *victim, int ttl, u16 ipid, u8 tos, - bool df, u8 *ipopt, int ipoptlen, u16 sport, u16 dport, + bool df, const u8 *ipopt, int ipoptlen, u16 sport, u16 dport, u32 seq, u32 ack, u8 reserved, u8 flags, u16 window, - u16 urp, u8 *tcpopt, int tcpoptlen, char *data, + u16 urp, const u8 *tcpopt, int tcpoptlen, char *data, u16 datalen, u32 *outpacketlen) { int packetlen = sizeof(struct ip) + ipoptlen + sizeof(struct tcp_hdr) + tcpoptlen + datalen; u8 *packet = (u8 *) safe_malloc(packetlen); @@ -1792,11 +1792,11 @@ int send_udp_raw_decoys(int sd, struct eth_nfo *eth, actually sent by this function. Caller must delete the buffer when finished with the packet. The packet length is returned in packetlen, which must be a valid int pointer. */ -u8 *build_udp_raw(struct in_addr *source, const struct in_addr *victim, +u8 *build_udp_raw(const struct in_addr *source, const struct in_addr *victim, int ttl, u16 ipid, u8 tos, bool df, u8 *ipopt, int ipoptlen, u16 sport, u16 dport, - char *data, u16 datalen, u32 *outpacketlen) { + const char *data, u16 datalen, u32 *outpacketlen) { int packetlen = sizeof(struct ip) + ipoptlen + sizeof(struct udp_hdr) + datalen; u8 *packet = (u8 *) safe_malloc(packetlen); diff --git a/tcpip.h b/tcpip.h index 5c11bd0e2..5db065bc6 100644 --- a/tcpip.h +++ b/tcpip.h @@ -505,10 +505,10 @@ int send_ip_raw( int sd, struct eth_nfo *eth, packetlen, which must be a valid int pointer. */ u8 *build_tcp_raw(const struct in_addr *source, const struct in_addr *victim, int ttl, u16 ipid, u8 tos, bool df, - u8* ipopt, int ipoptlen, + const u8* ipopt, int ipoptlen, u16 sport, u16 dport, u32 seq, u32 ack, u8 reserved, u8 flags, u16 window, u16 urp, - u8 *options, int optlen, + const u8 *options, int optlen, char *data, u16 datalen, u32 *packetlen); @@ -518,11 +518,11 @@ u8 *build_tcp_raw(const struct in_addr *source, const struct in_addr *victim, actually sent by this function. Caller must delete the buffer when finished with the packet. The packet length is returned in packetlen, which must be a valid int pointer. */ -u8 *build_udp_raw(struct in_addr *source, const struct in_addr *victim, +u8 *build_udp_raw(const struct in_addr *source, const struct in_addr *victim, int ttl, u16 ipid, u8 tos, bool df, u8* ipopt, int ipoptlen, u16 sport, u16 dport, - char *data, u16 datalen, + const char *data, u16 datalen, u32 *packetlen); /* Builds an SCTP packet (including an IP header) by packing the fields diff --git a/traceroute.cc b/traceroute.cc index 8740777fb..90d5d3147 100644 --- a/traceroute.cc +++ b/traceroute.cc @@ -86,1406 +86,1324 @@ * * ***************************************************************************/ - /* - * Written by Eddie Bell as part of SoC2006 - * A multi-protocol parallel traceroute implementation for nmap. - * - * For more information on how traceroutes work: - * http://en.wikipedia.org/wiki/Traceroute - * - * Traceroute takes in a list of scanned targets and determines a valid - * responsive port to trace to based on the scan results, scan protocol and - * various pieces of protocol data. - * - * Nmap first sends a probe to the target port, from the reply traceroute is - * able to infer how many hops away the target is. Nmap starts the trace by - * sending a packet with a TTL equal to that of the hop distance guess. If it - * gets an ICMP_TTL_EXCEEDED message back it know the hop distance guess was - * under so nmap will continue sending probes with incremental TTLs until it - * receives a reply from the target host. - * - * Once a reply from the host is received nmap sets the TTL to one below the hop - * guess and continues to send probes with decremental TTLs until it reaches TTL - * 0. Then we have a complete trace to the target. If nmap does not get a hop - * distance probe reply, the trace TTL starts at one and is incremented until it - * hits the target host. - * - * Forwards/Backwards tracing example - * hop guess:20 - * send:20 --> ICMP_TTL_EXCEEDED - * send:21 --> ICMP_TTL_EXCEEDED - * send:22 --> Reply from host - * send:19 --> ICMP_TTL_EXCEEDED - * .... - * send:1 --> ICMP_TTL_EXCEEDED - * - * The forward/backwards tracing method seems a little convoluted at first but - * there is a reason for it. The first host traced in a Target group is - * designated as the reference trace. All other traces (once they have reached - * their destination host) are compared against the reference trace. If a match - * is found the trace is ended prematurely and the remaining hops are assumed to - * be the same as the reference trace. This normally only happens in the lower - * TTls, which rarely change. On average nmap sends 5 less packets per host. If - * nmap is tracing related hosts (EG. 1.2.3.0/24) it will send a lot less - * packets. Depending on the network topology it may only have to send a single - * packet to each host. - * - * Nmap's traceroute employs a dynamic timing model similar to nmap's scanning - * engine but a little more light weight. It keeps track of sent, received and - * dropped packet, then adjusts timing parameters accordingly. The parameters - * are; number of retransmissions, delay between each sent packet and the amount - * of time to wait for a reply. They are initially based on the timing level - * (-T0 to -T5). Traceroute also has to watch out for rate-limiting of ICMP TTL - * EXCEEDED messages, sometimes there is nothing we can do and just have to - * settle with a timedout hop. - * - * The output from each trace is consolidated to save space, XML logging and - * debug mode ignore consolidation. There are two type of consolidation time-out - * and reference trace. - * - * Timed out - * 23 ... 24 no response - * - * Reference trace - * Hops 1-10 are the same as for X.X.X.X - * - * Traceroute does not work with connect scans or idle scans and has trouble - * with ICMP_TSTAMP and ICMP_MASK scans because so many host filter them out. - * The quickest seems to be SYN scan. - * - * Bugs - * ---- - * o The code, currently, only works with ipv4. - * o Should send both UDP and TCP hop distance probes no matter what the - * scan protocol - */ +Traceroute for Nmap. This traceroute is faster than a traditional traceroute +because it sends several probes in parallel and detects shared traces. + +The algorithm works by sending probes with varying TTL values and waiting for +TTL_EXCEEDED messages. As intermediate hops are discovered, they are entered +into a global hop cache that is shared between targets and across host groups. +When a hop is discovered and is found to be already in the cache, the trace for +that target is linked into the cached trace and there is no need to try lower +TTLs. The process results in the building of a tree of Hop structures. + +The order in which probes are sent does not matter to the accuracy of the +algorithm but it does matter to the speed. The sooner a shared trace can be +detected, and the higher the TTL at which it is detected, the fewer probes need +to be sent. The ideal situation is to start sending probes with a TTL equal to +the true distance and count downward from there. In that case it may only be +necessary to send two probes per target: one at the distance of the target to +get a response, and one at distance - 1 to get a cache hit. When the distance +isn't known in advance, the algorithm arbitrarily starts at a TTL of 10 and +counts downward, then counts upward from 11 until it reaches the target. So a +typeical trace may look like + +TTL 10 -> TTL_EXCEEDED +TTL 9 -> TTL_EXCEEDED +TTL 8 -> TTL_EXCEEDED +TTL 7 -> cache hit +TTL 11 -> TTL_EXCEEDED +TTL 12 -> TTL_EXCEEDED +TTL 13 -> SYN/ACK, or whatever is the target's response to the probe + +The output for this host would then say "Hops 1-7 as the same as for ...". + +The detection of shared traces rests on the assumption that all paths going +through a router at a certain TTL will be identical up to and including the +router. This assumption is not always true. Even if two targets are each one hop +past router X at TTL 10, packets may follow different paths to each host (and +those paths may even change over time). This traceroute algorithm will be fooled +by such a situation, and will report that the paths are identical up to +router X. The only way to be sure is to do a complete trace for each target +individually. +*/ -#include "traceroute.h" -#include "NmapOps.h" -#include "NmapOutputTable.h" -#include "nmap_tty.h" #include "nmap_dns.h" -#include "osscan2.h" -#include "protocols.h" +#include "nmap_error.h" +#include "nmap_tty.h" +#include "payload.h" #include "timing.h" -#include "utils.h" -#include -#include -#include +#include "NmapOps.h" +#include "Target.h" + +#include + +#include +#include +#include +#include +#include -using namespace std; extern NmapOps o; -static void enforce_scan_delay (struct timeval *, int); -static char *hostStr(u32 ip); +/* The highest TTL we go up to if the target itself doesn't respond. */ +#define MAX_TTL 30 +#define MAX_OUTSTANDING_PROBES 10 +#define MAX_RESENDS 2 +/* In milliseconds. */ +#define PROBE_TIMEOUT 1000 +/* If the hop cache (including timed-out hops) is bigger than this after a + round, the hop is cleared and rebuilt from scratch. */ +#define MAX_HOP_CACHE_SIZE 1000 -/* Each target group has a single reference trace. All other traces are compared - * to it and if a match is found the trace is ended prematurely and the - * remaining hops are assumed to match the reference trace */ -unsigned long commonPath[MAX_TTL + 1]; +struct Hop; +class HostState; +class Probe; -Traceroute::Traceroute(const char *device_name, devtype type, const scan_lists * ports) { - fd = -1; - scanlists = ports; - ethsd = NULL; - hops = NULL; - pd = NULL; - total_size = 0; - memset(&ref_ipaddr, '\0', sizeof(struct in_addr)); - cp_flag = 0; +/* A global random token used to distinguish this traceroute's probes from + those of other traceroutes possibly running on the same machine. */ +static u16 global_id; +/* A global cache of known hops, indexed by TTL and address. */ +static std::map hop_cache; +/* A list of timedout hops, which are not kept in hop_cache, so we can delete + all hops on occasion. */ +static std::list timedout_hops; - if (type == devt_loopback) - return; +static struct timeval get_now(struct timeval *now = NULL); +static const char *ss_to_string(const struct sockaddr_storage *ss); - /* open various socks to send and read from on windows and unix */ - if ((o.sendpref & PACKET_SEND_ETH) && type == devt_ethernet) { - /* We'll send ethernet packets with dnet */ - ethsd = eth_open_cached(device_name); - if (ethsd == NULL) - fatal("dnet: Failed to open device %s", device_name); - } else { -#ifdef WIN32 - win32_warn_raw_sockets(device_name); -#endif - if ((fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) - pfatal("Traceroute: socket troubles"); - broadcast_socket(fd); -#ifndef WIN32 - sethdrinclude(fd); -#endif - } +/* An object of this class is a (TTL, address) pair that uniquely identifies a + hop. Hops in the hop_cache are indexed by this type. */ +struct HopIdent { + u8 ttl; + struct sockaddr_storage addr; - /* rely on each group using the same device */ - pd = my_pcap_open_live(device_name, 100, o.spoofsource ? 1 : 0, 2); + HopIdent(u8 ttl, const struct sockaddr_storage &addr) { + this->addr = addr; + this->ttl = ttl; + } - memset(commonPath, 0, sizeof(commonPath)); + bool operator<(const struct HopIdent &other) const { + if (ttl < other.ttl) + return true; + else if (ttl > other.ttl) + return false; + else + return sockaddr_storage_cmp(&addr, &other.addr) < 0; + } +}; + +struct Hop { + Hop *parent; + struct sockaddr_storage tag; + /* When addr.ss_family == 0, this hop represents a timeout. */ + struct sockaddr_storage addr; + u8 ttl; + float rtt; /* In milliseconds. */ + std::string hostname; + + Hop() { + this->parent = NULL; + this->addr.ss_family = 0; + this->ttl = 0; + this->rtt = -1.0; + this->tag.ss_family = 0; + } + + Hop(u8 ttl, const struct sockaddr_storage &addr, float rtt) { + this->parent = NULL; + this->addr = addr; + this->ttl = ttl; + this->rtt = rtt; + this->tag.ss_family = 0; + } +}; + +class HostState { +public: + enum counting_state { COUNTING_DOWN, COUNTING_UP }; + + Target *target; + u8 initial_ttl; + u8 current_ttl; + enum counting_state state; + /* If nonzero, the known hop distance to the target. */ + int reached_target; + struct probespec pspec; + std::list unanswered_probes; + std::list active_probes; + std::list pending_resends; + Hop *hops; + + HostState(Target *target); + ~HostState(); + bool has_more_probes() const; + bool is_finished() const; + bool send_next_probe(int rawsd, eth_t *ethsd); + void count_up(); + int cancel_probe(std::list::iterator it); + int cancel_probes_below(u8 ttl); + int cancel_probes_above(u8 ttl); + Hop *insert_hop(u8 ttl, const struct sockaddr_storage *addr, float rtt); + void link_to(Hop *hop); + double completion_fraction() const; + +private: + void child_parent_ttl(u8 ttl, Hop **child, Hop **parent); + static u8 distance_guess(const Target *target); + static struct probespec get_probe(const Target *target); +}; + +class Probe { +private: + /* This is incremented with each instantiated probe. */ + static u16 token_counter; + + int num_resends; + +public: + HostState *host; + struct probespec pspec; + u8 ttl; + /* The token is used to match up probe replies. */ + u16 token; + struct timeval sent_time; + + Probe(HostState *host, struct probespec pspec, u8 ttl); + void send(int rawsd, eth_t *ethsd, struct timeval *now = NULL); + void resend(int rawsd, eth_t *ethsd, struct timeval *now = NULL); + bool is_timedout(struct timeval *now = NULL) const; + bool may_resend() const; + virtual unsigned char *build_packet(const struct in_addr *source, + u32 *len) const = 0; + + static Probe *make(HostState *host, struct probespec pspec, u8 ttl); +}; +u16 Probe::token_counter = 0x0000; + +class TracerouteState { +public: + std::list active_hosts; + /* The next send time for enforcing scan delay. */ + struct timeval next_send_time; + + TracerouteState(std::vector &targets); + ~TracerouteState(); + + void send_new_probes(); + void read_replies(long timeout); + void cull_timeouts(); + void remove_finished_hosts(); + void resolve_hops(); + void transfer_hops(); + + double completion_fraction() const; + +private: + eth_t *ethsd; + int rawsd; + pcap_t *pd; + int num_active_probes; + + std::vector hosts; + std::list::iterator next_sending_host; + + void next_active_host(); + Probe *lookup_probe(const struct sockaddr_storage *target_addr, u16 token); + void set_host_hop(HostState *host, u8 ttl, + const struct sockaddr_storage *from_addr, float rtt); + void set_host_hop_timedout(HostState *host, u8 ttl); +}; + +static Hop *merge_hops(const struct sockaddr_storage *tag, Hop *a, Hop *b); +static Hop *hop_cache_lookup(u8 ttl, const struct sockaddr_storage *addr); +static void hop_cache_insert(Hop *hop); +static unsigned int hop_cache_size(); + +HostState::HostState(Target *target) { + this->target = target; + initial_ttl = current_ttl = HostState::distance_guess(target); + state = HostState::COUNTING_DOWN; + reached_target = 0; + pspec = HostState::get_probe(target); + hops = NULL; } -Traceroute::~Traceroute() { - map < u32, TraceGroup * >::iterator it = TraceGroups.begin(); - while ((--total_size) >= 0) - delete(hops[total_size]); - if (hops) - free(hops); - for (; it != TraceGroups.end(); ++it) - delete it->second; - if (ethsd) - ethsd = NULL; - if (fd != -1) - close(fd); - if (pd) - pcap_close(pd); +HostState::~HostState() { + /* active_probes and pending_resends are subsets of unanswered_probes, so we + delete the allocated probes in unanswered_probes only. */ + while (!unanswered_probes.empty()) { + delete *unanswered_probes.begin(); + unanswered_probes.pop_front(); + } + while (!active_probes.empty()) + active_probes.pop_front(); + while (!pending_resends.empty()) + pending_resends.pop_front(); } -/* get an open or closed port from the portlist. Traceroute requires a positive - * response, positive responses are generated by different port states depending - * on the type of scan */ -inline const probespec -Traceroute::getTraceProbe(Target *t) { - struct probespec probe; - - probe = t->pingprobe; - if (probe.type == PS_NONE) { - /* No responsive probe known? The user probably skipped both ping and - port scan. Guess ICMP echo as the most likely to get a response. */ - probe.type = PS_ICMP; - probe.proto = IPPROTO_ICMP; - probe.pd.icmp.type = ICMP_ECHO; - probe.pd.icmp.code = 0; - } else if (probe.type == PS_PROTO) { - /* If this is an IP protocol probe, fill in some fields for some common - protocols. We cheat and store them in the TCP-, UDP-, SCTP- and - ICMP-specific fields. Traceroute::sendProbe checks for them there. */ - if (probe.proto == IPPROTO_TCP) { - probe.pd.tcp.flags = TH_ACK; - probe.pd.tcp.dport = get_random_u16(); - } else if (probe.proto == IPPROTO_UDP) { - probe.pd.udp.dport = get_random_u16(); - } else if (probe.proto == IPPROTO_SCTP) { - probe.pd.sctp.dport = get_random_u16(); - } else if (probe.proto == IPPROTO_ICMP) { - probe.pd.icmp.type = ICMP_ECHO; - } - } - - return probe; +bool HostState::has_more_probes() const { + return !(state == HostState::COUNTING_UP + && (reached_target || current_ttl > MAX_TTL)); } -/* finite state machine that reads all incoming packets and attempts to match - * them with sent probes */ -inline bool -Traceroute::readTraceResponses() { - struct ip *ip = NULL; - struct ip *ip2 = NULL; - struct icmp *icmp = NULL; - struct icmp *icmp2 = NULL; - struct tcp_hdr *tcp = NULL; - struct udp_hdr *udp = NULL; - struct sctp_hdr *sctp = NULL; - struct link_header linkhdr; - unsigned int bytes; - struct timeval rcvdtime; - TraceProbe *tp = NULL; - TraceGroup *tg = NULL; - u16 sport; - u32 ipaddr; - - /* Got to look into readip_pcap's timeout value, perhaps make it dynamic */ - ip = (struct ip *) readip_pcap(pd, &bytes, 10000, &rcvdtime, &linkhdr, true); - - if (ip == NULL) - return finished(); - - switch (ip->ip_p) { - case IPPROTO_ICMP: - if ((unsigned) ip->ip_hl * 4 + 8 > bytes) - break; - icmp = (struct icmp *) ((char *) ip + 4 * ip->ip_hl); - ipaddr = ip->ip_src.s_addr; - sport = ntohs(icmp->icmp_id); - - /* Process ICMP replies that encapsulate our original probe */ - if (icmp->icmp_type == ICMP_UNREACH || icmp->icmp_type == ICMP_TIMEXCEED) { - if ((unsigned) ip->ip_hl * 4 + 28 > bytes) - break; - ip2 = (struct ip *) (((char *) ip) + 4 * ip->ip_hl + 8); - if (ip2->ip_p == IPPROTO_TCP) { - tcp = (struct tcp_hdr *) ((u8 *) ip2 + ip2->ip_hl * 4); - if (ntohs(ip2->ip_len) - (ip2->ip_hl * 4) < 2) - break; - sport = ntohs(tcp->th_sport); - } else if (ip2->ip_p == IPPROTO_UDP) { - udp = (struct udp_hdr *) ((u8 *) ip2 + ip2->ip_hl * 4); - if (ntohs(ip2->ip_len) - (ip2->ip_hl * 4) < 2) - break; - sport = ntohs(udp->uh_sport); - } else if (ip2->ip_p == IPPROTO_SCTP) { - sctp = (struct sctp_hdr *) ((u8 *) ip2 + ip2->ip_hl * 4); - if (ntohs(ip2->ip_len) - (ip2->ip_hl * 4) < 2) - break; - sport = ntohs(sctp->sh_sport); - } else if (ip2->ip_p == IPPROTO_ICMP) { - icmp2 = (struct icmp *) ((char *) ip2 + 4 * ip2->ip_hl); - if (ntohs(ip2->ip_len) - (ip2->ip_hl * 4) < 8) - break; - sport = ntohs(icmp2->icmp_id); - } else { - sport = ntohs(ip2->ip_id); - } - ipaddr = ip2->ip_dst.s_addr; - } - - if (TraceGroups.find(ipaddr) != TraceGroups.end()) - tg = TraceGroups[ipaddr]; - else - break; - - if (tg->TraceProbes.find(sport) != tg->TraceProbes.end()) - tp = tg->TraceProbes[sport]; - else - break; - - if (tp->ipreplysrc.s_addr) - break; - - if ((tg->probe.proto == IPPROTO_UDP && (ip2 && ip2->ip_p == IPPROTO_UDP)) || - (icmp->icmp_type == ICMP_UNREACH)) { - switch (icmp->icmp_code) { - /* reply from a closed port */ - case ICMP_UNREACH_PORT: - /* replies from a filtered port */ - case ICMP_UNREACH_HOST: - case ICMP_UNREACH_PROTO: - case ICMP_UNREACH_NET_PROHIB: - case ICMP_UNREACH_HOST_PROHIB: - case ICMP_UNREACH_FILTER_PROHIB: - if (tp->probeType() == PROBE_TTL) { - tg->setHopDistance(o.ttl - ip2->ip_ttl, 0); - tg->start_ttl = tg->ttl = tg->hopDistance; - } else { - tg->gotReply = true; - if (tg->start_ttl < tg->ttl) - tg->ttl = tg->start_ttl + 1; - } - } - } - /* icmp ping scan replies */ - else if (tg->probe.proto == IPPROTO_ICMP && (icmp->icmp_type == ICMP_ECHOREPLY || - icmp->icmp_type == ICMP_MASKREPLY || icmp->icmp_type == ICMP_TSTAMPREPLY)) { - if (tp->probeType() == PROBE_TTL) { - tg->setHopDistance(get_initial_ttl_guess(ip->ip_ttl), ip->ip_ttl); - tg->start_ttl = tg->ttl = tg->hopDistance; - } else { - tg->gotReply = true; - if (tg->start_ttl < tg->ttl) - tg->ttl = tg->start_ttl + 1; - } - } - - if (tp->timing.getState() == P_TIMEDOUT) - tp->timing.setState(P_OK); - else - tg->decRemaining(); - - tg->repliedPackets++; - tg->consecTimeouts = 0; - tp->timing.adjustTimeouts(&rcvdtime, tg->scanDelay); - tp->ipreplysrc.s_addr = ip->ip_src.s_addr; - - /* check to see if this hop is in the referece trace. If it is then we - * stop tracing this target and assume all subsequent hops match the - * common path */ - if (commonPath[tp->ttl] == tp->ipreplysrc.s_addr && - tp->ttl > 1 && tg->gotReply && tg->getState() != G_FINISH) { - tg->setState(G_FINISH); - tg->consolidation_start = tp->ttl+1; - cp_flag = 1; - break; - } else if (commonPath[tp->ttl] == 0) { - commonPath[tp->ttl] = tp->ipreplysrc.s_addr; - /* remember which host is the reference trace */ - if (!cp_flag) { - ref_ipaddr.s_addr = tg->ipdst; - cp_flag = 1; - } - } - break; - case IPPROTO_TCP: - tcp = (struct tcp_hdr *) ((char *) ip + 4 * ip->ip_hl); - - if (TraceGroups.find(ip->ip_src.s_addr) != TraceGroups.end()) - tg = TraceGroups[ip->ip_src.s_addr]; - else - break; - - if (tg->TraceProbes.find(ntohs(tcp->th_dport)) != tg->TraceProbes.end()) - tp = tg->TraceProbes[ntohs(tcp->th_dport)]; - else - break; - - /* already got the tcp packet for this group, could be a left over rst - * or syn-ack */ - if (tp->ipreplysrc.s_addr) - break; - - /* We have reached the destination host and the trace can stop for this - * target */ - if ((tcp->th_flags & TH_RST) == TH_RST - || (tcp->th_flags & (TH_SYN | TH_ACK)) == (TH_SYN | TH_ACK)) { - /* We might have gotten a late reply */ - if (tp->timing.getState() == P_TIMEDOUT) - tp->timing.setState(P_OK); - else - tg->decRemaining(); - - tp->timing.recvTime = rcvdtime; - tp->ipreplysrc = ip->ip_src; - tg->repliedPackets++; - /* The probe was the reply from a ttl guess */ - if (tp->probeType() == PROBE_TTL) { - tg->setHopDistance(get_initial_ttl_guess(ip->ip_ttl), ip->ip_ttl); - tg->start_ttl = tg->ttl = tg->hopDistance; - } else { - tg->gotReply = true; - if (tg->start_ttl < tg->ttl) - tg->ttl = tg->start_ttl + 1; - } - } - break; - case IPPROTO_UDP: - udp = (udp_hdr *) ((u8 *) ip + ip->ip_hl * 4); - - if (TraceGroups.find(ip->ip_src.s_addr) != TraceGroups.end()) - tg = TraceGroups[ip->ip_src.s_addr]; - else - break; - - if (tg->TraceProbes.find(ntohs(udp->uh_dport)) != tg->TraceProbes.end()) - tp = tg->TraceProbes[ntohs(udp->uh_dport)]; - else - break; - - if (tp->ipreplysrc.s_addr) - break; - - /* We might have gotten a late reply */ - if (tp->timing.getState() == P_TIMEDOUT) - tp->timing.setState(P_OK); - else - tg->decRemaining(); - - tp->timing.recvTime = rcvdtime; - tp->ipreplysrc.s_addr = ip->ip_src.s_addr; - tg->repliedPackets++; - - if (tp->probeType() == PROBE_TTL) { - tg->setHopDistance(get_initial_ttl_guess(ip->ip_ttl), ip->ip_ttl); - tg->setState(G_OK); - tg->start_ttl = tg->ttl = tg->hopDistance; - } else { - tg->gotReply = true; - if (tg->start_ttl < tg->ttl) - tg->ttl = tg->start_ttl + 1; - } - break; - case IPPROTO_SCTP: - sctp = (struct sctp_hdr *) ((char *) ip + 4 * ip->ip_hl); - - if (TraceGroups.find(ip->ip_src.s_addr) != TraceGroups.end()) - tg = TraceGroups[ip->ip_src.s_addr]; - else - break; - - if (tg->TraceProbes.find(ntohs(sctp->sh_dport)) != tg->TraceProbes.end()) - tp = tg->TraceProbes[ntohs(sctp->sh_dport)]; - else - break; - - /* already got the sctp packet for this group, could be a left over - * abort or init-ack */ - if (tp->ipreplysrc.s_addr) - break; - - /* We might have gotten a late reply */ - if (tp->timing.getState() == P_TIMEDOUT) - tp->timing.setState(P_OK); - else - tg->decRemaining(); - - tp->timing.recvTime = rcvdtime; - tp->ipreplysrc = ip->ip_src; - tg->repliedPackets++; - /* The probe was the reply from a ttl guess */ - if (tp->probeType() == PROBE_TTL) { - tg->setHopDistance(get_initial_ttl_guess(ip->ip_ttl), ip->ip_ttl); - tg->start_ttl = tg->ttl = tg->hopDistance; - } else { - tg->gotReply = true; - if (tg->start_ttl < tg->ttl) - tg->ttl = tg->start_ttl + 1; - } - break; - default: - ; - } - return finished(); +bool HostState::is_finished() const { + return !this->has_more_probes() + && active_probes.empty() && pending_resends.empty(); } -/* Estimate how many hops away a host is by actively probing it. The hop - * distance is set by setHopDistance from readTraceResponses. */ -inline void -Traceroute::sendTTLProbes(vector < Target * >&Targets, vector < Target * >&valid_targets) { - Target *t = NULL; - struct probespec probe; - u16 sport = 0; - TraceProbe *tp; - TraceGroup *tg = NULL; - vector < Target * >::iterator it; +bool HostState::send_next_probe(int rawsd, eth_t *ethsd) { + Probe *probe; - for (it = Targets.begin(); it != Targets.end(); ++it) { - t = *it; - - /* No point in tracing directly connected nodes */ - if (t->directlyConnected()) - continue; - - /* This node has already been sent a hop distance probe */ - if (TraceGroups.find(t->v4hostip()->s_addr) != TraceGroups.end()) { - valid_targets.push_back(t); - continue; - } - - /* Determine active port to probe */ - probe = getTraceProbe(t); - assert(probe.type != PS_NONE); - - /* start off with a random source port and increment it for each probes - * sent. The source port is the distinguishing value used to identify - * each probe */ - sport = get_random_u16(); - tg = new TraceGroup(t->v4hostip()->s_addr, sport, probe); - tg->src_mac_addr = t->SrcMACAddress(); - tg->nxt_mac_addr = t->NextHopMACAddress(); - tg->sport++; - TraceGroups[tg->ipdst] = tg; - - /* OS fingerprint engine may already have the distance so - * we don't need to calculate it */ - if (t->distance != -1) { - tg->setHopDistance(0, t->distance); - } else { - tp = new TraceProbe(t->v4hostip()->s_addr, - t->v4sourceip()->s_addr, sport, probe); - tp->setProbeType(PROBE_TTL); - tp->ttl = o.ttl; - tg->TraceProbes[sport] = tp; - tg->incRemaining(); - sendProbe(tp); - } - valid_targets.push_back(t); - } -} - -/* Send a single traceprobe object */ -int -Traceroute::sendProbe(TraceProbe * tp) { - u8 *tcpopts = NULL; - int tcpoptslen = 0; - u32 ack = 0; - u8 *packet = NULL; - u32 packetlen = 0; - TraceGroup *tg = NULL; - int decoy = 0; - struct in_addr source; - struct eth_nfo eth; - struct eth_nfo *ethptr = NULL; - - if (tp->probe.type == PS_TCP && (tp->probe.pd.tcp.flags & TH_ACK) == TH_ACK) - ack = rand(); - if (tp->probe.type == PS_TCP && (tp->probe.pd.tcp.flags & TH_SYN) == TH_SYN) { - tcpopts = (u8 *) "\x02\x04\x05\xb4"; - tcpoptslen = 4; - } - - if (TraceGroups.find(tp->ipdst.s_addr) == TraceGroups.end()) - return -1; - tg = TraceGroups[tp->ipdst.s_addr]; - - /* required to send raw packets in windows */ - if (ethsd) { - memcpy(eth.srcmac, tg->src_mac_addr, 6); - memcpy(eth.dstmac, tg->nxt_mac_addr, 6); - eth.ethsd = ethsd; - eth.devname[0] = '\0'; - ethptr = ð - } - - if (tg->TraceProbes.find(tp->sport) == tg->TraceProbes.end()) { - tg->nextTTL(); - - if (tg->ttl > MAX_TTL) { - tg->setState(G_DEAD_TTL); - return -1; - } - if (!tg->ttl || (tg->gotReply && tg->noDistProbe) ) { - tg->setState(G_FINISH); - return 0; - } - tg->sport++; - tp->ttl = tg->ttl; - tg->incRemaining(); - } else { - /* this probe is a retransmission */ - tp->timing.setState(P_OK); - } - - tg->TraceProbes[tp->sport] = tp; - - for (decoy = 0; decoy < o.numdecoys; decoy++) { - enforce_scan_delay(&tp->timing.sendTime, tg->scanDelay); - - if (decoy == o.decoyturn) - source = tp->ipsrc; - else - source = o.decoys[decoy]; - - /* For TCP, UDP, SCTP and ICMP, also check if the probe is an IP - proto probe whose protocol happens to be one of those protocols. - The protocol-specific fields will have been filled in by - Traceroute::getTraceProbe. */ - if (tp->probe.type == PS_TCP - || (tp->probe.type == PS_PROTO && tp->probe.proto == IPPROTO_TCP)) { - packet = build_tcp_raw(&source, &tp->ipdst, tp->ttl, get_random_u16(), - get_random_u8(), false, NULL, 0, tp->sport, tp->probe.pd.tcp.dport, - get_random_u32(), ack, 0, tp->probe.pd.tcp.flags, - get_random_u16(), 0, tcpopts, tcpoptslen, - o.extra_payload, o.extra_payload_length, &packetlen); - } else if (tp->probe.type == PS_UDP - || (tp->probe.type == PS_PROTO && tp->probe.proto == IPPROTO_UDP)) { - packet = build_udp_raw(&source, &tp->ipdst, tp->ttl, get_random_u16(), - get_random_u8(), false, - NULL, 0, tp->sport, - tp->probe.pd.udp.dport, o.extra_payload, o.extra_payload_length, &packetlen); - } else if (tp->probe.type == PS_SCTP - || (tp->probe.type == PS_PROTO && tp->probe.proto == IPPROTO_SCTP)) { - struct sctp_chunkhdr_init chunk; - sctp_pack_chunkhdr_init(&chunk, SCTP_INIT, 0, - sizeof(struct sctp_chunkhdr_init), - get_random_u32()/*itag*/, - 32768, 10, 2048, - get_random_u32()/*itsn*/); - packet = build_sctp_raw(&source, &tp->ipdst, tp->ttl, - get_random_u16(), get_random_u8(), - false, NULL, 0, - tp->sport, tp->probe.pd.sctp.dport, - 0UL, (char*) &chunk, - sizeof(struct sctp_chunkhdr_init), - o.extra_payload, o.extra_payload_length, - &packetlen); - } else if (tp->probe.type == PS_ICMP - || (tp->probe.type == PS_PROTO && tp->probe.proto == IPPROTO_ICMP)) { - packet = build_icmp_raw(&source, &tp->ipdst, tp->ttl, 0, 0, false, - NULL, 0, get_random_u16(), tp->sport, tp->probe.pd.icmp.type, 0, - o.extra_payload, o.extra_payload_length, &packetlen); - } else if (tp->probe.type == PS_PROTO) { - packet = build_ip_raw(&source, &tp->ipdst, tp->probe.proto, tp->ttl, - tp->sport, get_random_u8(), false, NULL, 0, - o.extra_payload, o.extra_payload_length, &packetlen); - } else { - fatal("Unknown probespec type %d in %s\n", tp->probe.type, __func__); - } - send_ip_packet(fd, ethptr, packet, packetlen); - free(packet); - } - return 0; -} - -/* true if all groups have finished or failed */ -bool -Traceroute::finished() { - map < u32, TraceGroup * >::iterator it = TraceGroups.begin(); - for (; it != TraceGroups.end(); ++it) { - if (it->second->getState() == G_OK || it->second->getRemaining()) - return false; - } + /* Do a resend if possible. */ + if (!pending_resends.empty()) { + probe = pending_resends.front(); + pending_resends.pop_front(); + active_probes.push_back(probe); + probe->resend(rawsd, ethsd); return true; -} + } -/* Main parallel send and recv loop */ -void -Traceroute::trace(vector < Target * >&Targets) { - map < u32, TraceGroup * >::iterator it; - vector < Target * >::iterator targ; - vector < Target * >valid_targets; - vector < Target * >reference; - vector < TraceProbe * >retrans_probes; - vector < TraceGroup * >::size_type pcount; - TraceProbe *tp = NULL; - TraceGroup *tg = NULL; - Target *t = NULL; - ScanProgressMeter *SPM; - u16 total_size, total_complete; + if (!this->has_more_probes()) + return false; - if (o.af() == AF_INET6) { - error("Traceroute does not support ipv6"); - return; - } + probe = Probe::make(this, pspec, current_ttl); + unanswered_probes.push_back(probe); + active_probes.push_back(probe); + probe->send(rawsd, ethsd); - if (o.current_scantype != REF_TRACEROUTE) - o.current_scantype = TRACEROUTE; - - /* perform the reference trace first */ - if (Targets.size() > 1) { - o.current_scantype = REF_TRACEROUTE; - for (targ = Targets.begin(); targ != Targets.end(); ++targ) { - reference.push_back(*targ); - sendTTLProbes(reference, valid_targets); - if (valid_targets.size()) { - this->trace(valid_targets); - break; - } - } - o.current_scantype = TRACEROUTE; - } - - /* guess hop distance to targets. valid_targets is populated with all Target - * object that are legitimate to trace to */ - sendTTLProbes(Targets, valid_targets); - - if (!valid_targets.size()) - return; - - SPM = new ScanProgressMeter("Traceroute"); - - while (!readTraceResponses()) { - for (targ = valid_targets.begin(); targ != valid_targets.end(); ++targ) { - t = *targ; - tg = TraceGroups[t->v4host().s_addr]; - - /* Check for any timedout probes and retransmit them. If too many - * probes are outstanding we wait for replies or timeouts before - * sending any more */ - if (tg->getRemaining()) { - tg->retransmissions(retrans_probes); - for (pcount = 0; pcount < retrans_probes.size(); pcount++) - sendProbe(retrans_probes[pcount]); - retrans_probes.clear(); - /* Max number of packets outstanding is 2 if we don't have a - * reply yet otherwise it is equal to o.timing_level. If the - * timing level it 0 it is equal to 1 */ - if (tg->getRemaining() >= - (tg->gotReply ? (!o.timing_level ? 1 : o.timing_level) : 2)) - continue; - } - if (tg->getState() != G_OK || !tg->hopDistance) - continue; - - tp = new TraceProbe(t->v4hostip()->s_addr, - t->v4sourceip()->s_addr, tg->sport, tg->probe); - sendProbe(tp); - } - - if (!keyWasPressed()) - continue; - - total_size = total_complete = 0; - for (it = TraceGroups.begin(); it != TraceGroups.end(); ++it) { - total_complete += it->second->size(); - total_size += it->second->hopDistance; - } - - if (!total_size) - continue; - - if (total_size < total_complete) - swap(total_complete, total_size); - SPM->printStats(MIN((double) total_complete / total_size, 0.99), NULL); - } - - /* Now set the distance in the Target structure for each of the valid - * targets. */ - for (targ = valid_targets.begin(); targ != valid_targets.end(); ++targ) { - int distance; - distance = TraceGroups[t->v4host().s_addr]->getDistance(); - if (distance != -1) { - (*targ)->distance = distance; - (*targ)->distance_calculation_method = DIST_METHOD_TRACEROUTE; - } - } - - SPM->endTask(NULL, NULL); - delete SPM; -} - -/* Resolves traceroute hops through Nmap's parallel caching rdns infrastructure. - * The class variable should be NULL and needs freeing after the - * hostnames are finished with. - * - * N.B TraceProbes contain pointers into the Target structure, if it is free'ed - * prematurely something nasty will happen. */ -void Traceroute::resolveHops() { - map::iterator tg_iter; - map::iterator tp_iter; - int count = 0; - struct sockaddr_storage ss; - struct sockaddr_in *sin = (struct sockaddr_in *) &ss; - - if (o.noresolve) - return; - - assert(hops == NULL); - - memset(&ss, '\0', sizeof(ss)); - sin->sin_family = o.af(); - - for (tg_iter = TraceGroups.begin(); tg_iter != TraceGroups.end(); ++tg_iter) - total_size += tg_iter->second->size(); - if (!total_size) - return; - hops = (Target **) safe_zalloc(sizeof(Target *) * total_size); - - /* Move hop IP address to Target structures and point TraceProbes to - * Targets hostname */ - for (tg_iter = TraceGroups.begin(); tg_iter != TraceGroups.end(); ++tg_iter) { - tp_iter = tg_iter->second->TraceProbes.begin(); - for (; tp_iter != tg_iter->second->TraceProbes.end(); ++tp_iter) { - if (tp_iter->second->ipreplysrc.s_addr && tp_iter->second->probeType() != PROBE_TTL) { - sin->sin_addr = tp_iter->second->ipreplysrc; - hops[count] = new Target(); - hops[count]->setTargetSockAddr(&ss, sizeof(ss)); - hops[count]->flags = HOST_UP; - tp_iter->second->hostname = &hops[count]->hostname; - count++; - } - } - } - /* resolve all hops in this group at onces */ - nmap_mass_rdns(hops, count); -} - -void -Traceroute::addConsolidationMessage(NmapOutputTable *Tbl, unsigned short row_count, unsigned short ttl) { - char mbuf[64]; - int len; - - assert(ref_ipaddr.s_addr); - char *ip = inet_ntoa(ref_ipaddr); - - if (ttl == 1) - len = Snprintf(mbuf, sizeof(mbuf), "Hop 1 is the same as for %s", ip); + /* Increment or decrement the TTL, depending. */ + if (state == HostState::COUNTING_DOWN) { + if (current_ttl <= 1) + this->count_up(); else - len = Snprintf(mbuf, sizeof(mbuf), "Hops 1-%d are the same as for %s", ttl, ip); + current_ttl--; + } else { + current_ttl++; + } - assert(len); - Tbl->addItem(row_count, HOP_COL, true, "-", 1); - Tbl->addItem(row_count, RTT_COL, true, true, mbuf, len); + return true; } -/* print a trace in plain text format */ -void -Traceroute::outputTarget(Target * t) { - map < u8, TraceProbe * >::size_type ttl_count; - map < u8, TraceProbe * >ttlProbes; - TraceProbe *tp = NULL; - TraceGroup *tg = NULL; - NmapOutputTable *Tbl = NULL; - - bool last_consolidation = false; - bool common_consolidation = false; - char row_count = 0; - char timebuf[16]; - u8 consol_count = 0; - - if ((TraceGroups.find(t->v4host().s_addr)) == TraceGroups.end()) - return; - tg = TraceGroups[t->v4host().s_addr]; - - /* clean up and consolidate traces */ - ttlProbes = tg->consolidateHops(); - - this->outputXMLTrace(tg); - - /* table headers */ - Tbl = new NmapOutputTable(tg->hopDistance+1, 3); - Tbl->addItem(row_count, HOP_COL, false, "HOP", 3); - Tbl->addItem(row_count, RTT_COL, false, "RTT", 3); - Tbl->addItem(row_count, HOST_COL, false, "ADDRESS", 7); - - for (ttl_count = 1; ttl_count <= tg->hopDistance; ttl_count++) { - - assert(row_count <= tg->hopDistance); - - /* consolidate hops based on the reference trace (commonPath) */ - if (commonPath[ttl_count] && ttl_count < tg->consolidation_start) { - /* do not consolidate in debug mode */ - if (o.debugging) { - row_count++; - Tbl->addItemFormatted(row_count, HOP_COL, false, "%d", ttl_count); - Tbl->addItemFormatted(row_count, RTT_COL, false, "--"); - Tbl->addItemFormatted(row_count, HOST_COL, false, "%s", hostStr(commonPath[ttl_count])); - } else if (!common_consolidation) { - row_count++; - common_consolidation = true; - } - } - - /* here we print the final hop for a trace that is fully consolidated */ - if (ttlProbes.find(ttl_count) == ttlProbes.end()) { - if (common_consolidation && ttl_count == tg->hopDistance) { - if (ttl_count-2 == 1) { - Tbl->addItemFormatted(row_count, RTT_COL, false, "--"); - Tbl->addItemFormatted(row_count, HOST_COL,false, "%s", hostStr(commonPath[ttl_count-2])); - } else { - addConsolidationMessage(Tbl, row_count, ttl_count-2); - } - common_consolidation = false; - break; - } - continue; - } - /* Here we consolidate the probe that first matched the common path */ - if (ttl_count < tg->consolidation_start) - continue; - - tp = ttlProbes[ttl_count]; - - /* end of reference trace consolidation */ - if (common_consolidation) { - if (ttl_count-1 == 1) { - Tbl->addItemFormatted(row_count, RTT_COL, false, "--", ttl_count-1); - Tbl->addItemFormatted(row_count, HOST_COL,false, "%s", hostStr(commonPath[ttl_count-1])); - } else { - addConsolidationMessage(Tbl, row_count, ttl_count-1); - } - common_consolidation = false; - } - - row_count++; - - /* timeout consolidation */ - if (tp->timing.consolidated) { - consol_count++; - if (!last_consolidation) { - last_consolidation = true; - Tbl->addItemFormatted(row_count, HOP_COL, false, "%d", tp->ttl); - } else if (tg->getState() == G_DEAD_TTL && ttl_count == tg->hopDistance) { - Tbl->addItem(row_count, RTT_COL, false, "... 50"); - } - row_count--; - } else if (!tp->timing.consolidated && last_consolidation) { - Tbl->addItem(row_count, HOST_COL, false, "no response", 11); - if (consol_count>1) - Tbl->addItemFormatted(row_count, RTT_COL, false, "... %d", tp->ttl-1); - else - Tbl->addItemFormatted(row_count, RTT_COL, false, "..."); - - row_count++; - last_consolidation = false; - consol_count = 0; - } - - /* normal hop output (rtt, ip and hostname) */ - if (!tp->timing.consolidated && !last_consolidation) { - Snprintf(timebuf, sizeof(timebuf), "%.2f ms", - (float) TIMEVAL_SUBTRACT(tp->timing.recvTime, tp->timing.sendTime) / 1000); - Tbl->addItemFormatted(row_count, HOP_COL, false, "%d", tp->ttl); - if (tp->timing.getState() != P_TIMEDOUT) { - Tbl->addItem(row_count, RTT_COL, true, timebuf); - Tbl->addItem(row_count, HOST_COL, true, tp->nameIP()); - } else { - Tbl->addItemFormatted(row_count, RTT_COL, false, "..."); - } - } +/* Start counting the TTL upward from the initial TTL + 1. */ +void HostState::count_up() { + if (state == HostState::COUNTING_DOWN) { + if (o.debugging > 1) { + log_write(LOG_STDOUT, "Traceroute for %s now counting up\n", + target->targetipstr()); } - - /* Traceroute header and footer */ - if (tg->probe.type == PS_TCP) { - log_write(LOG_PLAIN, "\nTRACEROUTE (using port %d/%s)\n", tg->probe.pd.tcp.dport, proto2ascii(tg->probe.proto)); - } else if (tg->probe.type == PS_UDP) { - log_write(LOG_PLAIN, "\nTRACEROUTE (using port %d/%s)\n", tg->probe.pd.udp.dport, proto2ascii(tg->probe.proto)); - } else if (tg->probe.type == PS_SCTP) { - log_write(LOG_PLAIN, "\nTRACEROUTE (using port %d/%s)\n", tg->probe.pd.sctp.dport, proto2ascii(tg->probe.proto)); - } else if (tg->probe.type == PS_ICMP || tg->probe.type == PS_PROTO) { - struct protoent *proto = nmap_getprotbynum(htons(tg->probe.proto)); - log_write(LOG_PLAIN, "\nTRACEROUTE (using proto %d/%s)\n", tg->probe.proto, proto?proto->p_name:"unknown"); - } - log_write(LOG_PLAIN, "%s", Tbl->printableTable(NULL)); - - if (tg->getState() == G_DEAD_TTL) { - log_write(LOG_PLAIN, "! maximum TTL reached (50)\n"); - } else if (!tg->gotReply || (tp && (tp->ipreplysrc.s_addr != tg->ipdst))) { - struct in_addr addr = { tg->ipdst }; - log_write(LOG_PLAIN, "! destination not reached (%s)\n", inet_ntoa(addr)); - } - - log_flush(LOG_PLAIN); - delete Tbl; + state = HostState::COUNTING_UP; + current_ttl = initial_ttl + 1; + } } -/* print a trace in xml */ -void -Traceroute::outputXMLTrace(TraceGroup * tg) { - map < u8, TraceProbe * >::const_iterator it; - map < u8, TraceProbe * >ttlProbes; - TraceProbe *tp = NULL; - const char *hostname_tmp = NULL; - struct in_addr addr; - long timediff; - short ttl_count; +int HostState::cancel_probe(std::list::iterator it) { + int count; - /* XML traceroute header */ - log_write(LOG_XML, "probe.type == PS_TCP) { - log_write(LOG_XML, "port=\"%d\" ", tg->probe.pd.tcp.dport); - } else if (tg->probe.type == PS_UDP) { - log_write(LOG_XML, "port=\"%d\" ", tg->probe.pd.udp.dport); - } else if (tg->probe.type == PS_SCTP) { - log_write(LOG_XML, "port=\"%d\" ", tg->probe.pd.sctp.dport); - } else if (tg->probe.type == PS_ICMP || tg->probe.type == PS_PROTO) { - struct protoent *proto = nmap_getprotbynum(htons(tg->probe.proto)); - if (proto == NULL) - log_write(LOG_XML, "proto=\"%d\"", tg->probe.proto); - else - log_write(LOG_XML, "proto=\"%s\"", proto->p_name); - } - log_write(LOG_XML, ">\n"); + count = active_probes.size(); + active_probes.remove(*it); + count -= active_probes.size(); + pending_resends.remove(*it); + delete *it; + unanswered_probes.erase(it); - ttlProbes = tg->consolidateHops(); - - /* add missing hosts host from the common path */ - for (ttl_count = 1 ; ttl_count < ttlProbes.begin()->second->ttl; ttl_count++) { - addr.s_addr = commonPath[ttl_count]; - log_write(LOG_XML, "\n"); - } - - /* display normal traceroute nodes. Consolidation based on the common path - * is not performed */ - for (it = ttlProbes.begin(); it != ttlProbes.end(); it++) { - tp = it->second; - - if (tp->probeType() == PROBE_TTL) - break; - - if (tp->timing.getState() == P_TIMEDOUT) - continue; - - timediff = TIMEVAL_SUBTRACT(tp->timing.recvTime, tp->timing.sendTime); - - log_write(LOG_XML, "ttl, (float) timediff/1000, tp->ipReplyStr()); - if (tp->HostName() != NULL) - log_write(LOG_XML, " host=\"%s\"", tp->HostName()); - log_write(LOG_XML, "/>\n"); - } - - if (tg->getState() == G_DEAD_TTL) - log_write(LOG_XML, "\n"); - else if (!tg->gotReply || (tp && (tp->ipreplysrc.s_addr != tg->ipdst))) { - addr.s_addr = tg->ipdst; - log_write(LOG_XML, "\n", inet_ntoa(addr)); - } - - /* traceroute XML footer */ - log_write(LOG_XML, "\n"); - log_flush(LOG_XML); + return count; } -TraceGroup::TraceGroup(u32 dip, u16 sport, struct probespec& probe) { - this->ipdst = dip; - this->sport = sport; - this->probe = probe; - ttl = 0; - state = G_OK; - remaining = 0; - hopDistance = 0; - start_ttl = 0; - TraceProbes.clear(); - gotReply = false; - noDistProbe = false; - scanDelay = o.scan_delay ? o.scan_delay : 0; - maxRetransmissions = (o.getMaxRetransmissions() < 2) ? 2 : o.getMaxRetransmissions() / 2; - droppedPackets = 0; - repliedPackets = 0; - consecTimeouts = 0; - consolidation_start = 0; +int HostState::cancel_probes_below(u8 ttl) { + std::list::iterator it, next; + int count; + + count = 0; + for (it = unanswered_probes.begin(); it != unanswered_probes.end(); it = next) { + next = it; + next++; + if ((*it)->ttl < ttl) + count += this->cancel_probe(it); + } + + return count; } -TraceGroup::~TraceGroup() { - map < u16, TraceProbe * >::const_iterator it; - for (it = TraceProbes.begin(); it != TraceProbes.end(); ++it) - delete it->second; +int HostState::cancel_probes_above(u8 ttl) { + std::list::iterator it, next; + int count; + + count = 0; + for (it = unanswered_probes.begin(); it != unanswered_probes.end(); it = next) { + next = it; + next++; + if ((*it)->ttl > ttl) + count += this->cancel_probe(it); + } + + return count; } -/* go through all probes in a group and check if any have timedout. - * If too many packets have been dropped then the groups scan delay - * is increased */ -void -TraceGroup::retransmissions(vector < TraceProbe * >&retrans) { - map < u16, TraceProbe * >::iterator it; - u32 timediff; - struct timeval now; - double threshold = (o.timing_level >= 4) ? 0.40 : 0.30; +Hop *HostState::insert_hop(u8 ttl, const struct sockaddr_storage *addr, + float rtt) { + Hop *hop, *prev, *p; - for (it = TraceProbes.begin(); it != TraceProbes.end(); ++it) { - if (it->second->timing.gotReply() || it->second->timing.getState() == P_TIMEDOUT) - continue; - - gettimeofday(&now, NULL); - timediff = TIMEVAL_SUBTRACT(now, it->second->timing.sendTime); - - if (timediff < it->second->timing.probeTimeout()) - continue; - - if (it->second->timing.retranLimit() >= maxRetransmissions) { - /* this probe has timedout */ - it->second->timing.setState(P_TIMEDOUT); - decRemaining(); - - if ((++consecTimeouts) > 5 && maxRetransmissions > 2) - maxRetransmissions = 2; - if (it->second->probeType() == PROBE_TTL) { - noDistProbe = true; - /* Give up on this host. We should be able to do a trace against - an unresponsive target but for now it's too slow. */ - setState(G_DEAD_TTL); - if (o.verbose) - log_write(LOG_STDOUT, "%s: no reply to our hop distance probe!\n", IPStr()); - } else if (it->second->ttl > MAX_TTL) { - setState(G_DEAD_TTL); - } - } else { - droppedPackets++; - it->second->timing.setState(P_RETRANS); - retrans.push_back(it->second); - } - - /* Calculate dynamic timing adjustments */ - if (repliedPackets > droppedPackets / 5) - maxRetransmissions = (maxRetransmissions == 2) ? 2 : maxRetransmissions - 1; - else - maxRetransmissions = MIN(o.getMaxRetransmissions(), maxRetransmissions + 1); - - if (droppedPackets > 10 && (droppedPackets / - ((double) droppedPackets + repliedPackets) > threshold)) { - if (!scanDelay) - scanDelay = (probe.type == PS_TCP || probe.type == PS_SCTP) ? 5 : 50; - else - scanDelay = MIN(scanDelay * 2, MAX(scanDelay, 800)); - droppedPackets = 0; - repliedPackets = 0; - } else { - scanDelay = MAX(scanDelay - (scanDelay / 5), 5); - } - } -} - -/* Returns a map from TTLs to probes, stripped of all unneeded probes and with - * timed-out probes marked for consolidation. */ -map < u8, TraceProbe * > TraceGroup::consolidateHops() const { - map < u16, TraceProbe * >::size_type ttl_count; - map < u8, TraceProbe * >ttlProbes; - map < u16, TraceProbe * >::const_iterator probe_iter; - map < u16, u32 >::iterator com_iter; - TraceProbe *tp; - int timeout_count = 0; - - /* Make a map of probes indexed by TTL. */ - for (probe_iter = TraceProbes.begin(); probe_iter != TraceProbes.end(); ++probe_iter) - ttlProbes[probe_iter->second->ttl] = probe_iter->second; - - /* remove any superfluous probes */ - for (ttl_count = hopDistance + 1; ttl_count <= ttlProbes.size() + 1; ttl_count++) - ttlProbes.erase(ttl_count); - - for (ttl_count = 1; ttl_count <= hopDistance; ttl_count++) { - tp = ttlProbes[ttl_count]; - if (!tp) { - ttlProbes.erase(ttl_count); - continue; - } - - /* timeout consolidation flags, ignore if in debugging more */ - if (tp->timing.getState() != P_TIMEDOUT) { - timeout_count = 0; - } else { - if (++timeout_count > 1 && !o.debugging) { - ttlProbes[(ttl_count == 1) ? 1 : ttl_count - 1]->timing.consolidated = true; - ttlProbes[(ttl_count == 1) ? 1 : ttl_count]->timing.consolidated = true; - } - } - - if (tp->ipreplysrc.s_addr == ipdst) - break; - } - - /* we may have accidently shot past the intended destination */ - while (ttl_count <= hopDistance) - ttlProbes.erase(++ttl_count); - - return ttlProbes; -} - -/* This is the function that gives the traceroute its "up and down" nature. - gotReply is true if we've gotten a reply from the target (finished counting - up). */ -void TraceGroup::nextTTL() { - if (gotReply) { - ttl--; + this->child_parent_ttl(ttl, &prev, &p); + if (p != NULL && p->ttl == ttl) { + hop = p; + /* Collision with the same TTL and a different address. */ + if (hop->addr.ss_family == 0) { + /* Hit a timed-out hop. Fill in the missing address and RTT. */ + hop->addr = *addr; + hop->rtt = rtt; } else { - ttl++; - hopDistance++; + if (o.debugging) { + log_write(LOG_STDOUT, "Found existing %s", ss_to_string(&hop->addr)); + log_write(LOG_STDOUT, " while inserting %s at TTL %d for %s\n", + ss_to_string(addr), ttl, target->targetipstr()); + } } -} - -void TraceGroup::incRemaining() { - if (remaining < 255) - ++remaining; -} - -void TraceGroup::decRemaining() { - if (remaining > 0) - --remaining; -} - -char *TraceGroup::IPStr() { - struct in_addr s; - s.s_addr = ipdst; - return inet_ntoa (s); -} - -u8 -TraceGroup::setState(u8 state) { - if (state <= G_FINISH && state >= G_OK) - this->state = state; - else if (o.debugging) - log_write(LOG_STDOUT, "%s: invalid tracegroup state %d\n", IPStr(), state); - return this->state; -} - -u8 -TraceGroup::setHopDistance(u8 hop_distance, u8 ttl) { - if (this->hopDistance) - return 0; - - this->hopDistance = hop_distance; - - if (o.debugging) - log_write(LOG_STDOUT, "%s: hop distance parameters -> hg:%d ttl:%d\n", IPStr(), hop_distance, ttl); - - if (this->hopDistance && ttl) - this->hopDistance -= ttl; - else if (!this->hopDistance && ttl) - this->hopDistance = ttl; - else - this->hopDistance = hop_distance; - - /* guess is too big */ - if (this->hopDistance >= MAX_TTL) - this->hopDistance = MAX_TTL- 2; - /* guess is too small */ - else if (this->hopDistance == 0) - this->hopDistance = 1; - - if (o.verbose) - log_write(LOG_STDOUT, "%s: guessing hop distance at %d\n", IPStr(), this->hopDistance); - return this->hopDistance; -} - -/* Get the number of hops to the target, or -1 if unknown. Use this instead of - * reading hopDistance, which despite its name does not contain the final hop - * count. */ -int TraceGroup::getDistance() { - map < u8, TraceProbe * >ttlProbes; - int i; - - if (this->getState() != G_FINISH) - return -1; - - for (i = 1; i < consolidation_start; i++) { - if (commonPath[i] == 0) - return i - 1; - } - ttlProbes = consolidateHops(); - for ( ; i < MAX_TTL; i++) { - if (ttlProbes.find(i) == ttlProbes.end()) - break; - } - - return i - 1; -} - -TraceProbe::TraceProbe(u32 dip, u32 sip, u16 sport, struct probespec& probe) { - this->sport = sport; - this->probe = probe; - ipdst.s_addr = dip; - ipsrc.s_addr = sip; - ipreplysrc.s_addr = 0; - hostnameip = NULL; - hostname = NULL; - probetype = PROBE_TRACE; -} - -TraceProbe::~TraceProbe() { - if (hostnameip) - free(hostnameip); -} - -const char *TraceProbe::nameIP(void) { - hostnameip = (char *) safe_zalloc(NAMEIPLEN); - - if (hostname == NULL || *hostname == NULL) - Snprintf(hostnameip, NAMEIPLEN, "%s", inet_ntoa(ipreplysrc)); - else - Snprintf(hostnameip, NAMEIPLEN, "%s (%s)",*hostname, inet_ntoa(ipreplysrc)); - - return hostnameip; -} - -TimeInfo::TimeInfo() { - memset(&sendTime, 0, sizeof(struct timeval)); - memset(&recvTime, 0, sizeof(struct timeval)); - retransmissions = 0; - state = P_OK; - consolidated = false; - initialize_timeout_info(&to); -} - -u8 -TimeInfo::setState(u8 state) { - if (state <= P_OK) - this->state = state; - else if (o.debugging) - log_write(LOG_STDOUT, ": invalid traceprobe state %d\n", state); - return state; -} - -int -TimeInfo::retranLimit() { - return ++this->retransmissions; -} - -void -TimeInfo::adjustTimeouts(struct timeval *received, u16 scan_delay) { - long delta = 0; - - recvTime = *received; - - if (o.debugging > 3) { - log_write(LOG_STDOUT, "Timeout vals: srtt: %d rttvar: %d to: %d ", to.srtt, to.rttvar, - to.timeout); - } - - delta = TIMEVAL_SUBTRACT(*received, sendTime); - - /* Argh ... pcap receive time is sometimes a little off my - getimeofday() results on various platforms :(. So a packet may - appear to be received as much as a hundredth of a second before - it was sent. So I will allow small negative RTT numbers */ - if (delta < 0 && delta > -50000) { - if (o.debugging > 2) - log_write(LOG_STDOUT, "Small negative delta - adjusting from %lius to %dus\n", - delta, 10000); - delta = 10000; - } - - - if (to.srtt == -1 && to.rttvar == -1) { - /* We need to initialize the sucker ... */ - to.srtt = delta; - to.rttvar = MAX(5000, MIN(to.srtt, 2000000)); - to.timeout = to.srtt + (to.rttvar << 2); + } else { + hop = new Hop(ttl, *addr, rtt); + hop->parent = p; + if (prev == NULL) { + size_t sslen; + this->hops = hop; + sslen = sizeof(hop->tag); + target->TargetSockAddr(&hop->tag, &sslen); } else { - if (delta >= 8000000 || delta < 0) { - if (o.verbose) - error("adjust_timeout: packet supposedly had rtt of %lu microseconds. Ignoring time.", delta); - return; - } - delta -= to.srtt; - /* sanity check 2 */ - if (delta > 1500000 && delta > 3 * to.srtt + 2 * to.rttvar) { - if (o.debugging) - log_write(LOG_STDOUT, "Bogus delta: %ld (srtt %d) ... ignoring\n", delta, to.srtt); - return; - } - - to.srtt += delta >> 3; - to.rttvar += (ABS(delta) - to.rttvar) >> 2; - to.timeout = to.srtt + (to.rttvar << 2); + prev->parent = hop; + hop->tag = prev->tag; } + hop_cache_insert(hop); + } - if (to.rttvar > 2300000) { - log_write(LOG_STDOUT, "RTTVAR has grown to over 2.3 seconds, decreasing to 2.0\n"); - to.rttvar = 2000000; - } - - /* It hurts to do this ... it really does ... but otherwise we are being - too risky */ - to.timeout = box(o.minRttTimeout() * 1000, o.maxRttTimeout() * 1000, to.timeout); - - if (scan_delay) - to.timeout = MAX(to.timeout, scan_delay * 1000); - - if (o.debugging > 3) { - log_write(LOG_STDOUT, "delta %ld ==> srtt: %d rttvar: %d to: %d\n", - delta, to.srtt, to.rttvar, to.timeout); - } + return hop; } -/* Sleeps if necessary to ensure that it isn't called twice within less time - * than send_delay. If it is passed a non-null tv, the POST-SLEEP time is - * recorded in it */ -static void -enforce_scan_delay(struct timeval *tv, int scan_delay) { - static int init = -1; - static struct timeval lastcall; - struct timeval now; - int time_diff; +void HostState::link_to(Hop *hop) { + Hop *prev, *p; - if (!scan_delay) { - if (tv) - gettimeofday(tv, NULL); - return; - } - - if (init == -1) { - gettimeofday(&lastcall, NULL); - init = 0; - if (tv) - memcpy(tv, &lastcall, sizeof(struct timeval)); - return; - } - - gettimeofday(&now, NULL); - time_diff = TIMEVAL_MSEC_SUBTRACT(now, lastcall); - if (time_diff < (int) scan_delay) { - if (o.debugging > 2) - log_write (LOG_STDOUT, "Sleeping for %d milliseconds in %s()\n", - scan_delay - time_diff, __func__); - usleep((scan_delay - time_diff) * 1000); - gettimeofday(&lastcall, NULL); - } else - memcpy(&lastcall, &now, sizeof(struct timeval)); - if (tv) { - memcpy(tv, &lastcall, sizeof(struct timeval)); - } + this->child_parent_ttl(hop->ttl, &prev, &p); + if (hop == p) { + /* Already linked for this host. This can happen a reply for a higher TTL + results in a merge, and later a reply for a lower TTL comes back. */ return; + } + if (o.debugging > 1) { + log_write(LOG_STDOUT, "Merging traces below TTL %d for %s", + hop->ttl, ss_to_string(&hop->tag)); + log_write(LOG_STDOUT, " and %s\n", target->targetipstr()); + } + hop = merge_hops(&hop->tag, hop, p); + if (prev == NULL) + this->hops = hop; + else + prev->parent = hop; } -static char * -hostStr (u32 ip) { - static char nameipbuf[MAXHOSTNAMELEN + INET6_ADDRSTRLEN] = { '0' }; - const char *hname; - struct in_addr addr; - - memset(nameipbuf, '\0', MAXHOSTNAMELEN + INET6_ADDRSTRLEN); - addr.s_addr = ip; - if ((hname = lookup_cached_host(ip)) == NULL) - Snprintf(nameipbuf, sizeof(nameipbuf), "%s", inet_ntoa(addr)); - else - Snprintf(nameipbuf, sizeof(nameipbuf), "%s (%s)", hname, inet_ntoa(addr)); - return nameipbuf; +double HostState::completion_fraction() const { + if (this->is_finished()) + return 1.0; + else if (state == HostState::COUNTING_DOWN) + return (double) (initial_ttl - current_ttl) / (initial_ttl + 1); + else + return (double) current_ttl / (current_ttl + 1); +} + +void HostState::child_parent_ttl(u8 ttl, Hop **child, Hop **parent) { + *child = NULL; + *parent = this->hops; + while (*parent != NULL && (*parent)->ttl > ttl) { + *child = *parent; + *parent = (*parent)->parent; + } +} + +u8 HostState::distance_guess(const Target *target) { + /* Use the distance from OS detection if we have it. */ + if (target->distance != -1) + return target->distance; + else + return 10; +} + +/* Get the probe that will be used for the traceroute. This is the + highest-quality probe found in ping or port scanning, or ICMP echo if no + responsive probe is known. */ +struct probespec HostState::get_probe(const Target *target) { + struct probespec probe; + + probe = target->pingprobe; + if (probe.type == PS_NONE) { + /* No responsive probe known? The user probably skipped both ping and + port scan. Guess ICMP echo as the most likely to get a response. */ + probe.type = PS_ICMP; + probe.proto = IPPROTO_ICMP; + probe.pd.icmp.type = ICMP_ECHO; + probe.pd.icmp.code = 0; + } else if (probe.type == PS_PROTO) { + /* If this is an IP protocol probe, fill in some fields for some common + protocols. We cheat and store them in the TCP-, UDP-, SCTP- and + ICMP-specific fields. */ + if (probe.proto == IPPROTO_TCP) { + probe.pd.tcp.flags = TH_ACK; + probe.pd.tcp.dport = get_random_u16(); + } else if (probe.proto == IPPROTO_UDP) { + probe.pd.udp.dport = get_random_u16(); + } else if (probe.proto == IPPROTO_SCTP) { + probe.pd.sctp.dport = get_random_u16(); + } else if (probe.proto == IPPROTO_ICMP) { + probe.pd.icmp.type = ICMP_ECHO; + } + } + + return probe; +} + +Probe::Probe(HostState *host, struct probespec pspec, u8 ttl) { + this->host = host; + this->pspec = pspec; + this->ttl = ttl; + token = Probe::token_counter++; + sent_time.tv_sec = 0; + sent_time.tv_usec = 0; + num_resends = 0; +} + +void Probe::send(int rawsd, eth_t *ethsd, struct timeval *now) { + struct eth_nfo eth; + struct eth_nfo *ethp; + int decoy; + + /* Set up the Ethernet handle if we're using that. */ + if (ethsd != NULL) { + memcpy(eth.srcmac, host->target->SrcMACAddress(), 6); + memcpy(eth.dstmac, host->target->NextHopMACAddress(), 6); + eth.ethsd = ethsd; + eth.devname[0] = '\0'; + ethp = ð + } else { + ethp = NULL; + } + + for (decoy = 0; decoy < o.numdecoys; decoy++) { + const struct in_addr *source; + unsigned char *packet; + u32 packetlen; + + if (decoy == o.decoyturn) { + source = host->target->v4sourceip(); + sent_time = get_now(now); + } else { + source = &o.decoys[decoy]; + } + + packet = this->build_packet(source, &packetlen); + send_ip_packet(rawsd, ethp, packet, packetlen); + free(packet); + } +} + +void Probe::resend(int rawsd, eth_t *ethsd, struct timeval *now) { + num_resends++; + this->send(rawsd, ethsd, now); +} + +bool Probe::is_timedout(struct timeval *now) const { + struct timeval tv; + + tv = get_now(now); + + return TIMEVAL_MSEC_SUBTRACT(tv, sent_time) > PROBE_TIMEOUT; +} + +bool Probe::may_resend() const { + return num_resends < MIN(o.getMaxRetransmissions(), MAX_RESENDS); +} + +/* Probe is an abstract class with a missing build_packet method. These concrete + subclasses implement the method for different probe types. */ + +class ICMPProbe : public Probe +{ +public: + ICMPProbe(HostState *host, struct probespec pspec, u8 ttl) + : Probe(host, pspec, ttl) { + } + + unsigned char *build_packet(const struct in_addr *source, u32 *len) const { + return build_icmp_raw(source, host->target->v4hostip(), ttl, + 0x0000, 0x00, false, NULL, 0, token, global_id, + pspec.pd.icmp.type, pspec.pd.icmp.code, + o.extra_payload, o.extra_payload_length, len); + } +}; + +class TCPProbe : public Probe +{ +public: + TCPProbe(HostState *host, struct probespec pspec, u8 ttl) + : Probe(host, pspec, ttl) { + } + unsigned char *build_packet(const struct in_addr *source, u32 *len) const { + const char *tcpopts; + int tcpoptslen; + u32 ack; + + tcpopts = NULL; + tcpoptslen = 0; + ack = 0; + if ((pspec.pd.tcp.flags & TH_SYN) == TH_SYN) { + /* MSS 1460 bytes. */ + tcpopts = "\x02\x04\x05\xb4"; + tcpoptslen = 4; + } else if ((pspec.pd.tcp.flags & TH_ACK) == TH_ACK) { + ack = get_random_u32(); + } + + /* For TCP we encode the token in the source port. */ + return build_tcp_raw(source, host->target->v4hostip(), ttl, + get_random_u16(), get_random_u8(), false, NULL, 0, + token ^ global_id, pspec.pd.tcp.dport, get_random_u32(), ack, 0x00, + pspec.pd.tcp.flags, get_random_u16(), 0, (const u8 *) tcpopts, tcpoptslen, + o.extra_payload, o.extra_payload_length, len); + } +}; + +class UDPProbe : public Probe +{ +public: + UDPProbe(HostState *host, struct probespec pspec, u8 ttl) + : Probe(host, pspec, ttl) { + } + unsigned char *build_packet(const struct in_addr *source, u32 *len) const { + const char *payload; + size_t payload_length; + + payload = get_udp_payload(pspec.pd.udp.dport, &payload_length); + + /* For UDP we encode the token in the source port. */ + return build_udp_raw(source, host->target->v4hostip(), ttl, + get_random_u16(), get_random_u8(), false, NULL, 0, + token ^ global_id, pspec.pd.udp.dport, + payload, payload_length, len); + } +}; + +class SCTPProbe : public Probe +{ +public: + SCTPProbe(HostState *host, struct probespec pspec, u8 ttl) + : Probe(host, pspec, ttl) { + } + unsigned char *build_packet(const struct in_addr *source, u32 *len) const { + struct sctp_chunkhdr_init chunk; + + sctp_pack_chunkhdr_init(&chunk, SCTP_INIT, 0, sizeof(chunk), + get_random_u32() /*itag*/, 32768, 10, 2048, get_random_u32() /*itsn*/); + return build_sctp_raw(source, host->target->v4hostip(), ttl, + get_random_u16(), get_random_u8(), false, NULL, 0, + token ^ global_id, pspec.pd.sctp.dport, 0UL, + (char *) &chunk, sizeof(chunk), + o.extra_payload, o.extra_payload_length, len); + } +}; + +class IPProtoProbe : public Probe +{ +public: + IPProtoProbe(HostState *host, struct probespec pspec, u8 ttl) + : Probe(host, pspec, ttl) { + } + unsigned char *build_packet(const struct in_addr *source, u32 *len) const { + /* For IP proto scan the token is put in the IP ID. */ + return build_ip_raw(source, host->target->v4hostip(), pspec.proto, ttl, + token ^ global_id, get_random_u8(), false, NULL, 0, + o.extra_payload, o.extra_payload_length, len); + } +}; + +Probe *Probe::make(HostState *host, struct probespec pspec, u8 ttl) +{ + if (pspec.type == PS_ICMP || (pspec.type == PS_PROTO && pspec.proto == IPPROTO_ICMP)) + return new ICMPProbe(host, pspec, ttl); + else if (pspec.type == PS_TCP || (pspec.type == PS_PROTO && pspec.proto == IPPROTO_TCP)) + return new TCPProbe(host, pspec, ttl); + else if (pspec.type == PS_UDP || (pspec.type == PS_PROTO && pspec.proto == IPPROTO_UDP)) + return new UDPProbe(host, pspec, ttl); + else if (pspec.type == PS_SCTP || (pspec.type == PS_PROTO && pspec.proto == IPPROTO_SCTP)) + return new SCTPProbe(host, pspec, ttl); + else if (pspec.type == PS_PROTO) + return new IPProtoProbe(host, pspec, ttl); + else + fatal("Unknown probespec type in traceroute"); + + return NULL; +} + +TracerouteState::TracerouteState(std::vector &targets) { + std::vector::iterator it; + + assert(targets.size() > 0); + + if ((o.sendpref & PACKET_SEND_ETH) && targets[0]->ifType() == devt_ethernet) { + ethsd = eth_open_cached(targets[0]->deviceName()); + if (ethsd == NULL) + fatal("dnet: failed to open device %s", targets[0]->deviceName()); + rawsd = -1; + } else { +#ifdef WIN32 + win32_warn_raw_sockets(targets[0]->deviceName()); +#endif + rawsd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); + if (rawsd == -1) + pfatal("traceroute: socket troubles"); + broadcast_socket(rawsd); +#ifndef WIN32 + sethdrinclude(rawsd); +#endif + ethsd = NULL; + } + + /* Assume that all the targets share the same device. */ + pd = my_pcap_open_live(targets[0]->deviceName(), 128, o.spoofsource, 2); + + for (it = targets.begin(); it != targets.end(); it++) { + HostState *state = new HostState(*it); + hosts.push_back(state); + active_hosts.push_back(state); + } + + num_active_probes = 0; + next_sending_host = active_hosts.begin(); + next_send_time = get_now(); +} + +TracerouteState::~TracerouteState() { + std::vector::iterator it; + + if (rawsd != -1) + close(rawsd); + pcap_close(pd); + + for (it = hosts.begin(); it != hosts.end(); it++) + delete *it; +} + +void TracerouteState::next_active_host() { + assert(next_sending_host != active_hosts.end()); + next_sending_host++; + if (next_sending_host == active_hosts.end()) + next_sending_host = active_hosts.begin(); +} + +void TracerouteState::send_new_probes() { + std::list::iterator failed_host; + struct timeval now; + + now = get_now(); + + assert(!active_hosts.empty()); + failed_host = active_hosts.end(); + while (next_sending_host != failed_host + && num_active_probes < MAX_OUTSTANDING_PROBES + && !TIMEVAL_BEFORE(now, next_send_time)) { + if ((*next_sending_host)->send_next_probe(rawsd, ethsd)) { + num_active_probes++; + TIMEVAL_MSEC_ADD(next_send_time, next_send_time, o.scan_delay); + if (TIMEVAL_BEFORE(next_send_time, now)) + next_send_time = now; + failed_host = active_hosts.end(); + } else if (failed_host == active_hosts.end()) { + failed_host = next_sending_host; + } + next_active_host(); + } +} + +static Hop *hop_cache_lookup(u8 ttl, const struct sockaddr_storage *addr) { + std::map::iterator it; + HopIdent ident(ttl, *addr); + + it = hop_cache.find(ident); + if (it == hop_cache.end()) + return NULL; + else + return it->second; +} + +static void hop_cache_insert(Hop *hop) { + if (hop->addr.ss_family == 0) { + timedout_hops.push_back(hop); + } else { + hop_cache[HopIdent(hop->ttl, hop->addr)] = hop; + } +} + +static unsigned int hop_cache_size() { + return hop_cache.size() + timedout_hops.size(); +} + +void traceroute_hop_cache_clear() { + std::map::iterator map_iter; + std::list::iterator list_iter; + + for (map_iter = hop_cache.begin(); map_iter != hop_cache.end(); map_iter++) + delete map_iter->second; + hop_cache.clear(); + for (list_iter = timedout_hops.begin(); list_iter != timedout_hops.end(); list_iter++) + delete *list_iter; + timedout_hops.clear(); +} + +/* Merge two hop chains together and return the head of the merged chain. This + is done when a cache hit finds that two targets share the same intermediate + hop; rather than doing a full trace for each target, one is linked to the + other. "Merged" means that, for example, if chain a has an hop at TTL 3 and + chain b has one for TTL 2, the merged chain will include both. Usually, the + chains will not have hops at the same TTL (that implies different routes to + the same host), but see the next paragraph for what we do when that happens. + Each hop in the merged chain will be tagged with the given tag. + + There are many cases that must be handled correctly by this function: a and b + may be equal; either may be NULL; a and b may be disjoint chains or may be + joined somewhere. The biggest difficulty is when both of the chains have a + hop with the same TTL but a different address. When this happens we + arbitrarily choose one of the hops to unlink, on the presumption that any + route through the same intermediate host at a given TTL should be the same + and that differences aren't meaningful. (This has the same effect as if we + were to send probes strictly serially, because then there would be no parent + hops to potentially conflict, even if in fact they would if traced to + completion. */ +static Hop *merge_hops(const struct sockaddr_storage *tag, Hop *a, Hop *b) { + Hop head, *p; + + p = &head; + + while (a != NULL && b != NULL && a != b) { + Hop **next; + + if (a->ttl > b->ttl) { + next = &a; + } else if (b->ttl > a->ttl) { + next = &b; + } else { + Hop **discard, *parent; + + if (b->addr.ss_family == 0) { + next = &a; + discard = &b; + } else if (a->addr.ss_family == 0) { + next = &b; + discard = &a; + } else { + next = &a; + discard = &b; + if (o.debugging) { + log_write(LOG_STDOUT, "Warning: %s", ss_to_string(&(*next)->addr)); + log_write(LOG_STDOUT, " doesn't match %s at TTL %d;", + ss_to_string(&(*discard)->addr), a->ttl); + log_write(LOG_STDOUT, " arbitrarily discarding %s\n", + ss_to_string(&(*discard)->addr)); + } + } + parent = (*discard)->parent; + *discard = parent; + } + p->parent = *next; + p->tag = *tag; + p = p->parent; + *next = (*next)->parent; + } + /* At most one branch of this is taken, even when a == b. */ + if (a != NULL) + p->parent = a; + else if (b != NULL) + p->parent = b; + for ( ; p != NULL; p = p->parent) + p->tag = *tag; + + return head.parent; +} + +/* Record a hop at the given TTL for the given host. This takes care of linking + the hop into the host's chain as well as into the global hop tree. */ +void TracerouteState::set_host_hop(HostState *host, u8 ttl, + const struct sockaddr_storage *from_addr, float rtt) { + Hop *hop; + + if (o.debugging > 1) { + log_write(LOG_STDOUT, "Set hop %s TTL %d to %s RTT %.2f ms\n", + host->target->targetipstr(), ttl, ss_to_string(from_addr), rtt); + } + + hop = hop_cache_lookup(ttl, from_addr); + if (hop == NULL) { + /* A new hop, never before seen with this address and TTL. Add it to the + host's chain and to the global cache. */ + hop = host->insert_hop(ttl, from_addr, rtt); + } else { + /* An existing hop at this address and TTL. Link this host's chain to it. */ + if (o.debugging > 1) { + log_write(LOG_STDOUT, "Traceroute cache hit %s TTL %d while tracing", + ss_to_string(&hop->addr), hop->ttl); + log_write(LOG_STDOUT, " %s TTL %d\n", host->target->targetipstr(), ttl); + } + + host->link_to(hop); + + if (host->state == HostState::COUNTING_DOWN) { + /* Hit the cache going down. Seek to the end of the chain. If we have the + tag for the last node, we take responsibility for finishing the trace. + Otherwise, start counting up. */ + struct sockaddr_storage addr; + size_t sslen; + + while (hop->parent != NULL) + hop = hop->parent; + sslen = sizeof(addr); + host->target->TargetSockAddr(&addr, &sslen); + if (sockaddr_storage_cmp(&hop->tag, &addr) == 0) { + if (o.debugging > 1) { + log_write(LOG_STDOUT, "%s continuing trace from TTL %d\n", + host->target->targetipstr(), host->current_ttl); + } + } else { + host->count_up(); + num_active_probes -= host->cancel_probes_below(ttl); + } + } + } +} + +/* Record that a hop at the given TTL for the given host timed out. */ +void TracerouteState::set_host_hop_timedout(HostState *host, u8 ttl) { + static struct sockaddr_storage EMPTY_ADDR = { 0 }; + host->insert_hop(ttl, &EMPTY_ADDR, -1.0); +} + +struct Reply { + struct timeval rcvdtime; + struct sockaddr_storage from_addr; + struct sockaddr_storage target_addr; + u16 token; +}; + +static const void *ip_get_data(const struct ip *ip, unsigned int *len) +{ + unsigned int header_len; + + header_len = ip->ip_hl * 4; + if (header_len > *len) + return NULL; + *len -= header_len; + + return (char *) ip + header_len; +} + +static const void *icmp_get_data(const struct icmp *icmp, unsigned int *len) +{ + unsigned int header_len; + + if (icmp->icmp_type == ICMP_TIMEXCEED || icmp->icmp_type == ICMP_UNREACH) + header_len = 8; + else + fatal("%s passed ICMP packet with unhandled type %d", __func__, icmp->icmp_type); + if (header_len > *len) + return NULL; + *len -= header_len; + + return (char *) icmp + header_len; +} + +static bool parse_encapsulated_reply(const struct ip *ip, unsigned int len, + Reply *reply) { + sockaddr_in *addr_in; + + if (ip->ip_p == IPPROTO_ICMP) { + const struct icmp *icmp = (struct icmp *) ip_get_data(ip, &len); + if (icmp == NULL || len < 8 || ntohs(icmp->icmp_id) != global_id) + return false; + reply->token = ntohs(icmp->icmp_seq); + } else if (ip->ip_p == IPPROTO_TCP) { + const struct tcp_hdr *tcp = (struct tcp_hdr *) ip_get_data(ip, &len); + if (tcp == NULL || len < 2) + return false; + reply->token = ntohs(tcp->th_sport) ^ global_id; + } else if (ip->ip_p == IPPROTO_UDP) { + const struct udp_hdr *udp = (struct udp_hdr *) ip_get_data(ip, &len); + if (udp == NULL || len < 2) + return false; + reply->token = ntohs(udp->uh_sport) ^ global_id; + } else if (ip->ip_p == IPPROTO_SCTP) { + const struct sctp_hdr *sctp = (struct sctp_hdr *) ip_get_data(ip, &len); + if (sctp == NULL || len < 2) + return false; + reply->token = ntohs(sctp->sh_sport) ^ global_id; + } else { + if (len < 6) + return false; + /* Check IP ID for proto scan. */ + reply->token = ntohs(ip->ip_id) ^ global_id; + } + + addr_in = (struct sockaddr_in *) &reply->target_addr; + addr_in->sin_family = AF_INET; + addr_in->sin_addr = ip->ip_dst; + + return true; +} + +static bool read_reply(Reply *reply, pcap_t *pd, long timeout) { + const struct ip *ip; + unsigned int iplen; + struct link_header linkhdr; + sockaddr_in *addr_in; + + ip = (struct ip *) readip_pcap(pd, &iplen, timeout, &reply->rcvdtime, + &linkhdr, true); + if (ip == NULL) + return false; + if (ip->ip_v != 4) + return false; + + addr_in = (struct sockaddr_in *) &reply->from_addr; + addr_in->sin_family = AF_INET; + addr_in->sin_addr = ip->ip_src; + + if (ip->ip_p == IPPROTO_ICMP) { + /* ICMP responses comprise all the TTL exceeded messages we expect from all + probe types, as well as actual replies from ICMP probes. */ + const struct icmp *icmp; + unsigned int icmplen; + + icmplen = iplen; + icmp = (struct icmp *) ip_get_data(ip, &icmplen); + if (icmp == NULL || icmplen < 8) + return false; + if ((icmp->icmp_type == ICMP_TIMEXCEED + && icmp->icmp_code == ICMP_TIMEXCEED_INTRANS) + || icmp->icmp_type == ICMP_UNREACH) { + /* Get the encapsulated ICMP packet. */ + iplen = icmplen; + ip = (struct ip *) icmp_get_data(icmp, &iplen); + if (ip == NULL || ip->ip_v != 4 || iplen < 20) + return false; + if (!parse_encapsulated_reply(ip, iplen, reply)) + return false; + } else if (icmp->icmp_type == ICMP_ECHOREPLY + || icmp->icmp_type == ICMP_MASKREPLY + || icmp->icmp_type == ICMP_TSTAMPREPLY) { + if (ntohs(icmp->icmp_id) != global_id) + return false; + reply->token = ntohs(icmp->icmp_seq); + /* Reply came directly from the target. */ + reply->target_addr = reply->from_addr; + } else { + return false; + } + } else if (ip->ip_p == IPPROTO_TCP) { + const struct tcp_hdr *tcp; + unsigned int tcplen; + + tcplen = iplen; + tcp = (struct tcp_hdr *) ip_get_data(ip, &tcplen); + if (tcp == NULL || tcplen < 4) + return false; + reply->token = ntohs(tcp->th_dport) ^ global_id; + reply->target_addr = reply->from_addr; + } else if (ip->ip_p == IPPROTO_UDP) { + const struct udp_hdr *udp; + unsigned int udplen; + + udplen = iplen; + udp = (struct udp_hdr *) ip_get_data(ip, &udplen); + if (udp == NULL || udplen < 4) + return false; + reply->token = ntohs(udp->uh_dport) ^ global_id; + reply->target_addr = reply->from_addr; + } else if (ip->ip_p == IPPROTO_SCTP) { + const struct sctp_hdr *sctp; + unsigned int sctplen; + + sctplen = iplen; + sctp = (struct sctp_hdr *) ip_get_data(ip, &sctplen); + if (sctp == NULL || sctplen < 4) + return false; + reply->token = ntohs(sctp->sh_dport) ^ global_id; + reply->target_addr = reply->from_addr; + } else { + return false; + } + + return true; +} + +void TracerouteState::read_replies(long timeout) { + struct sockaddr_storage ss; + struct timeval now; + size_t sslen; + Reply reply; + + assert(timeout / 1000 <= (long) o.scan_delay); + timeout = MAX(timeout, 10000); + now = get_now(); + + while (timeout > 0 && read_reply(&reply, pd, timeout)) { + std::list::iterator it; + struct timeval oldnow; + HostState *host; + Probe *probe; + float rtt; + + oldnow = now; + now = get_now(); + timeout -= TIMEVAL_SUBTRACT(now, oldnow); + + probe = this->lookup_probe(&reply.target_addr, reply.token); + if (probe == NULL) + continue; + host = probe->host; + + sslen = sizeof(ss); + host->target->TargetSockAddr(&ss, &sslen); + if (sockaddr_storage_cmp(&ss, &reply.from_addr) == 0) { + if (host->reached_target == 0 || probe->ttl < host->reached_target) + host->reached_target = probe->ttl; + num_active_probes -= host->cancel_probes_above(probe->ttl); + } + + rtt = TIMEVAL_SUBTRACT(reply.rcvdtime, probe->sent_time) / 1000.0; + set_host_hop(host, probe->ttl, &reply.from_addr, rtt); + + it = find(host->unanswered_probes.begin(), host->unanswered_probes.end(), probe); + num_active_probes -= host->cancel_probe(it); + } +} + +void TracerouteState::cull_timeouts() { + std::list::iterator host_iter; + struct timeval now; + + now = get_now(); + + for (host_iter = active_hosts.begin(); host_iter != active_hosts.end(); host_iter++) { + while (!(*host_iter)->active_probes.empty() + && (*host_iter)->active_probes.front()->is_timedout(&now)) { + Probe *probe; + + probe = (*host_iter)->active_probes.front(); + if (o.debugging > 1) { + log_write(LOG_STDOUT, "Traceroute probe to %s TTL %d timed out\n", + probe->host->target->targetipstr(), probe->ttl); + } + set_host_hop_timedout(*host_iter, probe->ttl); + (*host_iter)->active_probes.pop_front(); + num_active_probes--; + if (probe->may_resend()) + (*host_iter)->pending_resends.push_front(probe); + } + } +} + +void TracerouteState::remove_finished_hosts() { + std::list::iterator it, next; + + for (it = active_hosts.begin(); it != active_hosts.end(); it = next) { + next = it; + next++; + if ((*it)->is_finished()) { + if (next_sending_host == it) + next_active_host(); + active_hosts.erase(it); + } + } +} + +/* Find the reverse-DNS names of the hops. */ +void TracerouteState::resolve_hops() { + std::set addrs; + std::set::iterator addr_iter; + std::vector::iterator host_iter; + std::map name_map; + Target **targets; + Hop *hop; + int i, n; + + /* First, put all the IPv4 addresses in a set to remove duplicates. This + re-resolves the addresses of the targets themselves, which is a little + inefficient. */ + for (host_iter = hosts.begin(); host_iter != hosts.end(); host_iter++) { + for (hop = (*host_iter)->hops; hop != NULL; hop = hop->parent) { + struct sockaddr_in *sin = (struct sockaddr_in *) &hop->addr; + if (hop->addr.ss_family == AF_INET) + addrs.insert(sin->sin_addr.s_addr); + } + } + n = addrs.size(); + /* Second, make an array of pointer to Target to suit the interface of + nmap_mass_rdns. */ + targets = (Target **) safe_malloc(sizeof(*targets) * n); + i = 0; + addr_iter = addrs.begin(); + while (i < n) { + struct sockaddr_in sin = { AF_INET }; + sin.sin_addr.s_addr = *addr_iter; + targets[i] = new Target(); + targets[i]->setTargetSockAddr((struct sockaddr_storage *) &sin, sizeof(sin)); + targets[i]->flags = HOST_UP; + i++; + addr_iter++; + } + nmap_mass_rdns(targets, n); + /* Third, make a map from addresses to names for easy lookup. */ + for (i = 0; i < n; i++) { + const char *hostname = targets[i]->HostName(); + if (*hostname == '\0') + hostname = NULL; + name_map[targets[i]->v4hostip()->s_addr] = hostname; + } + /* Finally, copy the names into the hops. */ + for (host_iter = hosts.begin(); host_iter != hosts.end(); host_iter++) { + for (hop = (*host_iter)->hops; hop != NULL; hop = hop->parent) { + struct sockaddr_in *sin = (struct sockaddr_in *) &hop->addr; + if (hop->addr.ss_family == AF_INET) { + const char *hostname = name_map[sin->sin_addr.s_addr]; + if (hostname != NULL) + hop->hostname = hostname; + } + } + } + for (i = 0; i < n; i++) + delete targets[i]; + free(targets); +} + +void TracerouteState::transfer_hops() { + std::vector::iterator it; + Hop *p; + + for (it = hosts.begin(); it != hosts.end(); it++) { + for (p = (*it)->hops; p != NULL; p = p->parent) { + TracerouteHop hop; + + /* Trim excessive hops. */ + if ((*it)->reached_target && p->ttl > (*it)->reached_target) + continue; + + hop.tag = p->tag; + if (p->addr.ss_family == 0) { + hop.timedout = true; + } else { + hop.timedout = false; + hop.rtt = p->rtt; + } + hop.name = p->hostname; + hop.addr = p->addr; + hop.ttl = p->ttl; + (*it)->target->traceroute_hops.push_front(hop); + } + + (*it)->target->traceroute_probespec = (*it)->pspec; + + /* Set the hop distance for OS fingerprints. */ + if ((*it)->reached_target) { + (*it)->target->distance = (*it)->reached_target; + (*it)->target->distance_calculation_method = DIST_METHOD_TRACEROUTE; + } + } +} + +Probe *TracerouteState::lookup_probe( + const struct sockaddr_storage *target_addr, u16 token) { + std::list::iterator host_iter; + std::list::iterator probe_iter; + + for (host_iter = active_hosts.begin(); host_iter != active_hosts.end(); host_iter++) { + struct sockaddr_storage ss; + size_t sslen; + + sslen = sizeof(ss); + (*host_iter)->target->TargetSockAddr(&ss, &sslen); + if (sockaddr_storage_cmp(&ss, target_addr) != 0) + continue; + for (probe_iter = (*host_iter)->unanswered_probes.begin(); + probe_iter != (*host_iter)->unanswered_probes.end(); + probe_iter++) { + if ((*probe_iter)->token == token) + return *probe_iter; + } + } + + return NULL; +} + +double TracerouteState::completion_fraction() const { + std::vector::const_iterator it; + double sum; + + sum = 0.0; + for (it = hosts.begin(); it != hosts.end(); it++) + sum += (*it)->completion_fraction(); + return sum / hosts.size(); +} + +int traceroute(std::vector &Targets) { + std::vector::iterator state_iter; + + if (Targets.empty() || Targets[0]->ifType() == devt_loopback) + return 1; + + TracerouteState global_state(Targets); + + global_id = get_random_u16(); + + ScanProgressMeter SPM("Traceroute"); + + o.current_scantype = TRACEROUTE; + + while (!global_state.active_hosts.empty()) { + struct timeval now; + long int timeout; + + global_state.send_new_probes(); + now = get_now(); + timeout = TIMEVAL_SUBTRACT(global_state.next_send_time, now); + global_state.read_replies(timeout); + global_state.cull_timeouts(); + global_state.remove_finished_hosts(); + + if (keyWasPressed()) + SPM.printStats(global_state.completion_fraction(), NULL); + } + + SPM.endTask(NULL, NULL); + + if (!o.noresolve) + global_state.resolve_hops(); + /* This puts the hops into the Targets known by the global_state. */ + global_state.transfer_hops(); + + if (hop_cache_size() > MAX_HOP_CACHE_SIZE) { + if (o.debugging) { + log_write(LOG_STDOUT, "Clearing hop cache that has grown to %d\n", + hop_cache_size()); + } + traceroute_hop_cache_clear(); + } + + return 1; +} + +static struct timeval get_now(struct timeval *now) { + struct timeval tv; + + if (now != NULL) + return *now; + gettimeofday(&tv, NULL); + + return tv; +} + +/* Convert the address in ss to a string. The result is returned in a static + buffer so you can't call this twice in arguments to printf, for example. */ +static const char *ss_to_string(const struct sockaddr_storage *ss) { + return inet_ntop_ez(ss, sizeof(*ss)); } diff --git a/traceroute.h b/traceroute.h index 668ec24b6..7f8c33f23 100644 --- a/traceroute.h +++ b/traceroute.h @@ -93,221 +93,6 @@ #include -/* Probe types */ -#define PROBE_TRACE 0 -#define PROBE_TTL 1 +int traceroute(std::vector &Targets); -/* Probe states */ -#define P_TIMEDOUT 0 /* probe has timedout */ -#define P_RETRANS 1 /* probe needs to be retransmitted */ -#define P_OK 2 /* waiting for response or received response */ - -/* Group states */ -#define G_OK P_OK -#define G_DEAD_TTL 3 /* TTL has reached maximum value */ -#define G_FINISH 5 /* tracing has complete successfully */ - -#define MAX_TTL 50 - -#define HOP_COL 0 -#define RTT_COL 1 -#define HOST_COL 2 - -#define NAMEIPLEN MAXHOSTNAMELEN+INET6_ADDRSTRLEN - -class NmapOutputTable; - -/* Keeps track of each probes timing state */ -class TimeInfo { - public: - TimeInfo(); - int retranLimit(); - void adjustTimeouts(struct timeval *recv, u16 scan_delay); - - unsigned long probeTimeout() { - return MIN(10000000, to.timeout * 10); - } - /* true if this probe has been replied to */ - u8 gotReply() { - return recvTime.tv_usec != 0 && recvTime.tv_sec != 0; - } - u8 getState() { - return state; - } - u8 setState(u8 state); - - struct timeout_info to; - /* set to true if this probe is going to - * consolidated because it has timed out */ - bool consolidated; - - /* Rtt and timeout calculation */ - struct timeval recvTime; - struct timeval sendTime; - - private: - u8 retransmissions; - u8 state; -}; - -/* traceprobes represent a single packet at a specific ttl. Traceprobes are - * stored inside tracegroups. */ -class TraceProbe { - public: - TraceProbe(u32 dip, u32 sip, u16 sport, struct probespec& probe); - ~TraceProbe(); - - /* Return the ip address and resolved hostname in a string such as - * "host.com (1.2.3.4)" or "6.6.6.6". */ - const char *nameIP(); - const char *HostName() { - if (!hostname || !(*hostname)) - return NULL; - else - return *hostname; - } - /* probe type is either a standard probe (PROBE_TRACE) or a hop distance - * probe (PROBE_TTL) */ - void setProbeType(u8 type) { - this->probetype = type; - } - u8 probeType() { - return probetype; - } - char *ipReplyStr() { - return inet_ntoa (ipreplysrc); - } - - /* protocol information for this probe */ - TimeInfo timing; - struct in_addr ipdst; - struct in_addr ipsrc; - struct in_addr ipreplysrc; - struct probespec probe; - u16 sport; - u8 ttl; - char **hostname; - - private: - u8 probetype; - char *hostnameip; -}; - -/* each trace group represents a target ip and contains a map of probes that - * have been sent/recv'ed to/from the ip */ -class TraceGroup { - public: - TraceGroup(u32 dip, u16 sport, struct probespec& probe); - ~TraceGroup(); - /* map of all probes sent to this TraceGroups IP address. The map is keyed - * by the source port number of the probe */ - std::map < u16, TraceProbe * >TraceProbes; - std::map < u16, TraceProbe * >::size_type size() { - return TraceProbes.size (); - } - /* checks for timedout probes and retransmits them Any probe that exceeds - * the timing limits is considered non-responsive */ - void retransmissions(std::vector < TraceProbe * >&retrans); - /* Returns a map from TTLs to probes, stripped of all unneeded probes and - * with timed-out probes marked for consolidation. */ - std::map < u8, TraceProbe * > consolidateHops() const; - /* the next ttl to send, if the destination has replied the ttl is - * decremented, if it hasn't it is incremented */ - void nextTTL(); - /* number of probes currently waiting for replies */ - void incRemaining(); - void decRemaining(); - char *IPStr(); - u8 getRemaining() { - return remaining; - } - u8 getState() { - return state; - } - u8 setState(u8 state); - u8 setHopDistance(u8 hop_distance, u8 ttl); - - /* Get the number of hops to the target, or -1 if unknown. Use this instead - * of reading hopDistance, which despite its name does not contain the final - * hop count. */ - int getDistance(); - - bool gotReply; - bool noDistProbe; - - /* Group wide timing */ - int scanDelay; - int maxRetransmissions; - u16 droppedPackets; - u16 repliedPackets; - u8 consecTimeouts; - /* protocol information */ - struct probespec probe; - u16 sport; - u32 ipdst; - /* estimated ttl distance to target */ - u8 hopDistance; - /* largest ttl send so far */ - u8 ttl; - /* the initial ttl guess. This is needed because the ttl may have to be - * incremented to reach the destination host. Once nmap has reached the - * destination it needs to start decrementing the ttl from the original - * value so no duplicate probes are sent. - * - * For example, if the guess is 20 but the target is at 23. We will start - * tracing backwards at 19. */ - u8 start_ttl; - u8 consolidation_start; - const u8 *src_mac_addr; - const u8 *nxt_mac_addr; - - private: - /* the number of probes sent but and not yet replied to */ - u8 remaining; - /* default state is G_OK, set to G_FINISH when - * complete or one of the G_* error codes if this - * group fails */ - u8 state; -}; - -/* Public interface to traceroute functionality */ -class Traceroute { - public: - Traceroute(const char *device_name, devtype type, const scan_lists * probe_ports); - ~Traceroute(); - - /* perform the traceroute on a list of targets */ - void trace(std::vector < Target * >&Targets); - /* Use nmaps rDNS functions to mass resolve the hops ip addresses */ - void resolveHops(); - /* display plain and XML traceroutes for target t */ - void outputTarget(Target * t); - - private: - /* map of all TraceGroups, keyed by - * the groups destination IP address */ - std::map < u32, TraceGroup * >TraceGroups; - - const struct scan_lists * scanlists; - Target **hops; - pcap_t *pd; - eth_t *ethsd; - int fd, total_size, cp_flag; - struct in_addr ref_ipaddr; - - /* called by outputTarget to log XML data */ - void outputXMLTrace(TraceGroup * tg); - /* find a responsive port for t based on scan results */ - const probespec getTraceProbe(Target * t); - /* sendTTLProbes() guesses the hop distance to a target by actively probing - * the host. */ - void sendTTLProbes(std::vector < Target * >&Targets, std::vector < Target * >&vaild_targets); - int sendProbe(TraceProbe * tp); - /* reads probe replies for all protocols. returns finished(), which returns - * true when all groups have finished or failed */ - bool readTraceResponses(); - bool finished(); - /* add message to output table "hops 1 to X are the same as ". - * This message should always come before none-consolidated hop output */ - void addConsolidationMessage(NmapOutputTable *Tbl, unsigned short row_count, unsigned short ttl); -}; +void traceroute_hop_cache_clear();