Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions doc/fping.pod
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,14 @@ the local receive time in the same format, in addition to normal output.
Cannot be used together with B<-b> because ICMP timestamp messages have a fixed size.
IPv4 only, requires root privileges or cap_net_raw.

=item B<--traceroute>

Sends a traceroute based on ICMP echo request. Root privileges are required.

Example usage:

$: fping --traceroute 127.0.0.1

=item B<-J>, B<--json>

Format output JSON (-c, -C, or -l required)
Expand Down
145 changes: 141 additions & 4 deletions src/fping.c
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,10 @@ extern int h_errno;
#define RESP_ERROR -3
#define RESP_TIMEOUT -4

/* Traceroute */
#define TRACEROUTE_DEFAULT_MAX_TTL 30
#define TRACEROUTE_DONE_TTL 100

/* debugging flags */
#if defined(DEBUG) || defined(_DEBUG)
#define DBG_TRACE 1
Expand Down Expand Up @@ -355,7 +359,8 @@ int opt_verbose_on = 0,
opt_quiet_on = 0,
opt_elapsed_on = 0,
opt_stats_on = 0,
opt_cumulative_stats_on = 0;
opt_cumulative_stats_on = 0,
opt_traceroute_on = 0;
int opt_generate_on = 0, /* flag for IP list generation */
opt_count_on = 0,
opt_loop_on;
Expand Down Expand Up @@ -545,6 +550,7 @@ int main(int argc, char **argv)
{ "rdns", 'd', OPTPARSE_NONE },
{ "timestamp", 'D', OPTPARSE_NONE },
{ "timestamp-format", 0, OPTPARSE_REQUIRED },
{ "traceroute", 0, OPTPARSE_NONE },
{ "elapsed", 'e', OPTPARSE_NONE },
{ "file", 'f', OPTPARSE_REQUIRED },
{ "generate", 'g', OPTPARSE_NONE },
Expand Down Expand Up @@ -601,6 +607,8 @@ int main(int argc, char **argv)
}else{
usage(1);
}
} else if (strcmp(optparse_state.optlongname, "traceroute") == 0) {
opt_traceroute_on = 1;
} else if (strstr(optparse_state.optlongname, "check-source") != NULL) {
opt_check_source_on = 1;
} else if (strstr(optparse_state.optlongname, "icmp-timestamp") != NULL) {
Expand Down Expand Up @@ -1308,6 +1316,16 @@ int main(int argc, char **argv)
exit(num_noaddress ? 2 : 1);
}

/*
if(opt_traceroute_on) {
// Preventing race conditions: Only one host allowed
if (num_hosts > 1) {
fprintf(stderr, "%s: traceroute mode only supports one target at a time\n", prog);
exit(1);
}
}
*/

if (socket4 >= 0 && (src_addr_set || socktype4 == SOCK_DGRAM)) {
socket_set_src_addr_ipv4(socket4, &src_addr, (socktype4 == SOCK_DGRAM) ? &ident4 : NULL);
}
Expand Down Expand Up @@ -1730,6 +1748,28 @@ void main_loop()

dbg_printf("%s [%d]: ping event\n", h->host, event->ping_index);

/* Traceroute */
if (opt_traceroute_on) {
if (h->trace_ttl == TRACEROUTE_DONE_TTL) {
continue;
}

int ttl_set = h->trace_ttl;
if (ttl_set > (int)opt_ttl) ttl_set = (int)opt_ttl;
if (socket4 >= 0) {
if (setsockopt(socket4, IPPROTO_IP, IP_TTL, &ttl_set, sizeof(ttl_set)))
perror("setsockopt IP_TTL");
Comment on lines +1760 to +1761
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Failing to set the IP_TTL socket option is a fatal error for traceroute mode, as it won't be able to control the TTL of outgoing packets. The program should exit with an error instead of just printing to perror and continuing, which would lead to incorrect traceroute behavior.

                    if (setsockopt(socket4, IPPROTO_IP, IP_TTL, &ttl_set, sizeof(ttl_set)))
                        errno_crash_and_burn("setsockopt IP_TTL");

}
#ifdef IPV6
if (socket6 >= 0) {
/* Set hop limit for IPv6 */
if (setsockopt(socket6, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl_set, sizeof(ttl_set))) {
perror("setsockopt IPV6_UNICAST_HOPS");
}
Comment on lines +1766 to +1768
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Failing to set the IPV6_UNICAST_HOPS socket option is a fatal error for traceroute mode, as it won't be able to control the hop limit of outgoing packets. The program should exit with an error instead of just printing to perror and continuing, which would lead to incorrect traceroute behavior.

                    if (setsockopt(socket6, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl_set, sizeof(ttl_set))) {
                        errno_crash_and_burn("setsockopt IPV6_UNICAST_HOPS");
                    }

}
#endif
}

