@@ -168,6 +168,10 @@ extern int h_errno;
168168#define RESP_ERROR -3
169169#define RESP_TIMEOUT -4
170170
171+ /* Traceroute */
172+ #define TRACEROUTE_DEFAULT_MAX_TTL 30
173+ #define TRACEROUTE_DONE_TTL 100
174+
171175/* debugging flags */
172176#if defined(DEBUG ) || defined(_DEBUG )
173177#define DBG_TRACE 1
@@ -297,6 +301,7 @@ typedef struct host_entry {
297301 int64_t min_reply_i ; /* shortest response time */
298302 int64_t total_time_i ; /* sum of response times */
299303 int64_t * resp_times ; /* individual response times */
304+ int trace_ttl ; /* current traceroute ttl */
300305
301306 /* to avoid allocating two struct events each time that we send a ping, we
302307 * preallocate here two struct events for each ping that we might send for
@@ -423,6 +428,7 @@ int timestamp_flag = 0;
423428int timestamp_format_flag = 0 ;
424429int random_data_flag = 0 ;
425430int cumulative_stats_flag = 0 ;
431+ int traceroute_flag = 0 ;
426432int check_source_flag = 0 ;
427433int icmp_request_typ = 0 ;
428434int print_tos_flag = 0 ;
@@ -642,6 +648,7 @@ int main(int argc, char **argv)
642648 { "rdns" , 'd' , OPTPARSE_NONE },
643649 { "timestamp" , 'D' , OPTPARSE_NONE },
644650 { "timestamp-format" , '0' , OPTPARSE_REQUIRED },
651+ { "traceroute" , '0' , OPTPARSE_NONE },
645652 { "elapsed" , 'e' , OPTPARSE_NONE },
646653 { "file" , 'f' , OPTPARSE_REQUIRED },
647654 { "generate" , 'g' , OPTPARSE_NONE },
@@ -698,6 +705,8 @@ int main(int argc, char **argv)
698705 }else {
699706 usage (1 );
700707 }
708+ } else if (strstr (optparse_state .optlongname , "traceroute" ) != NULL ) {
709+ traceroute_flag = 1 ;
701710 } else if (strstr (optparse_state .optlongname , "check-source" ) != NULL ) {
702711 check_source_flag = 1 ;
703712 } else if (strstr (optparse_state .optlongname , "icmp-timestamp" ) != NULL ) {
@@ -1092,6 +1101,31 @@ int main(int argc, char **argv)
10921101 exit (1 );
10931102 }
10941103
1104+ if (traceroute_flag && (count_flag || loop_flag || netdata_flag || quiet_flag || stats_flag || timestamp_flag || json_flag )) {
1105+ fprintf (stderr , "%s: can't combine --traceroute with -c, -C, -l, -N, -q, -Q, -s, -D or -J\n" , prog );
1106+ exit (1 );
1107+ }
1108+
1109+ if (traceroute_flag ) {
1110+ /* Preventing race conditions: Only one host allowed */
1111+ if (num_hosts > 1 ) {
1112+ fprintf (stderr , "%s: traceroute mode only supports one target at a time\n" , prog );
1113+ exit (1 );
1114+ }
1115+ #ifdef __linux__
1116+ if (using_sock_dgram4 ) {
1117+ fprintf (stderr , "%s: traceroute mode requires raw sockets (run as root)\n" , prog );
1118+ exit (1 );
1119+ }
1120+ #endif
1121+ if (ttl == 0 ) {
1122+ ttl = TRACEROUTE_DEFAULT_MAX_TTL ; /* Default traceroute limit */
1123+ } else if (ttl > TRACEROUTE_DEFAULT_MAX_TTL ) {
1124+ fprintf (stderr , "%s: traceroute ttl max is 30, clamping.\n" , prog );
1125+ ttl = TRACEROUTE_DEFAULT_MAX_TTL ;
1126+ }
1127+ }
1128+
10951129 if (interval < (float )MIN_INTERVAL_MS * 1000000 && getuid ()) {
10961130 fprintf (stderr , "%s: -i must be >= %g\n" , prog , (float )MIN_INTERVAL_MS );
10971131 exit (1 );
@@ -1138,6 +1172,9 @@ int main(int argc, char **argv)
11381172
11391173 trials = (count > retry + 1 ) ? count : retry + 1 ;
11401174
1175+ if (traceroute_flag )
1176+ trials = ttl ; /* Ensure enough space for up to 'ttl' hops */
1177+
11411178 /* auto-tune default timeout for count/loop modes
11421179 * see also github #32 */
11431180 if (loop_flag || count_flag ) {
@@ -1270,7 +1307,7 @@ int main(int argc, char **argv)
12701307 if (count_flag ) {
12711308 event_storage_count = count ;
12721309 }
1273- else if (loop_flag ) {
1310+ else if (loop_flag || traceroute_flag ) {
12741311 if (perhost_interval > timeout ) {
12751312 event_storage_count = 1 ;
12761313 }
@@ -1461,6 +1498,26 @@ int main(int argc, char **argv)
14611498
14621499 seqmap_init (seqmap_timeout );
14631500
1501+ /* Traceroute header output */
1502+ if (traceroute_flag ) {
1503+ int i ;
1504+ for (i = 0 ; i < num_hosts ; i ++ ) {
1505+ HOST_ENTRY * h = table [i ];
1506+ char ip_str [INET6_ADDRSTRLEN ];
1507+ int total_len = ping_data_size + SIZE_ICMP_HDR ;
1508+
1509+ /* Resolve IP string and calculate header length */
1510+ getnameinfo ((struct sockaddr * )& h -> saddr , h -> saddr_len , ip_str , sizeof (ip_str ), NULL , 0 , NI_NUMERICHOST );
1511+
1512+ if (h -> saddr .ss_family == AF_INET6 )
1513+ total_len += 40 ; /* IPv6 Header fix 40 bytes */
1514+ else
1515+ total_len += SIZE_IP_HDR ; /* IPv4 Header min 20 bytes */
1516+
1517+ printf ("fping traceroute to %s (%s), %d hops max, %d byte packets\n" , h -> name , ip_str , (int )ttl , total_len );
1518+ }
1519+ }
1520+
14641521 /* main loop */
14651522 main_loop ();
14661523
@@ -1791,6 +1848,14 @@ void main_loop()
17911848
17921849 stats_add (h , event -> ping_index , 0 , -1 );
17931850
1851+ if (traceroute_flag ) {
1852+ printf ("%s: hop %d no reply\n" , h -> host , h -> trace_ttl );
1853+ h -> trace_ttl ++ ;
1854+ if (h -> trace_ttl > (int )ttl ) h -> trace_ttl = (int )ttl ;
1855+ /* Continue to the next hop, no retry for this hop */
1856+ continue ;
1857+ }
1858+
17941859 if (per_recv_flag ) {
17951860 print_timeout (h , event -> ping_index );
17961861 }
@@ -1825,11 +1890,33 @@ void main_loop()
18251890
18261891 dbg_printf ("%s [%d]: ping event\n" , h -> host , event -> ping_index );
18271892
1893+ /* Traceroute */
1894+ if (traceroute_flag ) {
1895+ if (h -> trace_ttl == TRACEROUTE_DONE_TTL ) {
1896+ continue ;
1897+ }
1898+
1899+ int ttl_set = h -> trace_ttl ;
1900+ if (ttl_set > (int )ttl ) ttl_set = (int )ttl ;
1901+ if (socket4 >= 0 ) {
1902+ if (setsockopt (socket4 , IPPROTO_IP , IP_TTL , & ttl_set , sizeof (ttl_set )))
1903+ perror ("setsockopt IP_TTL" );
1904+ }
1905+ #ifdef IPV6
1906+ if (socket6 >= 0 ) {
1907+ /* Set hop limit for IPv6 */
1908+ if (setsockopt (socket6 , IPPROTO_IPV6 , IPV6_UNICAST_HOPS , & ttl_set , sizeof (ttl_set ))) {
1909+ perror ("setsockopt IPV6_UNICAST_HOPS" );
1910+ }
1911+ }
1912+ #endif
1913+ }
1914+
18281915 /* Send the ping */
18291916 send_ping (h , event -> ping_index );
18301917
1831- /* Loop and count mode: schedule next ping */
1832- if (loop_flag || (count_flag && event -> ping_index + 1 < count )) {
1918+ /* Loop, count and traceroute mode: schedule next ping */
1919+ if (loop_flag || (count_flag && event -> ping_index + 1 < count ) || ( traceroute_flag && h -> trace_ttl < ( int ) ttl ) ) {
18331920 host_add_ping_event (h , event -> ping_index + 1 , event -> ev_time + perhost_interval );
18341921 }
18351922 }
@@ -2974,6 +3061,31 @@ int decode_icmp_ipv4(
29743061
29753062 icp = (struct icmp * )(reply_buf + hlen );
29763063
3064+ if (traceroute_flag && icp -> icmp_type == ICMP_TIMXCEED ) {
3065+ struct ip * inner_ip ;
3066+ int inner_hlen ;
3067+ struct icmp * inner_icmp ;
3068+
3069+ if (reply_buf_len >= hlen + ICMP_MINLEN + sizeof (struct ip ) + ICMP_MINLEN ) {
3070+ inner_ip = (struct ip * ) (reply_buf + hlen + ICMP_MINLEN );
3071+ inner_hlen = inner_ip -> ip_hl << 2 ;
3072+
3073+ if (inner_hlen < sizeof (struct ip )) {
3074+ /* Invalid inner IP header length */
3075+ return -1 ;
3076+ }
3077+
3078+ if (reply_buf_len >= hlen + ICMP_MINLEN + inner_hlen + ICMP_MINLEN ) {
3079+ inner_icmp = (struct icmp * ) ((char * )inner_ip + inner_hlen );
3080+ if (inner_icmp -> icmp_id == ident4 ) {
3081+ * id = inner_icmp -> icmp_id ;
3082+ * seq = ntohs (inner_icmp -> icmp_seq );
3083+ return hlen ;
3084+ }
3085+ }
3086+ }
3087+ }
3088+
29773089 if ((icmp_request_typ == 0 && icp -> icmp_type != ICMP_ECHOREPLY ) ||
29783090 (icmp_request_typ == 13 && icp -> icmp_type != ICMP_TSTAMPREPLY )) {
29793091 /* Handle other ICMP packets */
@@ -3085,6 +3197,27 @@ int decode_icmp_ipv6(
30853197
30863198 icp = (struct icmp6_hdr * )reply_buf ;
30873199
3200+ /* Traceroute Logic for IPv6 Time Exceeded */
3201+ if (traceroute_flag && icp -> icmp6_type == ICMP6_TIME_EXCEEDED ) {
3202+ struct ip6_hdr * inner_ip6 ;
3203+ struct icmp6_hdr * inner_icmp6 ;
3204+
3205+ if (reply_buf_len >= sizeof (struct icmp6_hdr ) + sizeof (struct ip6_hdr ) + sizeof (struct icmp6_hdr )) {
3206+ inner_ip6 = (struct ip6_hdr * )(reply_buf + sizeof (struct icmp6_hdr ));
3207+
3208+ /* Check whether the inner packet is ICMPv6 */
3209+ if (inner_ip6 -> ip6_nxt == IPPROTO_ICMPV6 ) {
3210+ inner_icmp6 = (struct icmp6_hdr * )(reply_buf + sizeof (struct icmp6_hdr ) + sizeof (struct ip6_hdr ));
3211+
3212+ if (inner_icmp6 -> icmp6_id == ident6 ) {
3213+ * id = inner_icmp6 -> icmp6_id ;
3214+ * seq = ntohs (inner_icmp6 -> icmp6_seq );
3215+ return 1 ;
3216+ }
3217+ }
3218+ }
3219+ }
3220+
30883221 if (icp -> icmp6_type != ICMP6_ECHO_REPLY ) {
30893222 /* Handle other ICMPv6 packets */
30903223 struct ip6_hdr * sent_ipv6 ;
@@ -3189,6 +3322,7 @@ int wait_for_reply(int64_t wait_time)
31893322 static char buffer [RECV_BUFSIZE ];
31903323 struct sockaddr_storage response_addr ;
31913324 int n , avg ;
3325+ int ip_hlen = 0 ;
31923326 HOST_ENTRY * h ;
31933327 int64_t this_reply ;
31943328 int this_count ;
@@ -3219,7 +3353,7 @@ int wait_for_reply(int64_t wait_time)
32193353
32203354 /* Process ICMP packet and retrieve id/seq */
32213355 if (response_addr .ss_family == AF_INET ) {
3222- int ip_hlen = decode_icmp_ipv4 (
3356+ ip_hlen = decode_icmp_ipv4 (
32233357 (struct sockaddr * )& response_addr ,
32243358 sizeof (response_addr ),
32253359 buffer ,
@@ -3304,6 +3438,45 @@ int wait_for_reply(int64_t wait_time)
33043438 return 1 ;
33053439 }
33063440
3441+ if (traceroute_flag && response_addr .ss_family == AF_INET ) {
3442+ struct icmp * icp = (struct icmp * )(buffer + ip_hlen );
3443+ char ip_str [INET_ADDRSTRLEN ];
3444+ getnameinfo ((struct sockaddr * )& response_addr , sizeof (response_addr ), ip_str , sizeof (ip_str ), NULL , 0 , NI_NUMERICHOST );
3445+
3446+ if (icp -> icmp_type == ICMP_TIMXCEED ) {
3447+ printf ("%s: hop %d reached %s (%s ms)\n" , h -> host , h -> trace_ttl , ip_str , sprint_tm (this_reply ));
3448+ h -> trace_ttl ++ ;
3449+ if (h -> trace_ttl > (int )ttl ) {
3450+ h -> trace_ttl = (int )ttl ;
3451+ }
3452+ } else { /* ICMP_ECHOREPLY */
3453+ printf ("%s: hop %d reached DESTINATION %s (%s ms)\n" , h -> host , h -> trace_ttl , ip_str , sprint_tm (this_reply ));
3454+ h -> trace_ttl = TRACEROUTE_DONE_TTL ; /* Goal achieved: artificially increase TTL to stop loop in main_loop */
3455+ }
3456+ }
3457+ #ifdef IPV6
3458+ else if (traceroute_flag && response_addr .ss_family == AF_INET6 ) {
3459+ struct icmp6_hdr * icp = (struct icmp6_hdr * )buffer ;
3460+
3461+ if (icp -> icmp6_type == ICMP6_TIME_EXCEEDED || icp -> icmp6_type == ICMP6_ECHO_REPLY ) {
3462+ /* With IPv6, buffer is directly the ICMP header payload, since receive_packet uses recvmsg */
3463+ char ip_str [INET6_ADDRSTRLEN ];
3464+ getnameinfo ((struct sockaddr * )& response_addr , sizeof (response_addr ), ip_str , sizeof (ip_str ), NULL , 0 , NI_NUMERICHOST );
3465+
3466+ if (icp -> icmp6_type == ICMP6_TIME_EXCEEDED ) {
3467+ printf ("%s: hop %d reached %s (%s ms)\n" , h -> host , h -> trace_ttl , ip_str , sprint_tm (this_reply ));
3468+ h -> trace_ttl ++ ;
3469+ if (h -> trace_ttl > (int )ttl ) {
3470+ h -> trace_ttl = (int )ttl ;
3471+ }
3472+ } else { /* ICMP6_ECHO_REPLY */
3473+ printf ("%s: hop %d reached DESTINATION %s (%s ms)\n" , h -> host , h -> trace_ttl , ip_str , sprint_tm (this_reply ));
3474+ h -> trace_ttl = TRACEROUTE_DONE_TTL ; /* Goal achieved: artificially increase TTL to stop loop in main_loop */
3475+ }
3476+ }
3477+ }
3478+ #endif
3479+
33073480 /* update stats */
33083481 stats_add (h , this_count , 1 , this_reply );
33093482 // TODO: move to stats_add?
@@ -3323,6 +3496,10 @@ int wait_for_reply(int64_t wait_time)
33233496 ev_remove (& event_queue_timeout , timeout_event );
33243497 }
33253498
3499+ if (traceroute_flag ) {
3500+ return 1 ;
3501+ }
3502+
33263503 /* print "is alive" */
33273504 if (h -> num_recv == 1 ) {
33283505 num_alive ++ ;
@@ -3515,6 +3692,7 @@ void add_addr(char *name, char *host, struct sockaddr *ipaddr, socklen_t ipaddr_
35153692 p -> saddr_len = ipaddr_len ;
35163693 p -> timeout = timeout ;
35173694 p -> min_reply = 0 ;
3695+ p -> trace_ttl = 1 ;
35183696
35193697 if (netdata_flag ) {
35203698 char * s = p -> name ;
@@ -3925,6 +4103,7 @@ void usage(int is_error)
39254103 fprintf (out , " except with -l/-c/-C, where it's the -p period up to 2000 ms)\n" );
39264104 fprintf (out , " --check-source discard replies not from target address\n" );
39274105 fprintf (out , " --icmp-timestamp use ICMP Timestamp instead of ICMP Echo\n" );
4106+ fprintf (out , " --traceroute Sends a traceroute based on ICMP echo request (Only one host is allowed)\n" );
39284107 fprintf (out , "\n" );
39294108 fprintf (out , "Output options:\n" );
39304109 fprintf (out , " -a, --alive show targets that are alive\n" );
0 commit comments