Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,48 @@ jobs:
flag-name: ${{ matrix.os }}
parallel: true

- name: Upload fping binary
uses: actions/upload-artifact@v6
with:
name: fping-binary-${{ matrix.os }}-${{ github.sha }}
path: src/fping

Test-Asymmetric-Routing:
needs: [Test-Linux]
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-24.04]
steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Download fping binary from Test-Linux
id: download
uses: actions/download-artifact@v6
with:
name: fping-binary-${{ matrix.os }}-${{ github.sha }}
path: src/
continue-on-error: true

- name: Fallback - Build fping if download failed
if: steps.download.outcome == 'failure'
run: |
sudo apt-get update -qq
sudo apt-get install -y libcap2-bin libtest-command-perl
ci/build-1-autotools.sh
ci/build-4-compile.sh

- name: Make fping executable
run: chmod +x src/fping

- name: Install tcpdump and net-tools
run: |
sudo apt-get update -qq
sudo apt-get install -y tcpdump net-tools

- name: Test asymmetric routing (--oiface)
run: sudo ci/test-oiface.sh

Test-Mac:
runs-on: macos-latest
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
27 changes: 26 additions & 1 deletion ci/test-07-options-i-m.pl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/perl -w

use Test::Command tests => 25;
use Test::Command tests => 31;
use Test::More;

# -i n interval between sending ping packets (in millisec) (default 25)
Expand Down Expand Up @@ -54,6 +54,31 @@
$cmd->stderr_like(qr{binding to specific interface \(SO_BINDTODEVICE\):.*\n});
}

# fping --oiface=IFACE
Comment thread
gsnw-sebast marked this conversation as resolved.
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 --oiface=IFACE (IPv6)
SKIP: {
if($^O ne 'linux') {
skip '--oiface option functionality is only tested on Linux', 3;
}
if($ENV{SKIP_IPV6}) {
skip 'Skip IPv6 tests', 3;
}
my $cmd = Test::Command->new(cmd => 'fping -6 --oiface=lo ::1');
$cmd->exit_is_num(0);
$cmd->stdout_is_eq("::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');
Expand Down
79 changes: 79 additions & 0 deletions ci/test-oiface.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
set -ex

NS_TESTER="tester_ns"
NS_TARGET="target_ns"
LOG_FILE="/tmp/asym_trace.log"

cleanup() {
echo "--- FINALER TRACE-LOG ---"
[ -f "$LOG_FILE" ] && cat $LOG_FILE
sudo ip netns del $NS_TESTER 2>/dev/null || true
sudo ip netns del $NS_TARGET 2>/dev/null || true
}
trap cleanup EXIT

echo "--- Network-Setup ---"
sudo ip netns add $NS_TESTER
sudo ip netns add $NS_TARGET

sudo ip link add veth1_tst type veth peer name veth1_trg
sudo ip link add veth2_tst type veth peer name veth2_trg

sudo ip link set veth1_tst netns $NS_TESTER
sudo ip link set veth2_tst netns $NS_TESTER
sudo ip link set veth1_trg netns $NS_TARGET
sudo ip link set veth2_trg netns $NS_TARGET

sudo ip netns exec $NS_TESTER ip addr add 10.0.1.1/24 dev veth1_tst
sudo ip netns exec $NS_TESTER ip addr add 10.0.2.1/24 dev veth2_tst
sudo ip netns exec $NS_TARGET ip addr add 10.0.1.2/24 dev veth1_trg
sudo ip netns exec $NS_TARGET ip addr add 10.0.2.2/24 dev veth2_trg

sudo ip netns exec $NS_TESTER ip link set veth1_tst up
sudo ip netns exec $NS_TESTER ip link set veth2_tst up
sudo ip netns exec $NS_TESTER ip link set lo up
sudo ip netns exec $NS_TARGET ip link set veth1_trg up
sudo ip netns exec $NS_TARGET ip link set veth2_trg up
sudo ip netns exec $NS_TARGET ip link set lo up

echo "--- Configuration for asymmetric routing ---"
for ns in $NS_TESTER $NS_TARGET; do
sudo ip netns exec $ns sysctl -w net.ipv4.conf.all.rp_filter=0
sudo ip netns exec $ns sysctl -w net.ipv4.conf.default.rp_filter=0
sudo ip netns exec $ns sysctl -w net.ipv4.conf.all.accept_local=1
sudo ip netns exec $ns sysctl -w net.ipv4.ip_forward=1

for dev in $(sudo ip netns exec $ns ls /sys/class/net/); do
sudo ip netns exec $ns sysctl -w net.ipv4.conf.$dev.rp_filter=0 2>/dev/null || true
done
done

T_MAC2=$(sudo ip netns exec $NS_TARGET cat /sys/class/net/veth2_trg/address)
sudo ip netns exec $NS_TESTER arp -s 10.0.2.2 $T_MAC2 -i veth2_tst

echo "--- Tests ---"
sudo ip netns exec $NS_TESTER tcpdump -i any icmp -n -l > $LOG_FILE 2>&1 &
TCP_PID=$!
sleep 2

echo "Send fping (asymmetry check)..."
sudo ip netns exec $NS_TESTER ./src/fping -c 1 -t 1000 --oiface veth2_tst -S 10.0.1.1 10.0.2.2 || FPING_STATUS=$?

sleep 1
sudo kill $TCP_PID 2>/dev/null || true
sleep 1

echo "--- Analysis ---"

REQ_OK=$(grep "veth2_tst Out IP 10.0.1.1 > 10.0.2.2" $LOG_FILE | wc -l)
REP_OK=$(grep "veth1_tst In IP 10.0.2.2 > 10.0.1.1" $LOG_FILE | wc -l)

if [ "$REQ_OK" -gt 0 ] && [ "$REP_OK" -gt 0 ]; then
echo "RESULT: TEST SUCCESSFUL (True asymmetry detected)"
exit 0
else
echo "RESULT: TEST FAILED"
[ "$REQ_OK" -eq 0 ] && echo "- The request was not sent correctly with source 10.0.1.1 via veth2_tst."
[ "$REP_OK" -eq 0 ] && echo "- The reply was not received asymmetrically via veth1_tst."
exit 1
fi
4 changes: 4 additions & 0 deletions doc/fping.pod
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions src/flags.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ int opt_outage_on = 0;
int opt_random_data_on = 0;
int opt_check_source_on = 0;
int opt_size_on = 0;
int opt_oiface_on = 0;
int opt_bindiface_on = 0;
int opt_timestamp_on = 0;
int opt_timestamp_format = 0;
int opt_icmp_request_typ = 0;
2 changes: 2 additions & 0 deletions src/flags.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ extern int opt_outage_on;
extern int opt_random_data_on;
extern int opt_check_source_on;
extern int opt_size_on;
extern int opt_oiface_on;
extern int opt_bindiface_on;
extern int opt_timestamp_on;
extern int opt_timestamp_format;
extern int opt_icmp_request_typ;
Expand Down
25 changes: 25 additions & 0 deletions src/fping.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -598,6 +599,21 @@ 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) {
Comment thread
gsnw-sebast marked this conversation as resolved.
Comment thread
gsnw-sebast marked this conversation as resolved.
opt_oiface_on = 1;
#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(optparse_state.optarg);
}
#endif
#else
fprintf(stderr, "%s: --oiface is not supported on this platform (IP_PKTINFO unavailable)\n", prog);
exit(3);
#endif
Comment thread
gsnw-sebast marked this conversation as resolved.
} else {
usage(1);
}
Expand Down Expand Up @@ -848,6 +864,7 @@ int main(int argc, char **argv)
exit(1);