/* Send the ping */
send_ping(h, event->ping_index);

Expand Down Expand Up @@ -1978,6 +2018,14 @@ int send_ping(HOST_ENTRY *h, int index)
int ret = 1;
uint8_t proto = ICMP_ECHO;

// TTL
int send_ttl = 0;
if (opt_traceroute_on) {
send_ttl = h->trace_ttl;
} else if (opt_ttl > 0) {
send_ttl = opt_ttl;
}

update_current_time();
h->last_send_time = current_time_ns;
myseq = seqmap_add(h->i, index, current_time_ns);
Expand All @@ -1987,11 +2035,11 @@ int send_ping(HOST_ENTRY *h, int index)
if (h->saddr.ss_family == AF_INET && socket4 >= 0) {
if(opt_icmp_request_typ == 13)
proto = ICMP_TSTAMP;
n = socket_sendto_ping_ipv4(socket4, (struct sockaddr *)&h->saddr, h->saddr_len, myseq, ident4, proto);
n = socket_sendto_ping_ipv4(socket4, (struct sockaddr *)&h->saddr, h->saddr_len, myseq, ident4, proto, send_ttl);
}
#ifdef IPV6
else if (h->saddr.ss_family == AF_INET6 && socket6 >= 0) {
n = socket_sendto_ping_ipv6(socket6, (struct sockaddr *)&h->saddr, h->saddr_len, myseq, ident6);
n = socket_sendto_ping_ipv6(socket6, (struct sockaddr *)&h->saddr, h->saddr_len, myseq, ident6, send_ttl);
}
#endif
else {
Expand Down Expand Up @@ -2403,6 +2451,27 @@ int decode_icmp_ipv6(

icp = (struct icmp6_hdr *)reply_buf;

/* Traceroute Logic for IPv6 Time Exceeded */
if (opt_traceroute_on && icp->icmp6_type == ICMP6_TIME_EXCEEDED) {
struct ip6_hdr *inner_ip6;
struct icmp6_hdr *inner_icmp6;

if (reply_buf_len >= sizeof(struct icmp6_hdr) + sizeof(struct ip6_hdr) + sizeof(struct icmp6_hdr)) {
inner_ip6 = (struct ip6_hdr *)(reply_buf + sizeof(struct icmp6_hdr));

/* Check whether the inner packet is ICMPv6 */
if (inner_ip6->ip6_nxt == IPPROTO_ICMPV6) {
inner_icmp6 = (struct icmp6_hdr *)(reply_buf + sizeof(struct icmp6_hdr) + sizeof(struct ip6_hdr));

if (inner_icmp6->icmp6_id == ident6) {
*id = inner_icmp6->icmp6_id;
*seq = ntohs(inner_icmp6->icmp6_seq);
return 1;
}
}
}
}

if (icp->icmp6_type != ICMP6_ECHO_REPLY) {
/* Handle other ICMPv6 packets */
struct ip6_hdr *sent_ipv6;
Expand Down Expand Up @@ -2507,6 +2576,7 @@ int wait_for_reply(int64_t wait_time)
static char buffer[RECV_BUFSIZE];
struct sockaddr_storage response_addr;
int n, avg;
int ip_hlen = 0;
HOST_ENTRY *h;
int64_t this_reply;
int this_count;
Expand Down Expand Up @@ -2537,7 +2607,7 @@ int wait_for_reply(int64_t wait_time)

/* Process ICMP packet and retrieve id/seq */
if (response_addr.ss_family == AF_INET) {
int ip_hlen = decode_icmp_ipv4(
ip_hlen = decode_icmp_ipv4(
(struct sockaddr *)&response_addr,
sizeof(response_addr),
buffer,
Expand Down Expand Up @@ -2622,6 +2692,35 @@ int wait_for_reply(int64_t wait_time)
return 1;
}

if (opt_traceroute_on && response_addr.ss_family == AF_INET) {
struct icmp *icp = (struct icmp *)(buffer + ip_hlen);
char ip_str[INET_ADDRSTRLEN];
getnameinfo((struct sockaddr *)&response_addr, sizeof(response_addr), ip_str, sizeof(ip_str), NULL, 0, NI_NUMERICHOST);

if (icp->icmp_type == ICMP_TIMXCEED) {
handle_traceroute_hop(h, ip_str, 0, this_reply);
} else { /* ICMP_ECHOREPLY */
handle_traceroute_hop(h, ip_str, 1, this_reply);
}
}
#ifdef IPV6
else if (opt_traceroute_on && response_addr.ss_family == AF_INET6) {
struct icmp6_hdr *icp = (struct icmp6_hdr *)buffer;

if (icp->icmp6_type == ICMP6_TIME_EXCEEDED || icp->icmp6_type == ICMP6_ECHO_REPLY) {
/* With IPv6, buffer is directly the ICMP header payload, since receive_packet uses recvmsg */
char ip_str[INET6_ADDRSTRLEN];
getnameinfo((struct sockaddr *)&response_addr, sizeof(response_addr), ip_str, sizeof(ip_str), NULL, 0, NI_NUMERICHOST);

if (icp->icmp6_type == ICMP6_TIME_EXCEEDED) {
handle_traceroute_hop(h, ip_str, 0, this_reply);
} else { /* ICMP6_ECHO_REPLY */
handle_traceroute_hop(h, ip_str, 1, this_reply);
}
}
}
#endif

/* update stats */
stats_add(h, this_count, 1, this_reply);
// TODO: move to stats_add?
Expand All @@ -2641,6 +2740,10 @@ int wait_for_reply(int64_t wait_time)
ev_remove(&event_queue_timeout, timeout_event);
}

if (opt_traceroute_on) {
return 1;
}

/* print "is alive" */
if (h->num_recv == 1) {
num_alive++;
Expand Down Expand Up @@ -2833,6 +2936,7 @@ void add_addr(char *name, char *host, struct sockaddr *ipaddr, socklen_t ipaddr_
p->saddr_len = ipaddr_len;
p->timeout = opt_timeout;
p->min_reply = 0;
p->trace_ttl = 1;

if (opt_print_netdata_on) {
char *s = p->name;
Expand Down Expand Up @@ -3085,7 +3189,39 @@ void ev_remove(struct event_queue *queue, struct event *event)
event->ev_next = NULL;
}

/************************************************************

Function: handle_traceroute_hop

*************************************************************

Input:
h: host entry
ip_str: textual IP of reply source
reached_destination: non-zero if destination was reached
this_reply: reply latency in ns

Desciption:

A small helper to centralize printing and TTL handling for
traceroute replies (both IPv4 and IPv6). This reduces code
duplication and keeps behaviour consistent.

*************************************************************/

void handle_traceroute_hop(HOST_ENTRY *h, const char *ip_str, int reached_destination, int64_t this_reply)
{
if (reached_destination) {
printf("%s: hop %d reached DESTINATION %s (%s ms)\n", h->host, h->trace_ttl, ip_str, sprint_tm(this_reply));
h->trace_ttl = TRACEROUTE_DONE_TTL; /* Goal achieved: artificially increase TTL to stop loop in main_loop */
} else {
printf("%s: hop %d reached %s (%s ms)\n", h->host, h->trace_ttl, ip_str, sprint_tm(this_reply));
h->trace_ttl++;
if (h->trace_ttl > (int)opt_ttl) {
h->trace_ttl = (int)opt_ttl;
}
}
}

/************************************************************

Expand Down Expand Up @@ -3137,6 +3273,7 @@ void usage(int is_error)
fprintf(out, " except with -l/-c/-C, where it's the -p period up to 2000 ms)\n");
fprintf(out, " --check-source discard replies not from target address\n");
fprintf(out, " --icmp-timestamp use ICMP Timestamp instead of ICMP Echo\n");
fprintf(out, " --traceroute Sends a traceroute based on ICMP echo request (Root privileges are required)\n");
fprintf(out, "\n");
fprintf(out, "Output options:\n");
fprintf(out, " -a, --alive show targets that are alive\n");
Expand Down
6 changes: 4 additions & 2 deletions src/fping.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ typedef struct host_entry {
int64_t min_reply_i; /* shortest response time */
int64_t total_time_i; /* sum of response times */
int64_t *resp_times; /* individual response times */
int trace_ttl; /* current traceroute ttl */

/* to avoid allocating two struct events each time that we send a ping, we
* preallocate here two struct events for each ping that we might send for
Expand Down Expand Up @@ -143,17 +144,18 @@ void print_timestamp_format(int64_t current_time_ns, int timestamp_format);
void crash_and_burn( char *message );
void errno_crash_and_burn( char *message );
int in_cksum( unsigned short *p, int n );
void handle_traceroute_hop(HOST_ENTRY *h, const char *ip_str, int reached_destination, int64_t this_reply);

/* socket.c */
int open_ping_socket_ipv4(int *socktype);
void init_ping_buffer_ipv4(size_t ping_data_size);
void socket_set_src_addr_ipv4(int s, struct in_addr *src_addr, int *ident);
int socket_sendto_ping_ipv4(int s, struct sockaddr *saddr, socklen_t saddr_len, uint16_t icmp_seq, uint16_t icmp_id, uint8_t icmp_proto);
int socket_sendto_ping_ipv4(int s, struct sockaddr* saddr, socklen_t saddr_len, uint16_t icmp_seq_nr, uint16_t icmp_id_nr, uint8_t icmp_proto, int ttl);
#ifdef IPV6
int open_ping_socket_ipv6(int *socktype);
void init_ping_buffer_ipv6(size_t ping_data_size);
void socket_set_src_addr_ipv6(int s, struct in6_addr *src_addr, int *ident);
int socket_sendto_ping_ipv6(int s, struct sockaddr *saddr, socklen_t saddr_len, uint16_t icmp_seq, uint16_t icmp_id);
int socket_sendto_ping_ipv6(int s, struct sockaddr* saddr, socklen_t saddr_len, uint16_t icmp_seq_nr, uint16_t icmp_id_nr, int ttl);
#endif

#endif
31 changes: 29 additions & 2 deletions src/socket4.c
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,19 @@ unsigned short calcsum(unsigned short* buffer, int length)
return ~sum;
}

int socket_sendto_ping_ipv4(int s, struct sockaddr* saddr, socklen_t saddr_len, uint16_t icmp_seq_nr, uint16_t icmp_id_nr, uint8_t icmp_proto)
int socket_sendto_ping_ipv4(int s, struct sockaddr* saddr, socklen_t saddr_len, uint16_t icmp_seq_nr, uint16_t icmp_id_nr, uint8_t icmp_proto, int ttl)
{
struct icmp* icp;
struct timespec tsorig;
long tsorig_ms;
int n;

/* Variables for sendmsg */
struct msghdr msg = {0};
struct iovec iov[1];
char cmsgbuf[CMSG_SPACE(sizeof(int))];
struct cmsghdr *cmsg;

icp = (struct icmp*)ping_buffer_ipv4;

icp->icmp_type = icmp_proto;
Expand All @@ -160,7 +166,28 @@ int socket_sendto_ping_ipv4(int s, struct sockaddr* saddr, socklen_t saddr_len,

icp->icmp_cksum = calcsum((unsigned short*)icp, ping_pkt_size_ipv4);

n = sendto(s, icp, ping_pkt_size_ipv4, 0, saddr, saddr_len);
/* Prepare msghdr for sendmsg */
iov[0].iov_base = icp;
iov[0].iov_len = ping_pkt_size_ipv4;

msg.msg_name = saddr;
msg.msg_namelen = saddr_len;
msg.msg_iov = iov;
msg.msg_iovlen = 1;

/* Handle TTL via Ancillary Data (CMSG) if ttl is set */
if (ttl > 0) {
msg.msg_control = cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf);

cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = IPPROTO_IP;
cmsg->cmsg_type = IP_TTL;
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
memcpy(CMSG_DATA(cmsg), &ttl, sizeof(int));
}

n = sendmsg(s, &msg, 0);

return n;
}
Loading