From c4b19821b85cfa5c98c0667b9b277400e081fd3f Mon Sep 17 00:00:00 2001 From: German Service Network Date: Sat, 14 Feb 2026 10:33:24 +0100 Subject: [PATCH 1/2] Option to show used IP source address in output --- CHANGELOG.md | 2 ++ ci/test-11-unpriv.pl | 8 +++++++- ci/test-16-json-output.pl | 9 ++++++++- doc/fping.pod | 6 ++++++ src/flags.c | 1 + src/flags.h | 1 + src/fping.c | 34 ++++++++++++++++++++++++++++++---- src/fping.h | 1 + src/output.c | 8 ++++++++ 9 files changed, 64 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9caef9a3..9a3963f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ Next ## New features +- New option --print-srcaddr to show used IP source address in output (#461, thanks @gsnw-sebast) + ## Bugfixes and other changes - ci: Removed travis-ci (#446, thanks @gsnw-sebast) diff --git a/ci/test-11-unpriv.pl b/ci/test-11-unpriv.pl index aea3e8f2..5bd13d54 100755 --- a/ci/test-11-unpriv.pl +++ b/ci/test-11-unpriv.pl @@ -40,7 +40,7 @@ sub get_ping_gid_range { } sub test_unprivileged_works { - plan tests => 18; + plan tests => 21; { my $cmd = Test::Command->new(cmd => "$fping_copy 127.0.0.1"); @@ -48,6 +48,12 @@ sub test_unprivileged_works { $cmd->stdout_is_eq("127.0.0.1 is alive\n"); $cmd->stderr_is_eq(""); } + { + my $cmd = Test::Command->new(cmd => "$fping_copy --print-srcaddr 127.0.0.1"); + $cmd->exit_is_num(0); + $cmd->stdout_like(qr{127\.0\.0\.1 is alive \(SRC (?:\d+\.\d+\.\d+\.\d+|unknown)\)\n}); + $cmd->stderr_is_eq(""); + } { my $cmd = Test::Command->new(cmd => "$fping_copy --print-tos 127.0.0.1"); $cmd->exit_is_num(0); diff --git a/ci/test-16-json-output.pl b/ci/test-16-json-output.pl index 02e9fc65..f4add6ac 100644 --- a/ci/test-16-json-output.pl +++ b/ci/test-16-json-output.pl @@ -1,6 +1,6 @@ #!/usr/bin/perl -w -use Test::Command tests => 63; +use Test::Command tests => 66; use Test::More; # fping -J -c 2 127.0.0.1 @@ -98,6 +98,13 @@ $cmd->stderr_is_eq(""); } +# fping -J -c 1 --print-srcaddr 127.0.0.1 +my $cmd = Test::Command->new(cmd => "fping -J -c 1 --print-srcaddr 127.0.0.1"); +$cmd->exit_is_num(0); +$cmd->stdout_like(qr/^\{"resp":\s\{"host":\s"127\.0\.0\.1",\s"seq":\s0,\s"size":\s\d+,\s"rtt":\s\d+\.\d+,\s"src":\s"\d+\.\d+\.\d+\.\d+"\}\} +\{"summary":\s\{"host":\s"127\.0\.0\.1",\s"xmt":\s\d+,\s"rcv":\s\d+,\s"loss":\s\d+,\s"rttMin":\s\d+\.\d+,\s"rttAvg":\s\d+\.\d+,\s"rttMax":\s\d+\.\d+\}\}\n?$/); +$cmd->stderr_is_eq(""); + # fping -J -c 1 -q 127.0.0.1 { my $cmd = Test::Command->new(cmd => "fping -J -c 1 -q 127.0.0.1"); diff --git a/doc/fping.pod b/doc/fping.pod index e868ac50..51b096b6 100644 --- a/doc/fping.pod +++ b/doc/fping.pod @@ -255,6 +255,12 @@ Print cumulative statistics upon exit. Set source address. +=item B<--print-srcaddr> + +Displays the used IP source address in the output. If B cannot read the source address, +"(SRC unknown)" is returned. +IPv6 is currently not supported. + =item B<--seqmap-timeout>=I Timeout for sequence number mappings in milliseconds. Sequence numbers can diff --git a/src/flags.c b/src/flags.c index 2c491e1f..a389f1ad 100644 --- a/src/flags.c +++ b/src/flags.c @@ -32,6 +32,7 @@ int opt_print_netdata_on = 0; int opt_print_json_on = 0; int opt_print_tos_on = 0; int opt_print_ttl_on = 0; +int opt_print_srcaddr_on = 0; int opt_per_recv_on = 0; int opt_report_all_rtts_on = 0; int opt_name_on = 0; diff --git a/src/flags.h b/src/flags.h index 9ab79225..d8e274d9 100644 --- a/src/flags.h +++ b/src/flags.h @@ -36,6 +36,7 @@ extern int opt_print_netdata_on; extern int opt_print_json_on; extern int opt_print_tos_on; extern int opt_print_ttl_on; +extern int opt_print_srcaddr_on; extern int opt_per_recv_on; extern int opt_report_all_rtts_on; extern int opt_name_on; diff --git a/src/fping.c b/src/fping.c index 7c875387..e5ab7f58 100644 --- a/src/fping.c +++ b/src/fping.c @@ -264,7 +264,14 @@ char *icmp6_param_prob_str[ICMP6_PARAM_PROB_MAXCODE + 1] = { #endif IP_HEADER_RESULT default_ip_header_result() { - return (IP_HEADER_RESULT){-1, -1, 0x80000000U, 0x80000000U, 0x80000000U}; + IP_HEADER_RESULT res; + res.tos = -1; + res.ttl = -1; + res.otime_ms = 0x80000000U; + res.rtime_ms = 0x80000000U; + res.ttime_ms = 0x80000000U; + res.src_addr[0] = '\0'; + return res; } int event_storage_count; @@ -533,6 +540,7 @@ int main(int argc, char **argv) { "check-source", 0, OPTPARSE_NONE }, { "print-tos", 0, OPTPARSE_NONE }, { "print-ttl", 0, OPTPARSE_NONE }, + { "print-srcaddr", 0, OPTPARSE_NONE }, { "seqmap-timeout", 0, OPTPARSE_REQUIRED }, #if defined(DEBUG) || defined(_DEBUG) { NULL, 'z', OPTPARSE_REQUIRED }, @@ -596,6 +604,8 @@ int main(int argc, char **argv) } } #endif + } else if (strstr(optparse_state.optlongname, "print-srcaddr") != NULL) { + opt_print_srcaddr_on = 1; } else if (strstr(optparse_state.optlongname, "seqmap-timeout") != NULL) { opt_seqmap_timeout = strtod_strict(optparse_state.optarg) * 1000000; } else { @@ -2216,9 +2226,10 @@ int decode_icmp_ipv4( { struct icmp *icp; int hlen = 0; + struct ip *ip = NULL; if (!using_sock_dgram4) { - struct ip *ip = (struct ip *)reply_buf; + ip = (struct ip *)reply_buf; ip_header_res->tos = ip->ip_tos; ip_header_res->ttl = ip->ip_ttl; @@ -2331,6 +2342,13 @@ int decode_icmp_ipv4( ip_header_res->ttime_ms = ntohl(icp->icmp_dun.id_ts.its_ttime); } + if (opt_print_srcaddr_on) { + if (ip == NULL || inet_ntop(AF_INET, &ip->ip_dst, ip_header_res->src_addr, sizeof(ip_header_res->src_addr)) == NULL) { + strncpy(ip_header_res->src_addr, "unknown", sizeof(ip_header_res->src_addr) - 1); + ip_header_res->src_addr[sizeof(ip_header_res->src_addr) - 1] = '\0'; + } + } + return hlen; } @@ -2341,7 +2359,8 @@ int decode_icmp_ipv6( char *reply_buf, size_t reply_buf_len, unsigned short *id, - unsigned short *seq) + unsigned short *seq, + IP_HEADER_RESULT *ip_header_res) { struct icmp6_hdr *icp; @@ -2450,6 +2469,11 @@ int decode_icmp_ipv6( *id = icp->icmp6_id; *seq = ntohs(icp->icmp6_seq); + if (opt_print_srcaddr_on) { + strncpy(ip_header_res->src_addr, "not supported", sizeof(ip_header_res->src_addr) - 1); + ip_header_res->src_addr[sizeof(ip_header_res->src_addr) - 1] = '\0'; + } + return 1; } #endif @@ -2518,7 +2542,8 @@ int wait_for_reply(int64_t wait_time) buffer, sizeof(buffer), &id, - &seq)) { + &seq, + &ip_header_res)) { return 1; } if (id != ident6) { @@ -3113,5 +3138,6 @@ void usage(int is_error) fprintf(out, " -X, --fast-reachable=N exits true immediately when N hosts are found\n"); fprintf(out, " --print-tos show received TOS value\n"); fprintf(out, " --print-ttl show IP TTL value\n"); + fprintf(out, " --print-srcaddr show used IP source address (IPv6 is currently not supported).\n"); exit(is_error); } diff --git a/src/fping.h b/src/fping.h index 845fcaae..c897a2bd 100644 --- a/src/fping.h +++ b/src/fping.h @@ -18,6 +18,7 @@ typedef struct ip_header_result { uint32_t otime_ms; uint32_t rtime_ms; uint32_t ttime_ms; + char src_addr[INET6_ADDRSTRLEN]; } IP_HEADER_RESULT; typedef struct host_entry { diff --git a/src/output.c b/src/output.c index b2640c82..2922df63 100644 --- a/src/output.c +++ b/src/output.c @@ -232,6 +232,10 @@ void print_recv_ext(IP_HEADER_RESULT *ip_header_res, int64_t recv_time, int64_t ms_since_midnight_utc(recv_time)); } + if (ip_header_res->src_addr[0]) { + printf(" (SRC %s)", ip_header_res->src_addr); + } + if(opt_print_tos_on) { if(ip_header_res->tos != -1) { printf(" (TOS %d)", ip_header_res->tos); @@ -278,6 +282,10 @@ void print_recv_ext_json(IP_HEADER_RESULT *ip_header_res, int64_t recv_time, int printf("\"localreceive\": %u}", ms_since_midnight_utc(recv_time)); } + if (ip_header_res->src_addr[0]) { + printf(", \"src\": \"%s\"", ip_header_res->src_addr); + } + if(opt_print_tos_on) { if(ip_header_res->tos != -1) { printf(", \"tos\": %d", ip_header_res->tos); From 32586c8d1fcb04df48d33eec1a89e91751c4452f Mon Sep 17 00:00:00 2001 From: German Service Network Date: Sat, 21 Feb 2026 12:25:02 +0100 Subject: [PATCH 2/2] Add IPv6 support to --print-srcaddr --- CHANGELOG.md | 1 + ci/test-11-unpriv.pl | 8 ++++++- ci/test-16-json-output.pl | 13 ++++++++++- doc/fping.pod | 1 - src/fping.c | 46 +++++++++++++++++++++++++++++++++------ 5 files changed, 59 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a3963f8..a18986f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Next ## New features - New option --print-srcaddr to show used IP source address in output (#461, thanks @gsnw-sebast) +- Add IPv6 support to --print-srcaddr (#462, thanks @gsnw-sebast) ## Bugfixes and other changes diff --git a/ci/test-11-unpriv.pl b/ci/test-11-unpriv.pl index 5bd13d54..2d901ee2 100755 --- a/ci/test-11-unpriv.pl +++ b/ci/test-11-unpriv.pl @@ -40,7 +40,7 @@ sub get_ping_gid_range { } sub test_unprivileged_works { - plan tests => 21; + plan tests => 24; { my $cmd = Test::Command->new(cmd => "$fping_copy 127.0.0.1"); @@ -54,6 +54,12 @@ sub test_unprivileged_works { $cmd->stdout_like(qr{127\.0\.0\.1 is alive \(SRC (?:\d+\.\d+\.\d+\.\d+|unknown)\)\n}); $cmd->stderr_is_eq(""); } + { + my $cmd = Test::Command->new(cmd => "$fping_copy --print-srcaddr ::1"); + $cmd->exit_is_num(0); + $cmd->stdout_like(qr{::1 is alive \(SRC (?:::1|unknown)\)\n}); + $cmd->stderr_is_eq(""); + } { my $cmd = Test::Command->new(cmd => "$fping_copy --print-tos 127.0.0.1"); $cmd->exit_is_num(0); diff --git a/ci/test-16-json-output.pl b/ci/test-16-json-output.pl index f4add6ac..062e5567 100644 --- a/ci/test-16-json-output.pl +++ b/ci/test-16-json-output.pl @@ -1,6 +1,6 @@ #!/usr/bin/perl -w -use Test::Command tests => 66; +use Test::Command tests => 69; use Test::More; # fping -J -c 2 127.0.0.1 @@ -99,11 +99,22 @@ } # fping -J -c 1 --print-srcaddr 127.0.0.1 +{ my $cmd = Test::Command->new(cmd => "fping -J -c 1 --print-srcaddr 127.0.0.1"); $cmd->exit_is_num(0); $cmd->stdout_like(qr/^\{"resp":\s\{"host":\s"127\.0\.0\.1",\s"seq":\s0,\s"size":\s\d+,\s"rtt":\s\d+\.\d+,\s"src":\s"\d+\.\d+\.\d+\.\d+"\}\} \{"summary":\s\{"host":\s"127\.0\.0\.1",\s"xmt":\s\d+,\s"rcv":\s\d+,\s"loss":\s\d+,\s"rttMin":\s\d+\.\d+,\s"rttAvg":\s\d+\.\d+,\s"rttMax":\s\d+\.\d+\}\}\n?$/); $cmd->stderr_is_eq(""); +} + +# fping -J -c 1 --print-srcaddr ::1 +{ +my $cmd = Test::Command->new(cmd => "fping -J -c 1 --print-srcaddr ::1"); +$cmd->exit_is_num(0); +$cmd->stdout_like(qr/^\{"resp":\s\{"host":\s"::1",\s"seq":\s0,\s"size":\s\d+,\s"rtt":\s\d+\.\d+,\s"src":\s"::1"\}\} +\{"summary":\s\{"host":\s"::1",\s"xmt":\s\d+,\s"rcv":\s\d+,\s"loss":\s\d+,\s"rttMin":\s\d+\.\d+,\s"rttAvg":\s\d+\.\d+,\s"rttMax":\s\d+\.\d+\}\}\n?$/); +$cmd->stderr_is_eq(""); +} # fping -J -c 1 -q 127.0.0.1 { diff --git a/doc/fping.pod b/doc/fping.pod index 51b096b6..ebdd006d 100644 --- a/doc/fping.pod +++ b/doc/fping.pod @@ -259,7 +259,6 @@ Set source address. Displays the used IP source address in the output. If B cannot read the source address, "(SRC unknown)" is returned. -IPv6 is currently not supported. =item B<--seqmap-timeout>=I diff --git a/src/fping.c b/src/fping.c index e5ab7f58..f7ca33cb 100644 --- a/src/fping.c +++ b/src/fping.c @@ -606,6 +606,13 @@ int main(int argc, char **argv) #endif } else if (strstr(optparse_state.optlongname, "print-srcaddr") != NULL) { opt_print_srcaddr_on = 1; +#if defined(IPV6) && defined(IPV6_RECVPKTINFO) + if (socket6 >= 0) { + if (setsockopt(socket6, IPPROTO_IPV6, IPV6_RECVPKTINFO, &sock_opt_on, sizeof(sock_opt_on))) { + perror("setsockopt IPV6_RECVPKTINFO"); + } + } +#endif } else if (strstr(optparse_state.optlongname, "seqmap-timeout") != NULL) { opt_seqmap_timeout = strtod_strict(optparse_state.optarg) * 1000000; } else { @@ -2055,7 +2062,13 @@ int receive_packet(int64_t wait_time, char *reply_buf, size_t reply_buf_len, int *ip_header_tos, - int *ip_header_ttl) + int *ip_header_ttl, +#ifdef IPV6 + struct in6_addr *recv_dst_addr_ipv6 +#else + void *recv_dst_addr_ipv6 +#endif + ) { struct timeval to; int s = 0; @@ -2137,6 +2150,11 @@ int receive_packet(int64_t wait_time, if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_HOPLIMIT) { memcpy(ip_header_ttl, CMSG_DATA(cmsg), sizeof(*ip_header_ttl)); } + if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) { + struct in6_pktinfo *pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg); + if (recv_dst_addr_ipv6) + memcpy(recv_dst_addr_ipv6, &pktinfo->ipi6_addr, sizeof(*recv_dst_addr_ipv6)); + } #endif } } @@ -2360,7 +2378,8 @@ int decode_icmp_ipv6( size_t reply_buf_len, unsigned short *id, unsigned short *seq, - IP_HEADER_RESULT *ip_header_res) + IP_HEADER_RESULT *ip_header_res, + struct in6_addr *local_addr) { struct icmp6_hdr *icp; @@ -2470,8 +2489,10 @@ int decode_icmp_ipv6( *seq = ntohs(icp->icmp6_seq); if (opt_print_srcaddr_on) { - strncpy(ip_header_res->src_addr, "not supported", sizeof(ip_header_res->src_addr) - 1); - ip_header_res->src_addr[sizeof(ip_header_res->src_addr) - 1] = '\0'; + if (local_addr == NULL || IN6_IS_ADDR_UNSPECIFIED(local_addr) || inet_ntop(AF_INET6, local_addr, ip_header_res->src_addr, sizeof(ip_header_res->src_addr)) == NULL) { + strncpy(ip_header_res->src_addr, "unknown", sizeof(ip_header_res->src_addr) - 1); + ip_header_res->src_addr[sizeof(ip_header_res->src_addr) - 1] = '\0'; + } } return 1; @@ -2493,6 +2514,11 @@ int wait_for_reply(int64_t wait_time) unsigned short seq; IP_HEADER_RESULT ip_header_res = default_ip_header_result(); +#ifdef IPV6 + struct in6_addr recv_dst_addr_ipv6; + memset(&recv_dst_addr_ipv6, 0, sizeof(recv_dst_addr_ipv6)); +#endif + /* Receive packet */ result = receive_packet(wait_time, /* max. wait time, in ns */ &recv_time, /* reply_timestamp */ @@ -2501,7 +2527,12 @@ int wait_for_reply(int64_t wait_time) buffer, /* reply_buf */ sizeof(buffer), /* reply_buf_len */ &ip_header_res.tos, /* TOS resp. TC byte */ - &ip_header_res.ttl /* TTL resp. hop limit */ + &ip_header_res.ttl, /* TTL resp. hop limit */ +#ifdef IPV6 + &recv_dst_addr_ipv6 +#else + NULL +#endif ); if (result <= 0) { @@ -2543,7 +2574,8 @@ int wait_for_reply(int64_t wait_time) sizeof(buffer), &id, &seq, - &ip_header_res)) { + &ip_header_res, + &recv_dst_addr_ipv6)) { return 1; } if (id != ident6) { @@ -3138,6 +3170,6 @@ void usage(int is_error) fprintf(out, " -X, --fast-reachable=N exits true immediately when N hosts are found\n"); fprintf(out, " --print-tos show received TOS value\n"); fprintf(out, " --print-ttl show IP TTL value\n"); - fprintf(out, " --print-srcaddr show used IP source address (IPv6 is currently not supported).\n"); + fprintf(out, " --print-srcaddr show used IP source address\n"); exit(is_error); }