]> git.gsnw.org Git - fping.git/commitdiff
New option --oiface for outgoing interface
authorGerman Service Network <support@gsnw.de>
Sun, 22 Feb 2026 09:35:41 +0000 (10:35 +0100)
committerSebastian <176771227+gsnw-sebast@users.noreply.github.com>
Sat, 25 Apr 2026 05:42:10 +0000 (07:42 +0200)
CHANGELOG.md
ci/test-07-options-i-m.pl
doc/fping.pod
src/fping.c
src/fping.h
src/socket4.c
src/socket6.c

index 9a8239a563592560db8fb7e7a3a32502c312d0e6..198782333350920c0f863d8fa91c644d66a3fce8 100644 (file)
@@ -3,6 +3,8 @@ Next
 
 ## New features
 
+- New option --oiface for outgoing interface (#463, thanks @gsnw-sebast)
+
 ## Bugfixes and other changes
 
 - ci: Removed travis-ci (#446, thanks @gsnw-sebast)
index 66736e7a306776f8acc604e11f65d4bf0f10e477..3bb31698f275a5f021ee1486ae897063bb3730ca 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/perl -w
 
-use Test::Command tests => 25;
+use Test::Command tests => 28;
 use Test::More;
 
 #  -i n       interval between sending ping packets (in millisec) (default 25)
@@ -54,6 +54,17 @@ $cmd->stdout_is_eq("");
 $cmd->stderr_like(qr{binding to specific interface \(SO_BINDTODEVICE\):.*\n});
 }
 
+# fping --oiface=IFACE
+SKIP: {
+if($^O ne 'linux') {
+    skip '--oiface option functionality is only tested on Linux', 3;
+}
+my $cmd = Test::Command->new(cmd => 'fping --oiface=lo 127.0.0.1');
+$cmd->exit_is_num(0);
+$cmd->stdout_is_eq("127.0.0.1 is alive\n");
+$cmd->stderr_is_eq("");
+}
+
 # fping -l
 {
 my $cmd = Test::Command->new(cmd => '(sleep 0.5; pkill -INT fping)& fping -p 100 -l 127.0.0.1');
index e868ac50390b83db4594f4318eb38d50b5c8857f..f674b18f2922273a1b85b86bcface2c4262b822b 100644 (file)
@@ -157,6 +157,10 @@ was configured with C<--enable-safe-limits>)).
 
 Set the interface (requires SO_BINDTODEVICE support).
 
+=item B<--oiface>=I<IFACE>
+
+Send pings via a specific outgoing interface (receive from any)
+
 =item B<--icmp-timestamp>
 
 Send ICMP timestamp requests (ICMP type 13) instead of ICMP Echo requests.
index efb4c4fa088e975ac91b2706dbe6a4893dae8fae..be9dcfcfbaf3305418ec457dbc1e14dad88529a7 100644 (file)
@@ -505,6 +505,7 @@ int main(int argc, char **argv)
         { "ttl", 'H', OPTPARSE_REQUIRED },
         { "interval", 'i', OPTPARSE_REQUIRED },
         { "iface", 'I', OPTPARSE_REQUIRED },
+        { "oiface", 0, OPTPARSE_REQUIRED },
         { "json", 'J', OPTPARSE_NONE },
         { "icmp-timestamp", 0, OPTPARSE_NONE },
 #ifdef SO_MARK
@@ -598,6 +599,20 @@ int main(int argc, char **argv)
 #endif
             } else if (strstr(optparse_state.optlongname, "seqmap-timeout") != NULL) {
                 opt_seqmap_timeout = strtod_strict(optparse_state.optarg) * 1000000;
+            } else if (strstr(optparse_state.optlongname, "oiface") != NULL) {
+#ifdef IP_PKTINFO
+              if (socket4 >= 0) {
+                  socket_set_outgoing_iface_ipv4(socket4, optparse_state.optarg);
+              }
+#ifdef IPV6
+              if (socket6 >= 0) {
+                  socket_set_outgoing_iface_ipv6(socket6, optparse_state.optarg);
+              }
+#endif
+#else
+              fprintf(stderr, "%s: --oiface is not supported on this platform (IP_PKTINFO unavailable)\n", prog);
+              exit(3);
+#endif
             } else {
                 usage(1);
             }
@@ -3075,6 +3090,9 @@ void usage(int is_error)
 #ifdef SO_BINDTODEVICE
     fprintf(out, "   -I, --iface=IFACE  bind to a particular interface\n");
 #endif
+#ifdef IP_PKTINFO
+    fprintf(out, "       --oiface=IFACE  send pings via a specific outgoing interface (receive from any)\n");
+#endif
 #ifdef SO_MARK
     fprintf(out, "   -k, --fwmark=FWMARK set the routing mark\n");
 #endif
index 845fcaae25db49a9523403bba81c9967d06f25e9..7cdcf6a12eba7e620d95e0c2a840e2c82e2acf58 100644 (file)
@@ -128,11 +128,13 @@ int in_cksum( unsigned short *p, int n );
 
 /* socket.c */
 int  open_ping_socket_ipv4(int *socktype);
+void socket_set_outgoing_iface_ipv4(int s, const char *iface_name);
 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);
 #ifdef IPV6
 int  open_ping_socket_ipv6(int *socktype);
+void socket_set_outgoing_iface_ipv6(int s, const char *iface_name);
 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);
index 564791e684d0ac509f990e23f13d228b699f7d44..799987bfab4de333c5875a84769d0a0bb2eb5af0 100644 (file)
@@ -40,6 +40,7 @@
 #include <netinet/in_systm.h>
 #include <netinet/ip.h>
 #include <netinet/ip_icmp.h>
