1778 lines
40 KiB
C
Executable File
1778 lines
40 KiB
C
Executable File
/*
|
|
Copyright (c) 2006, 2007 Dmitry Butskoy
|
|
<buc@citadel.stu.neva.ru>
|
|
License: GPL v2 or any later
|
|
|
|
See COPYING for the status of this software.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/poll.h>
|
|
#include <netinet/icmp6.h>
|
|
#include <netinet/ip_icmp.h>
|
|
#include <netinet/in.h>
|
|
//#include <linux/in6.h>
|
|
#include <netinet/ip6.h>
|
|
#include <netdb.h>
|
|
#include <errno.h>
|
|
#include <locale.h>
|
|
#include <sys/utsname.h>
|
|
#include <linux/types.h>
|
|
#include <linux/errqueue.h>
|
|
|
|
/* XXX: Remove this when things will be defined properly in netinet/ ... */
|
|
#include "flowlabel.h"
|
|
|
|
#include <clif.h>
|
|
#include "version.h"
|
|
#include "traceroute.h"
|
|
|
|
|
|
#ifndef ICMP6_DST_UNREACH_BEYONDSCOPE
|
|
#ifdef ICMP6_DST_UNREACH_NOTNEIGHBOR
|
|
#define ICMP6_DST_UNREACH_BEYONDSCOPE ICMP6_DST_UNREACH_NOTNEIGHBOR
|
|
#else
|
|
#define ICMP6_DST_UNREACH_BEYONDSCOPE 2
|
|
#endif
|
|
#endif
|
|
|
|
#ifndef IPV6_RECVHOPLIMIT
|
|
#define IPV6_RECVHOPLIMIT IPV6_HOPLIMIT
|
|
#endif
|
|
|
|
#ifndef IP_PMTUDISC_PROBE
|
|
#define IP_PMTUDISC_PROBE 3
|
|
#endif
|
|
|
|
#ifndef IPV6_PMTUDISC_PROBE
|
|
#define IPV6_PMTUDISC_PROBE 3
|
|
#endif
|
|
|
|
|
|
#define MAX_HOPS 255
|
|
#define MAX_PROBES 10
|
|
#define MAX_GATEWAYS_4 8
|
|
#define MAX_GATEWAYS_6 127
|
|
#define DEF_HOPS 30
|
|
#define DEF_SIM_PROBES 16 /* including several hops */
|
|
#define DEF_NUM_PROBES 3
|
|
#define DEF_WAIT_SECS 5.0
|
|
#define DEF_SEND_SECS 0
|
|
#define DEF_DATA_LEN 40 /* all but IP header... */
|
|
#define MAX_PACKET_LEN 65000
|
|
#ifndef DEF_AF
|
|
#define DEF_AF AF_INET
|
|
#endif
|
|
|
|
#define ttl2hops(X) (((X) <= 64 ? 65 : ((X) <= 128 ? 129 : 256)) - (X))
|
|
|
|
|
|
static char version_string[] = "Modern traceroute for Linux, "
|
|
"version " _TEXT(VERSION) ", " __DATE__
|
|
"\nCopyright (c) 2008 Dmitry Butskoy, "
|
|
" License: GPL v2 or any later";
|
|
static int debug = 0;
|
|
static unsigned int first_hop = 1;
|
|
static unsigned int max_hops = DEF_HOPS;
|
|
static unsigned int sim_probes = DEF_SIM_PROBES;
|
|
static unsigned int probes_per_hop = DEF_NUM_PROBES;
|
|
|
|
static char **gateways = NULL;
|
|
static int num_gateways = 0;
|
|
static unsigned char *rtbuf = NULL;
|
|
static size_t rtbuf_len = 0;
|
|
static unsigned int ipv6_rthdr_type = 2; /* IPV6_RTHDR_TYPE_2 */
|
|
|
|
static size_t header_len = 0;
|
|
static size_t data_len = 0;
|
|
|
|
static int dontfrag = 0;
|
|
static int noresolve = 0;
|
|
static int extension = 0;
|
|
static int as_lookups = 0;
|
|
static unsigned int dst_port_seq = 0;
|
|
static unsigned int tos = 0;
|
|
static unsigned int flow_label = 0;
|
|
static int noroute = 0;
|
|
static unsigned int fwmark = 0;
|
|
static int packet_len = -1;
|
|
static double wait_secs = DEF_WAIT_SECS;
|
|
static double send_secs = DEF_SEND_SECS;
|
|
static int mtudisc = 0;
|
|
static int backward = 0;
|
|
|
|
static sockaddr_any dst_addr = {{ 0, }, };
|
|
static char *dst_name = NULL;
|
|
static char *device = NULL;
|
|
static sockaddr_any src_addr = {{ 0, }, };
|
|
static unsigned int src_port = 0;
|
|
|
|
static const char *module = "default";
|
|
static const tr_module *ops = NULL;
|
|
|
|
static char *opts[16] = { NULL, }; /* assume enough */
|
|
static unsigned int opts_idx = 1; /* first one reserved... */
|
|
|
|
|
|
static int af = 0;
|
|
|
|
static probe *probes = NULL;
|
|
static unsigned int num_probes = 0;
|
|
#if defined(TCSUPPORT_CT_PON_CZ_GD)
|
|
int got_there = 1;
|
|
#endif
|
|
#if defined(TCSUPPORT_CT_TRACEROUTEIPV6)
|
|
/* cwmp flag */
|
|
static int cwmpflag = 0;
|
|
#define CWMP_TR_PID_PATH "/tmp/cwmp/tr69diag_traceroute.pid"
|
|
#define TR_STAT_FILE "/tmp/cwmp/traceroutediagnostic_stat"
|
|
#define MAX_DOMAIN_LENGTH 256
|
|
|
|
/* recent traceroute time */
|
|
static double uResponseTime = 0.0;
|
|
/* route hops number */
|
|
static unsigned long uRouteHopsNum = 0;
|
|
struct traceroute_rec
|
|
{
|
|
char HopHost[MAX_DOMAIN_LENGTH];
|
|
char HopHostAddress[64];
|
|
char HopErrorCode[16];
|
|
char HopRTTimes[64];
|
|
int isUsed;
|
|
};
|
|
/* route hops records heaer. */
|
|
static struct traceroute_rec *m_pTraceRouteRec = NULL;
|
|
|
|
/* add host to record. */
|
|
int addRecHost(unsigned int ttl, char *host)
|
|
{
|
|
if ( !host || ttl > max_hops )
|
|
return -1;
|
|
|
|
snprintf(m_pTraceRouteRec[ttl].HopHost
|
|
, sizeof(m_pTraceRouteRec[ttl].HopHost) - 1
|
|
, "%s"
|
|
, host);
|
|
|
|
m_pTraceRouteRec[ttl].isUsed = 1;
|
|
return 0;
|
|
}
|
|
|
|
/* add host address to record. */
|
|
int addRecHostAddress(unsigned int ttl, char *address)
|
|
{
|
|
if ( !address || ttl > max_hops )
|
|
return -1;
|
|
|
|
snprintf(m_pTraceRouteRec[ttl].HopHostAddress
|
|
, sizeof(m_pTraceRouteRec[ttl].HopHostAddress) - 1
|
|
, "%s"
|
|
, address);
|
|
|
|
m_pTraceRouteRec[ttl].isUsed = 1;
|
|
return 0;
|
|
}
|
|
|
|
/* add hop errorcode to record. */
|
|
int addRecHopErrorCode(unsigned int ttl, int errorcode)
|
|
{
|
|
if ( ttl > max_hops || 1 != m_pTraceRouteRec[ttl].isUsed )
|
|
return -1;
|
|
|
|
snprintf(m_pTraceRouteRec[ttl].HopErrorCode
|
|
, sizeof(m_pTraceRouteRec[ttl].HopErrorCode) - 1
|
|
, "%d"
|
|
, errorcode);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* add hop rttime to record. */
|
|
int addRecHopRTTime(unsigned int ttl, char *hoprttime)
|
|
{
|
|
if ( !hoprttime || ttl > max_hops )
|
|
return -1;
|
|
|
|
if ( m_pTraceRouteRec[ttl].HopRTTimes[0] )
|
|
{
|
|
strcat(m_pTraceRouteRec[ttl].HopRTTimes, ",");
|
|
strcat(m_pTraceRouteRec[ttl].HopRTTimes, hoprttime);
|
|
}
|
|
else
|
|
strcpy(m_pTraceRouteRec[ttl].HopRTTimes, hoprttime);
|
|
|
|
m_pTraceRouteRec[ttl].isUsed = 1;
|
|
return 0;
|
|
}
|
|
|
|
/* generate statistics file for cwmp. */
|
|
int generateStatFile()
|
|
{
|
|
FILE *cfp = NULL;
|
|
int idx = 0, totalcnt = 0, ttl = 0;
|
|
|
|
if ( !m_pTraceRouteRec )
|
|
return -1;
|
|
|
|
cfp = fopen(TR_STAT_FILE, "wb");
|
|
if( cfp )
|
|
{
|
|
for ( idx = 0; idx < max_hops; idx ++ )
|
|
{
|
|
if ( 1 != m_pTraceRouteRec[idx].isUsed )
|
|
break;
|
|
totalcnt ++;
|
|
ttl = idx + 1;
|
|
fprintf(cfp, "HopHost%d=%s \n",
|
|
ttl, m_pTraceRouteRec[idx].HopHost);
|
|
fprintf(cfp, "HopHostAddress%d=%s \n",
|
|
ttl, m_pTraceRouteRec[idx].HopHostAddress);
|
|
fprintf(cfp, "HopErrorCode%d=%s \n",
|
|
ttl
|
|
, ( 0 == m_pTraceRouteRec[idx].HopErrorCode[0])
|
|
? "0" : m_pTraceRouteRec[idx].HopErrorCode);
|
|
fprintf(cfp, "HopRTTimes%d=%s \n",
|
|
ttl, m_pTraceRouteRec[idx].HopRTTimes);
|
|
}
|
|
uRouteHopsNum = totalcnt;
|
|
|
|
fprintf(cfp, "ResponseTime=%g ms\n", uResponseTime);
|
|
fprintf(cfp, "RouteHopsNumberOfEntries=%lu \n", uRouteHopsNum);
|
|
#if defined(TCSUPPORT_CT_PON_CZ_GD)
|
|
if ( uRouteHopsNum == max_hops || !got_there)
|
|
#else
|
|
if ( uRouteHopsNum == max_hops )
|
|
#endif
|
|
fprintf(cfp, "MaxHopCountExceeded=%s\n", "1");
|
|
|
|
fclose(cfp);
|
|
}
|
|
}
|
|
|
|
/* differ time */
|
|
static inline double
|
|
deltaTime(struct timeval *t1p, struct timeval *t2p)
|
|
{
|
|
double dt;
|
|
|
|
dt = (double)(t2p->tv_sec - t1p->tv_sec) * 1000.0 +
|
|
(double)(t2p->tv_usec - t1p->tv_usec) / 1000.0;
|
|
return (dt);
|
|
}
|
|
|
|
#endif
|
|
|
|
static void ex_error (const char *format, ...) {
|
|
va_list ap;
|
|
|
|
va_start (ap, format);
|
|
vfprintf (stderr, format, ap);
|
|
va_end (ap);
|
|
|
|
fprintf (stderr, "\n");
|
|
|
|
exit (2);
|
|
}
|
|
|
|
void error (const char *str) {
|
|
|
|
fprintf (stderr, "\n");
|
|
|
|
perror (str);
|
|
|
|
exit (1);
|
|
}
|
|
|
|
void error_or_perm (const char *str) {
|
|
|
|
if (errno == EPERM)
|
|
fprintf (stderr, "You have no enough privileges to use "
|
|
"this traceroute method.");
|
|
error (str);
|
|
}
|
|
|
|
|
|
/* Set initial parameters according to how we was called */
|
|
|
|
static void check_progname (const char *name) {
|
|
const char *p;
|
|
int l;
|
|
|
|
p = strrchr (name, '/');
|
|
if (p) p++;
|
|
else p = name;
|
|
|
|
l = strlen (p);
|
|
if (l <= 0) return;
|
|
l--;
|
|
|
|
if (p[l] == '6') af = AF_INET6;
|
|
else if (p[l] == '4') af = AF_INET;
|
|
|
|
if (!strncmp (p, "tcp", 3))
|
|
module = "tcp";
|
|
if (!strncmp (p, "tracert", 7))
|
|
module = "icmp";
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
static int getaddr (const char *name, sockaddr_any *addr) {
|
|
int ret;
|
|
struct addrinfo hints, *ai, *res = NULL;
|
|
|
|
memset (&hints, 0, sizeof (hints));
|
|
hints.ai_family = af;
|
|
//hints.ai_flags = AI_IDN;
|
|
|
|
ret = getaddrinfo (name, NULL, &hints, &res);
|
|
if (ret) {
|
|
fprintf (stderr, "%s: %s\n", name, gai_strerror (ret));
|
|
return -1;
|
|
}
|
|
|
|
for (ai = res; ai; ai = ai->ai_next) {
|
|
if (ai->ai_family == af) break;
|
|
/* when af not specified, choose DEF_AF if present */
|
|
if (!af && ai->ai_family == DEF_AF)
|
|
break;
|
|
}
|
|
if (!ai) ai = res; /* anything... */
|
|
|
|
if (ai->ai_addrlen > sizeof (*addr))
|
|
return -1; /* paranoia */
|
|
memcpy (addr, ai->ai_addr, ai->ai_addrlen);
|
|
|
|
freeaddrinfo (res);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void make_fd_used (int fd) {
|
|
int nfd;
|
|
|
|
if (fcntl (fd, F_GETFL) != -1)
|
|
return;
|
|
|
|
if (errno != EBADF)
|
|
error ("fcntl F_GETFL");
|
|
|
|
nfd = open ("/dev/null", O_RDONLY);
|
|
if (nfd < 0) error ("open /dev/null");
|
|
|
|
if (nfd != fd) {
|
|
dup2 (nfd, fd);
|
|
close (nfd);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
static char addr2str_buf[INET6_ADDRSTRLEN];
|
|
|
|
static const char *addr2str (const sockaddr_any *addr) {
|
|
|
|
getnameinfo (&addr->sa, sizeof (*addr),
|
|
addr2str_buf, sizeof (addr2str_buf), 0, 0, NI_NUMERICHOST);
|
|
|
|
return addr2str_buf;
|
|
}
|
|
|
|
|
|
/* IP options stuff */
|
|
|
|
static void init_ip_options (void) {
|
|
sockaddr_any *gates;
|
|
int i, max;
|
|
|
|
if (!num_gateways)
|
|
return;
|
|
|
|
/* check for TYPE,ADDR,ADDR... form */
|
|
if (af == AF_INET6 && num_gateways > 1 && gateways[0]) {
|
|
char *q;
|
|
unsigned int value = strtoul (gateways[0], &q, 0);
|
|
|
|
if (!*q) {
|
|
ipv6_rthdr_type = value;
|
|
num_gateways--;
|
|
for (i = 0; i < num_gateways; i++)
|
|
gateways[i] = gateways[i + 1];
|
|
}
|
|
}
|
|
|
|
|
|
max = af == AF_INET ? MAX_GATEWAYS_4 : MAX_GATEWAYS_6;
|
|
if (num_gateways > max)
|
|
ex_error ("Too many gateways specified. No more than %d", max);
|
|
|
|
|
|
gates = alloca (num_gateways * sizeof (*gates));
|
|
|
|
for (i = 0; i < num_gateways; i++) {
|
|
|
|
if (!gateways[i]) error ("strdup");
|
|
|
|
if (getaddr (gateways[i], &gates[i]) < 0)
|
|
ex_error (""); /* already reported */
|
|
if (gates[i].sa.sa_family != af)
|
|
ex_error ("IP versions mismatch in gateway addresses");
|
|
|
|
free (gateways[i]);
|
|
}
|
|
|
|
free (gateways);
|
|
gateways = NULL;
|
|
|
|
|
|
if (af == AF_INET) {
|
|
struct in_addr *in;
|
|
|
|
rtbuf_len = 4 + (num_gateways + 1) * sizeof (*in);
|
|
rtbuf = malloc (rtbuf_len);
|
|
if (!rtbuf) error ("malloc");
|
|
|
|
in = (struct in_addr *) &rtbuf[4];
|
|
for (i = 0; i < num_gateways; i++)
|
|
memcpy (&in[i], &gates[i].sin.sin_addr, sizeof (*in));
|
|
/* final hop */
|
|
memcpy (&in[i], &dst_addr.sin.sin_addr, sizeof (*in));
|
|
i++;
|
|
|
|
rtbuf[0] = IPOPT_NOP;
|
|
rtbuf[1] = IPOPT_LSRR;
|
|
rtbuf[2] = (i * sizeof (*in)) + 3;
|
|
rtbuf[3] = IPOPT_MINOFF;
|
|
|
|
}
|
|
else if (af == AF_INET6) {
|
|
struct in6_addr *in6;
|
|
struct ip6_rthdr *rth;
|
|
|
|
/* IPV6_RTHDR_TYPE_0 length is 8 */
|
|
rtbuf_len = 8 + num_gateways * sizeof (*in6);
|
|
rtbuf = malloc (rtbuf_len);
|
|
if (!rtbuf) error ("malloc");
|
|
|
|
rth = (struct ip6_rthdr *) rtbuf;
|
|
rth->ip6r_nxt = 0;
|
|
rth->ip6r_len = 2 * num_gateways;
|
|
rth->ip6r_type = ipv6_rthdr_type;
|
|
rth->ip6r_segleft = num_gateways;
|
|
|
|
*((u_int32_t *) (rth + 1)) = 0;
|
|
|
|
in6 = (struct in6_addr *) (rtbuf + 8);
|
|
for (i = 0; i < num_gateways; i++)
|
|
memcpy (&in6[i], &gates[i].sin6.sin6_addr, sizeof (*in6));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Command line stuff */
|
|
|
|
static int set_af (CLIF_option *optn, char *arg) {
|
|
int vers = (int) optn->data;
|
|
|
|
if (vers == 4) af = AF_INET;
|
|
else if (vers == 6) af = AF_INET6;
|
|
else
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int add_gateway (CLIF_option *optn, char *arg) {
|
|
|
|
if (num_gateways >= MAX_GATEWAYS_6) { /* 127 > 8 ... :) */
|
|
fprintf (stderr, "Too many gateways specified.");
|
|
return -1;
|
|
}
|
|
|
|
gateways = realloc (gateways, (num_gateways + 1) * sizeof (*gateways));
|
|
if (!gateways) error ("malloc");
|
|
gateways[num_gateways++] = strdup (arg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int set_source (CLIF_option *optn, char *arg) {
|
|
|
|
return getaddr (arg, &src_addr);
|
|
}
|
|
|
|
static int set_port (CLIF_option *optn, char *arg) {
|
|
unsigned int *up = (unsigned int *) optn->data;
|
|
char *q;
|
|
|
|
*up = strtoul (arg, &q, 0);
|
|
if (q == arg) {
|
|
struct servent *s = getservbyname (arg, NULL);
|
|
|
|
if (!s) return -1;
|
|
*up = ntohs (s->s_port);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int set_module (CLIF_option *optn, char *arg) {
|
|
|
|
module = (char *) optn->data;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int set_mod_option (CLIF_option *optn, char *arg) {
|
|
|
|
if (!strcmp (arg, "help")) {
|
|
const tr_module *mod = tr_get_module (module);
|
|
|
|
if (mod && mod->options) {
|
|
/* just to set common keyword flag... */
|
|
CLIF_parse (1, &arg, 0, 0, CLIF_KEYWORD);
|
|
CLIF_print_options (NULL, mod->options);
|
|
} else
|
|
fprintf (stderr, "No options for module `%s'\n", module);
|
|
|
|
exit (0);
|
|
}
|
|
|
|
if (opts_idx >= sizeof (opts) / sizeof (*opts)) {
|
|
fprintf (stderr, "Too many module options\n");
|
|
return -1;
|
|
}
|
|
|
|
opts[opts_idx] = strdup (arg);
|
|
if (!opts[opts_idx]) error ("strdup");
|
|
opts_idx++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int set_raw (CLIF_option *optn, char *arg) {
|
|
char buf[1024];
|
|
|
|
module = "raw";
|
|
|
|
snprintf (buf, sizeof (buf), "protocol=%s", arg);
|
|
return set_mod_option (optn, buf);
|
|
}
|
|
|
|
|
|
static int set_host (CLIF_argument *argm, char *arg, int index) {
|
|
|
|
if (getaddr (arg, &dst_addr) < 0)
|
|
return -1;
|
|
|
|
dst_name = arg;
|
|
|
|
/* i.e., guess it by the addr in cmdline... */
|
|
if (!af) af = dst_addr.sa.sa_family;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static CLIF_option option_list[] = {
|
|
{ "4", 0, 0, "Use IPv4", set_af, (void *) 4, 0, CLIF_EXTRA },
|
|
{ "6", 0, 0, "Use IPv6", set_af, (void *) 6, 0, 0 },
|
|
{ "d", "debug", 0, "Enable socket level debugging",
|
|
CLIF_set_flag, &debug, 0, 0 },
|
|
{ "F", "dont-fragment", 0, "Do not fragment packets",
|
|
CLIF_set_flag, &dontfrag, 0, CLIF_ABBREV },
|
|
{ "f", "first", "first_ttl", "Start from the %s hop (instead from 1)",
|
|
CLIF_set_uint, &first_hop, 0, 0 },
|
|
{ "g", "gateway", "gate", "Route packets through the specified gateway "
|
|
"(maximum " _TEXT(MAX_GATEWAYS_4) " for IPv4 and "
|
|
_TEXT(MAX_GATEWAYS_6) " for IPv6)",
|
|
add_gateway, 0, 0, CLIF_SEVERAL },
|
|
{ "I", "icmp", 0, "Use ICMP ECHO for tracerouting",
|
|
set_module, "icmp", 0, 0 },
|
|
{ "T", "tcp", 0, "Use TCP SYN for tracerouting",
|
|
set_module, "tcp", 0, 0 },
|
|
{ "i", "interface", "device", "Specify a network interface "
|
|
"to operate with",
|
|
CLIF_set_string, &device, 0, 0 },
|
|
{ "m", "max-hops", "max_ttl", "Set the max number of hops (max TTL "
|
|
"to be reached). Default is " _TEXT(DEF_HOPS) ,
|
|
CLIF_set_uint, &max_hops, 0, 0 },
|
|
{ "N", "sim-queries", "squeries", "Set the number of probes "
|
|
"to be tried simultaneously (default is "
|
|
_TEXT(DEF_SIM_PROBES) ")",
|
|
CLIF_set_uint, &sim_probes, 0, 0 },
|
|
{ "n", 0, 0, "Do not resolve IP addresses to their domain names",
|
|
CLIF_set_flag, &noresolve, 0, 0 },
|
|
{ "p", "port", "port", "Set the destination port to use. "
|
|
"It is either initial udp port value for "
|
|
"\"default\" method (incremented by each probe, "
|
|
"default is " _TEXT(DEF_START_PORT) "), "
|
|
"or initial seq for \"icmp\" (incremented as well, "
|
|
"default from 1), or some constant destination port"
|
|
" for other methods (with default of "
|
|
_TEXT(DEF_TCP_PORT) " for \"tcp\", "
|
|
_TEXT(DEF_UDP_PORT) " for \"udp\", etc.)",
|
|
set_port, &dst_port_seq, 0, 0 },
|
|
{ "t", "tos", "tos", "Set the TOS (IPv4 type of service) or TC "
|
|
"(IPv6 traffic class) value for outgoing packets",
|
|
CLIF_set_uint, &tos, 0, 0 },
|
|
{ "l", "flowlabel", "flow_label", "Use specified %s for IPv6 packets",
|
|
CLIF_set_uint, &flow_label, 0, 0 },
|
|
{ "w", "wait", "waittime", "Set the number of seconds to wait for "
|
|
"response to a probe (default is "
|
|
_TEXT(DEF_WAIT_SECS) "). Non-integer (float point) "
|
|
"values allowed too",
|
|
CLIF_set_double, &wait_secs, 0, 0 },
|
|
{ "q", "queries", "nqueries", "Set the number of probes per each hop. "
|
|
"Default is " _TEXT(DEF_NUM_PROBES),
|
|
CLIF_set_uint, &probes_per_hop, 0, 0 },
|
|
{ "r", 0, 0, "Bypass the normal routing and send directly to a host "
|
|
"on an attached network",
|
|
CLIF_set_flag, &noroute, 0, 0 },
|
|
{ "s", "source", "src_addr", "Use source %s for outgoing packets",
|
|
set_source, 0, 0, 0 },
|
|
{ "z", "sendwait", "sendwait", "Minimal time interval between probes "
|
|
"(default " _TEXT(DEF_SEND_SECS) "). If the value "
|
|
"is more than 10, then it specifies a number "
|
|
"in milliseconds, else it is a number of seconds "
|
|
"(float point values allowed too)",
|
|
CLIF_set_double, &send_secs, 0, 0 },
|
|
{ "e", "extensions", 0, "Show ICMP extensions (if present), "
|
|
"including MPLS",
|
|
CLIF_set_flag, &extension, 0, CLIF_ABBREV },
|
|
{ "A", "as-path-lookups", 0, "Perform AS path lookups in routing "
|
|
"registries and print results directly after "
|
|
"the corresponding addresses",
|
|
CLIF_set_flag, &as_lookups, 0, 0 },
|
|
{ "M", "module", "name", "Use specified module (either builtin or "
|
|
"external) for traceroute operations. Most methods "
|
|
"have their shortcuts (`-I' means `-M icmp' etc.)",
|
|
CLIF_set_string, &module, 0, CLIF_EXTRA },
|
|
{ "O", "options", "OPTS", "Use module-specific option %s for the "
|
|
"traceroute module. Several %s allowed, separated "
|
|
"by comma. If %s is \"help\", print info about "
|
|
"available options",
|
|
set_mod_option, 0, 0, CLIF_SEVERAL | CLIF_EXTRA },
|
|
{ 0, "sport", "num", "Use source port %s for outgoing packets. "
|
|
"Implies `-N 1'",
|
|
set_port, &src_port, 0, CLIF_EXTRA },
|
|
#ifdef SO_MARK
|
|
{ 0, "fwmark", "num", "Set firewall mark for outgoing packets",
|
|
CLIF_set_uint, &fwmark, 0, 0 },
|
|
#endif
|
|
{ "U", "udp", 0, "Use UDP to particular port for tracerouting "
|
|
"(instead of increasing the port per each probe), "
|
|
"default port is " _TEXT(DEF_UDP_PORT),
|
|
set_module, "udp", 0, CLIF_EXTRA },
|
|
{ 0, "UL", 0, "Use UDPLITE for tracerouting (default dest port is "
|
|
_TEXT(DEF_UDP_PORT) ")",
|
|
set_module, "udplite", 0, CLIF_ONEDASH|CLIF_EXTRA },
|
|
{ "P", "protocol", "prot", "Use raw packet of protocol %s "
|
|
"for tracerouting",
|
|
set_raw, 0, 0, CLIF_EXTRA },
|
|
{ 0, "mtu", 0, "Discover MTU along the path being traced. "
|
|
"Implies `-F -N 1'",
|
|
CLIF_set_flag, &mtudisc, 0, CLIF_EXTRA },
|
|
{ 0, "back", 0, "Guess the number of hops in the backward path "
|
|
"and print if it differs",
|
|
CLIF_set_flag, &backward, 0, CLIF_EXTRA },
|
|
#if defined(TCSUPPORT_CT_TRACEROUTEIPV6)
|
|
{ "x", "cwmpflag", "cwmpflag", "Cwmp traceroute",
|
|
CLIF_set_int, &cwmpflag, 0, 0 },
|
|
#endif
|
|
CLIF_VERSION_OPTION (version_string),
|
|
CLIF_HELP_OPTION,
|
|
CLIF_END_OPTION
|
|
};
|
|
|
|
static CLIF_argument arg_list[] = {
|
|
{ "host", "The host to traceroute to",
|
|
set_host, 0, CLIF_STRICT },
|
|
{ "packetlen", "The full packet length (default is the length of "
|
|
"an IP header plus " _TEXT(DEF_DATA_LEN) "). Can be "
|
|
"ignored or increased to a minimal allowed value",
|
|
CLIF_arg_int, &packet_len, 0 },
|
|
CLIF_END_ARGUMENT
|
|
};
|
|
|
|
|
|
static void do_it (void);
|
|
|
|
int main (int argc, char *argv[]) {
|
|
#if defined(TCSUPPORT_CT_TRACEROUTEIPV6)
|
|
struct timeval starttime = {0}, endtime = {0};
|
|
#endif
|
|
|
|
setlocale (LC_ALL, "");
|
|
setlocale (LC_NUMERIC, "C"); /* avoid commas in msec printed */
|
|
|
|
check_progname (argv[0]);
|
|
|
|
if (CLIF_parse (argc, argv, option_list, arg_list,
|
|
CLIF_MAY_JOIN_ARG | CLIF_HELP_EMPTY) < 0
|
|
) exit (2);
|
|
|
|
ops = tr_get_module (module);
|
|
if (!ops) ex_error ("Unknown traceroute module %s", module);
|
|
|
|
if (!first_hop || first_hop > max_hops)
|
|
ex_error ("first hop out of range");
|
|
if (max_hops > MAX_HOPS)
|
|
ex_error ("max hops cannot be more than " _TEXT(MAX_HOPS));
|
|
if (!probes_per_hop || probes_per_hop > MAX_PROBES)
|
|
ex_error ("no more than " _TEXT(MAX_PROBES) " probes per hop");
|
|
if (wait_secs < 0)
|
|
ex_error ("bad wait seconds `%g' specified", wait_secs);
|
|
if (packet_len > MAX_PACKET_LEN)
|
|
ex_error ("too big packetlen %d specified", packet_len);
|
|
if (src_addr.sa.sa_family && src_addr.sa.sa_family != af)
|
|
ex_error ("IP version mismatch in addresses specified");
|
|
if (send_secs < 0)
|
|
ex_error ("bad sendtime `%g' specified", send_secs);
|
|
if (send_secs >= 10) /* it is milliseconds */
|
|
send_secs /= 1000;
|
|
|
|
if (af == AF_INET6 && (tos || flow_label))
|
|
dst_addr.sin6.sin6_flowinfo =
|
|
htonl (((tos & 0xff) << 20) | (flow_label & 0x000fffff));
|
|
|
|
if (src_port) {
|
|
src_addr.sin.sin_port = htons ((u_int16_t) src_port);
|
|
src_addr.sa.sa_family = af;
|
|
}
|
|
|
|
if (src_port || ops->one_per_time)
|
|
sim_probes = 1;
|
|
|
|
|
|
/* make sure we don't std{in,out,err} to open sockets */
|
|
make_fd_used (0);
|
|
make_fd_used (1);
|
|
make_fd_used (2);
|
|
|
|
init_ip_options ();
|
|
|
|
header_len = (af == AF_INET ? sizeof (struct iphdr)
|
|
: sizeof (struct ip6_hdr)) +
|
|
rtbuf_len + ops->header_len;
|
|
|
|
if (mtudisc) {
|
|
dontfrag = 1;
|
|
sim_probes = 1;
|
|
packet_len = MAX_PACKET_LEN;
|
|
}
|
|
|
|
if (packet_len < 0) {
|
|
if (DEF_DATA_LEN >= ops->header_len)
|
|
data_len = DEF_DATA_LEN - ops->header_len;
|
|
} else {
|
|
if (packet_len >= header_len)
|
|
data_len = packet_len - header_len;
|
|
}
|
|
|
|
|
|
num_probes = max_hops * probes_per_hop;
|
|
probes = calloc (num_probes, sizeof (*probes));
|
|
if (!probes) error ("calloc");
|
|
|
|
if (ops->options && opts_idx > 1) {
|
|
opts[0] = strdup (module); /* aka argv[0] ... */
|
|
if (CLIF_parse (opts_idx, opts, ops->options, 0, CLIF_KEYWORD) < 0)
|
|
exit (2);
|
|
}
|
|
|
|
if (ops->init (&dst_addr, dst_port_seq, &data_len) < 0)
|
|
ex_error ("trace method's init failed");
|
|
|
|
#if defined(TCSUPPORT_CT_TRACEROUTEIPV6)
|
|
if ( 1 == cwmpflag )
|
|
{
|
|
FILE *cfp = NULL;
|
|
cfp = fopen(CWMP_TR_PID_PATH, "wb");
|
|
if( cfp )
|
|
{
|
|
fprintf(cfp, "%d", getpid());
|
|
fclose(cfp);
|
|
}
|
|
|
|
gettimeofday(&starttime, NULL);
|
|
|
|
m_pTraceRouteRec = (struct traceroute_rec *)
|
|
malloc(max_hops * sizeof (struct traceroute_rec));
|
|
bzero(m_pTraceRouteRec, max_hops * sizeof (*m_pTraceRouteRec));
|
|
}
|
|
#endif
|
|
|
|
do_it ();
|
|
|
|
#if defined(TCSUPPORT_CT_TRACEROUTEIPV6)
|
|
if ( 1 == cwmpflag )
|
|
{
|
|
gettimeofday(&endtime, NULL);
|
|
uResponseTime = deltaTime(&starttime, &endtime);
|
|
|
|
generateStatFile();
|
|
|
|
if ( m_pTraceRouteRec )
|
|
{
|
|
free(m_pTraceRouteRec);
|
|
m_pTraceRouteRec = NULL;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* PRINT STUFF */
|
|
|
|
static void print_header (void) {
|
|
|
|
/* Note, without ending new-line! */
|
|
printf ("traceroute to %s (%s), %u hops max, %zu byte packets",
|
|
dst_name, addr2str (&dst_addr), max_hops,
|
|
header_len + data_len);
|
|
fflush (stdout);
|
|
}
|
|
|
|
|
|
static void print_addr (sockaddr_any *res) {
|
|
const char *str;
|
|
|
|
if (!res->sa.sa_family)
|
|
return;
|
|
|
|
str = addr2str (res);
|
|
|
|
|
|
if (noresolve)
|
|
printf (" %s", str);
|
|
else {
|
|
char buf[1024];
|
|
|
|
buf[0] = '\0';
|
|
getnameinfo (&res->sa, sizeof (*res), buf, sizeof (buf),
|
|
0, 0, NI_IDN);
|
|
printf (" %s (%s)", buf[0] ? buf : str, str);
|
|
}
|
|
|
|
if (as_lookups)
|
|
printf (" [%s]", get_as_path (str));
|
|
}
|
|
|
|
|
|
static void print_probe (probe *pb) {
|
|
unsigned int idx = (pb - probes);
|
|
unsigned int ttl = idx / probes_per_hop + 1;
|
|
unsigned int np = idx % probes_per_hop;
|
|
#if defined(TCSUPPORT_CT_TRACEROUTEIPV6)
|
|
char timebuf[24] = {0};
|
|
#endif
|
|
|
|
if (np == 0)
|
|
printf ("\n%2u ", ttl);
|
|
|
|
|
|
if (!pb->res.sa.sa_family)
|
|
{
|
|
printf (" *");
|
|
#if defined(TCSUPPORT_CT_TRACEROUTEIPV6)
|
|
if ( 1 == cwmpflag )
|
|
{
|
|
addRecHopRTTime(ttl - 1, "*");
|
|
}
|
|
#endif
|
|
}
|
|
else {
|
|
int prn = !np; /* print if the first... */
|
|
|
|
if (np) { /* ...and if differs with previous */
|
|
probe *p;
|
|
|
|
/* skip expired */
|
|
for (p = pb - 1; np && !p->res.sa.sa_family; p--, np--) ;
|
|
|
|
if (!np ||
|
|
!equal_addr (&p->res, &pb->res) ||
|
|
(extension && p->ext != pb->ext &&
|
|
!(p->ext && pb->ext && !strcmp (p->ext, pb->ext))) ||
|
|
(backward && p->recv_ttl != pb->recv_ttl)
|
|
) prn = 1;
|
|
}
|
|
|
|
if (prn) {
|
|
print_addr (&pb->res);
|
|
#if defined(TCSUPPORT_CT_TRACEROUTEIPV6)
|
|
if ( 1 == cwmpflag && pb->res.sa.sa_family )
|
|
{
|
|
addRecHost(ttl - 1, addr2str(&pb->res));
|
|
addRecHostAddress(ttl - 1, addr2str(&pb->res));
|
|
}
|
|
#endif
|
|
if (pb->ext) printf (" <%s>", pb->ext);
|
|
|
|
if (backward && pb->recv_ttl) {
|
|
int hops = ttl2hops (pb->recv_ttl);
|
|
if (hops != ttl) printf (" '-%d'", hops);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (pb->recv_time) {
|
|
double diff = pb->recv_time - pb->send_time;
|
|
|
|
printf (" %.3f ms", diff * 1000);
|
|
#if defined(TCSUPPORT_CT_TRACEROUTEIPV6)
|
|
if ( 1 == cwmpflag )
|
|
{
|
|
snprintf(timebuf, sizeof(timebuf) - 1
|
|
, "%.3fms", diff * 1000);
|
|
addRecHopRTTime(ttl - 1, timebuf);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (pb->err_str[0])
|
|
printf (" %s", pb->err_str);
|
|
|
|
|
|
fflush (stdout);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
static void print_end (void) {
|
|
|
|
printf ("\n");
|
|
}
|
|
|
|
|
|
/* Check expiration stuff */
|
|
|
|
static void check_expired (probe *pb) {
|
|
int idx = (pb - probes);
|
|
probe *p, *endp = probes + num_probes;
|
|
probe *fp = NULL, *pfp = NULL;
|
|
|
|
if (!pb->done) /* an ops method still not release it */
|
|
return;
|
|
|
|
|
|
/* check all the previous in the same hop */
|
|
for (p = &probes[idx - (idx % probes_per_hop)]; p < pb; p++) {
|
|
|
|
if (!p->done || /* too early to decide something */
|
|
!p->final /* already ttl-exceeded in the same hop */
|
|
) return;
|
|
|
|
pfp = p; /* some of the previous probes is final */
|
|
}
|
|
|
|
/* check forward all the sent probes */
|
|
for (p = pb + 1; p < endp && p->send_time; p++) {
|
|
|
|
if (p->done) { /* some next probe already done... */
|
|
if (!p->final) /* ...was ttl-exceeded. OK, we are expired. */
|
|
return;
|
|
else {
|
|
fp = p;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!fp) /* no any final probe found. Assume expired. */
|
|
return;
|
|
|
|
|
|
/* Well. There is a situation "*(this) * * * * ... * * final"
|
|
We cannot guarantee that "final" is in its right place.
|
|
We've sent "sim_probes" simultaneously, and the final hop
|
|
can drop some of them and answer only for latest ones.
|
|
If we can detect/assume that it so, then just put "final"
|
|
to the (pseudo-expired) "this" place.
|
|
*/
|
|
|
|
if (pfp ||
|
|
(idx % probes_per_hop) + (fp - pb) < probes_per_hop
|
|
) {
|
|
/* Either some previous (pfp) or some next probe
|
|
in this hop is final. It means that the whole hop is final.
|
|
Do the replace (it also causes further "final"s to be shifted
|
|
here too).
|
|
*/
|
|
goto replace_by_final;
|
|
}
|
|
|
|
|
|
/* If the final probe is an icmp_unreachable report
|
|
(either in a case of some error, like "!H", or just port_unreach),
|
|
it could follow the "time-exceed" report from the *same* hop.
|
|
*/
|
|
for (p = pb - 1; p >= probes; p--) {
|
|
if (equal_addr (&p->res, &fp->res)) {
|
|
/* ...Yes. Put "final" to the "this" place. */
|
|
goto replace_by_final;
|
|
}
|
|
}
|
|
|
|
|
|
if (fp->recv_ttl) {
|
|
/* Consider the ttl value of the report packet and guess where
|
|
the "final" should be. If it seems that it should be
|
|
in the same hop as "this", then do replace.
|
|
*/
|
|
int back_hops, ttl;
|
|
|
|
/* We assume that the reporting one has an initial ttl value
|
|
of either 64, or 128, or 255. It is most widely used
|
|
in the modern routers and computers.
|
|
The idea comes from tracepath(1) routine.
|
|
*/
|
|
back_hops = ttl2hops (fp->recv_ttl);
|
|
|
|
/* It is possible that the back path differs from the forward
|
|
and therefore has different number of hops. To minimize
|
|
such an influence, get the nearest previous time-exceeded
|
|
probe and compare with it.
|
|
*/
|
|
for (p = pb - 1; p >= probes; p--) {
|
|
if (p->done && !p->final && p->recv_ttl) {
|
|
int hops = ttl2hops (p->recv_ttl);
|
|
|
|
if (hops < back_hops) {
|
|
ttl = (p - probes) / probes_per_hop + 1;
|
|
back_hops = (back_hops - hops) + ttl;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
ttl = idx / probes_per_hop + 1;
|
|
if (back_hops == ttl)
|
|
/* Yes! It seems that "final" should be at "this" place */
|
|
goto replace_by_final;
|
|
else if (back_hops < ttl)
|
|
/* Hmmm... Assume better to replace here too... */
|
|
goto replace_by_final;
|
|
|
|
}
|
|
|
|
|
|
/* No idea what to do. Assume expired. */
|
|
|
|
return;
|
|
|
|
|
|
replace_by_final:
|
|
|
|
*pb = *fp;
|
|
|
|
memset (fp, 0, sizeof (*fp));
|
|
/* block extra re-send */
|
|
fp->send_time = 1.;
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
probe *probe_by_seq (int seq) {
|
|
int n;
|
|
|
|
if (seq <= 0) return NULL;
|
|
|
|
for (n = 0; n < num_probes; n++) {
|
|
if (probes[n].seq == seq)
|
|
return &probes[n];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
probe *probe_by_sk (int sk) {
|
|
int n;
|
|
|
|
if (sk <= 0) return NULL;
|
|
|
|
for (n = 0; n < num_probes; n++) {
|
|
if (probes[n].sk == sk)
|
|
return &probes[n];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static void poll_callback (int fd, int revents) {
|
|
|
|
ops->recv_probe (fd, revents);
|
|
}
|
|
|
|
|
|
static void do_it (void) {
|
|
int start = (first_hop - 1) * probes_per_hop;
|
|
int end = num_probes;
|
|
double last_send = 0;
|
|
|
|
print_header ();
|
|
|
|
|
|
while (start < end) {
|
|
int n, num = 0;
|
|
double max_time = 0;
|
|
double now_time = get_time ();
|
|
|
|
|
|
for (n = start; n < end; n++) {
|
|
probe *pb = &probes[n];
|
|
|
|
if (!pb->done &&
|
|
pb->send_time &&
|
|
now_time - pb->send_time >= wait_secs
|
|
) {
|
|
ops->expire_probe (pb);
|
|
check_expired (pb);
|
|
}
|
|
|
|
|
|
if (pb->done) {
|
|
|
|
if (n == start) { /* can print it now */
|
|
print_probe (pb);
|
|
start++;
|
|
}
|
|
|
|
if (pb->final)
|
|
end = (n / probes_per_hop + 1) * probes_per_hop;
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
if (!pb->send_time) {
|
|
int ttl;
|
|
|
|
if (send_secs && (now_time - last_send) < send_secs) {
|
|
max_time = (last_send + send_secs) - wait_secs;
|
|
break;
|
|
}
|
|
|
|
ttl = n / probes_per_hop + 1;
|
|
|
|
ops->send_probe (pb, ttl);
|
|
|
|
if (!pb->send_time) {
|
|
if (max_time) break; /* have chances later */
|
|
else error ("send probe");
|
|
}
|
|
|
|
last_send = pb->send_time;
|
|
}
|
|
|
|
|
|
if (pb->send_time > max_time)
|
|
max_time = pb->send_time;
|
|
|
|
num++;
|
|
if (num >= sim_probes) break;
|
|
}
|
|
|
|
|
|
if (max_time) {
|
|
double timeout = (max_time + wait_secs) - now_time;
|
|
|
|
if (timeout < 0) timeout = 0;
|
|
|
|
do_poll (timeout, poll_callback);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
print_end ();
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
void tune_socket (int sk) {
|
|
int i = 0;
|
|
|
|
if (debug) {
|
|
i = 1;
|
|
if (setsockopt (sk, SOL_SOCKET, SO_DEBUG, &i, sizeof (i)) < 0)
|
|
error ("setsockopt SO_DEBUG");
|
|
}
|
|
|
|
|
|
#ifdef SO_MARK
|
|
if (fwmark) {
|
|
if (setsockopt (sk, SOL_SOCKET, SO_MARK,
|
|
&fwmark, sizeof (fwmark)) < 0
|
|
) error ("setsockopt SO_MARK");
|
|
}
|
|
#endif
|
|
|
|
|
|
if (rtbuf && rtbuf_len) {
|
|
if (af == AF_INET) {
|
|
if (setsockopt (sk, IPPROTO_IP, IP_OPTIONS,
|
|
rtbuf, rtbuf_len) < 0
|
|
) error ("setsockopt IP_OPTIONS");
|
|
}
|
|
else if (af == AF_INET6) {
|
|
if (setsockopt (sk, IPPROTO_IPV6, IPV6_RTHDR,
|
|
rtbuf, rtbuf_len) < 0
|
|
) error ("setsockopt IPV6_RTHDR");
|
|
}
|
|
}
|
|
|
|
|
|
bind_socket (sk);
|
|
|
|
|
|
if (af == AF_INET) {
|
|
|
|
i = dontfrag ? IP_PMTUDISC_PROBE : IP_PMTUDISC_DONT;
|
|
if (setsockopt (sk, SOL_IP, IP_MTU_DISCOVER, &i, sizeof(i)) < 0 &&
|
|
(!dontfrag || (i = IP_PMTUDISC_DO,
|
|
setsockopt (sk, SOL_IP, IP_MTU_DISCOVER, &i, sizeof(i)) < 0))
|
|
) error ("setsockopt IP_MTU_DISCOVER");
|
|
|
|
if (tos) {
|
|
i = tos;
|
|
if (setsockopt (sk, SOL_IP, IP_TOS, &i, sizeof (i)) < 0)
|
|
error ("setsockopt IP_TOS");
|
|
}
|
|
|
|
}
|
|
else if (af == AF_INET6) {
|
|
|
|
i = dontfrag ? IPV6_PMTUDISC_PROBE : IPV6_PMTUDISC_DONT;
|
|
if (setsockopt (sk, SOL_IPV6, IPV6_MTU_DISCOVER,&i,sizeof(i)) < 0 &&
|
|
(!dontfrag || (i = IPV6_PMTUDISC_DO,
|
|
setsockopt (sk, SOL_IPV6, IPV6_MTU_DISCOVER,&i,sizeof(i)) < 0))
|
|
) error ("setsockopt IPV6_MTU_DISCOVER");
|
|
|
|
|
|
if (flow_label) {
|
|
struct in6_flowlabel_req flr;
|
|
|
|
memset (&flr, 0, sizeof (flr));
|
|
flr.flr_label = htonl (flow_label & 0x000fffff);
|
|
flr.flr_action = IPV6_FL_A_GET;
|
|
flr.flr_flags = IPV6_FL_F_CREATE;
|
|
flr.flr_share = IPV6_FL_S_ANY;
|
|
memcpy (&flr.flr_dst, &dst_addr.sin6.sin6_addr,
|
|
sizeof (flr.flr_dst));
|
|
|
|
if (setsockopt (sk, IPPROTO_IPV6, IPV6_FLOWLABEL_MGR,
|
|
&flr, sizeof (flr)) < 0
|
|
) error ("setsockopt IPV6_FLOWLABEL_MGR");
|
|
}
|
|
|
|
if (tos) {
|
|
i = tos;
|
|
if (setsockopt (sk, IPPROTO_IPV6, IPV6_TCLASS,
|
|
&i, sizeof (i)) < 0
|
|
) error ("setsockopt IPV6_TCLASS");
|
|
}
|
|
|
|
if (tos || flow_label) {
|
|
i = 1;
|
|
if (setsockopt (sk, IPPROTO_IPV6, IPV6_FLOWINFO_SEND,
|
|
&i, sizeof (i)) < 0
|
|
) error ("setsockopt IPV6_FLOWINFO_SEND");
|
|
}
|
|
}
|
|
|
|
|
|
if (noroute) {
|
|
i = noroute;
|
|
if (setsockopt (sk, SOL_SOCKET, SO_DONTROUTE, &i, sizeof (i)) < 0)
|
|
error ("setsockopt SO_DONTROUTE");
|
|
}
|
|
|
|
|
|
use_timestamp (sk);
|
|
|
|
use_recv_ttl (sk);
|
|
|
|
fcntl (sk, F_SETFL, O_NONBLOCK);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
void parse_icmp_res (probe *pb, int type, int code, int info) {
|
|
char *str = NULL;
|
|
char buf[sizeof (pb->err_str)];
|
|
|
|
if (af == AF_INET) {
|
|
|
|
if (type == ICMP_TIME_EXCEEDED) {
|
|
if (code == ICMP_EXC_TTL)
|
|
return;
|
|
}
|
|
else if (type == ICMP_DEST_UNREACH) {
|
|
|
|
switch (code) {
|
|
case ICMP_UNREACH_NET:
|
|
case ICMP_UNREACH_NET_UNKNOWN:
|
|
case ICMP_UNREACH_ISOLATED:
|
|
case ICMP_UNREACH_TOSNET:
|
|
str = "!N";
|
|
break;
|
|
|
|
case ICMP_UNREACH_HOST:
|
|
case ICMP_UNREACH_HOST_UNKNOWN:
|
|
case ICMP_UNREACH_TOSHOST:
|
|
str = "!H";
|
|
break;
|
|
|
|
case ICMP_UNREACH_NET_PROHIB:
|
|
case ICMP_UNREACH_HOST_PROHIB:
|
|
case ICMP_UNREACH_FILTER_PROHIB:
|
|
str = "!X";
|
|
break;
|
|
|
|
case ICMP_UNREACH_PORT:
|
|
/* dest host is reached */
|
|
str = "";
|
|
break;
|
|
|
|
case ICMP_UNREACH_PROTOCOL:
|
|
str = "!P";
|
|
break;
|
|
|
|
case ICMP_UNREACH_NEEDFRAG:
|
|
snprintf (buf, sizeof (buf), "!F-%d", info);
|
|
str = buf;
|
|
break;
|
|
|
|
case ICMP_UNREACH_SRCFAIL:
|
|
str = "!S";
|
|
break;
|
|
|
|
case ICMP_UNREACH_HOST_PRECEDENCE:
|
|
str = "!V";
|
|
break;
|
|
|
|
case ICMP_UNREACH_PRECEDENCE_CUTOFF:
|
|
str = "!C";
|
|
break;
|
|
|
|
default:
|
|
snprintf (buf, sizeof (buf), "!<%u>", code);
|
|
str = buf;
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
else if (af == AF_INET6) {
|
|
|
|
if (type == ICMP6_TIME_EXCEEDED) {
|
|
if (code == ICMP6_TIME_EXCEED_TRANSIT)
|
|
return;
|
|
}
|
|
else if (type == ICMP6_DST_UNREACH) {
|
|
#if defined(TCSUPPORT_CT_PON_CZ_GD)
|
|
got_there = 0;
|
|
#endif
|
|
|
|
switch (code) {
|
|
|
|
case ICMP6_DST_UNREACH_NOROUTE:
|
|
str = "!N";
|
|
break;
|
|
|
|
case ICMP6_DST_UNREACH_BEYONDSCOPE:
|
|
case ICMP6_DST_UNREACH_ADDR:
|
|
str = "!H";
|
|
break;
|
|
|
|
case ICMP6_DST_UNREACH_ADMIN:
|
|
str = "!X";
|
|
break;
|
|
|
|
case ICMP6_DST_UNREACH_NOPORT:
|
|
/* dest host is reached */
|
|
str = "";
|
|
break;
|
|
|
|
default:
|
|
snprintf (buf, sizeof (buf), "!<%u>", code);
|
|
str = buf;
|
|
break;
|
|
}
|
|
}
|
|
else if (type == ICMP6_PACKET_TOO_BIG) {
|
|
snprintf (buf, sizeof (buf), "!F-%d", info);
|
|
str = buf;
|
|
}
|
|
}
|
|
|
|
|
|
if (!str) {
|
|
snprintf (buf, sizeof (buf), "!<%u-%u>", type, code);
|
|
str = buf;
|
|
}
|
|
|
|
if (*str) {
|
|
strncpy (pb->err_str, str, sizeof (pb->err_str));
|
|
pb->err_str[sizeof (pb->err_str) - 1] = '\0';
|
|
}
|
|
|
|
pb->final = 1;
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
void probe_done (probe *pb) {
|
|
|
|
if (pb->sk) {
|
|
del_poll (pb->sk);
|
|
close (pb->sk);
|
|
pb->sk = 0;
|
|
}
|
|
|
|
pb->seq = 0;
|
|
|
|
pb->done = 1;
|
|
}
|
|
|
|
|
|
void recv_reply (int sk, int err, check_reply_t check_reply) {
|
|
struct msghdr msg;
|
|
sockaddr_any from;
|
|
struct iovec iov;
|
|
int n;
|
|
probe *pb;
|
|
char buf[1280]; /* min mtu for ipv6 ( >= 576 for ipv4) */
|
|
char *bufp = buf;
|
|
char control[1024];
|
|
struct cmsghdr *cm;
|
|
struct sock_extended_err *ee = NULL;
|
|
#if defined(TCSUPPORT_CT_TRACEROUTEIPV6)
|
|
unsigned int idx = 0, ttl = 9999;
|
|
#endif
|
|
|
|
|
|
memset (&msg, 0, sizeof (msg));
|
|
msg.msg_name = &from;
|
|
msg.msg_namelen = sizeof (from);
|
|
msg.msg_control = control;
|
|
msg.msg_controllen = sizeof (control);
|
|
iov.iov_base = buf;
|
|
iov.iov_len = sizeof (buf);
|
|
msg.msg_iov = &iov;
|
|
msg.msg_iovlen = 1;
|
|
|
|
|
|
n = recvmsg (sk, &msg, err ? MSG_ERRQUEUE : 0);
|
|
if (n < 0) return;
|
|
|
|
|
|
/* when not MSG_ERRQUEUE, AF_INET returns full ipv4 header
|
|
on raw sockets...
|
|
*/
|
|
|
|
if (!err &&
|
|
af == AF_INET &&
|
|
/* XXX: Assume that the presence of an extra header means
|
|
that it is not a raw socket...
|
|
*/
|
|
ops->header_len == 0
|
|
) {
|
|
struct iphdr *ip = (struct iphdr *) bufp;
|
|
int hlen;
|
|
|
|
if (n < sizeof (struct iphdr)) return;
|
|
|
|
hlen = ip->ihl << 2;
|
|
if (n < hlen) return;
|
|
|
|
bufp += hlen;
|
|
n -= hlen;
|
|
}
|
|
|
|
|
|
pb = check_reply (sk, err, &from, bufp, n);
|
|
if (!pb) return;
|
|
|
|
|
|
if (!err)
|
|
memcpy (&pb->res, &from, sizeof (pb->res));
|
|
|
|
|
|
/* Parse CMSG stuff */
|
|
|
|
for (cm = CMSG_FIRSTHDR (&msg); cm; cm = CMSG_NXTHDR (&msg, cm)) {
|
|
|
|
if (cm->cmsg_level == SOL_SOCKET) {
|
|
|
|
if (cm->cmsg_type == SO_TIMESTAMP) {
|
|
struct timeval *tv = (struct timeval *) CMSG_DATA (cm);
|
|
|
|
pb->recv_time = tv->tv_sec + tv->tv_usec / 1000000.;
|
|
}
|
|
}
|
|
else if (cm->cmsg_level == SOL_IP) {
|
|
|
|
if (cm->cmsg_type == IP_TTL)
|
|
pb->recv_ttl = *((int *) CMSG_DATA (cm));
|
|
else if (cm->cmsg_type == IP_RECVERR) {
|
|
|
|
ee = (struct sock_extended_err *) CMSG_DATA (cm);
|
|
|
|
if (ee->ee_origin != SO_EE_ORIGIN_ICMP)
|
|
ee = NULL;
|
|
}
|
|
}
|
|
else if (cm->cmsg_level == SOL_IPV6) {
|
|
|
|
if (cm->cmsg_type == IPV6_HOPLIMIT)
|
|
pb->recv_ttl = *((int *) CMSG_DATA (cm));
|
|
else if (cm->cmsg_type == IPV6_RECVERR) {
|
|
|
|
ee = (struct sock_extended_err *) CMSG_DATA (cm);
|
|
|
|
if (ee->ee_origin != SO_EE_ORIGIN_ICMP6)
|
|
ee = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ee) {
|
|
memcpy (&pb->res, SO_EE_OFFENDER (ee), sizeof(pb->res));
|
|
parse_icmp_res (pb, ee->ee_type, ee->ee_code, ee->ee_info);
|
|
}
|
|
|
|
|
|
if (!pb->recv_time)
|
|
pb->recv_time = get_time ();
|
|
|
|
#if defined(TCSUPPORT_CT_TRACEROUTEIPV6)
|
|
if ( 1 == cwmpflag )
|
|
{
|
|
if ( pb )
|
|
{
|
|
idx = (pb - probes);
|
|
ttl = idx / probes_per_hop;
|
|
|
|
if ( ee )
|
|
{
|
|
if ( (af == AF_INET && (ee->ee_type == ICMP_TIME_EXCEEDED ||
|
|
ee->ee_type == ICMP_DEST_UNREACH ||
|
|
ee->ee_type == ICMP_PARAMETERPROB)) ||
|
|
(af == AF_INET6 && (ee->ee_type == ICMP6_TIME_EXCEEDED ||
|
|
ee->ee_type == ICMP6_DST_UNREACH)) )
|
|
addRecHopErrorCode(ttl, 0);
|
|
else
|
|
addRecHopErrorCode(ttl, ee->ee_type);
|
|
}
|
|
else
|
|
addRecHopErrorCode(ttl, 0);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (ee &&
|
|
mtudisc &&
|
|
ee->ee_info >= header_len &&
|
|
ee->ee_info < header_len + data_len
|
|
) {
|
|
data_len = ee->ee_info - header_len;
|
|
|
|
probe_done (pb);
|
|
|
|
/* clear this probe (as actually the previous hop answers here)
|
|
but fill its `err_str' by the info obtained. Ugly, but easy...
|
|
*/
|
|
memset (pb, 0, sizeof (*pb));
|
|
snprintf (pb->err_str, sizeof(pb->err_str)-1, "F=%d", ee->ee_info);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
if (ee &&
|
|
extension &&
|
|
header_len + n >= (128 + 8) && /* at least... (rfc4884) */
|
|
header_len <= 128 && /* paranoia */
|
|
((af == AF_INET && (ee->ee_type == ICMP_TIME_EXCEEDED ||
|
|
ee->ee_type == ICMP_DEST_UNREACH ||
|
|
ee->ee_type == ICMP_PARAMETERPROB)) ||
|
|
(af == AF_INET6 && (ee->ee_type == ICMP6_TIME_EXCEEDED ||
|
|
ee->ee_type == ICMP6_DST_UNREACH))
|
|
)
|
|
) {
|
|
int step;
|
|
int offs = 128 - header_len;
|
|
|
|
if (n > data_len) step = 0; /* guaranteed at 128 ... */
|
|
else
|
|
step = af == AF_INET ? 4 : 8;
|
|
|
|
handle_extensions (pb, bufp + offs, n - offs, step);
|
|
}
|
|
|
|
|
|
probe_done (pb);
|
|
}
|
|
|
|
|
|
int equal_addr (const sockaddr_any *a, const sockaddr_any *b) {
|
|
|
|
if (!a->sa.sa_family)
|
|
return 0;
|
|
|
|
if (a->sa.sa_family != b->sa.sa_family)
|
|
return 0;
|
|
|
|
if (a->sa.sa_family == AF_INET6)
|
|
return !memcmp (&a->sin6.sin6_addr, &b->sin6.sin6_addr,
|
|
sizeof (a->sin6.sin6_addr));
|
|
else
|
|
return !memcmp (&a->sin.sin_addr, &b->sin.sin_addr,
|
|
sizeof (a->sin.sin_addr));
|
|
return 0; /* not reached */
|
|
}
|
|
|
|
|
|
void bind_socket (int sk) {
|
|
sockaddr_any *addr, tmp;
|
|
|
|
if (device) {
|
|
if (setsockopt (sk, SOL_SOCKET, SO_BINDTODEVICE,
|
|
device, strlen (device) + 1) < 0
|
|
) error ("setsockopt SO_BINDTODEVICE");
|
|
}
|
|
|
|
if (!src_addr.sa.sa_family) {
|
|
memset (&tmp, 0, sizeof (tmp));
|
|
tmp.sa.sa_family = af;
|
|
addr = &tmp;
|
|
} else
|
|
addr = &src_addr;
|
|
|
|
if (bind (sk, &addr->sa, sizeof (*addr)) < 0)
|
|
error ("bind");
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
void use_timestamp (int sk) {
|
|
int n = 1;
|
|
|
|
setsockopt (sk, SOL_SOCKET, SO_TIMESTAMP, &n, sizeof (n));
|
|
/* foo on errors... */
|
|
}
|
|
|
|
|
|
void use_recv_ttl (int sk) {
|
|
int n = 1;
|
|
|
|
if (af == AF_INET)
|
|
setsockopt (sk, SOL_IP, IP_RECVTTL, &n, sizeof (n));
|
|
else if (af == AF_INET6)
|
|
setsockopt (sk, SOL_IPV6, IPV6_RECVHOPLIMIT, &n, sizeof (n));
|
|
/* foo on errors */
|
|
}
|
|
|
|
|
|
void use_recverr (int sk) {
|
|
int val = 1;
|
|
|
|
if (af == AF_INET) {
|
|
if (setsockopt (sk, SOL_IP, IP_RECVERR, &val, sizeof (val)) < 0)
|
|
error ("setsockopt IP_RECVERR");
|
|
}
|
|
else if (af == AF_INET6) {
|
|
if (setsockopt (sk, SOL_IPV6, IPV6_RECVERR, &val, sizeof (val)) < 0)
|
|
error ("setsockopt IPV6_RECVERR");
|
|
}
|
|
}
|
|
|
|
|
|
void set_ttl (int sk, int ttl) {
|
|
|
|
if (af == AF_INET) {
|
|
if (setsockopt (sk, SOL_IP, IP_TTL, &ttl, sizeof (ttl)) < 0)
|
|
error ("setsockopt IP_TTL");
|
|
}
|
|
else if (af == AF_INET6) {
|
|
if (setsockopt (sk, SOL_IPV6, IPV6_UNICAST_HOPS,
|
|
&ttl, sizeof (ttl)) < 0
|
|
) error ("setsockopt IPV6_UNICAST_HOPS");
|
|
}
|
|
}
|
|
|
|
|
|
int do_send (int sk, const void *data, size_t len, const sockaddr_any *addr) {
|
|
int res;
|
|
|
|
if (!addr || raw_can_connect ())
|
|
res = send (sk, data, len, 0);
|
|
else
|
|
res = sendto (sk, data, len, 0, &addr->sa, sizeof (*addr));
|
|
|
|
if (res < 0) {
|
|
if (errno == ENOBUFS || errno == EAGAIN)
|
|
return res;
|
|
if (errno == EMSGSIZE)
|
|
return 0; /* icmp will say more... */
|
|
error ("send"); /* not recoverable */
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
/* There is a bug in the kernel before 2.6.25, which prevents icmp errors
|
|
to be obtained by MSG_ERRQUEUE for ipv6 connected raw sockets.
|
|
*/
|
|
static int can_connect = -1;
|
|
|
|
#define VER(A,B,C,D) (((((((A) << 8) | (B)) << 8) | (C)) << 8) | (D))
|
|
|
|
int raw_can_connect (void) {
|
|
|
|
if (can_connect < 0) {
|
|
|
|
if (af == AF_INET)
|
|
can_connect = 1;
|
|
else { /* AF_INET6 */
|
|
struct utsname uts;
|
|
int n;
|
|
unsigned int a, b, c, d = 0;
|
|
|
|
if (uname (&uts) < 0)
|
|
return 0;
|
|
|
|
n = sscanf (uts.release, "%u.%u.%u.%u", &a, &b, &c, &d);
|
|
can_connect = (n >= 3 && VER (a, b, c, d) >= VER (2, 6, 25, 0));
|
|
}
|
|
}
|
|
|
|
return can_connect;
|
|
}
|