From: Erik Auerswald Date: Sun, 9 Mar 2025 19:28:42 +0000 (+0100) Subject: print TOS & TTL also for IPv6 and w/o privileges X-Git-Url: https://git.gsnw.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fb6f8b5613f1bcf2a98680ef7f27f6fe8459ab1d;p=fping.git print TOS & TTL also for IPv6 and w/o privileges When using IPv4 raw sockets, fping has access to the received IP packet data including the IP header. This allows to simply read the TOS byte and/or TTL value from the IP header. But, using IPv4 raw sockets requires privileges. Additionally, IPv6 raw sockets do not provide access to the IPv6 header. For IPv6, or when using IPv4 datagram sockets, i.e., when fping runs without privileges, a different method needs to be used to access TOS / TC and TTL / Hop Count of received packets. The so called "control messages", also called "ancillary data", can be used for this. This requires several actions: * set the respective socket options to request additional data, * ensure the buffer used to receive additional data is sized large enough, * read and process the requested additional data. --- diff --git a/CHANGELOG.md b/CHANGELOG.md index a9e163a..ca19552 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,9 @@ Next - Typo fix in error message when SO\_BINDTODEVICE fails +- Options --print-tos and --print-ttl now also work for IPv6, and no + longer require privileges (#384, thanks @auerswal) + fping 5.3 (2025-01-02) ====================== diff --git a/ci/test-04-options-a-b.pl b/ci/test-04-options-a-b.pl index ea7eb89..adbe521 100755 --- a/ci/test-04-options-a-b.pl +++ b/ci/test-04-options-a-b.pl @@ -135,7 +135,7 @@ SKIP: { } my $cmd = Test::Command->new(cmd => "fping --print-ttl ::1"); $cmd->exit_is_num(0); - $cmd->stdout_like(qr{::1 is alive \(TTL unknown\)\n}); + $cmd->stdout_like(qr{::1 is alive \(TTL \d+\)\n}); $cmd->stderr_is_eq(""); } diff --git a/ci/test-08-options-n-q.pl b/ci/test-08-options-n-q.pl index d614db1..f145e5d 100755 --- a/ci/test-08-options-n-q.pl +++ b/ci/test-08-options-n-q.pl @@ -83,7 +83,7 @@ SKIP: { } my $cmd = Test::Command->new(cmd => "fping --print-tos ::1"); $cmd->exit_is_num(0); - $cmd->stdout_like(qr{::1 is alive \(TOS unknown\)\n}); + $cmd->stdout_like(qr{::1 is alive \(TOS \d+\)\n}); $cmd->stderr_is_eq(""); } diff --git a/ci/test-11-unpriv.pl b/ci/test-11-unpriv.pl index 6ec0768..aea3e8f 100755 --- a/ci/test-11-unpriv.pl +++ b/ci/test-11-unpriv.pl @@ -51,13 +51,13 @@ sub test_unprivileged_works { { my $cmd = Test::Command->new(cmd => "$fping_copy --print-tos 127.0.0.1"); $cmd->exit_is_num(0); - $cmd->stdout_is_eq("127.0.0.1 is alive (TOS unknown)\n"); + $cmd->stdout_like(qr{127\.0\.0\.1 is alive \(TOS \d+\)\n}); $cmd->stderr_is_eq(""); } { my $cmd = Test::Command->new(cmd => "$fping_copy --print-ttl 127.0.0.1"); $cmd->exit_is_num(0); - $cmd->stdout_is_eq("127.0.0.1 is alive (TTL unknown)\n"); + $cmd->stdout_like(qr{127\.0\.0\.1 is alive \(TTL \d+\)\n}); $cmd->stderr_is_eq(""); } SKIP: { diff --git a/doc/fping.pod b/doc/fping.pod index 52c01c9..d10014f 100644 --- a/doc/fping.pod +++ b/doc/fping.pod @@ -146,7 +146,6 @@ Set the IP TTL field (time to live hops). Displays the IPv4 TTL value from the IP Header in the output. If B cannot read the TTL value, "(TTL unknown)" is returned. -IPv4 only, requires root privileges or cap_net_raw. =item B<-i>, B<--interval>=I @@ -206,7 +205,6 @@ Set the typ of service flag (TOS). I can be either decimal or hexadecimal Displays the TOS value in the output. If B cannot read the TOS value, "(TOS unknown)" is returned. -IPv4 only, requires root privileges or cap_net_raw. =item B<-p>, B<--period>=I diff --git a/src/fping.c b/src/fping.c index c71a4e8..86da559 100644 --- a/src/fping.c +++ b/src/fping.c @@ -297,6 +297,7 @@ struct event_queue event_queue_timeout; char *prog; int ident4 = 0; /* our icmp identity field */ int ident6 = 0; +const int sock_opt_on = 1; /* to activate a socket option */ int socket4 = -1; int socktype4 = -1; int using_sock_dgram4 = 0; @@ -611,8 +612,32 @@ int main(int argc, char **argv) ping_data_size = ICMP_TIMESTAMP_DATA_SIZE; } else if (strstr(optparse_state.optlongname, "print-tos") != NULL) { print_tos_flag = 1; + if (socket4 >= 0 && (socktype4 == SOCK_DGRAM)) { + if (setsockopt(socket4, IPPROTO_IP, IP_RECVTOS, &sock_opt_on, sizeof(sock_opt_on))) { + perror("setsockopt IP_RECVTOS"); + } + } +#if defined(IPV6) && defined(IPV6_RECVTCLASS) + if (socket6 >= 0) { + if (setsockopt(socket6, IPPROTO_IPV6, IPV6_RECVTCLASS, &sock_opt_on, sizeof(sock_opt_on))) { + perror("setsockopt IPV6_RECVTCLASS"); + } + } +#endif } else if (strstr(optparse_state.optlongname, "print-ttl") != NULL) { print_ttl_flag = 1; + if (socket4 >= 0 && (socktype4 == SOCK_DGRAM)) { + if (setsockopt(socket4, IPPROTO_IP, IP_RECVTTL, &sock_opt_on, sizeof(sock_opt_on))) { + perror("setsockopt IP_RECVTTL"); + } + } +#if defined(IPV6) && defined(IPV6_RECVHOPLIMIT) + if (socket6 >= 0) { + if (setsockopt(socket6, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &sock_opt_on, sizeof(sock_opt_on))) { + perror("setsockopt IPV6_RECVHOPLIMIT"); + } + } +#endif } else { usage(1); } @@ -2297,12 +2322,14 @@ int receive_packet(int64_t wait_time, struct sockaddr *reply_src_addr, size_t reply_src_addr_len, char *reply_buf, - size_t reply_buf_len) + size_t reply_buf_len, + int *ip_header_tos, + int *ip_header_ttl) { struct timeval to; int s = 0; int recv_len; - static unsigned char msg_control[40]; + static unsigned char msg_control[128]; struct iovec msg_iov = { reply_buf, reply_buf_len @@ -2314,9 +2341,7 @@ int receive_packet(int64_t wait_time, recv_msghdr.msg_iovlen = 1; recv_msghdr.msg_control = &msg_control; recv_msghdr.msg_controllen = sizeof(msg_control); -#if HAVE_SO_TIMESTAMPNS struct cmsghdr *cmsg; -#endif /* Wait for a socket to become ready */ if (wait_time) { @@ -2337,14 +2362,16 @@ int receive_packet(int64_t wait_time, return 0; } -#if HAVE_SO_TIMESTAMPNS /* ancilliary data */ { +#if HAVE_SO_TIMESTAMPNS struct timespec reply_timestamp_ts; struct timeval reply_timestamp_tv; +#endif for (cmsg = CMSG_FIRSTHDR(&recv_msghdr); cmsg != NULL; cmsg = CMSG_NXTHDR(&recv_msghdr, cmsg)) { +#if HAVE_SO_TIMESTAMPNS if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPNS) { memcpy(&reply_timestamp_ts, CMSG_DATA(cmsg), sizeof(reply_timestamp_ts)); *reply_timestamp = timespec_ns(&reply_timestamp_ts); @@ -2353,9 +2380,23 @@ int receive_packet(int64_t wait_time, memcpy(&reply_timestamp_tv, CMSG_DATA(cmsg), sizeof(reply_timestamp_tv)); *reply_timestamp = timeval_ns(&reply_timestamp_tv); } +#endif + if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_TOS) { + memcpy(ip_header_tos, CMSG_DATA(cmsg), sizeof(*ip_header_tos)); + } + if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_TTL) { + memcpy(ip_header_ttl, CMSG_DATA(cmsg), sizeof(*ip_header_ttl)); + } +#ifdef IPV6 + if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_TCLASS) { + memcpy(ip_header_tos, CMSG_DATA(cmsg), sizeof(*ip_header_tos)); + } + if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_HOPLIMIT) { + memcpy(ip_header_ttl, CMSG_DATA(cmsg), sizeof(*ip_header_ttl)); + } +#endif } } -#endif #if defined(DEBUG) || defined(_DEBUG) if (randomly_lose_flag) { @@ -2683,7 +2724,9 @@ int wait_for_reply(int64_t wait_time) (struct sockaddr *)&response_addr, /* reply_src_addr */ sizeof(response_addr), /* reply_src_addr_len */ buffer, /* reply_buf */ - sizeof(buffer) /* reply_buf_len */ + sizeof(buffer), /* reply_buf_len */ + &ip_header_tos, /* TOS resp. TC byte */ + &ip_header_ttl /* TTL resp. hop limit */ ); if (result <= 0) {