Skip to content

Commit c013498

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

File tree

2 files changed

+191
-4
lines changed

2 files changed

+191
-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 and only one host is allowed.
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: 183 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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;
423428
int timestamp_format_flag = 0;
424429
int random_data_flag = 0;
425430
int cumulative_stats_flag = 0;
431+
int traceroute_flag = 0;
426432
int check_source_flag = 0;
427433
int icmp_request_typ = 0;
428434
int 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

Comments
 (0)