diff --git a/CHANGELOG.md b/CHANGELOG.md index 127521c..d4e5108 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +Next +==== + +## New features + +- New option --icmp-timestamp send ICMP timestamp requests (ICMP type 13) + instead of ICMP Echo requests (#353, thanks @auerswal and @gsnw-sebast) + + fping 5.2 (2024-04-21) ====================== diff --git a/ci/test-04-options-a-b.pl b/ci/test-04-options-a-b.pl index 1e16ee3..90afa9b 100755 --- a/ci/test-04-options-a-b.pl +++ b/ci/test-04-options-a-b.pl @@ -1,6 +1,6 @@ #!/usr/bin/perl -w -use Test::Command tests => 41; +use Test::Command tests => 47; use Test::More; use Time::HiRes qw(gettimeofday tv_interval); @@ -84,6 +84,17 @@ $cmd->stderr_is_eq(""); } +# fping --icmp-timestamp +SKIP: { +if($^O eq 'darwin') { + skip 'On macOS, this test is unreliable', 3; +} +my $cmd = Test::Command->new(cmd => "fping --icmp-timestamp 127.0.0.1"); +$cmd->exit_is_num(0); +$cmd->stdout_like(qr{127\.0\.0\.1 is alive \(Timestamp Originate=\d+ Receive=\d+ Transmit=\d+\)}); +$cmd->stderr_is_eq(""); +} + # fping --print-ttl with IPv6 SKIP: { if($ENV{SKIP_IPV6}) { @@ -111,6 +122,14 @@ $cmd->stderr_is_eq(""); } +# fping --icmp-timestamp -b +{ +my $cmd = Test::Command->new(cmd => "fping --icmp-timestamp -b 1000 127.0.0.1"); +$cmd->exit_is_num(1); +$cmd->stdout_is_eq(""); +$cmd->stderr_like(qr{Usage:}); +} + # fping -B SKIP: { if($^O eq 'darwin') { diff --git a/ci/test-05-options-c-e.pl b/ci/test-05-options-c-e.pl index db5abd6..fc1d387 100755 --- a/ci/test-05-options-c-e.pl +++ b/ci/test-05-options-c-e.pl @@ -1,6 +1,6 @@ #!/usr/bin/perl -w -use Test::Command tests => 75; +use Test::Command tests => 78; use Test::More; # -c n count of pings to send to each target (default 1) @@ -77,6 +77,21 @@ ff02::1 : xmt/rcv/%loss = 1/1/0%, min/avg/max = \d\.\d+/\d\.\d+/\d\.\d+\n}); } +# fping --icmp-timestamp -c n 127.0.0.1 +SKIP: { +if($^O eq 'darwin') { + skip 'On macOS, this test is unreliable', 3; +} +my $cmd = Test::Command->new(cmd => "fping -4 --icmp-timestamp -c 2 127.0.0.1"); +$cmd->exit_is_num(0); +$cmd->stdout_like(qr{127\.0\.0\.1 : \[0\], 20 bytes, \d\.\d+ ms \(\d\.\d+ avg, 0% loss\), ICMP timestamp: Originate=\d+ Receive=\d+ Transmit=\d+ +127\.0\.0\.1 : \[1\], 20 bytes, \d\.\d+ ms \(\d\.\d+ avg, 0% loss\), ICMP timestamp: Originate=\d+ Receive=\d+ Transmit=\d+ +}); + +$cmd->stderr_like(qr{127\.0\.0\.1 : xmt/rcv/%loss = 2/2/0%, min/avg/max = \d\.\d+/\d\.\d+/\d\.\d+ +}); +} + # fping -C n { my $cmd = Test::Command->new(cmd => "fping -4 -C 2 -p 100 localhost 127.0.0.1"); diff --git a/doc/fping.pod b/doc/fping.pod index e20ed30..bc3a44d 100644 --- a/doc/fping.pod +++ b/doc/fping.pod @@ -155,6 +155,12 @@ to any target (default is 10, minimum is 1). Set the interface (requires SO_BINDTODEVICE support). +=item B<--icmp-timestamp> + +Send ICMP timestamp requests (ICMP type 13) instead of ICMP Echo requests. +Cannot be used together with B<-b> because ICMP timestamp messages have a fixed size. +IPv4 only, requires root privileges. + =item B<-k>, B<--fwmark>=I Set FWMARK on ping packets for policy-based routing. Requires Linux kernel diff --git a/src/fping.c b/src/fping.c index 87b5fb9..9209ac3 100644 --- a/src/fping.c +++ b/src/fping.c @@ -361,6 +361,7 @@ int timestamp_format_flag = 0; int random_data_flag = 0; int cumulative_stats_flag = 0; int check_source_flag = 0; +int icmp_request_typ = 0; int print_tos_flag = 0; int print_ttl_flag = 0; #if defined(DEBUG) || defined(_DEBUG) @@ -536,6 +537,7 @@ int main(int argc, char **argv) { "ttl", 'H', OPTPARSE_REQUIRED }, { "interval", 'i', OPTPARSE_REQUIRED }, { "iface", 'I', OPTPARSE_REQUIRED }, + { "icmp-timestamp", '0', OPTPARSE_NONE }, #ifdef SO_MARK { "fwmark", 'k', OPTPARSE_REQUIRED }, #endif @@ -584,6 +586,8 @@ int main(int argc, char **argv) } } else if (strstr(optparse_state.optlongname, "check-source") != NULL) { check_source_flag = 1; + } else if (strstr(optparse_state.optlongname, "icmp-timestamp") != NULL) { + icmp_request_typ = 13; } else if (strstr(optparse_state.optlongname, "print-tos") != NULL) { print_tos_flag = 1; } else if (strstr(optparse_state.optlongname, "print-ttl") != NULL) { @@ -687,6 +691,8 @@ int main(int argc, char **argv) case 'b': if (sscanf(optparse_state.optarg, "%u", &ping_data_size) != 1) usage(1); + if (icmp_request_typ > 0) + usage(1); break; @@ -1925,6 +1931,7 @@ int send_ping(HOST_ENTRY *h, int index) int n; int myseq; int ret = 1; + uint8_t proto = ICMP_ECHO; update_current_time(); h->last_send_time = current_time_ns; @@ -1933,7 +1940,9 @@ int send_ping(HOST_ENTRY *h, int index) dbg_printf("%s [%d]: send ping\n", h->host, index); if (h->saddr.ss_family == AF_INET && socket4 >= 0) { - n = socket_sendto_ping_ipv4(socket4, (struct sockaddr *)&h->saddr, h->saddr_len, myseq, ident4); + if(icmp_request_typ == 13) + proto = ICMP_TSTAMP; + n = socket_sendto_ping_ipv4(socket4, (struct sockaddr *)&h->saddr, h->saddr_len, myseq, ident4, proto); } #ifdef IPV6 else if (h->saddr.ss_family == AF_INET6 && socket6 >= 0) { @@ -2173,7 +2182,10 @@ int decode_icmp_ipv4( unsigned short *id, unsigned short *seq, int *ip_header_tos, - int *ip_header_ttl) + int *ip_header_ttl, + uint32_t *ip_header_otime_ms, + uint32_t *ip_header_rtime_ms, + uint32_t *ip_header_ttime_ms) { struct icmp *icp; int hlen = 0; @@ -2206,7 +2218,7 @@ int decode_icmp_ipv4( icp = (struct icmp *)(reply_buf + hlen); - if (icp->icmp_type != ICMP_ECHOREPLY) { + if (icp->icmp_type != ICMP_ECHOREPLY && icp->icmp_type != ICMP_TSTAMPREPLY) { /* Handle other ICMP packets */ struct icmp *sent_icmp; SEQMAP_VALUE *seqmap_value; @@ -2221,7 +2233,7 @@ int decode_icmp_ipv4( sent_icmp = (struct icmp *)(reply_buf + hlen + ICMP_MINLEN + sizeof(struct ip)); - if (sent_icmp->icmp_type != ICMP_ECHO || sent_icmp->icmp_id != ident4) { + if ((sent_icmp->icmp_type != ICMP_ECHO && sent_icmp->icmp_type != ICMP_TSTAMP) || sent_icmp->icmp_id != ident4) { /* not caused by us */ return -1; } @@ -2272,6 +2284,22 @@ int decode_icmp_ipv4( *id = icp->icmp_id; *seq = ntohs(icp->icmp_seq); + if(icp->icmp_type == ICMP_TSTAMPREPLY) { + + /* Check that reply_buf_len is sufficiently big to contain the timestamps */ + if (reply_buf_len < hlen + ICMP_MINLEN + 3 * sizeof(uint32_t)) { + if (verbose_flag) { + char buf[INET6_ADDRSTRLEN]; + getnameinfo(response_addr, response_addr_len, buf, INET6_ADDRSTRLEN, NULL, 0, NI_NUMERICHOST); + printf("received packet too short for ICMP Timestamp Reply (%d bytes from %s)\n", (int)reply_buf_len, buf); + } + return -1; + } + + *ip_header_otime_ms = ntohl(icp->icmp_dun.id_ts.its_otime); + *ip_header_rtime_ms = ntohl(icp->icmp_dun.id_ts.its_rtime); + *ip_header_ttime_ms = ntohl(icp->icmp_dun.id_ts.its_ttime); + } return hlen; } @@ -2384,6 +2412,10 @@ int wait_for_reply(int64_t wait_time) unsigned short seq; int ip_header_tos = -1; int ip_header_ttl = -1; + // ICMP Timestamp + uint32_t ip_header_otime_ms; + uint32_t ip_header_rtime_ms; + uint32_t ip_header_ttime_ms; /* Receive packet */ result = receive_packet(wait_time, /* max. wait time, in ns */ @@ -2412,7 +2444,10 @@ int wait_for_reply(int64_t wait_time) &id, &seq, &ip_header_tos, - &ip_header_ttl); + &ip_header_ttl, + &ip_header_otime_ms, + &ip_header_rtime_ms, + &ip_header_ttime_ms); if (ip_hlen < 0) { return 1; } @@ -2539,6 +2574,10 @@ int wait_for_reply(int64_t wait_time) } } + if (icmp_request_typ == 13) { + printf(" (Timestamp Originate=%u Receive=%u Transmit=%u)", ip_header_otime_ms, ip_header_rtime_ms, ip_header_ttime_ms); + } + if (elapsed_flag) printf(" (%s ms)", sprint_tm(this_reply)); @@ -2577,6 +2616,10 @@ int wait_for_reply(int64_t wait_time) fprintf(stderr, " [<- %s]", buf); } + if (icmp_request_typ == 13) { + printf(", ICMP timestamp: Originate=%u Receive=%u Transmit=%u", ip_header_otime_ms, ip_header_rtime_ms, ip_header_ttime_ms); + } + printf("\n"); } @@ -3088,6 +3131,7 @@ void usage(int is_error) fprintf(out, " -t, --timeout=MSEC individual target initial timeout (default: %.0f ms,\n", timeout / 1e6); 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, "\n"); fprintf(out, "Output options:\n"); fprintf(out, " -a, --alive show targets that are alive\n"); diff --git a/src/fping.h b/src/fping.h index c0acdfb..f50710b 100644 --- a/src/fping.h +++ b/src/fping.h @@ -28,7 +28,7 @@ extern int random_data_flag; 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); +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); #ifdef IPV6 int open_ping_socket_ipv6(int *socktype); void init_ping_buffer_ipv6(size_t ping_data_size); diff --git a/src/socket4.c b/src/socket4.c index d7c4ae3..c408c6d 100644 --- a/src/socket4.c +++ b/src/socket4.c @@ -43,6 +43,7 @@ #include #include #include +#include char* ping_buffer_ipv4 = 0; size_t ping_pkt_size_ipv4; @@ -129,14 +130,23 @@ 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) +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) { struct icmp* icp; + struct timespec tsorig; + long tsorig_ms; int n; icp = (struct icmp*)ping_buffer_ipv4; - icp->icmp_type = ICMP_ECHO; + icp->icmp_type = icmp_proto; + if(icmp_proto == ICMP_TSTAMP) { + clock_gettime(CLOCK_REALTIME, &tsorig); + tsorig_ms = (tsorig.tv_sec % (24*60*60)) * 1000 + tsorig.tv_nsec / 1000000; + icp->icmp_otime = htonl(tsorig_ms); + icp->icmp_rtime = 0; + icp->icmp_ttime = 0; + } icp->icmp_code = 0; icp->icmp_cksum = 0; icp->icmp_seq = htons(icmp_seq_nr);