]> git.gsnw.org Git - fping.git/commitdiff
print TOS & TTL also for IPv6 and w/o privileges
authorErik Auerswald <auerswal@unix-ag.uni-kl.de>
Sun, 9 Mar 2025 19:28:42 +0000 (20:28 +0100)
committerErik Auerswald <auerswal@unix-ag.uni-kl.de>
Sat, 7 Jun 2025 18:29:05 +0000 (20:29 +0200)
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.

CHANGELOG.md
ci/test-04-options-a-b.pl
ci/test-08-options-n-q.pl
ci/test-11-unpriv.pl
doc/fping.pod
src/fping.c

index a9e163af53a30a689dcd3616a1076ca011948523..ca1955249939da0b92c48b46a2532dc9680e24c0 100644 (file)
@@ -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)
 ======================
 
index ea7eb893d2e2d6d5dd7cb322917644d9426bd3c6..adbe521f755b4ff6c0c5bd4479d20a118b3b3d77 100755 (executable)
@@ -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("");
 }
 
index d614db149f4e938dc85a4b3ae5665a176557a229..f145e5dd20631e56a75a96a850d2ea4f98504b09 100755 (executable)
@@ -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("");
 }
 
index 6ec0768d77a028d311b378c2ea893df723932f32..aea3e8f235270a852238513c5ead00a8a65eb866 100755 (executable)
@@ -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: {
index 52c01c92a498be233c2b61897409a44bc4ead7fd..d10014f4cd301eccb4c345604288610202c018d5 100644 (file)
@@ -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<fping> cannot read the TTL value, "(TTL unknown)" is returned.
-IPv4 only, requires root privileges or cap_net_raw.
 
 =item B<-i>, B<--interval>=I<MSEC>
 
@@ -206,7 +205,6 @@ Set the typ of service flag (TOS). I<N> can be either decimal or hexadecimal
 
 Displays the TOS value in the output. If B<fping> cannot read the TOS value,
 "(TOS unknown)" is returned.
-IPv4 only, requires root privileges or cap_net_raw.
 
 =item B<-p>, B<--period>=I<MSEC>
 
index c71a4e8647218d969a05989e7e995f50720e16db..86da559df661db65804ca865f7d2d2e3b71bafe7 100644 (file)
@@ -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) {