From: German Service Network Date: Sun, 22 Feb 2026 09:35:41 +0000 (+0100) Subject: New option --oiface for outgoing interface X-Git-Url: https://git.gsnw.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6b0fdaece03f0bf899766f63944080cccb051cff;p=fping.git New option --oiface for outgoing interface --- diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a8239a..1987823 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/ci/test-07-options-i-m.pl b/ci/test-07-options-i-m.pl index 66736e7..3bb3169 100755 --- a/ci/test-07-options-i-m.pl +++ b/ci/test-07-options-i-m.pl @@ -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'); diff --git a/doc/fping.pod b/doc/fping.pod index e868ac5..f674b18 100644 --- a/doc/fping.pod +++ b/doc/fping.pod @@ -157,6 +157,10 @@ was configured with C<--enable-safe-limits>)). Set the interface (requires SO_BINDTODEVICE support). +=item B<--oiface>=I + +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. diff --git a/src/fping.c b/src/fping.c index efb4c4f..be9dcfc 100644 --- a/src/fping.c +++ b/src/fping.c @@ -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 diff --git a/src/fping.h b/src/fping.h index 845fcaa..7cdcf6a 100644 --- a/src/fping.h +++ b/src/fping.h @@ -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); diff --git a/src/socket4.c b/src/socket4.c index 564791e..799987b 100644 --- a/src/socket4.c +++ b/src/socket4.c @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -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; } diff --git a/src/socket6.c b/src/socket6.c index 7f94bfb..70eeb4c 100644 --- a/src/socket6.c +++ b/src/socket6.c @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -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; }