Skip to content

Commit fd6800b

Browse files
committed
New option --traceroute to send a traceroute with fping
1 parent f689c31 commit fd6800b

File tree

2 files changed

+197
-4
lines changed

2 files changed

+197
-4
lines changed

doc/fping.pod

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,14 @@ the local receive time in the same format, in addition to normal output.
165165
Cannot be used together with B<-b> because ICMP timestamp messages have a fixed size.
166166
IPv4 only, requires root privileges or cap_net_raw.
167167

168+
=item B<--traceroute>
169+
170+
Sends a traceroute based on ICMP echo request. Root privileges are required.
171+
172+
Example usage:
173+
174+
$: fping --traceroute 127.0.0.1
175+
168176
=item B<-J>, B<--json>
169177

170178
Format output JSON (-c, -C, or -l required)

src/fping.c

Lines changed: 189 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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;
423427
int timestamp_format_flag = 0;
424428
int random_data_flag = 0;
425429
int cumulative_stats_flag = 0;
430+
int traceroute_flag = 0;
426431
int check_source_flag = 0;
427432
int icmp_request_typ = 0;
428433
int 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 = 255; /* Ensure enough space for up to 255 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 (traceroute_flag && 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,69 @@ 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) h->trace_ttl = (int)ttl;
3402+
3403+
stats_add(h, this_count, 1, this_reply);
3404+
struct event *timeout_event = host_get_timeout_event(h, this_count);
3405+
if (timeout_event) {
3406+
ev_remove(&event_queue_timeout, timeout_event);
3407+
}
3408+
return 1;
3409+
} else if (icp->icmp_type == ICMP_ECHOREPLY) {
3410+
printf("%s: hop %d reached DESTINATION %s (%s ms)\n", h->host, h->trace_ttl, ip_str, sprint_tm(this_reply));
3411+
h->trace_ttl = TRACEROUTE_DONE_TTL; /* Goal achieved: artificially increase TTL to stop loop in main_loop */
3412+
3413+
/* Update stats and exit function to prevent duplicate outputs “is alive” or “hop 100” */
3414+
stats_add(h, this_count, 1, this_reply);
3415+
struct event *timeout_event = host_get_timeout_event(h, this_count);
3416+
if (timeout_event) {
3417+
ev_remove(&event_queue_timeout, timeout_event);
3418+
}
3419+
return 1;
3420+
}
3421+
}
3422+
#ifdef IPV6
3423+
else if (traceroute_flag && response_addr.ss_family == AF_INET6) {
3424+
struct icmp6_hdr *icp = (struct icmp6_hdr *)buffer;
3425+
3426+
/* With IPv6, buffer is directly the ICMP header payload, since receive_packet uses recvmsg */
3427+
char ip_str[INET6_ADDRSTRLEN];
3428+
getnameinfo((struct sockaddr *)&response_addr, sizeof(response_addr), ip_str, sizeof(ip_str), NULL, 0, NI_NUMERICHOST);
3429+
3430+
if (icp->icmp6_type == ICMP6_TIME_EXCEEDED) {
3431+
printf("%s: hop %d reached %s (%s ms)\n", h->host, h->trace_ttl, ip_str, sprint_tm(this_reply));
3432+
h->trace_ttl++;
3433+
if (h->trace_ttl > (int)ttl) h->trace_ttl = (int)ttl;
3434+
3435+
stats_add(h, this_count, 1, this_reply);
3436+
struct event *timeout_event = host_get_timeout_event(h, this_count);
3437+
if (timeout_event) {
3438+
ev_remove(&event_queue_timeout, timeout_event);
3439+
}
3440+
return 1;
3441+
} else if (icp->icmp6_type == ICMP6_ECHO_REPLY) {
3442+
printf("%s: hop %d reached DESTINATION %s (%s ms)\n", h->host, h->trace_ttl, ip_str, sprint_tm(this_reply));
3443+
h->trace_ttl = TRACEROUTE_DONE_TTL; /* Goal achieved: artificially increase TTL to stop loop in main_loop */
3444+
3445+
/* Update stats and exit function to prevent duplicate outputs “is alive” or “hop 100” */
3446+
stats_add(h, this_count, 1, this_reply);
3447+
struct event *timeout_event = host_get_timeout_event(h, this_count);
3448+
if (timeout_event) {
3449+
ev_remove(&event_queue_timeout, timeout_event);
3450+
}
3451+
return 1;
3452+
}
3453+
}
3454+
#endif
3455+
32733456
/* update stats that include invalid replies */
32743457
h->num_recv_total++;
32753458
num_pingreceived++;
@@ -3515,6 +3698,7 @@ void add_addr(char *name, char *host, struct sockaddr *ipaddr, socklen_t ipaddr_
35153698
p->saddr_len = ipaddr_len;
35163699
p->timeout = timeout;
35173700
p->min_reply = 0;
3701+
p->trace_ttl = 1;
35183702

35193703
if (netdata_flag) {
35203704
char *s = p->name;
@@ -3925,6 +4109,7 @@ void usage(int is_error)
39254109
fprintf(out, " except with -l/-c/-C, where it's the -p period up to 2000 ms)\n");
39264110
fprintf(out, " --check-source discard replies not from target address\n");
39274111
fprintf(out, " --icmp-timestamp use ICMP Timestamp instead of ICMP Echo\n");
4112+
fprintf(out, " --traceroute Send traceroute\n");
39284113
fprintf(out, "\n");
39294114
fprintf(out, "Output options:\n");
39304115
fprintf(out, " -a, --alive show targets that are alive\n");

0 commit comments

Comments
 (0)