/*
* scamper_do_trace.c
*
* $Id: scamper_do_trace.c,v 1.135 2007/05/23 05:13:56 mjl Exp $
*
* Copyright (C) 2005-2007 The University of Waikato
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sys/param.h>
#if !defined(__sun__)
#include <sys/sysctl.h>
#endif
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <net/if.h>
#if defined(__linux__)
#define __FAVOR_BSD
#else
#include <net/if_dl.h>
#endif
#if defined(__sun__)
#define BSD_COMP
#endif
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/udp.h>
#include <netinet/icmp6.h>
#include <netinet/tcp.h>
#if defined(__APPLE__)
#include <stdint.h>
#endif
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#if defined(DMALLOC)
#include <dmalloc.h>
#endif
#include "scamper.h"
#include "scamper_addr.h"
#include "scamper_list.h"
#include "scamper_tlv.h"
#include "scamper_trace.h"
#include "scamper_task.h"
#include "scamper_queue.h"
#include "scamper_icmp_resp.h"
#include "scamper_fds.h"
#include "scamper_dl.h"
#include "scamper_probe.h"
#include "scamper_rtsock.h"
#include "scamper_privsep.h"
#include "scamper_getsrc.h"
#include "scamper_file.h"
#include "scamper_outfiles.h"
#include "scamper_addresslist.h"
#include "scamper_debug.h"
#include "scamper_do_trace.h"
#include "scamper_addr2mac.h"
#include "scamper_options.h"
#include "scamper_icmp4.h"
#include "scamper_icmp6.h"
#include "scamper_tcp4.h"
#include "scamper_udp4.h"
#include "scamper_udp6.h"
#include "utils.h"
#define SCAMPER_DO_TRACE_ATTEMPTS_MIN 1
#define SCAMPER_DO_TRACE_ATTEMPTS_DEF 2
#define SCAMPER_DO_TRACE_ATTEMPTS_MAX 9
#define SCAMPER_DO_TRACE_DPORT_MIN 1
#define SCAMPER_DO_TRACE_DPORT_DEF (32768+777)
#define SCAMPER_DO_TRACE_DPORT_MAX 65535
#define SCAMPER_DO_TRACE_FIRSTHOP_MIN 1
#define SCAMPER_DO_TRACE_FIRSTHOP_DEF 1
#define SCAMPER_DO_TRACE_FIRSTHOP_MAX 255
#define SCAMPER_DO_TRACE_GAPLIMIT_MIN 1
#define SCAMPER_DO_TRACE_GAPLIMIT_DEF 5
#define SCAMPER_DO_TRACE_GAPLIMIT_MAX 255
#define SCAMPER_DO_TRACE_HOLDTIME_MIN 0
#define SCAMPER_DO_TRACE_HOLDTIME_DEF 0
#define SCAMPER_DO_TRACE_HOLDTIME_MAX 255
#define SCAMPER_DO_TRACE_HOPLIMIT_MIN 0
#define SCAMPER_DO_TRACE_HOPLIMIT_DEF 0
#define SCAMPER_DO_TRACE_HOPLIMIT_MAX 255
#define SCAMPER_DO_TRACE_LOOPS_MIN 0
#define SCAMPER_DO_TRACE_LOOPS_DEF 1 /* stop on the first loop found */
#define SCAMPER_DO_TRACE_LOOPS_MAX 255
#define SCAMPER_DO_TRACE_PPS_MIN 1
#define SCAMPER_DO_TRACE_PPS_MAX 1000
#define SCAMPER_DO_TRACE_PPS_DEF 20
#define SCAMPER_DO_TRACE_SPORT_MIN 1
#define SCAMPER_DO_TRACE_SPORT_MAX 65535
#define SCAMPER_DO_TRACE_TOS_MIN 0
#define SCAMPER_DO_TRACE_TOS_DEF 0
#define SCAMPER_DO_TRACE_TOS_MAX 255
#define SCAMPER_DO_TRACE_WAIT_MIN 1
#define SCAMPER_DO_TRACE_WAIT_DEF 5
#define SCAMPER_DO_TRACE_WAIT_MAX 10
/*
* pmtud_L2_state
*
* this struct records state when inferring the MTU of the underlying media.
*
* when scamper has to discover the MTU of the link itself, it uses the L2
* table above to choose a suitable initial guess. it records the index
* into the L2 table into L2_idx.
*/
typedef struct pmtud_L2_state
{
int idx; /* index into the L2 table */
int lower; /* lower bounds of the L2 search space */
int upper; /* upper bounds of the L2 search space */
int in; /* probe size not to get a suitable response */
int out; /* size of probe to infer the underlying MTU */
scamper_trace_hop_t *hop; /* the last probe to obtain a response */
} pmtud_L2_state_t;
/*
* pmtud_TTL_state
*
* this struct records state when inferring the TTL range of hops that
* are responsible for not sending a fragmentation required message where
* one is required.
*/
typedef struct pmtud_TTL_state
{
int lower; /* lower bounds of the TTL search space */
int upper; /* upper bounds of the TTL search space */
scamper_trace_hop_t *hop; /* the last TTL probe to obtain a response */
} pmtud_TTL_state_t;
/*
* pmtud_L2
*
* this struct associates a known MTU with an index into an array.
*/
typedef struct pmtud_L2
{
int idx; /* index into the L2 array where this node resides */
int mtu; /* the MTU of the link */
char *descr; /* some description of the L2 media */
} pmtud_L2_t;
/*
* trace_probe
*
* this struct keeps state of each probe sent with the trace
*/
typedef struct trace_probe
{
struct timeval tx_tv; /* the time we transmitted the probe */
struct timeval rx_tv; /* the time we received the first answer */
scamper_addr_t *rx_mac; /* the mac addr where we received the first answer */
uint16_t size; /* the size of the probe sent */
uint8_t ttl; /* the TTL that was set for the probe */
uint8_t id; /* the attempt number made with ttl/size params */
uint8_t rx; /* how many responses scamper got to the probe */
uint8_t mode; /* the mode scamper was in when probe was sent */
uint8_t flags; /* the probe's flags */
} trace_probe_t;
#define TRACE_PROBE_FLAG_DL_TX 0x01
#define TRACE_PROBE_FLAG_DL_RX 0x02
#define TRACE_ALLOC_HOPS 16
/*
* trace_state
*
* this is a fairly large struct that keeps state for the traceroute
* process. it also deals with state in the PMTUD phase, if used.
*/
typedef struct trace_state
{
uint8_t mode; /* current trace mode scamper is in */
uint8_t alive; /* non-zero if responsive */
uint8_t ttl; /* ttl to set in the probe packet */
uint8_t attempt; /* attempt number at the current probe */
uint16_t alloc_hops; /* number of trace->hops allocated */
uint16_t payload_size; /* how much payload to include */
uint16_t header_size; /* size of headers */
scamper_fd_t *route; /* fd to query route socket with */
scamper_fd_t *icmp; /* fd to listen to icmp packets with */
scamper_fd_t *probe; /* fd to probe with */
scamper_fd_t *dl; /* struct to use with datalink access */
uint8_t *dl_hdr; /* header to use with datalink */
uint16_t dl_size; /* how large the header is */
trace_probe_t **probes; /* probes sent so far */
uint16_t id_next; /* next id to use in probes */
uint16_t id_max; /* maximum id available */
pmtud_L2_state_t *L2; /* state kept when doing L2 MTU search */
pmtud_TTL_state_t *TTL; /* state kept when doing a TTL search */
scamper_trace_hop_t *last_fragmsg; /* last fragmentation msg stored */
} trace_state_t;
const uint8_t MODE_TRACE = 0;
const uint8_t MODE_CHECK_LIFE = 1;
const uint8_t MODE_PMTUD_DEFAULT = 2;
const uint8_t MODE_PMTUD_SILENT_L2 = 3;
const uint8_t MODE_PMTUD_SILENT_TTL = 4;
const uint8_t MODE_PMTUD_BADSUGG = 5;
const uint8_t MODE_RTSOCK = 6;
#define MODE_MIN MODE_TRACE
#define MODE_MAX MODE_RTSOCK
/* the callback functions registered with the trace task */
static scamper_task_funcs_t trace_funcs;
/* address cache used to avoid reallocating the same address multiple times */
extern scamper_addrcache_t *addrcache;
/* temporary buffer shared amongst traceroutes */
static uint8_t *pktbuf = NULL;
static size_t pktbuf_len = 0;
/* socket used to obtain the MTU of a particular interface */
static int if_sock = -1;
/*
* these MTUs were largely taken from the NetBSD version of traceroute, and
* are used to choose a packet size to probe with in the absense of a
* Fragmentation Needed message.
*
* they have been annoted with their corresponding Layer 2 type, largely
* taken from RFC 1191
*/
static const pmtud_L2_t L2[] =
{
{ 0, 68, "RFC791 MTU"}, /* Official RFC 791 minimum MTU */
{ 1, 296, "P2P low delay"}, /* Point-to-Point links, (low delay) */
{ 2, 508, ""},
{ 3, 512, "NetBIOS"}, /* NetBIOS */
{ 4, 544, "DEC Portal"}, /* DEC IP Portal */
{ 5, 552, ""},
{ 6, 576, "v4 min MTU"}, /* X25 MTU, IPv4 Minimum MTU */
{ 7, 1006, "SLIP"}, /* SLIP */
{ 8, 1280, "v6 min MTU"}, /* IPv6 Minimum MTU */
{ 9, 1454, "PPPoE ADSL"}, /* an optimally sized PPPoE frame in DSL */
{10, 1480, "v4tun Ether"}, /* Ethernet MTU with tunnel over IPv4 */
{11, 1492, "IEEE 802.3"}, /* IEEE 802.3 */
{12, 1500, "Ethernet"}, /* Ethernet MTU */
{13, 1514, "Ethernet Max"}, /* Ethernet Max MTU */
{14, 1536, "Exp. Ether"}, /* Exp. Ethernet Nets */
{15, 2002, "IEEE 802.5"}, /* IEEE 802.5, Recommended MTU */
{16, 2048, "Wideband"}, /* Wideband Network */
{17, 4352, "FDDI"}, /* FDDI */
{18, 4464, "IEEE 802.5"}, /* IEEE 802.5, Maximum MTU */
{19, 4470, "IP over ATM"}, /* ATM / T3 / SONET SDH */
{20, 8166, "IEEE 802.4"}, /* IEEE 802.4 */
{21, 9000, "Broadcom GigE"}, /* Broadcom GigE MTU */
{22, 9192, "OC-192"}, /* OC-192 and other really fast media */
{23, 16110, "Intel GigE"}, /* Intel Pro 1000 MTU */
{24, 17914, "Token Ring"}, /* 16Mb IBM Token Ring */
{25, 65535, "IPv[46] MTU"} /* The IPv[46] Maximum MTU */
};
static const pmtud_L2_t *L2_1454 = &L2[9];
static const pmtud_L2_t *L2_1500 = &L2[12];
static const int L2_cnt = sizeof(L2) / sizeof(pmtud_L2_t);
#define TRACE_OPT_DPORT 1
#define TRACE_OPT_FIRSTHOP 2
#define TRACE_OPT_GAPLIMIT 3
#define TRACE_OPT_LOOPS 4
#define TRACE_OPT_MAXTTL 5
#define TRACE_OPT_PMTUD 6
#define TRACE_OPT_PROTOCOL 7
#define TRACE_OPT_ATTEMPTS 8
#define TRACE_OPT_ALLATTEMPTS 9
#define TRACE_OPT_SPORT 10
#define TRACE_OPT_TOS 11
#define TRACE_OPT_WAIT 12
#define TRACE_OPT_GAPCONT 13
static const scamper_option_in_t trace_opts_in[] = {
{'d', NULL, TRACE_OPT_DPORT, SCAMPER_OPTION_TYPE_NUM},
{'f', NULL, TRACE_OPT_FIRSTHOP, SCAMPER_OPTION_TYPE_NUM},
{'g', NULL, TRACE_OPT_GAPLIMIT, SCAMPER_OPTION_TYPE_NUM},
{'G', NULL, TRACE_OPT_GAPCONT, SCAMPER_OPTION_TYPE_NULL},
{'l', NULL, TRACE_OPT_LOOPS, SCAMPER_OPTION_TYPE_NUM},
{'m', NULL, TRACE_OPT_MAXTTL, SCAMPER_OPTION_TYPE_NUM},
{'M', NULL, TRACE_OPT_PMTUD, SCAMPER_OPTION_TYPE_NULL},
{'P', NULL, TRACE_OPT_PROTOCOL, SCAMPER_OPTION_TYPE_STR},
{'q', NULL, TRACE_OPT_ATTEMPTS, SCAMPER_OPTION_TYPE_NUM},
{'Q', NULL, TRACE_OPT_ALLATTEMPTS, SCAMPER_OPTION_TYPE_NULL},
{'s', NULL, TRACE_OPT_SPORT, SCAMPER_OPTION_TYPE_NUM},
{'t', NULL, TRACE_OPT_TOS, SCAMPER_OPTION_TYPE_NUM},
{'w', NULL, TRACE_OPT_WAIT, SCAMPER_OPTION_TYPE_NUM},
};
static const int trace_opts_cnt = SCAMPER_OPTION_COUNT(trace_opts_in);
/*
* if_getmtu
*
* given an interface index, return the MTU of it. return zero if
* we can't get the interface's MTU.
*/
static int if_getmtu(const int ifindex, uint16_t *ifmtu)
{
struct ifreq ifr;
int mtu;
assert(ifindex >= 0);
/* given the index, return the interface name to query */
if(if_indextoname((unsigned int)ifindex, ifr.ifr_name) == NULL)
{
printerror(errno, strerror, __func__, "could not if_indextoname");
return -1;
}
if(ioctl(if_sock, SIOCGIFMTU, &ifr) == -1)
{
printerror(errno, strerror, __func__, "could not SIOCGIFMTU");
return -1;
}
#if defined(__sun__)
mtu = ifr.ifr_metric;
#else
mtu = ifr.ifr_mtu;
#endif
if(mtu >= 0 && mtu <= 65535)
{
*ifmtu = mtu;
return 0;
}
return -1;
}
#if defined(__linux__) || defined(__sun__)
static int if_getmac(const int ifindex, uint8_t *mac)
{
struct ifreq ifr;
if(if_indextoname(ifindex, ifr.ifr_name) == NULL)
{
printerror(errno, strerror, __func__, "could not if_indextoname");
return -1;
}
#if defined(__linux__)
if(ioctl(if_sock, SIOCGIFHWADDR, &ifr) == -1)
{
printerror(errno, strerror, __func__, "could not SIOCGIFHWADDR");
return -1;
}
memcpy(mac, ifr.ifr_hwaddr.sa_data, 6);
#elif defined(__sun__)
if(ioctl(if_sock, SIOCGENADDR, &ifr) == -1)
{
printerror(errno, strerror, __func__, "could not SIOCGENADDR");
return -1;
}
memcpy(mac, ifr.ifr_enaddr, 6);
#endif
return 0;
}
#else
static int if_getmac(const int ifindex, uint8_t *mac)
{
struct if_msghdr *ifm;
struct sockaddr_dl *sdl;
int mib[6];
size_t len;
uint8_t *buf;
mib[0] = CTL_NET;
mib[1] = AF_ROUTE;
mib[2] = 0;
mib[3] = AF_LINK;
mib[4] = NET_RT_IFLIST;
mib[5] = ifindex;
if(sysctl(mib, 6, NULL, &len, NULL, 0) == -1)
{
printerror(errno, strerror, __func__, "could not sysctl buflen");
return -1;
}
if((buf = malloc(len)) == NULL)
{
printerror(errno, strerror, __func__, "could not malloc buf");
return -1;
}
if(sysctl(mib, 6, buf, &len, NULL, 0) < 0)
{
printerror(errno, strerror, __func__, "could not sysctl data");
free(buf);
return -1;
}
ifm = (struct if_msghdr *)buf;
sdl = (struct sockaddr_dl *)(buf+sizeof(struct if_msghdr));
memcpy(mac, LLADDR(sdl), 6);
free(buf);
return 0;
}
#endif
/*
* pmtud_L2_hop
*
* utility to replace the currently cached hop record recorded when scamper
* does the L2 MTU search.
*/
static void pmtud_L2_hop(trace_state_t *state, scamper_trace_hop_t *hop)
{
assert(state->L2 != NULL);
if(state->L2->hop != NULL) scamper_trace_hop_free(state->L2->hop);
state->L2->hop = hop;
return;
}
/*
* pmtud_L2_set_probesize
*
* given the lower and upper values of the PMTU search, suggest a packet
* size to probe next. apply a few heuristics to the search to try and
* find the PMTU to the next node faster.
*/
static void pmtud_L2_set_probesize(trace_state_t *state,
const int lower, const int upper)
{
int idx = state->L2->idx;
int size;
/* callers should detect end of L2 search before calling this function */
assert(lower + 1 != upper);
/* make sure the L2->idx parameter has been set (to something reasonable) */
assert(idx >= 0);
assert(idx < L2_cnt);
/* make sure the suggested window size is within the current window */
assert(state->L2->lower == -1 || lower >= state->L2->lower);
assert(state->L2->upper == -1 || upper <= state->L2->upper);
/*
* if we've narrowed it down to between two entries in the L2 table,
* then try one byte higher than the lower, as there's a fair chance
* the underlying mtu will be L2[idx].mtu.
*
* we make an exception if the lower bounds is Ethernet: there exists
* a strong possibility the underlying MTU is Ethernet, and the cost
* of guessing wrong [i.e. getting an unexpected response] is small.
*/
if(lower == 1500 || (lower == L2[idx].mtu && upper <= L2[idx+1].mtu))
{
size = lower + 1;
}
/*
* if there is a media MTU higher than the current lower bounds that
* is smaller than the upper bounds, then try it
*/
else if(lower >= L2[idx].mtu && L2[idx+1].mtu < upper)
{
size = L2[++idx].mtu;
}
/*
* if we did not get a response to the last media MTU probe, and there
* is a smaller known media MTU to try, then try it now
*/
else if(upper == L2[idx].mtu && lower < L2[idx-1].mtu)
{
size = L2[--idx].mtu;
}
/*
* scamper is operating between two known MTU types, do a binary chop
*/
else
{
size = (lower + upper) / 2;
}
state->attempt = 0;
state->payload_size = size - state->header_size;
state->L2->idx = idx;
state->L2->lower = lower;
state->L2->upper = upper;
return;
}
/*
* pmtud_L2_init
*
* utility to search the L2 table for a suitable initial probe size, based
* on known [to scamper] L2 media MTUs in relation to the last probe sent that
* went unacknowledged.
*/
static int pmtud_L2_init(trace_state_t *state)
{
pmtud_L2_state_t *L2s;
int size = state->header_size + state->payload_size;
int idx;
/*
* if the probe that was not answered is > 1500 bytes and scamper has
* not got a response to a packet 1500 bytes or larger yet, then
* forcibly try the ethernet MTU next, as the chances are good that the
* media will be plain old ethernet.
*/
if(size > 1500)
{
idx = L2_1500->idx;
}
/*
* if the probe that was not answered is > 1454 bytes, then forcibly try
* the lower bounds of X-over-ethernet types.
*/
else if(size > 1454)
{
idx = L2_1454->idx;
}
else
{
for(idx=0; idx<L2_cnt-1; idx++)
{
if(size > L2[idx].mtu && size <= L2[idx+1].mtu)
{
break;
}
}
}
if((L2s = malloc(sizeof(pmtud_L2_state_t))) == NULL)
{
return -1;
}
L2s->idx = idx;
L2s->hop = NULL;
L2s->lower = -1;
L2s->upper = size;
L2s->in = size;
L2s->out = -1;
state->L2 = L2s;
state->payload_size = L2[idx].mtu - state->header_size;
state->attempt = 0;
return 0;
}
/*
* pmtud_TTL_hop
*
* utility to replace the currently cached hop record recorded when scamper
* does the TTL search.
*/
static void pmtud_TTL_hop(trace_state_t *state, scamper_trace_hop_t *hop)
{
if(state->TTL->hop != NULL)
{
scamper_trace_hop_free(state->TTL->hop);
}
state->TTL->hop = hop;
return;
}
/*
* pmtud_TTL_set_probettl
*
* return: 0 if there are no more TTLs to probe, 1 if probing should continue
*/
static int pmtud_TTL_set_probettl(scamper_task_t *task,
const int lower, int upper)
{
scamper_trace_t *trace = task->data;
trace_state_t *state = task->state;
int cur;
/* check to see if we have to do any more TTL searching */
while(lower + 1 < upper)
{
/* halve the TTL space */
cur = (lower + upper) / 2;
/*
* check to see if experience at soliciting a TTL expired message has
* been good. skip TTLs that have been non-responsive
*/
while(cur < upper && trace->hops[cur-1] == NULL)
{
cur++;
}
/* scamper got a suitable TTL probe value, so we are done */
if(cur != upper)
{
state->TTL->lower = lower;
state->TTL->upper = upper;
state->ttl = cur;
state->attempt = 0;
return 1;
}
/*
* there are no TTLs above the half-way point to probe for, so try for
* ones lower
*/
upper = (lower + upper) / 2;
}
return 0;
}
/*
* hop_find
*
* check to see if there is any other hop in the trace with the
* same address
*/
static scamper_trace_hop_t *hop_find(const scamper_trace_t *trace,
const scamper_addr_t *addr)
{
scamper_trace_hop_t *hop;
uint16_t i;
for(i=0; i<trace->hop_count; i++)
{
for(hop = trace->hops[i]; hop != NULL; hop = hop->hop_next)
{
if(scamper_addr_cmp(hop->hop_addr, addr) == 0)
{
return hop;
}
}
}
return NULL;
}
/*
* pmtud_TTL_init
*
* initialise the bounds of a TTL search
*/
static int pmtud_TTL_init(scamper_task_t *task)
{
scamper_trace_hop_t *hop;
scamper_trace_t *trace = task->data;
trace_state_t *state = task->state;
int lower, upper;
uint8_t turn_ttl;
if((state->TTL = malloc_zero(sizeof(pmtud_TTL_state_t))) == NULL)
{
return -1;
}
/*
* the packet size that is dropped silently is the size we are
* doing a TTL limited search with
*/
state->payload_size = state->L2->in - state->header_size;
/*
* use the last ICMP fragmentation required message recorded in the
* path MTU discovery phase to infer a suitable lower-bound for inferring
* the range of TTLs that could be responsible for not sending an ICMP
* fragmentation required message
*/
if(state->last_fragmsg == NULL)
{
lower = 0;
}
else
{
SCAMPER_TRACE_HOP_GET_TURN_TTL(state->last_fragmsg, turn_ttl);
if((lower = state->last_fragmsg->hop_probe_ttl - turn_ttl) < 1)
{
lower = 0;
}
}
/*
* the upper bound of TTLs to search is set by closest response past
* the hop that sends nothing
*/
if((hop = hop_find(trace, state->L2->hop->hop_addr)) != NULL)
{
upper = hop->hop_probe_ttl;
}
else
{
SCAMPER_TRACE_HOP_GET_TURN_TTL(state->L2->hop, turn_ttl);
upper = state->L2->hop->hop_probe_ttl - turn_ttl + 1;
}
/* if the TTL limited search is a null operation, then say so */
if(pmtud_TTL_set_probettl(task, lower, upper) == 0)
{
return 0;
}
return 1;
}
/*
* pmtud_hopins
*
* take the hop structure and put it into the list of hops based on the
* TTL distance into the path.
*/
static void pmtud_hopins(scamper_trace_t *trace, scamper_trace_hop_t *hop)
{
scamper_trace_hop_t *cur, *pre;
assert(hop != NULL);
if((pre = trace->pmtud->hops) == NULL)
{
trace->pmtud->hops = hop;
return;
}
for(cur = pre->hop_next; cur != NULL; cur = cur->hop_next)
{
pre = cur;
}
pre->hop_next = hop;
hop->hop_next = cur;
return;
}
/*
* pmtu_L2_search_end
*
* scamper has had to infer the underlying next-hop MTU due to a pmtud
* fault. given the hop used to infer the nhmtu, insert that into the
* trace and tidy up.
*/
static int pmtud_L2_search_end(scamper_task_t *task)
{
scamper_trace_t *trace = task->data;
trace_state_t *state = task->state;
scamper_trace_hop_t *hop;
uint16_t out;
assert(state->L2->out >= 0);
assert(state->L2->out <= 65535);
out = state->L2->out;
/*
* copy details of the TTL-expired message furthest into the path
* into the trace if there is one to copy
*/
if(state->TTL != NULL)
{
/*
* if there was no TTL response with the large packet from anywhere
* in the path
*/
if(state->TTL->hop == NULL)
{
/*
* if the lowest TTL tried was zero, then we infer that the host
* itself has an MTU mismatch with the particular router it is
* using for the destination
*/
if(state->TTL->lower == 0)
{
scamper_tlv_set(&trace->pmtud->tlvs,
SCAMPER_TRACE_PMTUD_TLV_OUTMTU, 2, &out);
}
}
else
{
pmtud_hopins(trace, state->TTL->hop);
}
free(state->TTL);
state->TTL = NULL;
}
/*
* copy details of the hop to terminate the largest probe into
* the pmtu struct. hops between the TTL expired message (if we
* have one) and the ICMP unreach message have their PMTU inferred
*/
if((hop = state->L2->hop) != NULL)
{
pmtud_hopins(trace, hop);
state->last_fragmsg = hop;
free(state->L2);
state->L2 = NULL;
/*
* if the hop that we last recorded is a hop message that would
* ordinarily have caused scamper to stop PMTU discovery, then
* stop it now
*/
if(!SCAMPER_TRACE_HOP_IS_ICMP_PACKET_TOO_BIG(hop))
{
trace->pmtud->pmtu = hop->hop_probe_size;
scamper_queue_done(task->queue, scamper_holdtime_get()*1000);
return 1;
}
}
if(state->L2 != NULL)
{
free(state->L2);
state->L2 = NULL;
}
state->payload_size = out - state->header_size;
state->mode = MODE_PMTUD_DEFAULT;
state->attempt = 0;
state->ttl = 255;
return 0;
}
/*
* trace_ipid_fudge
*
* play games with the embedded IP ID, which may come back with a different
* IP ID than what was sent; return the ID of the corresponding probe in *id.
* this code was inspired by information from David Malone.
*
* the IPID transmitted is assigned from a counter (state->id_next) which
* starts from one -- *not* zero. this is so systems that zero the IPID
* will not confuse this algorithm.
*
* the IPID is transmitted by scamper in network byte order.
*
*/
static int trace_ipid_fudge(const trace_state_t *state,
const uint16_t ipid, uint16_t *id)
{
/* ensure the IP ID is not zero */
if(ipid == 0)
{
return -1;
}
/* check if the IP ID is in range */
if(ipid <= state->id_next)
{
*id = ipid - 1;
return 0;
}
/* check if the IP ID was incremented */
if(ipid == state->id_next + 1)
{
scamper_debug(__func__, "ip id one greater than sent");
*id = ipid - 2;
return 0;
}
/* check if the IP ID was byte swapped. XXX: is this correct? */
if(byteswap16(ipid) <= state->id_next)
{
scamper_debug(__func__, "ip id byte swapped");
*id = byteswap16(ipid) - 1;
return 0;
}
return -1;
}
/*
* trace_stop
*
* set the trace's stop parameters to whatever it is passed
*/
static void trace_stop(scamper_trace_t *trace,
const uint8_t reason, const uint8_t data)
{
/* if we've already set a stop reason, then don't clobber it */
if(trace->stop_reason != SCAMPER_TRACE_STOP_NONE)
{
scamper_debug(__func__, "reason %d/%d preceeds %d/%d",
trace->stop_reason, trace->stop_data, reason, data);
return;
}
trace->stop_reason = reason;
trace->stop_data = data;
return;
}
static void trace_stop_completed(scamper_trace_t *trace)
{
trace_stop(trace, SCAMPER_TRACE_STOP_COMPLETED, 0);
return;
}
static void trace_stop_dead(scamper_trace_t *trace)
{
trace_stop(trace, SCAMPER_TRACE_STOP_DEAD, 0);
return;
}
static void trace_stop_error(scamper_trace_t *trace, int error)
{
trace_stop(trace, SCAMPER_TRACE_STOP_ERROR, error);
return;
}
static void trace_stop_hoplimit(scamper_trace_t *trace)
{
trace_stop(trace, SCAMPER_TRACE_STOP_HOPLIMIT, 0);
return;
}
/*
* trace_isloop
*
* given a trace and the last hop record, determine if there is a loop.
*/
static int trace_isloop(const scamper_trace_t *trace,
const scamper_trace_hop_t *last)
{
scamper_trace_hop_t *hop;
scamper_trace_hop_t *last_loop;
int i, cnt;
/* check all probe ttl values */
for(i=0, cnt=0; i<last->hop_probe_ttl-1; i++)
{
/* for each hop, see if we have a matching address */
for(hop = trace->hops[i]; hop != NULL; hop = hop->hop_next)
{
/*
* if the addresses match, then we either return if we've detected
* a loop, or skip to the next hop now that we know we've got
* a mtach for this hop
*/
if(scamper_addr_cmp(last->hop_addr, hop->hop_addr) == 0)
{
last_loop = hop;
if(++cnt >= trace->loops)
{
return last->hop_probe_ttl - last_loop->hop_probe_ttl;
}
else
{
break;
}
}
}
}
return 0;
}
/*
* trace_hopins
*
* insert the hop record into the trace at the appropriate place
*/
static int trace_hopins(scamper_trace_t *trace, scamper_trace_hop_t *hop)
{
scamper_trace_hop_t *pre, *cur;
cur = trace->hops[hop->hop_probe_ttl-1];
pre = NULL;
/* search for the place to insert this hop record */
while(cur != NULL && cur->hop_probe_id <= hop->hop_probe_id)
{
pre = cur;
cur = cur->hop_next;
}
/* there were no hop responses for this hop, insert at head */
if(pre == NULL)
{
trace->hops[hop->hop_probe_ttl-1] = hop;
}
else
{
pre->hop_next = hop;
}
hop->hop_next = cur;
return 0;
}
/*
* trace_handlerror
*
* the code encountered some error when doing the traceroute, so stop the
* trace now.
*/
static int trace_handleerror(scamper_task_t *task, const int error)
{
trace_stop_error((scamper_trace_t *)task->data, error);
scamper_queue_done(task->queue, scamper_holdtime_get()*1000);
return 0;
}
/*
* trace_hop
*
* this function creates a generic hop record with the basic details from
* the probe structure copied in, as well as an address based on the details
* passed in
*/
static scamper_trace_hop_t *trace_hop(const trace_probe_t *probe,
const int af, const void *addr)
{
scamper_trace_hop_t *hop;
int type;
/* determine the scamper address type to use from the address family */
if(af == AF_INET) type = SCAMPER_ADDR_TYPE_IPV4;
else if(af == AF_INET6) type = SCAMPER_ADDR_TYPE_IPV6;
else return NULL;
if((hop = scamper_trace_hop_alloc()) == NULL)
{
return NULL;
}
if((hop->hop_addr = scamper_addrcache_get(addrcache, type, addr)) == NULL)
{
scamper_trace_hop_free(hop);
return NULL;
}
hop->hop_probe_ttl = probe->ttl;
hop->hop_probe_id = probe->id + 1;
hop->hop_probe_size = probe->size;
/*
* if the probe's datalink tx timestamp flag is set, scamper has a tx
* timestamp recorded
*/
if(probe->flags & TRACE_PROBE_FLAG_DL_TX)
{
hop->hop_flags |= SCAMPER_TRACE_HOP_FLAG_TS_DL_TX;
}
return hop;
}
/*
* trace_icmp_hop
*
* given a trace probe and an ICMP response, allocate and initialise a
* scamper_trace_hop record.
*/
static scamper_trace_hop_t *trace_icmp_hop(trace_probe_t *probe,
scamper_icmp_resp_t *ir)
{
scamper_trace_hop_t *hop;
scamper_addr_t addr;
/* get a pointer to the source address of the ICMP response */
if(scamper_icmp_resp_src(ir, &addr) != 0)
{
return NULL;
}
/* create a generic hop record without any special bits filled out */
if((hop = trace_hop(probe, ir->ir_af, addr.addr)) == NULL)
{
return NULL;
}
/* fill out the basic bits of the hop structure */
hop->hop_reply_size = ir->ir_ip_size;
hop->hop_icmp_type = ir->ir_icmp_type;
hop->hop_icmp_code = ir->ir_icmp_code;
/*
* we cannot depend on the TTL field of the IP packet being made available,
* so we signal explicitly when the reply ttl is valid
*/
if(ir->ir_ip_ttl != -1)
{
hop->hop_reply_ttl = ir->ir_ip_ttl;
hop->hop_flags |= SCAMPER_TRACE_HOP_FLAG_REPLY_TTL;
}
/*
* if the probe's datalink rx timestamp flag is set, scamper has a rx
* timestamp recorded
*/
if(probe->flags & TRACE_PROBE_FLAG_DL_RX)
{
hop->hop_flags |= SCAMPER_TRACE_HOP_FLAG_TS_DL_RX;
timeval_rtt(&hop->hop_rtt, &probe->tx_tv, &probe->rx_tv);
}
else
{
timeval_rtt(&hop->hop_rtt, &probe->tx_tv, &ir->ir_rx);
if(ir->ir_flags & SCAMPER_ICMP_RESP_FLAG_KERNRX)
{
hop->hop_flags |= SCAMPER_TRACE_HOP_FLAG_TS_SOCK_RX;
}
}
if(SCAMPER_ICMP_RESP_IS_PACKET_TOO_BIG(ir))
{
if(scamper_tlv_set(&hop->hop_tlvs, SCAMPER_TRACE_HOP_TLV_NHMTU,
2, &ir->ir_icmp_nhmtu) == NULL)
{
goto err;
}
}
if(ir->ir_af == AF_INET)
{
if(scamper_tlv_set(&hop->hop_tlvs, SCAMPER_TRACE_HOP_TLV_REPLY_IPID,
2, &ir->ir_ip_id) == NULL ||
scamper_tlv_set(&hop->hop_tlvs, SCAMPER_TRACE_HOP_TLV_REPLY_IPTOS,
1, &ir->ir_ip_tos) == NULL)
{
goto err;
}
}
if(SCAMPER_ICMP_RESP_INNER_IS_SET(ir))
{
if(ir->ir_inner_ip_ttl != 1)
{
if(scamper_tlv_set(&hop->hop_tlvs, SCAMPER_TRACE_HOP_TLV_INNER_IPTTL,
1, &ir->ir_inner_ip_ttl) == NULL)
{
goto err;
}
}
if(ir->ir_inner_ip_size != probe->size)
{
if(scamper_tlv_set(&hop->hop_tlvs, SCAMPER_TRACE_HOP_TLV_INNER_IPLEN,
2, &ir->ir_inner_ip_size) == NULL)
{
goto err;
}
}
}
/* record the fact that we have a hop record thanks to this probe */
probe->rx++;
return hop;
err:
scamper_trace_hop_free(hop);
return NULL;
}
static scamper_trace_hop_t *trace_tcp_hop(trace_probe_t *probe,
scamper_dl_rec_t *dl)
{
scamper_trace_hop_t *hop;
/* create a generic hop record without any special bits filled out */
if((hop = trace_hop(probe, dl->dl_af, dl->dl_ip_src)) == NULL)
{
return NULL;
}
/* fill out the basic bits of the hop structure */
hop->hop_reply_size = dl->dl_ip_size;
hop->hop_reply_ttl = dl->dl_ip_ttl;
hop->hop_tcp_flags = dl->dl_tcp_flags;
timeval_rtt(&hop->hop_rtt, &probe->tx_tv, &dl->dl_tv);
/* set the flags that are known to apply to this hop record */
hop->hop_flags |= (SCAMPER_TRACE_HOP_FLAG_REPLY_TTL |
SCAMPER_TRACE_HOP_FLAG_TCP |
SCAMPER_TRACE_HOP_FLAG_TS_DL_RX);
if(dl->dl_af == AF_INET)
{
if(scamper_tlv_set(&hop->hop_tlvs, SCAMPER_TRACE_HOP_TLV_REPLY_IPID,
2, &dl->dl_ip_id) == NULL ||
scamper_tlv_set(&hop->hop_tlvs, SCAMPER_TRACE_HOP_TLV_REPLY_IPTOS,
1, &dl->dl_ip_tos) == NULL)
{
goto err;
}
}
return hop;
err:
scamper_trace_hop_free(hop);
return NULL;
}
/*
* trace_next_mode
*
* if the trace is going into another mode, this function figures out
* which mode to put it into
*/
static void trace_next_mode(scamper_task_t *task)
{
scamper_trace_t *trace = task->data;
trace_state_t *state = task->state;
uint16_t ifmtu;
int ifindex;
if((trace->flags & SCAMPER_TRACE_FLAG_PMTUD) == 0 ||
trace->stop_reason == SCAMPER_TRACE_STOP_HOPLIMIT ||
trace->stop_reason == SCAMPER_TRACE_STOP_LOOP ||
trace->stop_reason == SCAMPER_TRACE_STOP_NONE)
{
scamper_queue_done(task->queue, scamper_holdtime_get()*1000);
return;
}
/* if the interface's MTU is useless, then we can't do PMTUD */
scamper_fd_ifindex(state->dl, &ifindex);
if(if_getmtu(ifindex, &ifmtu) == -1 || ifmtu <= state->header_size)
{
scamper_queue_done(task->queue, scamper_holdtime_get()*1000);
return;
}
if((trace->pmtud = malloc_zero(sizeof(struct scamper_trace_pmtud))) == NULL)
{
scamper_queue_done(task->queue, scamper_holdtime_get()*1000);
return;
}
trace->pmtud->ifmtu = ifmtu;
state->attempt = 0;
state->mode = MODE_PMTUD_DEFAULT;
state->payload_size = ifmtu - state->header_size;
state->ttl = 255;
scamper_queue_probe(task->queue);
return;
}
/*
* trace_stop_reason
*
* check to see if we have a stop condition based on the hop record
*/
static void trace_stop_reason(scamper_trace_t *trace, scamper_trace_hop_t *hop,
uint8_t *stop_reason, uint8_t *stop_data)
{
int rc;
if(SCAMPER_TRACE_HOP_IS_ICMP_UNREACH_PORT(hop))
{
*stop_reason = SCAMPER_TRACE_STOP_COMPLETED;
*stop_data = 0;
}
else if(SCAMPER_TRACE_HOP_IS_ICMP_UNREACH(hop))
{
*stop_reason = SCAMPER_TRACE_STOP_UNREACH;
*stop_data = hop->hop_icmp_code;
}
else if(SCAMPER_TRACE_HOP_IS_ICMP_ECHO_REPLY(hop))
{
*stop_reason = SCAMPER_TRACE_STOP_COMPLETED;
*stop_data = 0;
}
else if(trace->dst->type == SCAMPER_ADDR_TYPE_IPV6 &&
hop->hop_icmp_type == ICMP6_PACKET_TOO_BIG)
{
*stop_reason = SCAMPER_TRACE_STOP_ICMP;
*stop_data = hop->hop_icmp_type;
}
else if(trace->loops != 0 && (rc = trace_isloop(trace, hop)) != 0)
{
*stop_reason = SCAMPER_TRACE_STOP_LOOP;
*stop_data = rc;
}
else if(scamper_addr_cmp(trace->dst, hop->hop_addr) == 0)
{
/*
* in IPv6, we can have anonymous interfaces on the path that
* send ICMP errors 'spoofing' their source address as the
* destination we sent. if we get a TTL expired / fragmentation
* needed message on the path from the 'destination address' then
* it is an anonymous interface
*/
if(trace->dst->type != SCAMPER_ADDR_TYPE_IPV6 ||
hop->hop_icmp_type == ICMP6_DST_UNREACH)
{
*stop_reason = SCAMPER_TRACE_STOP_COMPLETED;
*stop_data = 0;
}
else
{
*stop_reason = SCAMPER_TRACE_STOP_NONE;
*stop_data = 0;
}
}
else
{
*stop_reason = SCAMPER_TRACE_STOP_NONE;
*stop_data = 0;
}
return;
}
/*
* handleicmp_trace
*
* we received an ICMP response in the traceroute state. check to see
* if the probe is in sequence, and adjust the trace accordingly.
*/
static int handleicmp_trace(scamper_task_t *task,
scamper_icmp_resp_t *ir,
trace_probe_t *probe)
{
scamper_trace_t *trace = task->data;
trace_state_t *state = task->state;
scamper_trace_hop_t *hop;
uint8_t stop_reason;
uint8_t stop_data;
int cwh = 0;
/* we should only have to deal with probes sent while in the trace state */
if(probe->mode != MODE_TRACE)
{
return 0;
}
/* create a hop record and insert it into the trace */
if((hop = trace_icmp_hop(probe, ir)) == NULL)
{
return -1;
}
trace_hopins(trace, hop);
/* if the response is for the current working hop */
if(hop->hop_probe_ttl - 1 == trace->hop_count)
{
/* current working hop */
cwh = 1;
/* if we are sending all allotted probes to the target */
if(trace->flags & SCAMPER_TRACE_FLAG_ALLATTEMPTS)
{
/*
* if we get an out of order reply, then we go back to waiting for
* the one we just probed for
*/
if(probe->id+1 != state->attempt)
{
return 0;
}
/*
* this response is for the last probe sent. if there are still
* probes to send for this hop, then send the next one
*/
if(state->attempt < trace->attempts)
{
scamper_queue_probe(task->queue);
return 0;
}
}
trace->hop_count++;
state->attempt = 0;
state->ttl++;
}
/* check to see if we have a stop reason from the ICMP response */
trace_stop_reason(trace, hop, &stop_reason, &stop_data);
if(stop_reason != SCAMPER_TRACE_STOP_NONE)
{
/* did we get a stop condition out of all that? */
trace_stop(trace, stop_reason, stop_data);
trace_next_mode(task);
}
else if(trace->hop_count == 255 || trace->hop_count == trace->hoplimit)
{
/* if not, has the hop limit now reached? */
trace_stop_hoplimit(trace);
trace_next_mode(task);
}
else if(cwh != 0)
{
/* if not, keep probing */
scamper_queue_probe(task->queue);
}
return 0;
}
/*
* handleicmp_check_life
*
* we received an ICMP response while checking if the end-host is
* responsive.
*/
static int handleicmp_check_life(scamper_task_t *task,
scamper_icmp_resp_t *ir,
trace_probe_t *probe)
{
scamper_trace_t *trace = task->data;
trace_state_t *state = task->state;
scamper_trace_hop_t *hop;
if(probe->mode == MODE_TRACE)
{
/* record the response in the trace */
if((hop = trace_icmp_hop(probe, ir)) == NULL)
{
return -1;
}
trace_hopins(trace, hop);
}
else if(probe->mode == MODE_CHECK_LIFE)
{
/*
* if we get a response to one of the packets checking existence,
* then the path is not dead
*/
state->alive = 1;
state->mode = MODE_TRACE;
state->ttl = trace->hop_count+1; /* XXX */
state->attempt = 0;
scamper_queue_probe(task->queue);
}
return 0;
}
static int handleicmp_pmtud_default(scamper_task_t *task,
scamper_icmp_resp_t *ir,
trace_probe_t *probe)
{
scamper_trace_t *trace = task->data;
trace_state_t *state = task->state;
scamper_trace_hop_t *hop;
/*
* if the response is for a probe that fits with the current
* probing details, then record it
*/
if(probe->mode == MODE_PMTUD_DEFAULT &&
probe->size == state->header_size + state->payload_size)
{
if((hop = trace_icmp_hop(probe, ir)) == NULL)
{
return -1;
}
pmtud_hopins(trace, hop);
state->last_fragmsg = hop;
if(SCAMPER_ICMP_RESP_IS_PACKET_TOO_BIG(ir))
{
/*
* if the fragmentation required message did not include the
* suggested packet size for the next hop, or if the suggested
* MTU is larger than (or the same size) as the probe packet, then
* abandon the pmtud operation at this point
*/
if(ir->ir_icmp_nhmtu == 0 || ir->ir_icmp_nhmtu >= probe->size)
{
state->mode = MODE_PMTUD_BADSUGG;
pmtud_L2_init(state);
scamper_queue_probe(task->queue);
return 0;
}
/*
* if the message supplied an MTU to use, but it is too small
* to be probed, then stop probing here.
*/
if(ir->ir_icmp_nhmtu < state->header_size)
{
scamper_queue_done(task->queue, scamper_holdtime_get()*1000);
return 0;
}
state->attempt = 0;
state->payload_size = ir->ir_icmp_nhmtu - state->header_size;
scamper_queue_probe(task->queue);
}
else if(SCAMPER_ICMP_RESP_IS_TTL_EXP(ir) ||
SCAMPER_ICMP_RESP_IS_UNREACH(ir) ||
SCAMPER_ICMP_RESP_IS_ECHO_REPLY(ir))
{
trace->pmtud->pmtu = probe->size;
scamper_queue_done(task->queue, scamper_holdtime_get()*1000);
}
}
return 0;
}
static int handleicmp_pmtud_silent_L2(scamper_task_t *task,
scamper_icmp_resp_t *ir,
trace_probe_t *probe)
{
trace_state_t *state = task->state;
scamper_trace_hop_t *hop;
assert(state->L2 != NULL);
/*
* if we get a response that is out of the bounds we are searching, it
* could be a delayed message. at the moment, we just ignore the response.
*/
if(probe->size < state->L2->lower || state->L2->upper <= probe->size)
{
scamper_debug(__func__, "L2 search %d < %d || %d <= %d",
probe->size, state->L2->lower,
state->L2->upper, probe->size);
return 0;
}
/* record the hop details */
if((hop = trace_icmp_hop(probe, ir)) == NULL)
{
return -1;
}
pmtud_L2_hop(state, hop);
/*
* if there is still space to search, reduce the search space and send
* another probe
*/
if(probe->size + 1 != state->L2->upper)
{
/*
* raise the lower bounds of our search based on successfully
* receiving a response for a given packet size.
*/
pmtud_L2_set_probesize(state, probe->size, state->L2->upper);
}
else
{
state->L2->lower = state->L2->out = probe->size;
if(pmtud_TTL_init(task) == 1)
{
state->mode = MODE_PMTUD_SILENT_TTL;
}
else
{
scamper_queue_done(task->queue, scamper_holdtime_get()*1000);
return 0;
}
}
scamper_queue_probe(task->queue);
return 0;
}
static int handleicmp_pmtud_silent_TTL(scamper_task_t *task,
scamper_icmp_resp_t *ir,
trace_probe_t *probe)
{
trace_state_t *state = task->state;
scamper_trace_hop_t *hop;
/* we got a TTL expired message */
if(SCAMPER_ICMP_RESP_IS_TTL_EXP(ir))
{
/* record the hop details */
if((hop = trace_icmp_hop(probe, ir)) == NULL)
{
return -1;
}
pmtud_TTL_hop(state, hop);
/* if there is no more TTL space to search, then we are done */
if(pmtud_TTL_set_probettl(task, probe->ttl, state->TTL->upper) == 0)
{
/*
* if we are not finished with PMTU yet, put the trace back in
* the queue
*/
if(pmtud_L2_search_end(task) == 1)
{
return 0;
}
}
}
/*
* if we get a fragmentation required message during a TTL limited
* search for the MTU inferred, then record the message and stop
* the TTL limited search
*/
else if(SCAMPER_ICMP_RESP_IS_PACKET_TOO_BIG(ir) &&
ir->ir_icmp_nhmtu == state->L2->out)
{
/* record the hop details */
if((hop = trace_icmp_hop(probe, ir)) == NULL)
{
return -1;
}
pmtud_hopins((scamper_trace_t *)task->data, hop);
state->attempt = 0;
state->payload_size = ir->ir_icmp_nhmtu - state->header_size;
state->ttl = 255;
state->mode = MODE_PMTUD_DEFAULT;
free(state->L2); state->L2 = NULL;
free(state->TTL); state->TTL = NULL;
}
/* put the trace back into the probe queue */
scamper_queue_probe(task->queue);
return 0;
}
/*
* handleicmp_pmtud_badsugg
*
* we are in the badsugg state, which is used to infer a 'correct' next-hop
* mtu size when the suggested packet size is no help.
*/
static int handleicmp_pmtud_badsugg(scamper_task_t *task,
scamper_icmp_resp_t *ir,
trace_probe_t *probe)
{
trace_state_t *state = task->state;
scamper_trace_hop_t *hop;
scamper_addr_t addr;
int upper, lower;
if(scamper_icmp_resp_src(ir, &addr) != 0)
{
return -1;
}
/*
* adjust the window we are searching based on where the response came
* from and the size of the probe that caused the response
*/
if(scamper_addr_cmp(state->last_fragmsg->hop_addr, &addr) == 0)
{
lower = state->L2->lower;
upper = probe->size;
}
else
{
lower = probe->size;
upper = state->L2->upper;
/* replace the layer-2 hop we get a response for with this hop */
if((hop = trace_icmp_hop(probe, ir)) == NULL)
{
return -1;
}
pmtud_L2_hop(state, hop);
}
if(lower + 1 != upper)
{
pmtud_L2_set_probesize(state, lower, upper);
}
else
{
/* terminate the search now */
state->L2->lower = state->L2->out = lower;
state->L2->upper = upper;
/* if the pmtud is completed, then move on */
if(pmtud_L2_search_end(task) == 1)
{
return 0;
}
}
/* put the trace back into the probe queue */
scamper_queue_probe(task->queue);
return 0;
}
static int do_trace_handle_icmp(scamper_task_t *task, scamper_icmp_resp_t *ir)
{
static int (*const func[])(scamper_task_t *, scamper_icmp_resp_t *,
trace_probe_t *) = {
handleicmp_trace, /* MODE_TRACE == 0x00 */
handleicmp_check_life, /* MODE_CHECK_LIFE == 0x01 */
handleicmp_pmtud_default, /* MODE_PMTUD_DEFAULT == 0x02 */
handleicmp_pmtud_silent_L2, /* MODE_PMTUD_SILENT_L2 == 0x03 */
handleicmp_pmtud_silent_TTL, /* MODE_PMTUD_SILENT_TTL == 0x04 */
handleicmp_pmtud_badsugg, /* MODE_PMTUD_BADSUGG == 0x05 */
NULL, /* MODE_RTSOCK == 0x06 */
};
scamper_trace_t *trace = task->data;
trace_state_t *state = task->state;
uint16_t id;
uint8_t proto;
assert(state->mode <= MODE_MAX);
/*
* if the trace is in a mode that does not handle ICMP responses, then
* stop now
*/
if(func[state->mode] == NULL)
{
return 0;
}
if(trace->type == SCAMPER_TRACE_TYPE_UDP ||
trace->type == SCAMPER_TRACE_TYPE_UDP_PARIS)
{
/* if the ICMP type is not something that we care for, then drop it */
if(SCAMPER_ICMP_RESP_IS_TTL_EXP(ir) == 0 &&
SCAMPER_ICMP_RESP_IS_UNREACH(ir) == 0 &&
SCAMPER_ICMP_RESP_IS_PACKET_TOO_BIG(ir) == 0)
{
return 0;
}
/*
* if the ICMP response does not reference a UDP probe sent from our
* source port to a destination probe we're likely to have probed, then
* ignore the packet
*/
if(SCAMPER_ICMP_RESP_INNER_IS_SET(ir) == 0 ||
ir->ir_inner_ip_proto != IPPROTO_UDP ||
ir->ir_inner_udp_sport != trace->sport)
{
return 0;
}
if(trace->type == SCAMPER_TRACE_TYPE_UDP)
{
if(ir->ir_inner_udp_dport < trace->dport ||
ir->ir_inner_udp_dport >= trace->dport+state->id_next)
{
return 0;
}
/* XXX: handle wrap-around */
id = ir->ir_inner_udp_dport - trace->dport;
}
else if(trace->type == SCAMPER_TRACE_TYPE_UDP_PARIS)
{
if(ir->ir_inner_udp_dport != trace->dport)
{
return 0;
}
if(ir->ir_af == AF_INET)
{
if(ir->ir_inner_udp_sum == ir->ir_inner_ip_id &&
ir->ir_inner_udp_sum != 0)
{
id = ntohs(ir->ir_inner_udp_sum) - 1;
}
else if(trace_ipid_fudge(state, ir->ir_inner_ip_id, &id) != 0)
{
return 0;
}
}
else
{
if(ir->ir_inner_udp_sum == 0)
{
id = ir->ir_inner_ip_flow - 1;
}
else
{
id = ntohs(ir->ir_inner_udp_sum) - 1;
}
}
}
else return 0;
}
else if(trace->type == SCAMPER_TRACE_TYPE_ICMP_ECHO ||
trace->type == SCAMPER_TRACE_TYPE_ICMP_ECHO_PARIS)
{
/* if the ICMP type is not something that we care for, then drop it */
if(SCAMPER_ICMP_RESP_IS_ECHO_REPLY(ir) == 0)
{
if(SCAMPER_ICMP_RESP_IS_TTL_EXP(ir) == 0 &&
SCAMPER_ICMP_RESP_IS_UNREACH(ir) == 0 &&
SCAMPER_ICMP_RESP_IS_PACKET_TOO_BIG(ir) == 0)
{
return 0;
}
if(ir->ir_af == AF_INET) proto = IPPROTO_ICMP;
else if(ir->ir_af == AF_INET6) proto = IPPROTO_ICMPV6;
else return 0;
if(SCAMPER_ICMP_RESP_INNER_IS_SET(ir) == 0 ||
ir->ir_inner_ip_proto != proto ||
ir->ir_inner_icmp_id != trace->sport ||
ir->ir_inner_icmp_seq >= state->id_next)
{
return 0;
}
id = ir->ir_inner_icmp_seq;
}
else
{
if(ir->ir_icmp_id != trace->sport ||
ir->ir_icmp_seq >= state->id_next)
{
return 0;
}
id = ir->ir_icmp_seq;
}
}
else if(trace->type == SCAMPER_TRACE_TYPE_TCP)
{
/* if the ICMP type is not something that we care for, then drop it */
if(SCAMPER_ICMP_RESP_IS_TTL_EXP(ir) == 0 &&
SCAMPER_ICMP_RESP_IS_UNREACH(ir) == 0 &&
SCAMPER_ICMP_RESP_IS_PACKET_TOO_BIG(ir) == 0)
{
return 0;
}
/*
* if the ICMP response does not reference a TCP probe sent from our
* source port to the destination port specified then ignore the
* ICMP packet
*/
if(SCAMPER_ICMP_RESP_INNER_IS_SET(ir) == 0 ||
ir->ir_inner_ip_proto != IPPROTO_TCP ||
ir->ir_inner_tcp_sport != trace->sport ||
ir->ir_inner_tcp_dport != trace->dport)
{
return 0;
}
if(ir->ir_af == AF_INET)
{
/* determine which probe id the ip id corresponds to */
if(trace_ipid_fudge(state, ir->ir_inner_ip_id, &id) != 0)
{
return 0;
}
}
else
{
if(ir->ir_inner_ip_flow == 0)
{
return 0;
}
id = ir->ir_inner_ip_flow - 1;
}
}
else
{
return 0;
}
if(id < state->id_next)
{
func[state->mode](task, ir, state->probes[id]);
}
return 0;
}
/*
* timeout_trace
*
* this function is called if the trace timed out on the wait queue, and
* all allotted attempts have been sent.
*/
static int timeout_trace(scamper_task_t *task)
{
scamper_trace_t *trace = task->data;
trace_state_t *state = task->state;
scamper_trace_hop_t *hop;
int i, deadpath;
uint8_t stop_reason, stop_data;
/* we tried this hop, so move onto the next */
trace->hop_count++;
state->ttl++;
/*
* if we probed for all attempts on the hop, then check to see if we
* got any responses on this hop, and if we did, check to see if we
* should stop probing this target yet
*/
if(trace->flags & SCAMPER_TRACE_FLAG_ALLATTEMPTS)
{
for(hop = trace->hops[trace->hop_count-1];hop != NULL; hop=hop->hop_next)
{
/*
* first, check to see if there is a reason to stop probing with
* this particular hop record
*/
trace_stop_reason(trace, hop, &stop_reason, &stop_data);
if(stop_reason != SCAMPER_TRACE_STOP_NONE)
{
trace_stop(trace, stop_reason, stop_data);
trace_next_mode(task);
return 0;
}
}
}
if(trace->hop_count == 255 || trace->hop_count == trace->hoplimit)
{
trace_stop_hoplimit(trace);
trace_next_mode(task);
return 0;
}
/*
* if we haven't checked to see if the path is dead yet, check to see
* if we should do so at this time. a dead path is defined as a path
* that has an unresponsive target host, which we stop tracing after
* five unresponsive hops.
*/
if(state->alive == 0 && trace->hop_count >= trace->gaplimit)
{
deadpath = 1;
for(i=0; i<trace->gaplimit; i++)
{
if(trace->hops[trace->hop_count-1-i] != NULL)
{
deadpath = 0;
break;
}
}
if(deadpath != 0)
{
if((trace->flags & SCAMPER_TRACE_FLAG_GAPCONT) != 0)
{
state->mode = MODE_CHECK_LIFE;
state->ttl = 255;
}
else
{
trace_stop_dead(trace);
trace_next_mode(task);
}
}
}
return 0;
}
static int timeout_check_life(scamper_task_t *task)
{
/* if we get no response, then the path is considered dead */
trace_stop_dead((scamper_trace_t *)task->data);
scamper_queue_done(task->queue, scamper_holdtime_get()*1000);
return 0;
}
static int timeout_pmtud_default(scamper_task_t *task)
{
trace_state_t *state = task->state;
pmtud_L2_init(state);
state->mode = MODE_PMTUD_SILENT_L2;
return 0;
}
static int timeout_pmtud_silent_L2(scamper_task_t *task)
{
trace_state_t *state = task->state;
int size = state->header_size + state->payload_size;
/*
* have we scanned the L2 table to the official minimum MTU?
* if we have, then PMTU fails and we abort.
*/
if(state->L2->idx == 0)
{
scamper_queue_done(task->queue, scamper_holdtime_get()*1000);
return 0;
}
/*
* we did not get a response for this probe size
* if we can halve the search space again, then do that
*/
if(state->L2->lower + 1 != size)
{
pmtud_L2_set_probesize(state, state->L2->lower, size);
}
else
{
state->L2->out = state->L2->lower;
/* set the bounds of the TTL search */
if(pmtud_TTL_init(task) == 1)
{
state->mode = MODE_PMTUD_SILENT_TTL;
}
else
{
scamper_queue_done(task->queue, scamper_holdtime_get()*1000);
}
}
return 0;
}
static int timeout_pmtud_silent_TTL(scamper_task_t *task)
{
trace_state_t *state = task->state;
/*
* select another TTL to probe with, if possible. if not, then
* the search halts and we move on
*/
if(pmtud_TTL_set_probettl(task, state->TTL->lower, state->ttl) == 0)
{
/* finish this portion of the TTL limited search */
pmtud_L2_search_end(task);
}
return 0;
}
/*
* timeout_pmtud_badsugg
*
* if we timeout while trying to determine the underlying MTU on a path
* where a router gives a bad suggestion, chances are that an ICMP blackhole
* exists later in the path. try sending a larger packet, if we can.
*/
static int timeout_pmtud_badsugg(scamper_task_t *task)
{
trace_state_t *state = task->state;
int lower, upper;
lower = state->header_size + state->payload_size;
upper = state->L2->upper;
pmtud_L2_hop(state, NULL);
if(lower + 1 != upper)
{
pmtud_L2_set_probesize(state, lower, upper);
}
else
{
/* terminate the search now */
state->L2->lower = state->L2->out = lower;
state->L2->upper = upper;
/* if the pmtud is completed, then move on */
if(pmtud_L2_search_end(task) == 1)
{
return 0;
}
}
return 0;
}
static int timeout_rtsock(scamper_task_t *task)
{
scamper_trace_t *trace = task->data;
trace_state_t *state = task->state;
/*
* if we can't get sense out of the route socket, then do the trace
* anyway, but without pmtud or datalink support
*/
trace->flags &= ~(SCAMPER_TRACE_FLAG_PMTUD | SCAMPER_TRACE_FLAG_DL);
state->mode = MODE_TRACE;
return 0;
}
/*
* do_trace_handle_timeout
*
* the trace has expired while sitting on the wait queue.
* handle this event appropriately.
*/
static int do_trace_handle_timeout(scamper_task_t *task)
{
static int (* const func[])(scamper_task_t *) = {
timeout_trace, /* MODE_TRACE == 0x00 */
timeout_check_life, /* MODE_CHECK_LIFE == 0x01 */
timeout_pmtud_default, /* MODE_PMTUD_DEFAULT == 0x02 */
timeout_pmtud_silent_L2, /* MODE_PMTUD_SILENT_L2 == 0x03 */
timeout_pmtud_silent_TTL, /* MODE_PMTUD_SILENT_TTL == 0x04 */
timeout_pmtud_badsugg, /* MODE_PMTUD_BADSUGG == 0x05 */
timeout_rtsock, /* MODE_RTSOCK == 0x06 */
};
scamper_trace_t *trace = task->data;
trace_state_t *state = task->state;
assert(state->mode <= MODE_MAX);
/*
* if we have sent all alloted attempts for this probe type, then
* handle this particular probe failing
*/
if(state->attempt == trace->attempts)
{
/* we're probably going to send another probe, so reset the attempt # */
state->attempt = 0;
/* call the function that handles a timeout in this particular mode */
func[state->mode](task);
}
return 0;
}
/*
* dl_trace_in
*
* handle a datalink record for an inbound packet which was sent
* for a probe in the trace state.
*
* in this case, we use the timestamp and the ethernet mac address
* [if available] to update the hop record.
*/
static void dl_trace_in(scamper_task_t *task,
scamper_dl_rec_t *dl, trace_probe_t *probe)
{
scamper_trace_t *trace = task->data;
scamper_trace_hop_t *hop;
struct timeval tv;
#ifndef NDEBUG
int adjflag = 0;
#endif
/*
* if the datalink record does not contain anything we care about,
* then return now
*/
if((dl->dl_flags & SCAMPER_DL_FLAG_TIMESTAMP) == 0 &&
(dl->dl_type != SCAMPER_DL_TYPE_ETHERNET) &&
trace->type != SCAMPER_TRACE_TYPE_TCP)
{
return;
}
/* only record a datalink record for the first time */
if((probe->flags & TRACE_PROBE_FLAG_DL_RX) || probe->rx_mac != NULL)
{
return;
}
/*
* if this is an inbound TCP packet, then we need to create a hop record
* with data out of it.
*/
if(dl->dl_ip_proto == IPPROTO_TCP)
{
/* create a hop record based off the TCP data */
if((hop = trace_tcp_hop(probe, dl)) == NULL)
{
return;
}
trace_hopins(trace, hop);
probe->rx++;
/*
* we can probe for the next hop only if we have sent the allotted
* attempts, or if we are only probing for the first response.
*/
if(probe->id+1 != trace->attempts &&
(trace->flags & SCAMPER_TRACE_FLAG_ALLATTEMPTS))
{
scamper_queue_probe(task->queue);
}
else
{
trace->hop_count++;
trace_stop_completed(trace);
scamper_queue_done(task->queue, scamper_holdtime_get()*1000);
}
}
if(dl->dl_type == SCAMPER_DL_TYPE_ETHERNET)
{
probe->rx_mac = scamper_addrcache_get_ethernet(addrcache,
dl->dl_lladdr_src);
if(probe->rx_mac == NULL)
{
return;
}
}
/* adjust the rtt based on the timestamp included in the datalink record */
if((dl->dl_flags & SCAMPER_DL_FLAG_TIMESTAMP) != 0 &&
dl->dl_ip_proto != IPPROTO_TCP)
{
probe->flags |= TRACE_PROBE_FLAG_DL_RX;
timeval_cpy(&probe->rx_tv, &dl->dl_tv);
if(probe->rx > 0)
{
timeval_rtt(&tv, &probe->tx_tv, &probe->rx_tv);
for(hop=trace->hops[probe->ttl-1]; hop != NULL; hop = hop->hop_next)
{
if(probe->id + 1 < hop->hop_probe_id) continue;
if(probe->id + 1 > hop->hop_probe_id) break;
if(dl->dl_flags & SCAMPER_DL_FLAG_TIMESTAMP)
{
#ifndef NDEBUG
adjflag++;
scamper_debug(__func__,
"hop %d.%06d dl_rec %d.%06d diff %lld",
hop->hop_rtt.tv_sec, hop->hop_rtt.tv_usec,
tv.tv_sec, tv.tv_usec,
timeval_diff_usec(&hop->hop_rtt, &tv));
#endif
hop->hop_flags &= ~(SCAMPER_TRACE_HOP_FLAG_TS_SOCK_RX);
hop->hop_flags |= SCAMPER_TRACE_HOP_FLAG_TS_DL_RX;
timeval_cpy(&hop->hop_rtt, &tv);
}
}
#ifndef NDEBUG
if(adjflag == 0)
{
scamper_debug(__func__, "rtt %d.%06d", tv.tv_sec, tv.tv_usec);
}
#endif
}
}
return;
}
/*
* dl_trace_out
*
* handle a datalink record for an outbound packet which was sent
* for a probe in the trace state.
*
* in this case, we use the timestamp to record the time at which the
* packet was queued at the network interface.
*/
static void dl_trace_out(scamper_task_t *task,
scamper_dl_rec_t *dl, trace_probe_t *probe)
{
scamper_trace_t *trace = task->data;
scamper_trace_hop_t *hop;
int64_t diff;
if((dl->dl_flags & SCAMPER_DL_FLAG_TIMESTAMP) == 0)
{
return;
}
/* print the difference in timestamps if built for debugging */
scamper_debug(__func__, "probe %d.%06d dl_rec %d.%06d diff %lld",
probe->tx_tv.tv_sec, probe->tx_tv.tv_usec,
dl->dl_tv.tv_sec, dl->dl_tv.tv_usec,
timeval_diff_usec(&probe->tx_tv, &dl->dl_tv));
if(probe->rx > 0)
{
/*
* figure out the difference between the transmit timestamp recorded
* with gettimeofday and the transmit timestamp reported by the
* datalink; we use this value to adjust the rtt field.
*
* the difference calculated will almost certainly be negative.
* the datalink timestamp will occur after the probe's user-space
* timestamp, as the datalink timestamp represents when the probe was
* seen 'on the wire' and not when sendto() was called.
*
* want a negative value so we can 'add' it to the hop's rtt value.
*/
diff = timeval_diff_usec(&probe->tx_tv, &dl->dl_tv);
for(hop = trace->hops[probe->ttl-1]; hop != NULL; hop = hop->hop_next)
{
if(probe->id + 1 < hop->hop_probe_id) continue;
if(probe->id + 1 > hop->hop_probe_id) break;
/* datalink provided transmit timestamp */
hop->hop_flags |= SCAMPER_TRACE_HOP_FLAG_TS_DL_TX;
timeval_add_usec(&hop->hop_rtt, diff);
}
}
/* update the TX timestamp of the probe */
probe->flags |= TRACE_PROBE_FLAG_DL_TX;
timeval_cpy(&probe->tx_tv, &dl->dl_tv);
return;
}
/*
* do_trace_handle_dl
*
* handle a datalink record that may have something useful for the
* traceroute, such as a more accurate timestamp or a mac address of
* the host that delivered the response to us.
*/
static int do_trace_handle_dl(scamper_task_t *task, scamper_dl_rec_t *dl)
{
static void (* const func[][2])(scamper_task_t *, scamper_dl_rec_t *,
trace_probe_t *) =
{
{ dl_trace_in, dl_trace_out }, /* MODE_TRACE == 0x00 */
{ NULL, NULL}, /* MODE_CHECK_LIFE == 0x01 */
{ NULL, NULL}, /* MODE_PMTUD_DEFAULT == 0x02 */
{ NULL, NULL}, /* MODE_PMTUD_SILENT_L2 == 0x03 */
{ NULL, NULL}, /* MODE_PMTUD_SILENT_TTL == 0x04 */
{ NULL, NULL}, /* MODE_PMTUD_BADSUGG == 0x05 */
{ NULL, NULL}, /* MODE_RTSOCK == 0x06 */
};
scamper_trace_t *trace = task->data;
trace_state_t *state = task->state;
trace_probe_t *probe;
uint16_t probe_id;
int direction;
if(trace->type == SCAMPER_TRACE_TYPE_UDP ||
trace->type == SCAMPER_TRACE_TYPE_UDP_PARIS)
{
if(dl->dl_ip_proto == IPPROTO_UDP)
{
if(dl->dl_udp_sport != trace->sport) return 0;
direction = 1;
if(trace->type == SCAMPER_TRACE_TYPE_UDP)
{
probe_id = dl->dl_udp_dport - trace->dport;
}
else
{
probe_id = ntohs(dl->dl_udp_sum) - 1;
}
}
else if(SCAMPER_DL_IS_ICMP(dl))
{
if(SCAMPER_DL_IS_ICMP_TTL_EXP(dl) == 0 &&
SCAMPER_DL_IS_ICMP_UNREACH(dl) == 0 &&
SCAMPER_DL_IS_ICMP_PACKET_TOO_BIG(dl) == 0)
{
return 0;
}
if(dl->dl_icmp_ip_proto != IPPROTO_UDP) return 0;
if(dl->dl_icmp_udp_sport != trace->sport) return 0;
direction = 0;
if(trace->type == SCAMPER_TRACE_TYPE_UDP)
{
probe_id = dl->dl_icmp_udp_dport - trace->dport;
}
else
{
if(dl->dl_icmp_udp_dport != trace->dport)
{
return 0;
}
if(dl->dl_af == AF_INET)
{
if(dl->dl_icmp_udp_sum == dl->dl_icmp_ip_id &&
dl->dl_icmp_udp_sum != 0)
{
probe_id = ntohs(dl->dl_icmp_udp_sum) - 1;
}
else if(trace_ipid_fudge(state,dl->dl_icmp_ip_id,
&probe_id) != 0)
{
return 0;
}
}
else
{
assert(dl->dl_af == AF_INET6);
if(dl->dl_icmp_udp_sum == 0)
{
if(dl->dl_icmp_ip_flow == 0)
{
return 0;
}
probe_id = dl->dl_icmp_ip_flow - 1;
}
else
{
probe_id = ntohs(dl->dl_icmp_udp_sum) - 1;
}
}
}
}
else return 0;
}
else if(trace->type == SCAMPER_TRACE_TYPE_ICMP_ECHO ||
trace->type == SCAMPER_TRACE_TYPE_ICMP_ECHO_PARIS)
{
if(SCAMPER_DL_IS_ICMP(dl) == 0) return 0;
if(SCAMPER_DL_IS_ICMP_ECHO_REQUEST(dl))
{
if(dl->dl_icmp_id != trace->sport) return 0;
probe_id = dl->dl_icmp_seq;
direction = 1;
}
else if(SCAMPER_DL_IS_ICMP_ECHO_REPLY(dl))
{
if(dl->dl_icmp_id != trace->sport) return 0;
probe_id = dl->dl_icmp_seq;
direction = 0;
}
else if((SCAMPER_DL_IS_ICMP_TTL_EXP(dl) ||
SCAMPER_DL_IS_ICMP_UNREACH(dl) ||
SCAMPER_DL_IS_ICMP_PACKET_TOO_BIG(dl)) &&
SCAMPER_DL_IS_ICMP_PROTO_ICMP_ECHO_REQ(dl))
{
if(dl->dl_icmp_icmp_id != trace->sport) return 0;
probe_id = dl->dl_icmp_icmp_seq;
direction = 0;
}
else return 0;
}
else if(trace->type == SCAMPER_TRACE_TYPE_TCP)
{
if(dl->dl_ip_proto == IPPROTO_TCP)
{
/*
* if the syn flag (and only the syn flag is set) and the sport
* and dport match what we probe with, then the probe is probably
* an outgoing one.
*/
if((dl->dl_tcp_flags & TH_SYN) == TH_SYN &&
(dl->dl_tcp_flags & ~TH_SYN) == 0 &&
dl->dl_tcp_sport == trace->sport)
{
if(dl->dl_af == AF_INET)
probe_id = dl->dl_ip_id - 1;
else
probe_id = dl->dl_ip_flow - 1;
direction = 1;
}
else if(dl->dl_tcp_sport == trace->dport &&
dl->dl_tcp_dport == trace->sport)
{
probe_id = state->id_next - 1;
direction = 0;
}
else return 0;
}
else if(SCAMPER_DL_IS_ICMP(dl))
{
if(SCAMPER_DL_IS_ICMP_TTL_EXP(dl) == 0 &&
SCAMPER_DL_IS_ICMP_UNREACH(dl) == 0 &&
SCAMPER_DL_IS_ICMP_PACKET_TOO_BIG(dl) == 0)
{
return 0;
}
if(dl->dl_icmp_ip_proto != IPPROTO_TCP) return 0;
if(dl->dl_icmp_tcp_sport != trace->sport) return 0;
if(dl->dl_icmp_tcp_dport != trace->dport) return 0;
/* determine which probe the ICMP response corresponds to */
if(dl->dl_af == AF_INET)
{
if(trace_ipid_fudge(state, dl->dl_icmp_ip_id, &probe_id) != 0)
{
return 0;
}
}
else
{
if(dl->dl_icmp_ip_flow == 0) return 0;
probe_id = dl->dl_icmp_ip_flow - 1;
}
direction = 0;
}
else return 0;
}
else return 0;
/* make sure the probe id is something sane */
if(probe_id >= state->id_next)
{
return 0;
}
probe = state->probes[probe_id];
assert(probe->mode <= MODE_MAX);
if(func[probe->mode][direction] != NULL)
{
func[probe->mode][direction](task, dl, probe);
}
return 0;
}
static int rt_framing(scamper_task_t *task, scamper_rt_rec_t *rt)
{
scamper_trace_t *trace = task->data;
trace_state_t *state = task->state;
scamper_dl_t *dl = scamper_fd_write_state(state->dl);
int af, tx_type;
/*
* determine the header to append when sending packets on the datalink.
* if the datalink code cannot send packets, then we can't do PMTUD
*/
tx_type = scamper_dl_tx_type(dl);
/*
* determine the size of the header to prepend to the packet to put it
* on the datalink
*/
switch(tx_type)
{
case SCAMPER_DL_TX_UNSUPPORTED:
return -1;
case SCAMPER_DL_TX_ETHERNET:
case SCAMPER_DL_TX_ETHLOOP:
state->dl_size = 14;
break;
case SCAMPER_DL_TX_NULL:
state->dl_size = sizeof(int);
break;
case SCAMPER_DL_TX_RAW:
state->dl_size = 0;
return 0;
default:
scamper_debug(__func__, "unhandled tx_type %d", tx_type);
return -1;
}
/* allocate the header */
if((state->dl_hdr = malloc(state->dl_size)) == NULL)
{
printerror(errno, strerror, __func__, "could not malloc dl_hdr");
return -1;
}
if(tx_type == SCAMPER_DL_TX_ETHERNET)
{
/*
* allocate a datalink header to use, and determine the source mac
* address to use
*/
if(if_getmac(rt->ifindex, state->dl_hdr+6) == -1)
{
scamper_debug(__func__, "could not get source mac");
return -1;
}
/*
* determine the destination mac address (the target).
*/
if(rt->gwaddr == NULL)
{
/* no gateway address means destination is on local network */
memcpy(state->dl_hdr, state->dl_hdr+6, 6);
if(scamper_addr2mac_whohas(rt->ifindex, trace->src, trace->dst,
state->dl_hdr) != 1)
{
scamper_debug(__func__, "could not get destination mac");
return -1;
}
}
else if(rt->gwaddr->type == SCAMPER_ADDR_TYPE_ETHERNET)
{
/* the gateway mac address was provided by the route socket */
memcpy(state->dl_hdr, rt->gwaddr->addr, 6);
}
else
{
/* the gateway address was returned as an IP */
memcpy(state->dl_hdr, state->dl_hdr+6, 6);
if(scamper_addr2mac_whohas(rt->ifindex, trace->src, rt->gwaddr,
state->dl_hdr) != 1)
{
scamper_debug(__func__, "could not get gateway mac");
return -1;
}
}
if(trace->dst->type == SCAMPER_ADDR_TYPE_IPV4)
{
state->dl_hdr[12] = 0x08;
state->dl_hdr[13] = 0x00;
}
else if(trace->dst->type == SCAMPER_ADDR_TYPE_IPV6)
{
state->dl_hdr[12] = 0x86;
state->dl_hdr[13] = 0xDD;
}
else return -1;
}
else if(tx_type == SCAMPER_DL_TX_NULL)
{
if(trace->dst->type == SCAMPER_ADDR_TYPE_IPV4)
{
af = AF_INET;
}
else if(trace->dst->type == SCAMPER_ADDR_TYPE_IPV6)
{
af = AF_INET6;
}
else return -1;
memcpy(state->dl_hdr, &af, sizeof(int));
}
else if(tx_type == SCAMPER_DL_TX_ETHLOOP)
{
memset(state->dl_hdr, 0, 12);
if(trace->dst->type == SCAMPER_ADDR_TYPE_IPV4)
{
state->dl_hdr[12] = 0x08;
state->dl_hdr[13] = 0x00;
}
else if(trace->dst->type == SCAMPER_ADDR_TYPE_IPV6)
{
state->dl_hdr[12] = 0x86;
state->dl_hdr[13] = 0xDD;
}
else return -1;
}
else return -1;
return 0;
}
static int do_trace_handle_rt(scamper_task_t *task, scamper_rt_rec_t *rt)
{
scamper_trace_t *trace = task->data;
trace_state_t *state = task->state;
if(state->mode != MODE_RTSOCK)
{
return 0;
}
/* if there was a problem getting the ifindex, handle that */
if(rt->error != 0 || rt->ifindex < 0)
{
printerror(errno, strerror, __func__, "could not get ifindex");
goto err;
}
/*
* if scamper is supposed to get tx timestamps from the datalink, or
* scamper needs the datalink to transmit packets, then try and get a
* datalink on the ifindex specified.
*/
if((state->dl = scamper_fd_dl(rt->ifindex)) == NULL)
{
scamper_debug(__func__, "could not get dl for %d", rt->ifindex);
goto err;
}
/*
* if we're doing path MTU discovery debugging, or doing tcp traceroute,
* or doing udp paris traceroute, determine the underlying framing to use
* with each probe packet that will be sent on the datalink.
*/
if((trace->flags & SCAMPER_TRACE_FLAG_PMTUD) != 0 ||
trace->type == SCAMPER_TRACE_TYPE_TCP ||
trace->type == SCAMPER_TRACE_TYPE_UDP_PARIS)
{
if(rt_framing(task, rt) != 0)
{
goto err;
}
}
done:
state->mode = MODE_TRACE;
state->attempt = 0;
scamper_queue_probe(task->queue);
return 0;
err:
if(trace->type == SCAMPER_TRACE_TYPE_TCP ||
trace->type == SCAMPER_TRACE_TYPE_UDP_PARIS)
{
goto harderr;
}
trace->flags &= ~(SCAMPER_TRACE_FLAG_PMTUD | SCAMPER_TRACE_FLAG_DL);
goto done;
harderr:
trace_handleerror(task, errno);
return -1;
}
static int do_trace_write(scamper_task_t *task)
{
scamper_outfile_t *outfile = scamper_source_getoutfile(task->source);
scamper_file_t *sf = scamper_outfile_getfile(outfile);
scamper_file_write_trace(sf, (scamper_trace_t *)task->data);
return 0;
}
static void trace_state_free(trace_state_t *state)
{
trace_probe_t *probe;
int i;
/* free the probe records scamper kept */
if(state->probes != NULL)
{
for(i=0; i<state->id_next; i++)
{
probe = state->probes[i];
if(probe->rx_mac != NULL)
{
scamper_addr_free(probe->rx_mac);
}
free(probe);
}
free(state->probes);
}
if(state->dl != NULL) scamper_fd_free(state->dl);
if(state->icmp != NULL) scamper_fd_free(state->icmp);
if(state->probe != NULL) scamper_fd_free(state->probe);
if(state->route != NULL) scamper_fd_free(state->route);
if(state->dl_hdr != NULL) free(state->dl_hdr);
free(state);
return;
}
static int trace_state_alloc(scamper_task_t *task)
{
scamper_trace_t *trace = task->data;
trace_state_t *state;
int id_max;
assert(trace != NULL);
/* allocate struct to keep state while processing the trace */
if((state = malloc_zero(sizeof(trace_state_t))) == NULL)
{
goto err;
}
/* allocate memory to record hops */
state->alloc_hops = TRACE_ALLOC_HOPS;
if(trace->firsthop >= state->alloc_hops)
{
if(state->alloc_hops + (uint16_t)trace->firsthop > 256)
{
state->alloc_hops = 256;
}
else
{
state->alloc_hops += trace->firsthop;
}
}
if(scamper_trace_hops_alloc(trace, state->alloc_hops) == -1)
{
goto err;
}
/* allocate enough ids to probe each hop with max number of attempts */
id_max = (state->alloc_hops - trace->firsthop + 2) * trace->attempts;
/* allocate enough space to store state for each probe */
if((state->probes = malloc(sizeof(trace_probe_t *) * id_max)) == NULL)
{
goto err;
}
/* if scamper has to get the ifindex, then start in the rtsock mode */
if((trace->flags & (SCAMPER_TRACE_FLAG_PMTUD|SCAMPER_TRACE_FLAG_DL)) != 0 ||
trace->type == SCAMPER_TRACE_TYPE_TCP ||
trace->type == SCAMPER_TRACE_TYPE_UDP_PARIS)
{
state->mode = MODE_RTSOCK;
if((state->route = scamper_fd_rtsock()) == NULL)
{
goto err;
}
}
else
{
state->mode = MODE_TRACE;
}
state->dl = NULL;
state->dl_hdr = NULL;
state->dl_size = 0;
state->alive = 0;
state->ttl = trace->firsthop;
state->attempt = 0;
state->header_size = scamper_trace_probe_headerlen(trace);
state->payload_size = trace->probe_size - state->header_size;
state->id_next = 0;
state->id_max = id_max;
if(trace->dst->type == SCAMPER_ADDR_TYPE_IPV4)
{
state->icmp = scamper_fd_icmp4();
}
else if(trace->dst->type == SCAMPER_ADDR_TYPE_IPV6)
{
state->icmp = scamper_fd_icmp6();
}
else goto err;
switch(trace->type)
{
case SCAMPER_TRACE_TYPE_TCP:
if(trace->dst->type == SCAMPER_ADDR_TYPE_IPV4)
state->probe = scamper_fd_tcp4(scamper_sport_get());
else
state->probe = scamper_fd_tcp6(scamper_sport_get());
break;
case SCAMPER_TRACE_TYPE_ICMP_ECHO:
case SCAMPER_TRACE_TYPE_ICMP_ECHO_PARIS:
if(trace->dst->type == SCAMPER_ADDR_TYPE_IPV4)
state->probe = scamper_fd_icmp4();
else
state->probe = scamper_fd_icmp6();
break;
case SCAMPER_TRACE_TYPE_UDP:
case SCAMPER_TRACE_TYPE_UDP_PARIS:
if(trace->dst->type == SCAMPER_ADDR_TYPE_IPV4)
state->probe = scamper_fd_udp4(scamper_sport_get());
else
state->probe = scamper_fd_udp6(scamper_sport_get());
break;
}
if(state->icmp == NULL || state->probe == NULL)
{
goto err;
}
task->state = state;
return 0;
err:
if(state != NULL) trace_state_free(state);
return -1;
}
static void do_trace_free(scamper_task_t *task)
{
scamper_trace_t *trace;
trace_state_t *state;
/* free any state kept */
if((state = task->state) != NULL)
{
trace_state_free(state);
}
/* free any trace data collected */
if((trace = task->data) != NULL)
{
scamper_trace_free(trace);
}
return;
}
/*
* do_trace_probe
*
* time to probe, so send the packet.
*/
static int do_trace_probe(scamper_task_t *task)
{
scamper_trace_t *trace = task->data;
trace_state_t *state = task->state;
trace_probe_t *tp = NULL;
scamper_probe_t probe;
uint32_t sum;
uint16_t u16, i;
trace_probe_t **probes;
uint8_t *buf;
assert(trace != NULL);
if(state != NULL)
{
assert(state->attempt < trace->attempts);
assert(state->id_next <= state->id_max);
assert(state->alloc_hops > 0);
assert(state->alloc_hops <= 256);
assert(state->ttl != 0);
}
else
{
/* timestamp when the trace began */
gettimeofday_wrap(&trace->start);
/* determine the source address used for sending probes */
if((trace->src = scamper_getsrc(trace->dst)) == NULL)
{
trace_handleerror(task, errno);
return -1;
}
/* allocate state and store it with the task */
if(trace_state_alloc(task) != 0)
{
trace_handleerror(task, errno);
return -1;
}
state = task->state;
}
/* allocate some more space in the trace to store replies, if necessary */
if(state->alloc_hops == trace->hop_count)
{
/*
* figure out exactly how many hops should be allocated in the
* trace structure
*/
if(256 - state->alloc_hops <= TRACE_ALLOC_HOPS)
{
u16 = state->alloc_hops + TRACE_ALLOC_HOPS;
}
else
{
u16 = 256;
}
/* allocate the new hops */
if(scamper_trace_hops_alloc(trace, u16) != 0)
{
printerror(errno, strerror, __func__, "could not realloc hops");
trace_handleerror(task, errno);
return -1;
}
/* initialise the new hops to have null pointers */
for(i=state->alloc_hops; i<u16; i++)
{
trace->hops[i] = NULL;
}
state->alloc_hops = u16;
}
/* allocate some more space to store probes, if necessary */
if(state->id_next == state->id_max)
{
u16 = state->id_max + TRACE_ALLOC_HOPS;
probes = realloc(state->probes, sizeof(trace_probe_t *) * u16);
if(probes == NULL)
{
printerror(errno, strerror, __func__, "could not realloc");
trace_handleerror(task, errno);
return -1;
}
state->id_max = u16;
state->probes = probes;
}
if(state->mode == MODE_RTSOCK)
{
if(scamper_rtsock_getroute(state->route, trace->dst) != 0)
{
trace->flags &= ~(SCAMPER_TRACE_FLAG_PMTUD | SCAMPER_TRACE_FLAG_DL);
state->mode = MODE_TRACE;
state->attempt = 0;
}
else
{
state->attempt++;
scamper_queue_wait(task->queue, trace->wait * 1000);
return 0;
}
}
/* allocate a larger global pktbuf if needed */
if(pktbuf_len < state->payload_size)
{
if((buf = realloc(pktbuf, state->payload_size)) == NULL)
{
printerror(errno, strerror, __func__, "could not realloc");
trace_handleerror(task, errno);
return -1;
}
pktbuf = buf;
pktbuf_len = state->payload_size;
memset(pktbuf, 0, pktbuf_len);
}
probe.pr_ip_src = trace->src;
probe.pr_ip_dst = trace->dst;
probe.pr_ip_tos = trace->tos;
probe.pr_ip_ttl = state->ttl;
probe.pr_ip_id = 0;
probe.pr_ip_flow = 0;
probe.pr_data = pktbuf;
probe.pr_len = state->payload_size;
probe.pr_ipoptc = 0;
probe.pr_ipopts = NULL;
probe.pr_fd = scamper_fd_fd_get(state->probe);
if((state->mode == MODE_PMTUD_DEFAULT ||
state->mode == MODE_PMTUD_SILENT_L2 ||
state->mode == MODE_PMTUD_SILENT_TTL ||
state->mode == MODE_PMTUD_BADSUGG ||
trace->type == SCAMPER_TRACE_TYPE_TCP ||
trace->type == SCAMPER_TRACE_TYPE_UDP_PARIS) &&
state->dl != NULL)
{
probe.pr_dl = scamper_fd_write_state(state->dl);
probe.pr_dl_hdr = state->dl_hdr;
probe.pr_dl_size = state->dl_size;
}
else
{
probe.pr_dl = NULL;
probe.pr_dl_hdr = NULL;
probe.pr_dl_size = 0;
}
if(trace->type == SCAMPER_TRACE_TYPE_UDP ||
trace->type == SCAMPER_TRACE_TYPE_UDP_PARIS)
{
probe.pr_ip_proto = IPPROTO_UDP;
probe.pr_udp_sport = trace->sport;
probe.pr_udp_dport = trace->dport;
/*
* traditional traceroute identifies probes by varying the UDP
* destination port number. UDP-based paris traceroute identifies
* probes by varying the UDP checksum -- accomplished by manipulating
* the payload of the packet to get sequential values for the checksum
*/
if(trace->type == SCAMPER_TRACE_TYPE_UDP)
{
probe.pr_udp_dport += state->id_next;
}
else
{
/*
* hack the checksum to be our id field by setting the checksum
* id we want into the packet's body, then calculate the checksum
* across the packet, and then set the packet's body to be the
* value returned for the checksum. this effectively swaps two
* 16 bit quantities in the packet
*/
u16 = htons(state->id_next + 1);
memcpy(probe.pr_data, &u16, 2);
if(trace->dst->type == SCAMPER_ADDR_TYPE_IPV4)
{
/*
* while the paris traceroute paper says that the payload of the
* packet is set so that the checksum field can be used to
* identify a returned probe, the paris traceroute code uses the
* IP ID field.
* this is presumably because FreeBSD systems seem to reset the
* UDP checksum quoted in ICMP destination unreachable messages.
* scamper's paris traceroute implementation used both IP ID and
* UDP checksum.
*/
probe.pr_ip_id = state->id_next + 1;
u16 = scamper_udp4_cksum(&probe);
}
else
{
/*
* set the flow ID in case checksum is mangled like the IPv4
* UDP checksum seems to be on some systems
*/
probe.pr_ip_flow = state->id_next + 1;
u16 = scamper_udp6_cksum(&probe);
}
memcpy(probe.pr_data, &u16, 2);
}
}
else if(trace->type == SCAMPER_TRACE_TYPE_ICMP_ECHO ||
trace->type == SCAMPER_TRACE_TYPE_ICMP_ECHO_PARIS)
{
switch(trace->dst->type)
{
case SCAMPER_ADDR_TYPE_IPV4:
probe.pr_ip_proto = IPPROTO_ICMP;
probe.pr_icmp_type = ICMP_ECHO;
break;
case SCAMPER_ADDR_TYPE_IPV6:
probe.pr_ip_proto = IPPROTO_ICMPV6;
probe.pr_icmp_type = ICMP6_ECHO_REQUEST;
break;
}
probe.pr_icmp_code = 0;
probe.pr_icmp_id = trace->sport;
probe.pr_icmp_seq = state->id_next;
/*
* ICMP-based paris traceroute tries to ensure the same path is taken
* through a load balancer by sending all probes with a constant value
* for the checksum. manipulate the payload so this happens.
* the value chosen to seed the checksum is the trace->sport value, but
* it could really be anything.
*/
if(trace->type == SCAMPER_TRACE_TYPE_ICMP_ECHO_PARIS)
{
sum = trace->sport;
sum += htons(((probe.pr_icmp_type << 8) | probe.pr_icmp_code));
sum += htons(probe.pr_icmp_id);
sum += htons(probe.pr_icmp_seq);
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
u16 = ~sum;
memcpy(probe.pr_data, &u16, 2);
}
}
else if(trace->type == SCAMPER_TRACE_TYPE_TCP)
{
probe.pr_ip_proto = IPPROTO_TCP;
probe.pr_tcp_sport = trace->sport;
probe.pr_tcp_dport = trace->dport;
probe.pr_tcp_seq = 0;
probe.pr_tcp_ack = 0;
probe.pr_tcp_flags = TH_SYN;
probe.pr_tcp_win = 0;
if(trace->dst->type == SCAMPER_ADDR_TYPE_IPV4)
probe.pr_ip_id = state->id_next + 1;
else
probe.pr_ip_flow = state->id_next + 1;
}
else return -1;
/*
* allocate a trace probe state record before we try and send the probe
* as there is no point sending something into the wild that we can't
* record
*/
if((tp = malloc_zero(sizeof(trace_probe_t))) == NULL)
{
trace_handleerror(task, errno);
return -1;
}
/* send the probe */
if(scamper_probe(&probe) == -1)
{
free(tp);
trace_handleerror(task, probe.pr_errno);
return -1;
}
timeval_cpy(&tp->tx_tv, &probe.pr_tx);
tp->ttl = probe.pr_ip_ttl;
tp->size = probe.pr_len + state->header_size;
tp->mode = state->mode;
tp->id = state->attempt;
state->probes[state->id_next] = tp;
state->id_next++;
state->attempt++;
scamper_queue_wait(task->queue, trace->wait * 1000);
return 0;
}
/*
* scamper_do_trace_alloc
*
* given a string representing a traceroute task, parse the parameters and
* assemble a trace. return the trace structure so that it is all ready to
* go.
*/
scamper_trace_t *scamper_do_trace_alloc(char *str)
{
/* default values of various trace parameters */
uint8_t type = SCAMPER_TRACE_TYPE_UDP;
uint8_t flags = 0;
uint8_t attempts = SCAMPER_DO_TRACE_ATTEMPTS_DEF;
uint8_t firsthop = SCAMPER_DO_TRACE_FIRSTHOP_DEF;
uint8_t gaplimit = SCAMPER_DO_TRACE_GAPLIMIT_DEF;
uint8_t hoplimit = SCAMPER_DO_TRACE_HOPLIMIT_DEF;
uint8_t tos = SCAMPER_DO_TRACE_TOS_DEF;
uint8_t wait = SCAMPER_DO_TRACE_WAIT_DEF;
uint8_t loops = SCAMPER_DO_TRACE_LOOPS_DEF;
uint16_t sport = scamper_sport_get();
uint16_t dport = SCAMPER_DO_TRACE_DPORT_DEF;
scamper_option_out_t *opts_out = NULL, *opt;
scamper_trace_t *trace = NULL;
char *addr;
long tmp;
/* try and parse the string passed in */
if(scamper_options_parse(str, trace_opts_in, trace_opts_cnt,
&opts_out, &addr) != 0)
{
goto err;
}
/* if there is no IP address after the options string, then stop now */
if(addr == NULL)
{
goto err;
}
/* parse the options, do preliminary sanity checks */
for(opt = opts_out; opt != NULL; opt = opt->next)
{
switch(opt->id)
{
case TRACE_OPT_DPORT:
if(string_tolong(opt->str, &tmp) == -1 ||
tmp < SCAMPER_DO_TRACE_DPORT_MIN ||
tmp > SCAMPER_DO_TRACE_DPORT_MAX)
{
goto err;
}
dport = tmp;
break;
case TRACE_OPT_FIRSTHOP:
if(string_tolong(opt->str, &tmp) == -1 ||
tmp < SCAMPER_DO_TRACE_FIRSTHOP_MIN ||
tmp > SCAMPER_DO_TRACE_FIRSTHOP_MAX)
{
goto err;
}
firsthop = tmp;
break;
case TRACE_OPT_GAPLIMIT:
if(string_tolong(opt->str, &tmp) == -1 ||
tmp < SCAMPER_DO_TRACE_GAPLIMIT_MIN ||
tmp > SCAMPER_DO_TRACE_GAPLIMIT_MAX)
{
goto err;
}
gaplimit = tmp;
break;
case TRACE_OPT_GAPCONT:
flags |= SCAMPER_TRACE_FLAG_GAPCONT;
break;
case TRACE_OPT_LOOPS:
if(string_tolong(opt->str, &tmp) == -1 ||
tmp < SCAMPER_DO_TRACE_LOOPS_MIN ||
tmp > SCAMPER_DO_TRACE_LOOPS_MAX)
{
goto err;
}
loops = tmp;
break;
case TRACE_OPT_MAXTTL:
if(string_tolong(opt->str, &tmp) == -1 ||
tmp < SCAMPER_DO_TRACE_HOPLIMIT_MIN ||
tmp > SCAMPER_DO_TRACE_HOPLIMIT_MAX)
{
goto err;
}
hoplimit = tmp;
break;
case TRACE_OPT_PMTUD:
flags |= SCAMPER_TRACE_FLAG_PMTUD;
break;
case TRACE_OPT_PROTOCOL:
if(strcasecmp(opt->str, "UDP") == 0)
type = SCAMPER_TRACE_TYPE_UDP;
else if(strcasecmp(opt->str, "TCP") == 0)
type = SCAMPER_TRACE_TYPE_TCP;
else if(strcasecmp(opt->str, "ICMP") == 0)
type = SCAMPER_TRACE_TYPE_ICMP_ECHO;
else if(strcasecmp(opt->str, "ICMP-paris") == 0)
type = SCAMPER_TRACE_TYPE_ICMP_ECHO_PARIS;
else if(strcasecmp(opt->str, "UDP-paris") == 0)
type = SCAMPER_TRACE_TYPE_UDP_PARIS;
else goto err;
break;
case TRACE_OPT_ATTEMPTS:
if(string_tolong(opt->str, &tmp) == -1 ||
tmp < SCAMPER_DO_TRACE_ATTEMPTS_MIN ||
tmp > SCAMPER_DO_TRACE_ATTEMPTS_MAX)
{
goto err;
}
attempts = tmp;
break;
case TRACE_OPT_ALLATTEMPTS:
flags |= SCAMPER_TRACE_FLAG_ALLATTEMPTS;
break;
case TRACE_OPT_SPORT:
if(string_tolong(opt->str, &tmp) == -1 ||
tmp < SCAMPER_DO_TRACE_SPORT_MIN ||
tmp > SCAMPER_DO_TRACE_SPORT_MAX)
{
goto err;
}
sport = tmp;
break;
case TRACE_OPT_TOS:
if(string_tolong(opt->str, &tmp) == -1 ||
tmp < SCAMPER_DO_TRACE_TOS_MIN ||
tmp > SCAMPER_DO_TRACE_TOS_MAX)
{
goto err;
}
tos = tmp;
break;
case TRACE_OPT_WAIT:
if(string_tolong(opt->str, &tmp) == -1 ||
tmp < SCAMPER_DO_TRACE_WAIT_MIN ||
tmp > SCAMPER_DO_TRACE_WAIT_MAX)
{
goto err;
}
wait = tmp;
break;
}
}
scamper_options_free(opts_out); opts_out = NULL;
/* sanity check that we don't begin beyond our probe hoplimit */
if(firsthop > hoplimit && hoplimit != 0)
{
goto err;
}
/* can't really do pmtud properly without all of the path */
if(firsthop > 1 && (flags & SCAMPER_TRACE_FLAG_PMTUD))
{
goto err;
}
/* can't really do pmtud properly without a UDP traceroute method */
if((flags & SCAMPER_TRACE_FLAG_PMTUD) == 1 &&
type != SCAMPER_TRACE_TYPE_UDP && type != SCAMPER_TRACE_TYPE_UDP_PARIS)
{
goto err;
}
if((trace = scamper_trace_alloc()) == NULL)
{
goto err;
}
if((trace->dst= scamper_addrcache_resolve(addrcache,AF_UNSPEC,addr)) == NULL)
{
goto err;
}
trace->type = type;
trace->flags = flags;
trace->attempts = attempts;
trace->hoplimit = hoplimit;
trace->gaplimit = gaplimit;
trace->firsthop = firsthop;
trace->tos = tos;
trace->wait = wait;
trace->loops = loops;
trace->sport = sport;
trace->dport = dport;
switch(trace->dst->type)
{
case SCAMPER_ADDR_TYPE_IPV4:
if(trace->type == SCAMPER_TRACE_TYPE_TCP)
{
trace->probe_size = 40;
}
else
{
trace->probe_size = 44;
}
break;
case SCAMPER_ADDR_TYPE_IPV6:
trace->probe_size = 60;
break;
default:
goto err;
}
if(scamper_option_dl() != 0)
{
trace->flags |= SCAMPER_TRACE_FLAG_DL;
}
return trace;
err:
if(trace != NULL) scamper_trace_free(trace);
if(opts_out != NULL) scamper_options_free(opts_out);
return NULL;
}
scamper_task_t *scamper_do_trace_alloctask(scamper_trace_t *trace,
scamper_list_t *list,
scamper_cycle_t *cycle)
{
scamper_task_t *task;
/* associate the list and cycle with the trace */
trace->list = scamper_list_use(list);
trace->cycle = scamper_cycle_use(cycle);
/* allocate the task structure and store the trace with it */
if((task = scamper_task_alloc(trace->dst, &trace_funcs)) != NULL)
{
task->data = trace;
return task;
}
return NULL;
}
void scamper_do_trace_cleanup()
{
if(pktbuf != NULL)
{
free(pktbuf);
pktbuf = NULL;
}
if(if_sock != -1)
{
close(if_sock);
if_sock = -1;
}
return;
}
int scamper_do_trace_init()
{
if((if_sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
return -1;
}
trace_funcs.probe = do_trace_probe;
trace_funcs.handle_icmp = do_trace_handle_icmp;
trace_funcs.handle_dl = do_trace_handle_dl;
trace_funcs.handle_rt = do_trace_handle_rt;
trace_funcs.handle_timeout = do_trace_handle_timeout;
trace_funcs.write = do_trace_write;
trace_funcs.task_free = do_trace_free;
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1