diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index 7729892e0b04..5dae18ebac13 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -50,6 +50,7 @@ TEST_PROGS := test_kmod.sh \
 	test_xdp_redirect.sh \
 	test_xdp_meta.sh \
 	test_xdp_veth.sh \
+	test_xdp_egress.sh \
 	test_offload.py \
 	test_sock_addr.sh \
 	test_tunnel.sh \
diff --git a/tools/testing/selftests/bpf/progs/xdp_drop.c b/tools/testing/selftests/bpf/progs/xdp_drop.c
new file mode 100644
index 000000000000..cffabc53a5e1
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/xdp_drop.c
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/bpf.h>
+#include <linux/if_ether.h>
+#include <bpf/bpf_helpers.h>
+
+SEC("drop")
+int xdp_drop(struct xdp_md *ctx)
+{
+	void *data_end = (void *)(long)ctx->data_end;
+	void *data = (void *)(long)ctx->data;
+	struct ethhdr *eth = data;
+	void *nh;
+
+	nh = data + sizeof(*eth);
+	if (nh > data_end)
+		return XDP_DROP;
+
+	if (eth->h_proto == 0x0008)
+		return XDP_DROP;
+
+	return XDP_PASS;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/test_xdp_egress.sh b/tools/testing/selftests/bpf/test_xdp_egress.sh
new file mode 100755
index 000000000000..7efa59fdf823
--- /dev/null
+++ b/tools/testing/selftests/bpf/test_xdp_egress.sh
@@ -0,0 +1,160 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# XDP egress tests.
+
+# Kselftest framework requirement - SKIP code is 4.
+ksft_skip=4
+
+TESTNAME=xdp_egress
+BPF_FS=$(awk '$3 == "bpf" {print $2; exit}' /proc/mounts)
+
+ret=0
+
+################################################################################
+#
+log_test()
+{
+	local rc=$1
+	local expected=$2
+	local msg="$3"
+
+	if [ ${rc} -eq ${expected} ]; then
+		printf "TEST: %-60s  [ OK ]\n" "${msg}"
+	else
+		ret=1
+		printf "TEST: %-60s  [FAIL]\n" "${msg}"
+	fi
+}
+
+################################################################################
+# create namespaces and connect them
+
+create_ns()
+{
+	local ns=$1
+	local addr=$2
+	local addr6=$3
+
+	ip netns add ${ns}
+
+	ip -netns ${ns} link set lo up
+	ip -netns ${ns} addr add dev lo ${addr}
+	ip -netns ${ns} -6 addr add dev lo ${addr6}
+
+	ip -netns ${ns} ro add unreachable default metric 8192
+	ip -netns ${ns} -6 ro add unreachable default metric 8192
+
+	ip netns exec ${ns} sysctl -qw net.ipv4.ip_forward=1
+	ip netns exec ${ns} sysctl -qw net.ipv6.conf.all.keep_addr_on_down=1
+	ip netns exec ${ns} sysctl -qw net.ipv6.conf.all.forwarding=1
+	ip netns exec ${ns} sysctl -qw net.ipv6.conf.all.forwarding=1
+	ip netns exec ${ns} sysctl -qw net.ipv6.conf.all.accept_dad=0
+}
+
+connect_ns()
+{
+	local ns1=$1
+	local ns1_dev=$2
+	local ns1_addr=$3
+	local ns1_addr6=$4
+	local ns2=$5
+	local ns2_dev=$6
+	local ns2_addr=$7
+	local ns2_addr6=$8
+	local ns1arg
+	local ns2arg
+
+	if [ -n "${ns1}" ]; then
+		ns1arg="-netns ${ns1}"
+	fi
+	if [ -n "${ns2}" ]; then
+		ns2arg="-netns ${ns2}"
+	fi
+
+	ip ${ns1arg} li add ${ns1_dev} type veth peer name tmp
+	ip ${ns1arg} li set ${ns1_dev} up
+	ip ${ns1arg} li set tmp netns ${ns2} name ${ns2_dev}
+	ip ${ns2arg} li set ${ns2_dev} up
+
+	ip ${ns1arg} addr add dev ${ns1_dev} ${ns1_addr}
+	ip ${ns2arg} addr add dev ${ns2_dev} ${ns2_addr}
+
+	ip ${ns1arg} addr add dev ${ns1_dev} ${ns1_addr6} nodad
+	ip ${ns2arg} addr add dev ${ns2_dev} ${ns2_addr6} nodad
+}
+
+################################################################################
+#
+
+setup()
+{
+	create_ns host 172.16.101.1/32 2001:db8:101::1/128
+	connect_ns "" veth-host 172.16.1.1/24 2001:db8:1::1/64 host eth0 172.16.1.2/24 2001:db8:1::2/64
+	ip ro add 172.16.101.1 via 172.16.1.2
+	ip -6 ro add 2001:db8:101::1 via 2001:db8:1::2
+	ping -c1 -w1 172.16.101.1 >/dev/null 2>&1
+	ping -c1 -w1 2001:db8:101::1 >/dev/null 2>&1
+}
+
+cleanup()
+{
+	ip li del veth-host 2>/dev/null
+	ip netns del host 2>/dev/null
+	rm -f $BPF_FS/test_$TESTNAME
+}
+
+################################################################################
+# main
+
+if [ $(id -u) -ne 0 ]; then
+	echo "selftests: $TESTNAME [SKIP] Need root privileges"
+	exit $ksft_skip
+fi
+
+if ! ip link set dev lo xdp off > /dev/null 2>&1; then
+	echo "selftests: $TESTNAME [SKIP] Could not run test without the ip xdp support"
+	exit $ksft_skip
+fi
+
+if [ -z "$BPF_FS" ]; then
+	echo "selftests: $TESTNAME [SKIP] Could not run test without bpffs mounted"
+	exit $ksft_skip
+fi
+
+if ! bpftool version > /dev/null 2>&1; then
+	echo "selftests: $TESTNAME [SKIP] Could not run test without bpftool"
+	exit $ksft_skip
+fi
+
+cleanup
+trap cleanup EXIT
+
+set -e
+setup
+set +e
+
+bpftool prog load xdp_drop.o $BPF_FS/test_$TESTNAME type xdp_egress || exit 1
+ID=$(bpftool prog show name xdp_drop | awk '$4 == "xdp_drop" {print $1}')
+
+# attach egress program
+bpftool net attach xdp_egress id ${ID/:/} dev veth-host
+ping -c1 -w1 172.16.101.1 >/dev/null 2>&1
+log_test $? 1 "IPv4 connectivity disabled by xdp_egress"
+ping -c1 -w1 2001:db8:101::1 >/dev/null 2>&1
+log_test $? 0 "IPv6 connectivity not disabled by egress drop program"
+
+# detach program should restore connectivity
+bpftool net detach xdp_egress dev veth-host
+ping -c1 -w1 172.16.101.1 >/dev/null 2>&1
+log_test $? 0 "IPv4 connectivity restored"
+
+# cleanup on delete
+ip netns exec host bpftool net attach xdp_egress id ${ID/:/} dev eth0
+bpftool net attach xdp_egress id ${ID/:/} dev veth-host
+ip li del veth-host
+rm -f $BPF_FS/test_$TESTNAME
+sleep 5  # rcu grace pass; verify program is dropped
+bpftool prog show name xdp_drop
+
+exit $ret
