## New features
+- New option --oiface for outgoing interface (#463, thanks @gsnw-sebast)
+
## Bugfixes and other changes
- ci: Removed travis-ci (#446, thanks @gsnw-sebast)
#!/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)
$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');
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.
{ "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
#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);
}
#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
/* 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);
#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>
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;
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 */
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;
}
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
+#include <net/if.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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;
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 */
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;
}