Skip to content

Commit c450285

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

File tree

2 files changed

+216
-4
lines changed

2 files changed

+216
-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: 208 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;
@@ -486,6 +492,7 @@ void stats_add(HOST_ENTRY *h, int index, int success, int64_t latency);
486492
void update_current_time();
487493
void print_timestamp_format(int64_t current_time_ns, int timestamp_format);
488494
static 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

Comments
 (0)