1
0
Files
2016-11-30 09:03:17 +08:00

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;
}