@@ -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 (strcmp (optparse_state .optlongname , "traceroute" ) == 0 ) {
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,26 @@ 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+ #ifdef __linux__
1112+ if (using_sock_dgram4 ) {
1113+ fprintf (stderr , "%s: traceroute mode requires raw sockets (run as root)\n" , prog );
1114+ exit (1 );
1115+ }
1116+ #endif
1117+ if (ttl == 0 ) {
1118+ ttl = TRACEROUTE_DEFAULT_MAX_TTL ; /* Default traceroute limit */
1119+ } else if (ttl > TRACEROUTE_DEFAULT_MAX_TTL ) {
1120+ fprintf (stderr , "%s: traceroute ttl max is 30, clamping.\n" , prog );
1121+ ttl = TRACEROUTE_DEFAULT_MAX_TTL ;
1122+ }
1123+ }
1124+
10951125 if (interval < (float )MIN_INTERVAL_MS * 1000000 && getuid ()) {
10961126 fprintf (stderr , "%s: -i must be >= %g\n" , prog , (float )MIN_INTERVAL_MS );
10971127 exit (1 );
@@ -1138,6 +1168,9 @@ int main(int argc, char **argv)
11381168
11391169 trials = (count > retry + 1 ) ? count : retry + 1 ;
11401170
1171+ if (traceroute_flag )
1172+ trials = ttl ; /* Ensure enough space for up to 'ttl' hops */
1173+
11411174 /* auto-tune default timeout for count/loop modes
11421175 * see also github #32 */
11431176 if (loop_flag || count_flag ) {
@@ -1270,7 +1303,7 @@ int main(int argc, char **argv)
12701303 if (count_flag ) {
12711304 event_storage_count = count ;
12721305 }
1273- else if (loop_flag ) {
1306+ else if (loop_flag || traceroute_flag ) {
12741307 if (perhost_interval > timeout ) {
12751308 event_storage_count = 1 ;
12761309 }
@@ -1403,6 +1436,14 @@ int main(int argc, char **argv)
14031436 exit (num_noaddress ? 2 : 1 );
14041437 }
14051438
1439+ if (traceroute_flag ) {
1440+ /* Preventing race conditions: Only one host allowed */
1441+ if (num_hosts > 1 ) {
1442+ fprintf (stderr , "%s: traceroute mode only supports one target at a time\n" , prog );
1443+ exit (1 );
1444+ }
1445+ }
1446+
14061447 if (socket4 >= 0 && (src_addr_set || socktype4 == SOCK_DGRAM )) {
14071448 socket_set_src_addr_ipv4 (socket4 , & src_addr , (socktype4 == SOCK_DGRAM ) ? & ident4 : NULL );
14081449 }
@@ -1461,6 +1502,26 @@ int main(int argc, char **argv)
14611502
14621503 seqmap_init (seqmap_timeout );
14631504
1505+ /* Traceroute header output */
1506+ if (traceroute_flag ) {
1507+ int i ;
1508+ for (i = 0 ; i < num_hosts ; i ++ ) {
1509+ HOST_ENTRY * h = table [i ];
1510+ char ip_str [INET6_ADDRSTRLEN ];
1511+ int total_len = ping_data_size + SIZE_ICMP_HDR ;
1512+
1513+ /* Resolve IP string and calculate header length */
1514+ getnameinfo ((struct sockaddr * )& h -> saddr , h -> saddr_len , ip_str , sizeof (ip_str ), NULL , 0 , NI_NUMERICHOST );
1515+
1516+ if (h -> saddr .ss_family == AF_INET6 )
1517+ total_len += 40 ; /* IPv6 Header fix 40 bytes */
1518+ else
1519+ total_len += SIZE_IP_HDR ; /* IPv4 Header min 20 bytes */
1520+
1521+ printf ("fping traceroute to %s (%s), %d hops max, %d byte packets\n" , h -> name , ip_str , (int )ttl , total_len );
1522+ }
1523+ }
1524+
14641525 /* main loop */
14651526 main_loop ();
14661527
@@ -1791,6 +1852,14 @@ void main_loop()
17911852
17921853 stats_add (h , event -> ping_index , 0 , -1 );
17931854
1855+ if (traceroute_flag ) {
1856+ printf ("%s: hop %d no reply\n" , h -> host , h -> trace_ttl );
1857+ h -> trace_ttl ++ ;
1858+ if (h -> trace_ttl > (int )ttl ) h -> trace_ttl = (int )ttl ;
1859+ /* Continue to the next hop, no retry for this hop */
1860+ continue ;
1861+ }
1862+
17941863 if (per_recv_flag ) {
17951864 print_timeout (h , event -> ping_index );
17961865 }
@@ -1825,11 +1894,33 @@ void main_loop()
18251894
18261895 dbg_printf ("%s [%d]: ping event\n" , h -> host , event -> ping_index );
18271896
1897+ /* Traceroute */
1898+ if (traceroute_flag ) {
1899+ if (h -> trace_ttl == TRACEROUTE_DONE_TTL ) {
1900+ continue ;
1901+ }
1902+
1903+ int ttl_set = h -> trace_ttl ;
1904+ if (ttl_set > (int )ttl ) ttl_set = (int )ttl ;
1905+ if (socket4 >= 0 ) {
1906+ if (setsockopt (socket4 , IPPROTO_IP , IP_TTL , & ttl_set , sizeof (ttl_set )))
1907+ perror ("setsockopt IP_TTL" );
1908+ }
1909+ #ifdef IPV6
1910+ if (socket6 >= 0 ) {
1911+ /* Set hop limit for IPv6 */
1912+ if (setsockopt (socket6 , IPPROTO_IPV6 , IPV6_UNICAST_HOPS , & ttl_set , sizeof (ttl_set ))) {
1913+ perror ("setsockopt IPV6_UNICAST_HOPS" );
1914+ }
1915+ }
1916+ #endif
1917+ }
1918+
18281919 /* Send the ping */
18291920 send_ping (h , event -> ping_index );
18301921
1831- /* Loop and count mode: schedule next ping */
1832- if (loop_flag || (count_flag && event -> ping_index + 1 < count )) {
1922+ /* Loop, count and traceroute mode: schedule next ping */
1923+ if (loop_flag || (count_flag && event -> ping_index + 1 < count ) || ( traceroute_flag && h -> trace_ttl < ( int ) ttl ) ) {
18331924 host_add_ping_event (h , event -> ping_index + 1 , event -> ev_time + perhost_interval );
18341925 }
18351926 }
@@ -2986,6 +3077,31 @@ int decode_icmp_ipv4(
29863077
29873078 icp = (struct icmp * )(reply_buf + hlen );
29883079
3080+ if (traceroute_flag && icp -> icmp_type == ICMP_TIMXCEED ) {
3081+ struct ip * inner_ip ;
3082+ int inner_hlen ;
3083+ struct icmp * inner_icmp ;
3084+
3085+ if (reply_buf_len >= hlen + ICMP_MINLEN + sizeof (struct ip ) + ICMP_MINLEN ) {
3086+ inner_ip = (struct ip * ) (reply_buf + hlen + ICMP_MINLEN );
3087+ inner_hlen = inner_ip -> ip_hl << 2 ;
3088+
3089+ if (inner_hlen < sizeof (struct ip )) {
3090+ /* Invalid inner IP header length */
3091+ return -1 ;
3092+ }
3093+
3094+ if (reply_buf_len >= hlen + ICMP_MINLEN + inner_hlen + ICMP_MINLEN ) {
3095+ inner_icmp = (struct icmp * ) ((char * )inner_ip + inner_hlen );
3096+ if (inner_icmp -> icmp_id == ident4 ) {
3097+ * id = inner_icmp -> icmp_id ;
3098+ * seq = ntohs (inner_icmp -> icmp_seq );
3099+ return hlen ;
3100+ }
3101+ }
3102+ }
3103+ }
3104+
29893105 if ((icmp_request_typ == 0 && icp -> icmp_type != ICMP_ECHOREPLY ) ||
29903106 (icmp_request_typ == 13 && icp -> icmp_type != ICMP_TSTAMPREPLY )) {
29913107 /* Handle other ICMP packets */
@@ -3097,6 +3213,27 @@ int decode_icmp_ipv6(
30973213
30983214 icp = (struct icmp6_hdr * )reply_buf ;
30993215
3216+ /* Traceroute Logic for IPv6 Time Exceeded */
3217+ if (traceroute_flag && icp -> icmp6_type == ICMP6_TIME_EXCEEDED ) {
3218+ struct ip6_hdr * inner_ip6 ;
3219+ struct icmp6_hdr * inner_icmp6 ;
3220+
3221+ if (reply_buf_len >= sizeof (struct icmp6_hdr ) + sizeof (struct ip6_hdr ) + sizeof (struct icmp6_hdr )) {
3222+ inner_ip6 = (struct ip6_hdr * )(reply_buf + sizeof (struct icmp6_hdr ));
3223+
3224+ /* Check whether the inner packet is ICMPv6 */
3225+ if (inner_ip6 -> ip6_nxt == IPPROTO_ICMPV6 ) {
3226+ inner_icmp6 = (struct icmp6_hdr * )(reply_buf + sizeof (struct icmp6_hdr ) + sizeof (struct ip6_hdr ));
3227+
3228+ if (inner_icmp6 -> icmp6_id == ident6 ) {
3229+ * id = inner_icmp6 -> icmp6_id ;
3230+ * seq = ntohs (inner_icmp6 -> icmp6_seq );
3231+ return 1 ;
3232+ }
3233+ }
3234+ }
3235+ }
3236+
31003237 if (icp -> icmp6_type != ICMP6_ECHO_REPLY ) {
31013238 /* Handle other ICMPv6 packets */
31023239 struct ip6_hdr * sent_ipv6 ;
@@ -3201,6 +3338,7 @@ int wait_for_reply(int64_t wait_time)
32013338 static char buffer [RECV_BUFSIZE ];
32023339 struct sockaddr_storage response_addr ;
32033340 int n , avg ;
3341+ int ip_hlen = 0 ;
32043342 HOST_ENTRY * h ;
32053343 int64_t this_reply ;
32063344 int this_count ;
@@ -3231,7 +3369,7 @@ int wait_for_reply(int64_t wait_time)
32313369
32323370 /* Process ICMP packet and retrieve id/seq */
32333371 if (response_addr .ss_family == AF_INET ) {
3234- int ip_hlen = decode_icmp_ipv4 (
3372+ ip_hlen = decode_icmp_ipv4 (
32353373 (struct sockaddr * )& response_addr ,
32363374 sizeof (response_addr ),
32373375 buffer ,
@@ -3316,6 +3454,35 @@ int wait_for_reply(int64_t wait_time)
33163454 return 1 ;
33173455 }
33183456
3457+ if (traceroute_flag && response_addr .ss_family == AF_INET ) {
3458+ struct icmp * icp = (struct icmp * )(buffer + ip_hlen );
3459+ char ip_str [INET_ADDRSTRLEN ];
3460+ getnameinfo ((struct sockaddr * )& response_addr , sizeof (response_addr ), ip_str , sizeof (ip_str ), NULL , 0 , NI_NUMERICHOST );
3461+
3462+ if (icp -> icmp_type == ICMP_TIMXCEED ) {
3463+ handle_traceroute_hop (h , ip_str , 0 , this_reply );
3464+ } else { /* ICMP_ECHOREPLY */
3465+ handle_traceroute_hop (h , ip_str , 1 , this_reply );
3466+ }
3467+ }
3468+ #ifdef IPV6
3469+ else if (traceroute_flag && response_addr .ss_family == AF_INET6 ) {
3470+ struct icmp6_hdr * icp = (struct icmp6_hdr * )buffer ;
3471+
3472+ if (icp -> icmp6_type == ICMP6_TIME_EXCEEDED || icp -> icmp6_type == ICMP6_ECHO_REPLY ) {
3473+ /* With IPv6, buffer is directly the ICMP header payload, since receive_packet uses recvmsg */
3474+ char ip_str [INET6_ADDRSTRLEN ];
3475+ getnameinfo ((struct sockaddr * )& response_addr , sizeof (response_addr ), ip_str , sizeof (ip_str ), NULL , 0 , NI_NUMERICHOST );
3476+
3477+ if (icp -> icmp6_type == ICMP6_TIME_EXCEEDED ) {
3478+ handle_traceroute_hop (h , ip_str , 0 , this_reply );
3479+ } else { /* ICMP6_ECHO_REPLY */
3480+ handle_traceroute_hop (h , ip_str , 1 , this_reply );
3481+ }
3482+ }
3483+ }
3484+ #endif
3485+
33193486 /* update stats */
33203487 stats_add (h , this_count , 1 , this_reply );
33213488 // TODO: move to stats_add?
@@ -3335,6 +3502,10 @@ int wait_for_reply(int64_t wait_time)
33353502 ev_remove (& event_queue_timeout , timeout_event );
33363503 }
33373504
3505+ if (traceroute_flag ) {
3506+ return 1 ;
3507+ }
3508+
33383509 /* print "is alive" */
33393510 if (h -> num_recv == 1 ) {
33403511 num_alive ++ ;
@@ -3527,6 +3698,7 @@ void add_addr(char *name, char *host, struct sockaddr *ipaddr, socklen_t ipaddr_
35273698 p -> saddr_len = ipaddr_len ;
35283699 p -> timeout = timeout ;
35293700 p -> min_reply = 0 ;
3701+ p -> trace_ttl = 1 ;
35303702
35313703 if (netdata_flag ) {
35323704 char * s = p -> name ;
@@ -3887,6 +4059,40 @@ static uint32_t ms_since_midnight_utc(int64_t time_val)
38874059 return (uint32_t )((time_val / 1000000 ) % (24 * 60 * 60 * 1000 ));
38884060}
38894061
4062+ /************************************************************
4063+
4064+ Function: handle_traceroute_hop
4065+
4066+ *************************************************************
4067+
4068+ Input:
4069+ h: host entry
4070+ ip_str: textual IP of reply source
4071+ reached_destination: non-zero if destination was reached
4072+ this_reply: reply latency in ns
4073+
4074+ Desciption:
4075+
4076+ A small helper to centralize printing and TTL handling for
4077+ traceroute replies (both IPv4 and IPv6). This reduces code
4078+ duplication and keeps behaviour consistent.
4079+
4080+ *************************************************************/
4081+
4082+ void handle_traceroute_hop (HOST_ENTRY * h , const char * ip_str , int reached_destination , int64_t this_reply )
4083+ {
4084+ if (reached_destination ) {
4085+ printf ("%s: hop %d reached DESTINATION %s (%s ms)\n" , h -> host , h -> trace_ttl , ip_str , sprint_tm (this_reply ));
4086+ h -> trace_ttl = TRACEROUTE_DONE_TTL ; /* Goal achieved: artificially increase TTL to stop loop in main_loop */
4087+ } else {
4088+ printf ("%s: hop %d reached %s (%s ms)\n" , h -> host , h -> trace_ttl , ip_str , sprint_tm (this_reply ));
4089+ h -> trace_ttl ++ ;
4090+ if (h -> trace_ttl > (int )ttl ) {
4091+ h -> trace_ttl = (int )ttl ;
4092+ }
4093+ }
4094+ }
4095+
38904096/************************************************************
38914097
38924098 Function: usage
@@ -3937,6 +4143,7 @@ void usage(int is_error)
39374143 fprintf (out , " except with -l/-c/-C, where it's the -p period up to 2000 ms)\n" );
39384144 fprintf (out , " --check-source discard replies not from target address\n" );
39394145 fprintf (out , " --icmp-timestamp use ICMP Timestamp instead of ICMP Echo\n" );
4146+ fprintf (out , " --traceroute Sends a traceroute based on ICMP echo request (Only one host is allowed)\n" );
39404147 fprintf (out , "\n" );
39414148 fprintf (out , "Output options:\n" );
39424149 fprintf (out , " -a, --alive show targets that are alive\n" );
0 commit comments