+#include <net/if.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -49,6 +50,9 @@
 char* ping_buffer_ipv4 = 0;
 size_t ping_pkt_size_ipv4;
 
+/* Interface index for outgoing packets (0 = not set, use routing table) */
+static int outgoing_iface_idx_ipv4 = 0;
+
 int open_ping_socket_ipv4(int *socktype)
 {
     struct protoent* proto;
@@ -84,6 +88,22 @@ int open_ping_socket_ipv4(int *socktype)
     return s;
 }
 
+void socket_set_outgoing_iface_ipv4(int s, const char *iface_name)
+{
+    unsigned int idx = if_nametoindex(iface_name);
+    if (idx == 0) {
+        fprintf(stderr, "fping: unknown interface '%s'\n", iface_name);
+        exit(1);
+    }
+    outgoing_iface_idx_ipv4 = (int)idx;
+
+    int on = 1;
+    if (setsockopt(s, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on)) < 0) {
+        perror("setsockopt IP_PKTINFO");
+        exit(1);
+    }
+}
+
 void init_ping_buffer_ipv4(size_t ping_data_size)
 {
     /* allocate ping buffer */
@@ -161,7 +181,38 @@ 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);
+    if (outgoing_iface_idx_ipv4 > 0) {
+        struct iovec iov = {
+            .iov_base = icp,
+            .iov_len  = ping_pkt_size_ipv4
+        };
+
+        char cmsg_buf[CMSG_SPACE(sizeof(struct in_pktinfo))];
+        memset(cmsg_buf, 0, sizeof(cmsg_buf));
+
+        struct msghdr msg = {
+            .msg_name       = saddr,
+            .msg_namelen    = saddr_len,
+            .msg_iov        = &iov,
+            .msg_iovlen     = 1,
+            .msg_control    = cmsg_buf,
+            .msg_controllen = sizeof(cmsg_buf),
+            .msg_flags      = 0
+        };
+
+        struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
+        cmsg->cmsg_level = IPPROTO_IP;
+        cmsg->cmsg_type  = IP_PKTINFO;
+        cmsg->cmsg_len   = CMSG_LEN(sizeof(struct in_pktinfo));
+
+        struct in_pktinfo *pktinfo = (struct in_pktinfo *)CMSG_DATA(cmsg);
+        memset(pktinfo, 0, sizeof(*pktinfo));
+        pktinfo->ipi_ifindex = outgoing_iface_idx_ipv4;
+
+        n = sendmsg(s, &msg, 0);
+    } else {
+        n = sendto(s, icp, ping_pkt_size_ipv4, 0, saddr, saddr_len);
+    }
 
     return n;
 }
index 7f94bfb00416e55f0e4b86ac023645accf0e51a8..70eeb4ccaf58f089fb30e1b01927db3cf1cf476a 100644 (file)
@@ -37,6 +37,7 @@
 #include <fcntl.h>
 #include <netdb.h>
 #include <netinet/in.h>
+#include <net/if.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -47,6 +48,9 @@
 char* ping_buffer_ipv6 = 0;
 size_t ping_pkt_size_ipv6;
 
+/* Interface index for outgoing packets (0 = not set, use routing table) */
+static int outgoing_iface_idx_ipv6 = 0;
+
 int open_ping_socket_ipv6(int *socktype)
 {
     struct protoent* proto;
@@ -96,6 +100,22 @@ int open_ping_socket_ipv6(int *socktype)
     return s;
 }
 
+void socket_set_outgoing_iface_ipv6(int s, const char *iface_name)
+{
+    unsigned int idx = if_nametoindex(iface_name);
+    if (idx == 0) {
+        fprintf(stderr, "fping: unknown interface '%s'\n", iface_name);
+        exit(1);
+    }
+    outgoing_iface_idx_ipv6 = (int)idx;
+
+    int on = 1;
+    if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on)) < 0) {
+        perror("setsockopt IPV6_RECVPKTINFO");
+        exit(1);
+    }
+}
+
 void init_ping_buffer_ipv6(size_t ping_data_size)
 {
     /* allocate ping buffer */
@@ -145,7 +165,38 @@ int socket_sendto_ping_ipv6(int s, struct sockaddr* saddr, socklen_t saddr_len,
 
     icp->icmp6_cksum = 0; /* The IPv6 stack calculates the checksum for us... */
 
-    n = sendto(s, icp, ping_pkt_size_ipv6, 0, saddr, saddr_len);
+    if (outgoing_iface_idx_ipv6 > 0) {
+        struct iovec iov = {
+            .iov_base = icp,
+            .iov_len  = ping_pkt_size_ipv6
+        };
+
+        char cmsg_buf[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+        memset(cmsg_buf, 0, sizeof(cmsg_buf));
+
+        struct msghdr msg = {
+            .msg_name       = saddr,
+            .msg_namelen    = saddr_len,
+            .msg_iov        = &iov,
+            .msg_iovlen     = 1,
+            .msg_control    = cmsg_buf,
+            .msg_controllen = sizeof(cmsg_buf),
+            .msg_flags      = 0
+        };
+
+        struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
+        cmsg->cmsg_level = IPPROTO_IPV6;
+        cmsg->cmsg_type  = IPV6_PKTINFO;
+        cmsg->cmsg_len   = CMSG_LEN(sizeof(struct in6_pktinfo));
+
+        struct in6_pktinfo *pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg);
+        memset(pktinfo, 0, sizeof(*pktinfo));
+        pktinfo->ipi6_ifindex = outgoing_iface_idx_ipv6;
+
+        n = sendmsg(s, &msg, 0);
+    } else {
+        n = sendto(s, icp, ping_pkt_size_ipv6, 0, saddr, saddr_len);
+    }
 
     return n;
 }