Skip to content

Commit 3d057ad

Browse files
committed
New option --traceroute to send a traceroute with fping
1 parent 7dcd667 commit 3d057ad

File tree

2 files changed

+219
-4
lines changed

2 files changed

+219
-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: 211 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 (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

Comments
 (0)