@@ -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 ;
@@ -486,6 +492,7 @@ void stats_add(HOST_ENTRY *h, int index, int success, int64_t latency);
486492void update_current_time ();
487493void print_timestamp_format (int64_t current_time_ns , int timestamp_format );
488494static uint32_t ms_since_midnight_utc (int64_t time_val );
495+ void handle_traceroute_hop (HOST_ENTRY * h , const char * ip_str , int reached_destination , int64_t this_reply );
489496
490497/************************************************************
491498
@@ -642,6 +649,7 @@ int main(int argc, char **argv)
642649 { "rdns" , 'd' , OPTPARSE_NONE },
643650 { "timestamp" , 'D' , OPTPARSE_NONE },
644651 { "timestamp-format" , '0' , OPTPARSE_REQUIRED },
652+ { "traceroute" , '0' , OPTPARSE_NONE },
645653 { "elapsed" , 'e' , OPTPARSE_NONE },
646654 { "file" , 'f' , OPTPARSE_REQUIRED },
647655 { "generate" , 'g' , OPTPARSE_NONE },
@@ -698,6 +706,8 @@ int main(int argc, char **argv)
698706 }else {
699707 usage (1 );
700708 }
709+ } else if (strstr (optparse_state .optlongname , "traceroute" ) != NULL ) {
710+ traceroute_flag = 1 ;
701711 } else if (strstr (optparse_state .optlongname , "check-source" ) != NULL ) {
702712 check_source_flag = 1 ;
703713 } else if (strstr (optparse_state .optlongname , "icmp-timestamp" ) != NULL ) {
@@ -1092,6 +1102,31 @@ int main(int argc, char **argv)
10921102 exit (1 );
10931103 }
10941104
1105+ if (traceroute_flag && (count_flag || loop_flag || netdata_flag || quiet_flag || stats_flag || timestamp_flag || json_flag )) {
1106+ fprintf (stderr , "%s: can't combine --traceroute with -c, -C, -l, -N, -q, -Q, -s, -D or -J\n" , prog );
1107+ exit (1 );
1108+ }
1109+
1110+ if (traceroute_flag ) {
1111+ /* Preventing race conditions: Only one host allowed */
1112+ if (num_hosts > 1 ) {
1113+ fprintf (stderr , "%s: traceroute mode only supports one target at a time\n" , prog );
1114+ exit (1 );
1115+ }
1116+ #ifdef __linux__
1117+ if (using_sock_dgram4 ) {
1118+ fprintf (stderr , "%s: traceroute mode requires raw sockets (run as root)\n" , prog );
1119+ exit (1 );
1120+ }
1121+ #endif
1122+ if (ttl == 0 ) {
1123+ ttl = TRACEROUTE_DEFAULT_MAX_TTL ; /* Default traceroute limit */
1124+ } else if (ttl > TRACEROUTE_DEFAULT_MAX_TTL ) {
1125+ fprintf (stderr , "%s: traceroute ttl max is 30, clamping.\n" , prog );
1126+ ttl = TRACEROUTE_DEFAULT_MAX_TTL ;
1127+ }
1128+ }
1129+
10951130 if (interval < (float )MIN_INTERVAL_MS * 1000000 && getuid ()) {
10961131 fprintf (stderr , "%s: -i must be >= %g\n" , prog , (float )MIN_INTERVAL_MS );
10971132 exit (1 );
@@ -1138,6 +1173,9 @@ int main(int argc, char **argv)
11381173
11391174 trials = (count > retry + 1 ) ? count : retry + 1 ;
11401175
1176+ if (traceroute_flag )
1177+ trials = ttl ; /* Ensure enough space for up to 'ttl' hops */
1178+
11411179 /* auto-tune default timeout for count/loop modes
11421180 * see also github #32 */
11431181 if (loop_flag || count_flag ) {
@@ -1270,7 +1308,7 @@ int main(int argc, char **argv)
12701308 if (count_flag ) {
12711309 event_storage_count = count ;
12721310 }
1273- else if (loop_flag ) {
1311+ else if (loop_flag || traceroute_flag ) {
12741312 if (perhost_interval > timeout ) {
12751313 event_storage_count = 1 ;
12761314 }
@@ -1461,6 +1499,26 @@ int main(int argc, char **argv)
14611499
14621500 seqmap_init (seqmap_timeout );
14631501
1502+ /* Traceroute header output */
1503+ if (traceroute_flag ) {
1504+ int i ;
1505+ for (i = 0 ; i < num_hosts ; i ++ ) {
1506+ HOST_ENTRY * h = table [i ];
1507+ char ip_str [INET6_ADDRSTRLEN ];
1508+ int total_len = ping_data_size + SIZE_ICMP_HDR ;
1509+
1510+ /* Resolve IP string and calculate header length */
1511+ getnameinfo ((struct sockaddr * )& h -> saddr , h -> saddr_len , ip_str , sizeof (ip_str ), NULL , 0 , NI_NUMERICHOST );
1512+
1513+ if (h -> saddr .ss_family == AF_INET6 )
1514+ total_len += 40 ; /* IPv6 Header fix 40 bytes */
1515+ else
1516+ total_len += SIZE_IP_HDR ; /* IPv4 Header min 20 bytes */
1517+
1518+ printf ("fping traceroute to %s (%s), %d hops max, %d byte packets\n" , h -> name , ip_str , (int )ttl , total_len );
1519+ }
1520+ }
1521+
14641522 /* main loop */
14651523 main_loop ();
14661524
@@ -1791,6 +1849,14 @@ void main_loop()
17911849
17921850 stats_add (h , event -> ping_index , 0 , -1 );
17931851
1852+ if (traceroute_flag ) {
1853+ printf ("%s: hop %d no reply\n" , h -> host , h -> trace_ttl );
1854+ h -> trace_ttl ++ ;
1855+ if (h -> trace_ttl > (int )ttl ) h -> trace_ttl = (int )ttl ;
1856+ /* Continue to the next hop, no retry for this hop */
1857+ continue ;
1858+ }
1859+
17941860 if (per_recv_flag ) {
17951861 print_timeout (h , event -> ping_index );
17961862 }
@@ -1825,11 +1891,33 @@ void main_loop()
18251891
18261892 dbg_printf ("%s [%d]: ping event\n" , h -> host , event -> ping_index );
18271893
1894+ /* Traceroute */
1895+ if (traceroute_flag ) {
1896+ if (h -> trace_ttl == TRACEROUTE_DONE_TTL ) {
1897+ continue ;
1898+ }
1899+
1900+ int ttl_set = h -> trace_ttl ;
1901+ if (ttl_set > (int )ttl ) ttl_set = (int )ttl ;
1902+ if (socket4 >= 0 ) {
1903+ if (setsockopt (socket4 , IPPROTO_IP , IP_TTL , & ttl_set , sizeof (ttl_set )))
1904+ perror ("setsockopt IP_TTL" );
1905+ }
1906+ #ifdef IPV6
1907+ if (socket6 >= 0 ) {
1908+ /* Set hop limit for IPv6 */
1909+ if (setsockopt (socket6 , IPPROTO_IPV6 , IPV6_UNICAST_HOPS , & ttl_set , sizeof (ttl_set ))) {
1910+ perror ("setsockopt IPV6_UNICAST_HOPS" );
1911+ }
1912+ }
1913+ #endif
1914+ }
1915+
18281916 /* Send the ping */
18291917 send_ping (h , event -> ping_index );
18301918
1831- /* Loop and count mode: schedule next ping */
1832- if (loop_flag || (count_flag && event -> ping_index + 1 < count )) {
1919+ /* Loop, count and traceroute mode: schedule next ping */
1920+ if (loop_flag || (count_flag && event -> ping_index + 1 < count ) || ( traceroute_flag && h -> trace_ttl < ( int ) ttl ) ) {
18331921 host_add_ping_event (h , event -> ping_index + 1 , event -> ev_time + perhost_interval );
18341922 }
18351923 }
@@ -2974,6 +3062,31 @@ int decode_icmp_ipv4(
29743062
29753063 icp = (struct icmp * )(reply_buf + hlen );
29763064
3065+ if (traceroute_flag && icp -> icmp_type == ICMP_TIMXCEED ) {
3066+ struct ip * inner_ip ;
3067+ int inner_hlen ;
3068+ struct icmp * inner_icmp ;
3069+
3070+ if (reply_buf_len >= hlen + ICMP_MINLEN + sizeof (struct ip ) + ICMP_MINLEN ) {
3071+ inner_ip = (struct ip * ) (reply_buf + hlen + ICMP_MINLEN );
3072+ inner_hlen = inner_ip -> ip_hl << 2 ;
3073+
3074+ if (inner_hlen < sizeof (struct ip )) {
3075+ /* Invalid inner IP header length */
3076+ return -1 ;
3077+ }
3078+
3079+ if (reply_buf_len >= hlen + ICMP_MINLEN + inner_hlen + ICMP_MINLEN ) {
3080+ inner_icmp = (struct icmp * ) ((char * )inner_ip + inner_hlen );
3081+ if (inner_icmp -> icmp_id == ident4 ) {
3082+ * id = inner_icmp -> icmp_id ;
3083+ * seq = ntohs (inner_icmp -> icmp_seq );
3084+ return hlen ;
3085+ }
3086+ }
3087+ }
3088+ }
3089+
29773090 if ((icmp_request_typ == 0 && icp -> icmp_type != ICMP_ECHOREPLY ) ||
29783091 (icmp_request_typ == 13 && icp -> icmp_type != ICMP_TSTAMPREPLY )) {
29793092 /* Handle other ICMP packets */
@@ -3085,6 +3198,27 @@ int decode_icmp_ipv6(
30853198
30863199 icp = (struct icmp6_hdr * )reply_buf ;
30873200
3201+ /* Traceroute Logic for IPv6 Time Exceeded */
3202+ if (traceroute_flag && icp -> icmp6_type == ICMP6_TIME_EXCEEDED ) {
3203+ struct ip6_hdr * inner_ip6 ;
3204+ struct icmp6_hdr * inner_icmp6 ;
3205+
3206+ if (reply_buf_len >= sizeof (struct icmp6_hdr ) + sizeof (struct ip6_hdr ) + sizeof (struct icmp6_hdr )) {
3207+ inner_ip6 = (struct ip6_hdr * )(reply_buf + sizeof (struct icmp6_hdr ));
3208+
3209+ /* Check whether the inner packet is ICMPv6 */
3210+ if (inner_ip6 -> ip6_nxt == IPPROTO_ICMPV6 ) {
3211+ inner_icmp6 = (struct icmp6_hdr * )(reply_buf + sizeof (struct icmp6_hdr ) + sizeof (struct ip6_hdr ));
3212+
3213+ if (inner_icmp6 -> icmp6_id == ident6 ) {
3214+ * id = inner_icmp6 -> icmp6_id ;
3215+ * seq = ntohs (inner_icmp6 -> icmp6_seq );
3216+ return 1 ;
3217+ }
3218+ }
3219+ }
3220+ }
3221+
30883222 if (icp -> icmp6_type != ICMP6_ECHO_REPLY ) {
30893223 /* Handle other ICMPv6 packets */
30903224 struct ip6_hdr * sent_ipv6 ;
@@ -3189,6 +3323,7 @@ int wait_for_reply(int64_t wait_time)
31893323 static char buffer [RECV_BUFSIZE ];
31903324 struct sockaddr_storage response_addr ;
31913325 int n , avg ;
3326+ int ip_hlen = 0 ;
31923327 HOST_ENTRY * h ;
31933328 int64_t this_reply ;
31943329 int this_count ;
@@ -3219,7 +3354,7 @@ int wait_for_reply(int64_t wait_time)
32193354
32203355 /* Process ICMP packet and retrieve id/seq */
32213356 if (response_addr .ss_family == AF_INET ) {
3222- int ip_hlen = decode_icmp_ipv4 (
3357+ ip_hlen = decode_icmp_ipv4 (
32233358 (struct sockaddr * )& response_addr ,
32243359 sizeof (response_addr ),
32253360 buffer ,
@@ -3304,6 +3439,35 @@ int wait_for_reply(int64_t wait_time)
33043439 return 1 ;
33053440 }
33063441
3442+ if (traceroute_flag && response_addr .ss_family == AF_INET ) {
3443+ struct icmp * icp = (struct icmp * )(buffer + ip_hlen );
3444+ char ip_str [INET_ADDRSTRLEN ];
3445+ getnameinfo ((struct sockaddr * )& response_addr , sizeof (response_addr ), ip_str , sizeof (ip_str ), NULL , 0 , NI_NUMERICHOST );
3446+
3447+ if (icp -> icmp_type == ICMP_TIMXCEED ) {
3448+ handle_traceroute_hop (h , ip_str , 0 , this_reply );
3449+ } else { /* ICMP_ECHOREPLY */
3450+ handle_traceroute_hop (h , ip_str , 1 , this_reply );
3451+ }
3452+ }
3453+ #ifdef IPV6
3454+ else if (traceroute_flag && response_addr .ss_family == AF_INET6 ) {
3455+ struct icmp6_hdr * icp = (struct icmp6_hdr * )buffer ;
3456+
3457+ if (icp -> icmp6_type == ICMP6_TIME_EXCEEDED || icp -> icmp6_type == ICMP6_ECHO_REPLY ) {
3458+ /* With IPv6, buffer is directly the ICMP header payload, since receive_packet uses recvmsg */
3459+ char ip_str [INET6_ADDRSTRLEN ];
3460+ getnameinfo ((struct sockaddr * )& response_addr , sizeof (response_addr ), ip_str , sizeof (ip_str ), NULL , 0 , NI_NUMERICHOST );
3461+
3462+ if (icp -> icmp6_type == ICMP6_TIME_EXCEEDED ) {
3463+ handle_traceroute_hop (h , ip_str , 0 , this_reply );
3464+ } else { /* ICMP6_ECHO_REPLY */
3465+ handle_traceroute_hop (h , ip_str , 1 , this_reply );
3466+ }
3467+ }
3468+ }
3469+ #endif
3470+
33073471 /* update stats */
33083472 stats_add (h , this_count , 1 , this_reply );
33093473 // TODO: move to stats_add?
@@ -3323,6 +3487,10 @@ int wait_for_reply(int64_t wait_time)
33233487 ev_remove (& event_queue_timeout , timeout_event );
33243488 }
33253489
3490+ if (traceroute_flag ) {
3491+ return 1 ;
3492+ }
3493+
33263494 /* print "is alive" */
33273495 if (h -> num_recv == 1 ) {
33283496 num_alive ++ ;
@@ -3515,6 +3683,7 @@ void add_addr(char *name, char *host, struct sockaddr *ipaddr, socklen_t ipaddr_
35153683 p -> saddr_len = ipaddr_len ;
35163684 p -> timeout = timeout ;
35173685 p -> min_reply = 0 ;
3686+ p -> trace_ttl = 1 ;
35183687
35193688 if (netdata_flag ) {
35203689 char * s = p -> name ;
@@ -3875,6 +4044,40 @@ static uint32_t ms_since_midnight_utc(int64_t time_val)
38754044 return (uint32_t )((time_val / 1000000 ) % (24 * 60 * 60 * 1000 ));
38764045}
38774046
4047+ /************************************************************
4048+
4049+ Function: handle_traceroute_hop
4050+
4051+ *************************************************************
4052+
4053+ Input:
4054+ h: host entry
4055+ ip_str: textual IP of reply source
4056+ reached_destination: non-zero if destination was reached
4057+ this_reply: reply latency in ns
4058+
4059+ Desciption:
4060+
4061+ A small helper to centralize printing and TTL handling for
4062+ traceroute replies (both IPv4 and IPv6). This reduces code
4063+ duplication and keeps behaviour consistent.
4064+
4065+ *************************************************************/
4066+
4067+ void handle_traceroute_hop (HOST_ENTRY * h , const char * ip_str , int reached_destination , int64_t this_reply )
4068+ {
4069+ if (reached_destination ) {
4070+ printf ("%s: hop %d reached DESTINATION %s (%s ms)\n" , h -> host , h -> trace_ttl , ip_str , sprint_tm (this_reply ));
4071+ h -> trace_ttl = TRACEROUTE_DONE_TTL ; /* Goal achieved: artificially increase TTL to stop loop in main_loop */
4072+ } else {
4073+ printf ("%s: hop %d reached %s (%s ms)\n" , h -> host , h -> trace_ttl , ip_str , sprint_tm (this_reply ));
4074+ h -> trace_ttl ++ ;
4075+ if (h -> trace_ttl > (int )ttl ) {
4076+ h -> trace_ttl = (int )ttl ;
4077+ }
4078+ }
4079+ }
4080+
38784081/************************************************************
38794082
38804083 Function: usage
@@ -3925,6 +4128,7 @@ void usage(int is_error)
39254128 fprintf (out , " except with -l/-c/-C, where it's the -p period up to 2000 ms)\n" );
39264129 fprintf (out , " --check-source discard replies not from target address\n" );
39274130 fprintf (out , " --icmp-timestamp use ICMP Timestamp instead of ICMP Echo\n" );
4131+ fprintf (out , " --traceroute Sends a traceroute based on ICMP echo request (Only one host is allowed)\n" );
39284132 fprintf (out , "\n" );
39294133 fprintf (out , "Output options:\n" );
39304134 fprintf (out , " -a, --alive show targets that are alive\n" );
0 commit comments