case 'I':
opt_bindiface_on = 1;
#ifdef SO_BINDTODEVICE
if (socket4 >= 0) {
if (p_setsockopt(suid, socket4, SOL_SOCKET, SO_BINDTODEVICE, optparse_state.optarg, strlen(optparse_state.optarg))) {
Expand Down Expand Up @@ -940,6 +957,11 @@ int main(int argc, char **argv)
exit(1);
}

if (opt_oiface_on && opt_bindiface_on) {
fprintf(stderr, "%s: specify only --oiface or -I, --iface\n", prog);
exit(1);
}

if (opt_count_on && opt_loop_on) {
fprintf(stderr, "%s: specify only one of c, l\n", prog);
exit(1);
Expand Down Expand Up @@ -3075,6 +3097,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
Expand Down
2 changes: 2 additions & 0 deletions src/fping.h
Original file line number Diff line number Diff line change
Expand Up @@ -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(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);
Expand Down
64 changes: 63 additions & 1 deletion src/socket4.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -49,6 +50,13 @@
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;

/* Source address for outgoing packets (used to populate ipi_spec_dst) */
static struct in_addr outgoing_src_addr_ipv4;
static int outgoing_src_addr_set_ipv4 = 0;

int open_ping_socket_ipv4(int *socktype)
{
struct protoent* proto;
Expand Down Expand Up @@ -84,6 +92,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 */
Expand All @@ -98,6 +122,10 @@ void socket_set_src_addr_ipv4(int s, struct in_addr* src_addr, int *ident)
struct sockaddr_in sa;
socklen_t len = sizeof(sa);

/* Remember source address so we can set ipi_spec_dst in sendmsg() */
outgoing_src_addr_ipv4 = *src_addr;
outgoing_src_addr_set_ipv4 = 1;

memset(&sa, 0, len);
sa.sin_family = AF_INET;
sa.sin_addr = *src_addr;
Expand Down Expand Up @@ -161,7 +189,41 @@ 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;
if (outgoing_src_addr_set_ipv4) {
pktinfo->ipi_spec_dst = outgoing_src_addr_ipv4;
}

n = sendmsg(s, &msg, 0);
} else {
n = sendto(s, icp, ping_pkt_size_ipv4, 0, saddr, saddr_len);
}

return n;
}
Loading
Loading