@@ -168,6 +168,9 @@ extern int h_errno;
168168#define RESP_ERROR -3
169169#define RESP_TIMEOUT -4
170170
171+ /* Traceroute */
172+ #define TRACEROUTE_DONE_TTL 100
173+
171174/* debugging flags */
172175#if defined(DEBUG ) || defined(_DEBUG )
173176#define DBG_TRACE 1
@@ -297,6 +300,7 @@ typedef struct host_entry {
297300 int64_t min_reply_i ; /* shortest response time */
298301 int64_t total_time_i ; /* sum of response times */
299302 int64_t * resp_times ; /* individual response times */
303+ int trace_ttl ; /* current traceroute ttl */
300304
301305 /* to avoid allocating two struct events each time that we send a ping, we
302306 * preallocate here two struct events for each ping that we might send for
@@ -423,6 +427,7 @@ int timestamp_flag = 0;
423427int timestamp_format_flag = 0 ;
424428int random_data_flag = 0 ;
425429int cumulative_stats_flag = 0 ;
430+ int traceroute_flag = 0 ;
426431int check_source_flag = 0 ;
427432int icmp_request_typ = 0 ;
428433int print_tos_flag = 0 ;
@@ -642,6 +647,7 @@ int main(int argc, char **argv)
642647 { "rdns" , 'd' , OPTPARSE_NONE },
643648 { "timestamp" , 'D' , OPTPARSE_NONE },
644649 { "timestamp-format" , '0' , OPTPARSE_REQUIRED },
650+ { "traceroute" , '0' , OPTPARSE_NONE },
645651 { "elapsed" , 'e' , OPTPARSE_NONE },
646652 { "file" , 'f' , OPTPARSE_REQUIRED },
647653 { "generate" , 'g' , OPTPARSE_NONE },
@@ -698,6 +704,8 @@ int main(int argc, char **argv)
698704 }else {
699705 usage (1 );
700706 }
707+ } else if (strstr (optparse_state .optlongname , "traceroute" ) != NULL ) {
708+ traceroute_flag = 1 ;
701709 } else if (strstr (optparse_state .optlongname , "check-source" ) != NULL ) {
702710 check_source_flag = 1 ;
703711 } else if (strstr (optparse_state .optlongname , "icmp-timestamp" ) != NULL ) {
@@ -1092,6 +1100,26 @@ int main(int argc, char **argv)
10921100 exit (1 );
10931101 }
10941102
1103+ if (traceroute_flag && (count_flag || loop_flag || netdata_flag || quiet_flag || stats_flag || json_flag )) {
1104+ fprintf (stderr , "%s: can't combine --traceroute with -c, -C, -l, -N, -q, -Q, -s or -J\n" , prog );
1105+ exit (1 );
1106+ }
1107+
1108+ if (traceroute_flag ) {
1109+ #ifdef __linux__
1110+ if (using_sock_dgram4 ) {
1111+ fprintf (stderr , "%s: traceroute mode requires raw sockets (run as root)\n" , prog );
1112+ exit (1 );
1113+ }
1114+ #endif
1115+ if (ttl == 0 ) {
1116+ ttl = 30 ; /* Default traceroute limit */
1117+ } else if (ttl > 30 ) {
1118+ fprintf (stderr , "%s: traceroute ttl max is 30, clamping.\n" , prog );
1119+ ttl = 30 ;
1120+ }
1121+ }
1122+
10951123 if (interval < (float )MIN_INTERVAL_MS * 1000000 && getuid ()) {
10961124 fprintf (stderr , "%s: -i must be >= %g\n" , prog , (float )MIN_INTERVAL_MS );
10971125 exit (1 );
@@ -1138,6 +1166,9 @@ int main(int argc, char **argv)
11381166
11391167 trials = (count > retry + 1 ) ? count : retry + 1 ;
11401168
1169+ if (traceroute_flag )
1170+ trials = ttl ; /* Ensure enough space for up to 'ttl' hops */
1171+
11411172 /* auto-tune default timeout for count/loop modes
11421173 * see also github #32 */
11431174 if (loop_flag || count_flag ) {
@@ -1270,7 +1301,7 @@ int main(int argc, char **argv)
12701301 if (count_flag ) {
12711302 event_storage_count = count ;
12721303 }
1273- else if (loop_flag ) {
1304+ else if (loop_flag || traceroute_flag ) {
12741305 if (perhost_interval > timeout ) {
12751306 event_storage_count = 1 ;
12761307 }
@@ -1461,6 +1492,26 @@ int main(int argc, char **argv)
14611492
14621493 seqmap_init (seqmap_timeout );
14631494
1495+ /* Traceroute header output */
1496+ if (traceroute_flag ) {
1497+ int i ;
1498+ for (i = 0 ; i < num_hosts ; i ++ ) {
1499+ HOST_ENTRY * h = table [i ];
1500+ char ip_str [INET6_ADDRSTRLEN ];
1501+ int total_len = ping_data_size + SIZE_ICMP_HDR ;
1502+
1503+ /* Resolve IP string and calculate header length */
1504+ getnameinfo ((struct sockaddr * )& h -> saddr , h -> saddr_len , ip_str , sizeof (ip_str ), NULL , 0 , NI_NUMERICHOST );
1505+
1506+ if (h -> saddr .ss_family == AF_INET6 )
1507+ total_len += 40 ; /* IPv6 Header fix 40 bytes */
1508+ else
1509+ total_len += 20 ; /* IPv4 Header min 20 bytes */
1510+
1511+ printf ("fping traceroute to %s (%s), %d hops max, %d byte packets\n" , h -> name , ip_str , (int )ttl , total_len );
1512+ }
1513+ }
1514+
14641515 /* main loop */
14651516 main_loop ();
14661517
@@ -1791,6 +1842,14 @@ void main_loop()
17911842
17921843 stats_add (h , event -> ping_index , 0 , -1 );
17931844
1845+ if (traceroute_flag ) {
1846+ printf ("%s: hop %d no reply\n" , h -> host , h -> trace_ttl );
1847+ h -> trace_ttl ++ ;
1848+ if (h -> trace_ttl > (int )ttl ) h -> trace_ttl = (int )ttl ;
1849+ /* Continue to the next hop, no retry for this hop */
1850+ continue ;
1851+ }
1852+
17941853 if (per_recv_flag ) {
17951854 print_timeout (h , event -> ping_index );
17961855 }
@@ -1825,11 +1884,33 @@ void main_loop()
18251884
18261885 dbg_printf ("%s [%d]: ping event\n" , h -> host , event -> ping_index );
18271886
1887+ /* Traceroute */
1888+ if (traceroute_flag ) {
1889+ if (h -> trace_ttl == TRACEROUTE_DONE_TTL ) {
1890+ continue ;
1891+ }
1892+
1893+ int ttl_set = h -> trace_ttl ;
1894+ if (ttl_set > (int )ttl ) ttl_set = (int )ttl ;
1895+ if (socket4 >= 0 ) {
1896+ if (setsockopt (socket4 , IPPROTO_IP , IP_TTL , & ttl_set , sizeof (ttl_set )))
1897+ perror ("setsockopt IP_TTL" );
1898+ }
1899+ #ifdef IPV6
1900+ if (socket6 >= 0 ) {
1901+ /* Set hop limit for IPv6 */
1902+ if (setsockopt (socket6 , IPPROTO_IPV6 , IPV6_UNICAST_HOPS , & ttl_set , sizeof (ttl_set ))) {
1903+ perror ("setsockopt IPV6_UNICAST_HOPS" );
1904+ }
1905+ }
1906+ #endif
1907+ }
1908+
18281909 /* Send the ping */
18291910 send_ping (h , event -> ping_index );
18301911
1831- /* Loop and count mode: schedule next ping */
1832- if (loop_flag || (count_flag && event -> ping_index + 1 < count )) {
1912+ /* Loop, count and traceroute mode: schedule next ping */
1913+ if (loop_flag || (count_flag && event -> ping_index + 1 < count ) || ( traceroute_flag && h -> trace_ttl < ( int ) ttl ) ) {
18331914 host_add_ping_event (h , event -> ping_index + 1 , event -> ev_time + perhost_interval );
18341915 }
18351916 }
@@ -2974,6 +3055,23 @@ int decode_icmp_ipv4(
29743055
29753056 icp = (struct icmp * )(reply_buf + hlen );
29763057
3058+ if (traceroute_flag && icp -> icmp_type == ICMP_TIMXCEED ) {
3059+ struct ip * inner_ip ;
3060+ int inner_hlen ;
3061+ struct icmp * inner_icmp ;
3062+
3063+ if (reply_buf_len >= hlen + ICMP_MINLEN + sizeof (struct ip ) + ICMP_MINLEN ) {
3064+ inner_ip = (struct ip * ) (reply_buf + hlen + ICMP_MINLEN );
3065+ inner_hlen = inner_ip -> ip_hl << 2 ;
3066+ inner_icmp = (struct icmp * ) ((char * )inner_ip + inner_hlen );
3067+ if (inner_icmp -> icmp_id == ident4 ) {
3068+ * id = inner_icmp -> icmp_id ;
3069+ * seq = ntohs (inner_icmp -> icmp_seq );
3070+ return hlen ;
3071+ }
3072+ }
3073+ }
3074+
29773075 if ((icmp_request_typ == 0 && icp -> icmp_type != ICMP_ECHOREPLY ) ||
29783076 (icmp_request_typ == 13 && icp -> icmp_type != ICMP_TSTAMPREPLY )) {
29793077 /* Handle other ICMP packets */
@@ -3085,6 +3183,27 @@ int decode_icmp_ipv6(
30853183
30863184 icp = (struct icmp6_hdr * )reply_buf ;
30873185
3186+ /* Traceroute Logic for IPv6 Time Exceeded */
3187+ if (traceroute_flag && icp -> icmp6_type == ICMP6_TIME_EXCEEDED ) {
3188+ struct ip6_hdr * inner_ip6 ;
3189+ struct icmp6_hdr * inner_icmp6 ;
3190+
3191+ if (reply_buf_len >= sizeof (struct icmp6_hdr ) + sizeof (struct ip6_hdr ) + sizeof (struct icmp6_hdr )) {
3192+ inner_ip6 = (struct ip6_hdr * )(reply_buf + sizeof (struct icmp6_hdr ));
3193+
3194+ /* Check whether the inner packet is ICMPv6 */
3195+ if (inner_ip6 -> ip6_nxt == IPPROTO_ICMPV6 ) {
3196+ inner_icmp6 = (struct icmp6_hdr * )(reply_buf + sizeof (struct icmp6_hdr ) + sizeof (struct ip6_hdr ));
3197+
3198+ if (inner_icmp6 -> icmp6_id == ident6 ) {
3199+ * id = inner_icmp6 -> icmp6_id ;
3200+ * seq = ntohs (inner_icmp6 -> icmp6_seq );
3201+ return 1 ;
3202+ }
3203+ }
3204+ }
3205+ }
3206+
30883207 if (icp -> icmp6_type != ICMP6_ECHO_REPLY ) {
30893208 /* Handle other ICMPv6 packets */
30903209 struct ip6_hdr * sent_ipv6 ;
@@ -3189,6 +3308,7 @@ int wait_for_reply(int64_t wait_time)
31893308 static char buffer [RECV_BUFSIZE ];
31903309 struct sockaddr_storage response_addr ;
31913310 int n , avg ;
3311+ int ip_hlen = 0 ;
31923312 HOST_ENTRY * h ;
31933313 int64_t this_reply ;
31943314 int this_count ;
@@ -3219,7 +3339,7 @@ int wait_for_reply(int64_t wait_time)
32193339
32203340 /* Process ICMP packet and retrieve id/seq */
32213341 if (response_addr .ss_family == AF_INET ) {
3222- int ip_hlen = decode_icmp_ipv4 (
3342+ ip_hlen = decode_icmp_ipv4 (
32233343 (struct sockaddr * )& response_addr ,
32243344 sizeof (response_addr ),
32253345 buffer ,
@@ -3270,6 +3390,59 @@ int wait_for_reply(int64_t wait_time)
32703390 this_count = seqmap_value -> ping_count ;
32713391 this_reply = recv_time - seqmap_value -> ping_ts ;
32723392
3393+ if (traceroute_flag && response_addr .ss_family == AF_INET ) {
3394+ struct icmp * icp = (struct icmp * )(buffer + ip_hlen );
3395+ char ip_str [INET_ADDRSTRLEN ];
3396+ getnameinfo ((struct sockaddr * )& response_addr , sizeof (response_addr ), ip_str , sizeof (ip_str ), NULL , 0 , NI_NUMERICHOST );
3397+
3398+ if (icp -> icmp_type == ICMP_TIMXCEED ) {
3399+ printf ("%s: hop %d reached %s (%s ms)\n" , h -> host , h -> trace_ttl , ip_str , sprint_tm (this_reply ));
3400+ h -> trace_ttl ++ ;
3401+ if (h -> trace_ttl > (int )ttl ) {
3402+ h -> trace_ttl = (int )ttl ;
3403+ }
3404+ } else { /* ICMP_ECHOREPLY */
3405+ printf ("%s: hop %d reached DESTINATION %s (%s ms)\n" , h -> host , h -> trace_ttl , ip_str , sprint_tm (this_reply ));
3406+ h -> trace_ttl = TRACEROUTE_DONE_TTL ; /* Goal achieved: artificially increase TTL to stop loop in main_loop */
3407+ }
3408+
3409+ stats_add (h , this_count , 1 , this_reply );
3410+ struct event * timeout_event = host_get_timeout_event (h , this_count );
3411+ if (timeout_event ) {
3412+ ev_remove (& event_queue_timeout , timeout_event );
3413+ }
3414+ return 1 ;
3415+ }
3416+ #ifdef IPV6
3417+ else if (traceroute_flag && response_addr .ss_family == AF_INET6 ) {
3418+ struct icmp6_hdr * icp = (struct icmp6_hdr * )buffer ;
3419+
3420+ if (icp -> icmp6_type == ICMP6_TIME_EXCEEDED || icp -> icmp6_type == ICMP6_ECHO_REPLY ) {
3421+ /* With IPv6, buffer is directly the ICMP header payload, since receive_packet uses recvmsg */
3422+ char ip_str [INET6_ADDRSTRLEN ];
3423+ getnameinfo ((struct sockaddr * )& response_addr , sizeof (response_addr ), ip_str , sizeof (ip_str ), NULL , 0 , NI_NUMERICHOST );
3424+
3425+ if (icp -> icmp6_type == ICMP6_TIME_EXCEEDED ) {
3426+ printf ("%s: hop %d reached %s (%s ms)\n" , h -> host , h -> trace_ttl , ip_str , sprint_tm (this_reply ));
3427+ h -> trace_ttl ++ ;
3428+ if (h -> trace_ttl > (int )ttl ) {
3429+ h -> trace_ttl = (int )ttl ;
3430+ }
3431+ } else { /* ICMP6_ECHO_REPLY */
3432+ printf ("%s: hop %d reached DESTINATION %s (%s ms)\n" , h -> host , h -> trace_ttl , ip_str , sprint_tm (this_reply ));
3433+ h -> trace_ttl = TRACEROUTE_DONE_TTL ; /* Goal achieved: artificially increase TTL to stop loop in main_loop */
3434+ }
3435+
3436+ stats_add (h , this_count , 1 , this_reply );
3437+ struct event * timeout_event = host_get_timeout_event (h , this_count );
3438+ if (timeout_event ) {
3439+ ev_remove (& event_queue_timeout , timeout_event );
3440+ }
3441+ return 1 ;
3442+ }
3443+ }
3444+ #endif
3445+
32733446 /* update stats that include invalid replies */
32743447 h -> num_recv_total ++ ;
32753448 num_pingreceived ++ ;
@@ -3515,6 +3688,7 @@ void add_addr(char *name, char *host, struct sockaddr *ipaddr, socklen_t ipaddr_
35153688 p -> saddr_len = ipaddr_len ;
35163689 p -> timeout = timeout ;
35173690 p -> min_reply = 0 ;
3691+ p -> trace_ttl = 1 ;
35183692
35193693 if (netdata_flag ) {
35203694 char * s = p -> name ;
@@ -3925,6 +4099,7 @@ void usage(int is_error)
39254099 fprintf (out , " except with -l/-c/-C, where it's the -p period up to 2000 ms)\n" );
39264100 fprintf (out , " --check-source discard replies not from target address\n" );
39274101 fprintf (out , " --icmp-timestamp use ICMP Timestamp instead of ICMP Echo\n" );
4102+ fprintf (out , " --traceroute Send traceroute\n" );
39284103 fprintf (out , "\n" );
39294104 fprintf (out , "Output options:\n" );
39304105 fprintf (out , " -a, --alive show targets that are alive\n" );
0 commit comments