mirror of
https://github.com/nmap/nmap.git
synced 2025-12-06 04:31:29 +00:00
the network distance in SCAN.DS was calculated. Its value can be "L" for localhost, "D" for a direct connection, "I" for an ICMP TTL calculation, and "T" for a traceroute hop count. This is mainly for the benefit of OS integration, when it is sometimes important to distinguish between DS=1%DC=I (probably the result of forged TTLs) and DS=1%DC=D (a true one-hop connection.) [David]
1485 lines
55 KiB
C++
1485 lines
55 KiB
C++
/***************************************************************************
|
|
* traceroute.cc -- Parallel multi-protocol traceroute feature *
|
|
* *
|
|
***********************IMPORTANT NMAP LICENSE TERMS************************
|
|
* *
|
|
* The Nmap Security Scanner is (C) 1996-2009 Insecure.Com LLC. Nmap is *
|
|
* also a registered trademark of Insecure.Com LLC. This program is free *
|
|
* software; you may redistribute and/or modify it under the terms of the *
|
|
* GNU General Public License as published by the Free Software *
|
|
* Foundation; Version 2 with the clarifications and exceptions described *
|
|
* below. This guarantees your right to use, modify, and redistribute *
|
|
* this software under certain conditions. If you wish to embed Nmap *
|
|
* technology into proprietary software, we sell alternative licenses *
|
|
* (contact sales@insecure.com). Dozens of software vendors already *
|
|
* license Nmap technology such as host discovery, port scanning, OS *
|
|
* detection, and version detection. *
|
|
* *
|
|
* Note that the GPL places important restrictions on "derived works", yet *
|
|
* it does not provide a detailed definition of that term. To avoid *
|
|
* misunderstandings, we consider an application to constitute a *
|
|
* "derivative work" for the purpose of this license if it does any of the *
|
|
* following: *
|
|
* o Integrates source code from Nmap *
|
|
* o Reads or includes Nmap copyrighted data files, such as *
|
|
* nmap-os-db or nmap-service-probes. *
|
|
* o Executes Nmap and parses the results (as opposed to typical shell or *
|
|
* execution-menu apps, which simply display raw Nmap output and so are *
|
|
* not derivative works.) *
|
|
* o Integrates/includes/aggregates Nmap into a proprietary executable *
|
|
* installer, such as those produced by InstallShield. *
|
|
* o Links to a library or executes a program that does any of the above *
|
|
* *
|
|
* The term "Nmap" should be taken to also include any portions or derived *
|
|
* works of Nmap. This list is not exclusive, but is meant to clarify our *
|
|
* interpretation of derived works with some common examples. Our *
|
|
* interpretation applies only to Nmap--we don't speak for other people's *
|
|
* GPL works. *
|
|
* *
|
|
* If you have any questions about the GPL licensing restrictions on using *
|
|
* Nmap in non-GPL works, we would be happy to help. As mentioned above, *
|
|
* we also offer alternative license to integrate Nmap into proprietary *
|
|
* applications and appliances. These contracts have been sold to dozens *
|
|
* of software vendors, and generally include a perpetual license as well *
|
|
* as providing for priority support and updates as well as helping to *
|
|
* fund the continued development of Nmap technology. Please email *
|
|
* sales@insecure.com for further information. *
|
|
* *
|
|
* As a special exception to the GPL terms, Insecure.Com LLC grants *
|
|
* permission to link the code of this program with any version of the *
|
|
* OpenSSL library which is distributed under a license identical to that *
|
|
* listed in the included COPYING.OpenSSL file, and distribute linked *
|
|
* combinations including the two. You must obey the GNU GPL in all *
|
|
* respects for all of the code used other than OpenSSL. If you modify *
|
|
* this file, you may extend this exception to your version of the file, *
|
|
* but you are not obligated to do so. *
|
|
* *
|
|
* If you received these files with a written license agreement or *
|
|
* contract stating terms other than the terms above, then that *
|
|
* alternative license agreement takes precedence over these comments. *
|
|
* *
|
|
* Source is provided to this software because we believe users have a *
|
|
* right to know exactly what a program is going to do before they run it. *
|
|
* This also allows you to audit the software for security holes (none *
|
|
* have been found so far). *
|
|
* *
|
|
* Source code also allows you to port Nmap to new platforms, fix bugs, *
|
|
* and add new features. You are highly encouraged to send your changes *
|
|
* to nmap-dev@insecure.org for possible incorporation into the main *
|
|
* distribution. By sending these changes to Fyodor or one of the *
|
|
* Insecure.Org development mailing lists, it is assumed that you are *
|
|
* offering the Nmap Project (Insecure.Com LLC) the unlimited, *
|
|
* non-exclusive right to reuse, modify, and relicense the code. Nmap *
|
|
* will always be available Open Source, but this is important because the *
|
|
* inability to relicense code has caused devastating problems for other *
|
|
* Free Software projects (such as KDE and NASM). We also occasionally *
|
|
* relicense the code to third parties as discussed above. If you wish to *
|
|
* specify special license conditions of your contributions, just say so *
|
|
* when you send them. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, but *
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
|
* General Public License v2.0 for more details at *
|
|
* http://www.gnu.org/licenses/gpl-2.0.html , or in the COPYING file *
|
|
* included with Nmap. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
|
|
/*
|
|
* Written by Eddie Bell <ejlbell@gmail.com> 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
|
|
*/
|
|
|
|
#include "traceroute.h"
|
|
#include "NmapOps.h"
|
|
#include "NmapOutputTable.h"
|
|
#include "nmap_tty.h"
|
|
#include "nmap_dns.h"
|
|
#include "osscan2.h"
|
|
#include "protocols.h"
|
|
#include "timing.h"
|
|
#include "utils.h"
|
|
#include <algorithm>
|
|
#include <dnet.h>
|
|
#include <stdlib.h>
|
|
|
|
using namespace std;
|
|
extern NmapOps o;
|
|
|
|
static void enforce_scan_delay (struct timeval *, int);
|
|
static char *hostStr(u32 ip);
|
|
|
|
/* 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];
|
|
|
|
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;
|
|
|
|
if (type == devt_loopback)
|
|
return;
|
|
|
|
/* 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
|
|
}
|
|
|
|
/* rely on each group using the same device */
|
|
pd = my_pcap_open_live(device_name, 100, o.spoofsource ? 1 : 0, 2);
|
|
|
|
memset(commonPath, 0, sizeof(commonPath));
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* 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();
|
|
}
|
|
|
|
/* 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;
|
|
|
|
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;
|
|
}
|
|
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 (o.af() == AF_INET6) {
|
|
error("Traceroute does not support ipv6");
|
|
return;
|
|
}
|
|
|
|
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 <hops> 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<u32, TraceGroup *>::iterator tg_iter;
|
|
map<u16, TraceProbe *>::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);
|
|
else
|
|
len = Snprintf(mbuf, sizeof(mbuf), "Hops 1-%d are the same as for %s", ttl, ip);
|
|
|
|
assert(len);
|
|
Tbl->addItem(row_count, HOP_COL, true, "-", 1);
|
|
Tbl->addItem(row_count, RTT_COL, true, true, mbuf, len);
|
|
}
|
|
|
|
/* 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, "...");
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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)))
|
|
log_write(LOG_PLAIN, "! destination not reached (%s)\n", inet_ntoa(tp->ipdst));
|
|
|
|
log_flush(LOG_PLAIN);
|
|
delete Tbl;
|
|
}
|
|
|
|
/* print a trace in xml */
|
|
void
|
|
Traceroute::outputXMLTrace(TraceGroup * tg) {
|
|
map < u16, TraceProbe * >::const_iterator it;
|
|
TraceProbe *tp = NULL;
|
|
const char *hostname_tmp = NULL;
|
|
struct in_addr addr;
|
|
long timediff;
|
|
short ttl_count;
|
|
|
|
/* XML traceroute header */
|
|
log_write(LOG_XML, "<trace ");
|
|
if (tg->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");
|
|
|
|
/* add missing hosts host from the common path */
|
|
for (ttl_count = 1 ; ttl_count < tg->TraceProbes.begin()->second->ttl; ttl_count++) {
|
|
addr.s_addr = commonPath[ttl_count];
|
|
log_write(LOG_XML, "<hop ttl=\"%d\" rtt=\"--\" ", ttl_count);
|
|
log_write(LOG_XML, "ipaddr=\"%s\"", inet_ntoa(addr));
|
|
if ((hostname_tmp = lookup_cached_host(commonPath[ttl_count])) != NULL)
|
|
log_write(LOG_XML, " host=\"%s\"", hostname_tmp);
|
|
log_write(LOG_XML, "/>\n");
|
|
}
|
|
|
|
/* display normal traceroute nodes. Consolidation based on the common path
|
|
* is not performed */
|
|
for (it = tg->TraceProbes.begin() ;it != tg->TraceProbes.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, "<hop ttl=\"%d\" rtt=\"%.2f\" ipaddr=\"%s\"", tp->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, "<error errorstr=\"maximum TTL reached\"/>\n");
|
|
else if (!tg->gotReply || (tp && (tp->ipreplysrc.s_addr != tg->ipdst)))
|
|
log_write(LOG_XML, "<error errorstr=\"destination not reached (%s)\"/>\n", inet_ntoa(tp->ipdst));
|
|
|
|
/* traceroute XML footer */
|
|
log_write(LOG_XML, "</trace>\n");
|
|
log_flush(LOG_XML);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
TraceGroup::~TraceGroup() {
|
|
map < u16, TraceProbe * >::const_iterator it;
|
|
for (it = TraceProbes.begin(); it != TraceProbes.end(); ++it)
|
|
delete it->second;
|
|
}
|
|
|
|
/* 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;
|
|
|
|
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--;
|
|
} else {
|
|
ttl++;
|
|
hopDistance++;
|
|
}
|
|
}
|
|
|
|
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 {
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
/* 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;
|
|
|
|
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));
|
|
}
|
|
return;
|
|
}
|
|
|
|
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;
|
|
}
|