summaryrefslogtreecommitdiffstats
path: root/xdp-filter
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 07:10:00 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 07:10:00 +0000
commit4ba2b326284765e942044db13a7f0dae702bec93 (patch)
treecbdfaec33eed4f3a970c54cd10e8ddfe3003b3b1 /xdp-filter
parentInitial commit. (diff)
downloadxdp-tools-4ba2b326284765e942044db13a7f0dae702bec93.tar.xz
xdp-tools-4ba2b326284765e942044db13a7f0dae702bec93.zip
Adding upstream version 1.3.1.upstream/1.3.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xdp-filter')
-rw-r--r--xdp-filter/.gitignore3
-rw-r--r--xdp-filter/Makefile24
-rw-r--r--xdp-filter/README.org294
-rw-r--r--xdp-filter/common_kern_user.h28
-rw-r--r--xdp-filter/extract_features.sh37
-rw-r--r--xdp-filter/tests/common.py56
-rw-r--r--xdp-filter/tests/test-xdp-filter.sh391
-rw-r--r--xdp-filter/tests/test_basic.py279
-rw-r--r--xdp-filter/tests/test_slow.py127
-rw-r--r--xdp-filter/xdp-filter.8371
-rw-r--r--xdp-filter/xdp-filter.c1097
-rw-r--r--xdp-filter/xdpfilt_alw_all.c10
-rw-r--r--xdp-filter/xdpfilt_alw_eth.c10
-rw-r--r--xdp-filter/xdpfilt_alw_ip.c10
-rw-r--r--xdp-filter/xdpfilt_alw_tcp.c10
-rw-r--r--xdp-filter/xdpfilt_alw_udp.c10
-rw-r--r--xdp-filter/xdpfilt_dny_all.c10
-rw-r--r--xdp-filter/xdpfilt_dny_eth.c10
-rw-r--r--xdp-filter/xdpfilt_dny_ip.c10
-rw-r--r--xdp-filter/xdpfilt_dny_tcp.c10
-rw-r--r--xdp-filter/xdpfilt_dny_udp.c10
-rw-r--r--xdp-filter/xdpfilt_prog.h257
22 files changed, 3064 insertions, 0 deletions
diff --git a/xdp-filter/.gitignore b/xdp-filter/.gitignore
new file mode 100644
index 0000000..87a8a62
--- /dev/null
+++ b/xdp-filter/.gitignore
@@ -0,0 +1,3 @@
+*.ll
+xdp-filter
+prog_features.h
diff --git a/xdp-filter/Makefile b/xdp-filter/Makefile
new file mode 100644
index 0000000..0d54e8e
--- /dev/null
+++ b/xdp-filter/Makefile
@@ -0,0 +1,24 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+
+XDP_TARGETS := xdpfilt_dny_udp xdpfilt_dny_tcp xdpfilt_dny_ip \
+ xdpfilt_dny_eth xdpfilt_dny_all \
+ xdpfilt_alw_udp xdpfilt_alw_tcp xdpfilt_alw_ip \
+ xdpfilt_alw_eth xdpfilt_alw_all
+
+TOOL_NAME := xdp-filter
+USER_TARGETS := xdp-filter
+EXTRA_DEPS := xdpfilt_prog.h
+MAN_PAGE := xdp-filter.8
+TEST_FILE := tests/test-xdp-filter.sh
+TEST_FILE_DEPS := $(wildcard tests/*.py)
+
+USER_GEN := prog_features.h
+EXTRA_USER_DEPS := $(USER_GEN)
+
+LIB_DIR = ../lib
+
+include $(LIB_DIR)/common.mk
+
+prog_features.h: ${XDP_TARGETS:=.o} extract_features.sh
+ $(Q)sh extract_features.sh $^ > $@ || ( ret=$$?; rm -f $@; exit $$ret )
+
diff --git a/xdp-filter/README.org b/xdp-filter/README.org
new file mode 100644
index 0000000..3a4df60
--- /dev/null
+++ b/xdp-filter/README.org
@@ -0,0 +1,294 @@
+#+EXPORT_FILE_NAME: xdp-filter
+#+TITLE: xdp-filter
+#+MAN_CLASS_OPTIONS: :section-id "8\" \"DATE\" \"VERSION\" \"A simple XDP-powered packet filter"
+# This file serves both as a README on github, and as the source for the man
+# page; the latter through the org-mode man page export support.
+# .
+# To export the man page, simply use the org-mode exporter; (require 'ox-man) if
+# it's not available. There's also a Makefile rule to export it.
+
+* xdp-filter - a simple XDP-powered packet filter
+
+XDP-filter is a packet filtering utility powered by XDP. It is deliberately
+simple and so does not have the same matching capabilities as, e.g., netfilter.
+Instead, thanks to XDP, it can achieve very high drop rates: tens of millions of
+packets per second on a single CPU core.
+
+** Running xdp-filter
+The syntax for running xdp-filter is:
+
+#+begin_src sh
+xdp-filter COMMAND [options]
+
+Where COMMAND can be one of:
+ load - load xdp-filter on an interface
+ unload - unload xdp-filter from an interface
+ port - add a port to the filter list
+ ip - add an IP address to the filter list
+ ether - add an Ethernet MAC address to the filter list
+ status - show current xdp-filter status
+ poll - poll statistics output
+ help - show the list of available commands
+#+end_src
+
+Each command, and its options are explained below. Or use =xdp-filter COMMAND
+--help= to see the options for each command.
+
+* The LOAD command
+To use =xdp-filter=, it must first be loaded onto an interface. This is
+accomplished with the =load= command, which takes the name of the interface as a
+parameter, and optionally allows specifying the features that should be
+included. By default all features are loaded, but de-selecting some features can
+speed up the packet matching, and increase performance by a substantial amount.
+
+The syntax for the =load= command is:
+
+=xdp-filter load [options] <ifname>=
+
+Where =<ifname>= is the name of the interface to load =xdp-filter= onto, and
+must be specified. The supported options are:
+
+** -m, --mode <mode>
+Specifies which mode to load the XDP program to be loaded in. The valid values
+are 'native', which is the default in-driver XDP mode, 'skb', which causes the
+so-called /skb mode/ (also known as /generic XDP/) to be used, or 'hw' which
+causes the program to be offloaded to the hardware.
+
+** -p, --policy <policy>
+This sets the policy =xdp-filter= applies to packets *not* matched by any of the
+filter rules. The default is /allow/, in which packets not matching any rules
+are allowed to pass. The other option is /deny/, in which *all* packets are
+dropped *except* those matched by the filter options.
+
+=xdp-filter= cannot be loaded simultaneously in /deny/ and /allow/ policy modes
+on the system. Note that loading =xdp-filter= in /deny/ mode will drop all
+traffic on the interface until suitable allow rules are installed, so some care
+is needed to avoid being locked out of a remote system.
+
+** -f, --features <feats>
+Use this option to select which features to include when loaded =xdp-filter=.
+The default is to load all available features. So select individual features
+specify one or more of these:
+
+ * *tcp*: Support filtering on TCP port number
+ * *udp*: Support filtering on UDP port number
+ * *ipv6*: Support filtering on IPv6 addresses
+ * *ipv4*: Support filtering on IPv4 addresses
+ * *ethernet*: Support filtering on Ethernet MAC addresses
+
+Specify multiple features by separating them with a comma. E.g.: =tcp,udp,ipv6=.
+
+** -v, --verbose
+Enable debug logging. Specify twice for even more verbosity.
+
+** -h, --help
+Display a summary of the available options
+
+* The UNLOAD command
+The =unload= command unloads =xdp-filter= from one (or all) interfaces, and
+cleans up the program state.
+
+The syntax for the =load= command is:
+
+=xdp-filter unload [options] <ifname>=
+
+Where =<ifname>= is the name of the interface to unload =xdp-filter= from, and
+must be specified unless the *--all* option is used. The supported options are:
+
+** -a, --all
+Specify this option to remove =xdp-filter= from all interfaces it was loaded
+onto. If this option is specified, no =<ifname>= is needed.
+
+This option can also be used to clean up all =xdp-filter= state if the XDP
+program(s) were unloaded by other means.
+
+** -k, --keep-maps
+Specify this option to prevent =xdp-filter= from clearing its map state. By
+default, all BPF maps no longer needed by any loaded program are removed.
+However, this will also remove the contents of the maps (the filtering rules),
+so this option can be used to keep the maps around so the rules persist until
+=xdp-filter= is loaded again.
+
+** -v, --verbose
+Enable debug logging. Specify twice for even more verbosity.
+
+** -h, --help
+Display a summary of the available options
+
+* The PORT command
+Use the =port= command to add a TCP or UDP port to the =xdp-filter= match list.
+For this to work, =xdp-filter= must be loaded with either the *udp* or the *tcp*
+feature (or both) on at least one interface.
+
+The syntax for the =port= command is:
+
+=xdp-filter port [options] <port>=
+
+Where =<port>= is the port number to add (or remove if the *--remove* is
+specified). The supported options are:
+
+** -r, --remove
+Remove the port instead of adding it.
+
+** -m, --mode <mode>
+Select filtering mode. Valid options are *src* and *dst*, both of which may be
+specified as =src,dst=. If *src* is specified, the port number will added as a
+/source port/ match, while if *dst* is specified, the port number will be added
+as a /destination port/ match. If both are specified, a packet will be matched
+if *either* its source or destination port is the specified port number.
+
+** -p, --proto <proto>
+Specify one (or both) of *udp* and/or *tcp* to match UDP or TCP ports,
+respectively.
+
+** -s, --status
+If this option is specified, the current list of matched ports will be printed
+after inserting the port number. Otherwise, nothing will be printed.
+
+** -v, --verbose
+Enable debug logging. Specify twice for even more verbosity.
+
+** -h, --help
+Display a summary of the available options
+
+
+* The IP command
+Use the =ip= command to add an IPv6 or an IPv4 address to the =xdp-filter= match
+list.
+
+The syntax for the =ip= command is:
+
+=xdp-filter ip [options] <ip>=
+
+Where =<ip>= is the IP address to add (or remove if the *--remove* is
+specified). Either IPv4 or IPv6 addresses can be specified, but =xdp-filter=
+must be loaded with the corresponding features (*ipv4* and *ipv6*,
+respectively). The supported options are:
+
+** -r, --remove
+Remove the IP address instead of adding it.
+
+** -m, --mode <mode>
+Select filtering mode. Valid options are *src* and *dst*, both of which may be
+specified as =src,dst=. If *src* is specified, the IP address will added as a
+/source IP/ match, while if *dst* is specified, the IP address will be added
+as a /destination IP/ match. If both are specified, a packet will be matched
+if *either* its source or destination IP is the specified IP address.
+
+** -s, --status
+If this option is specified, the current list of matched ips will be printed
+after inserting the IP address. Otherwise, nothing will be printed.
+
+** -v, --verbose
+Enable debug logging. Specify twice for even more verbosity.
+
+** -h, --help
+Display a summary of the available options
+
+* The ETHER command
+Use the =ether= command to add an Ethernet MAC address to the =xdp-filter= match
+list. For this to work, =xdp-filter= must be loaded with either the *ethernet*
+feature on at least one interface.
+
+The syntax for the =ether= command is:
+
+=xdp-filter ether [options] <addr>=
+
+Where =<addr>= is the MAC address to add (or remove if the *--remove* is
+specified). The supported options are:
+
+** -r, --remove
+Remove the MAC address instead of adding it.
+
+** -m, --mode <mode>
+Select filtering mode. Valid options are *src* and *dst*, both of which may be
+specified as =src,dst=. If *src* is specified, the MAC address will added as a
+/source MAC/ match, while if *dst* is specified, the MAC address will be added
+as a /destination MAC/ match. If both are specified, a packet will be matched
+if *either* its source or destination MAC is the specified MAC address.
+
+** -s, --status
+If this option is specified, the current list of matched ips will be printed
+after inserting the MAC address. Otherwise, nothing will be printed.
+
+** -v, --verbose
+Enable debug logging. Specify twice for even more verbosity.
+
+** -h, --help
+Display a summary of the available options
+
+* The STATUS command
+The =status= command prints the current status of =xdp-filter=: Which interfaces
+it is loaded on, the current list of rules, and some statistics for how many
+packets have been processed in total, and how many times each rule has been hit.
+
+The syntax for the =status= command is:
+
+=xdp-filter status [options]=
+
+Where the supported options are:
+
+** -v, --verbose
+Enable debug logging. Specify twice for even more verbosity.
+
+** -h, --help
+Display a summary of the available options
+
+* The POLL command
+The =poll= command periodically polls the =xdp-filter= statistics map and prints
+out the total number of packets and bytes processed by =xdp-filter=, as well as
+the number in the last polling interval, converted to packets (and bytes) per
+second. This can be used to inspect the performance of =xdp-filter=, and to
+compare the performance of the different feature sets selectable by the =load=
+parameter.
+
+The syntax for the =poll= command is:
+
+=xdp-filter poll [options]=
+
+Where the supported options are:
+
+** -i, --interval <interval>
+The polling interval, in milliseconds. Defaults to 1000 (1 second).
+
+** -v, --verbose
+Enable debug logging. Specify twice for even more verbosity.
+
+** -h, --help
+Display a summary of the available options
+
+* Examples
+
+To filter all packets arriving on port 80 on eth0, issue the
+following commands:
+
+#+begin_src sh
+# xdp-filter load eth0 -f tcp,udp
+# xdp-filter port 80
+#+end_src
+
+To filter all packets *except* those from IP address fc00:dead:cafe::1 issue the
+following commands (careful, this can lock you out of remote access!):
+
+#+begin_src sh
+# xdp-filter load eth0 -f ipv6 -p deny
+# xdp-filter ip fc00:dead:cafe::1 -m src
+#+end_src
+
+To allow packets from *either* IP fc00:dead:cafe::1 *or* arriving on port 22,
+issue the following (careful, this can lock you out of remote access!):
+
+#+begin_src sh
+# xdp-filter load eth0 -f ipv6,tcp -p deny
+# xdp-filter port 22
+# xdp-filter ip fc00:dead:cafe::1 -m src
+#+end_src
+
+* BUGS
+
+Please report any bugs on Github: https://github.com/xdp-project/xdp-tools/issues
+
+* AUTHOR
+
+xdp-filter was written by Toke Høiland-Jørgensen and Jesper Dangaard Brouer.
+This man page was written by Toke Høiland-Jørgensen.
diff --git a/xdp-filter/common_kern_user.h b/xdp-filter/common_kern_user.h
new file mode 100644
index 0000000..a7da1af
--- /dev/null
+++ b/xdp-filter/common_kern_user.h
@@ -0,0 +1,28 @@
+#ifndef COMMON_KERN_USER_H
+#define COMMON_KERN_USER_H
+
+#define FEAT_TCP (1<<0)
+#define FEAT_UDP (1<<1)
+#define FEAT_IPV6 (1<<2)
+#define FEAT_IPV4 (1<<3)
+#define FEAT_ETHERNET (1<<4)
+#define FEAT_ALL (FEAT_TCP|FEAT_UDP|FEAT_IPV6|FEAT_IPV4|FEAT_ETHERNET)
+#define FEAT_ALLOW (1<<5)
+#define FEAT_DENY (1<<6)
+
+#define MAP_FLAG_SRC (1<<0)
+#define MAP_FLAG_DST (1<<1)
+#define MAP_FLAG_TCP (1<<2)
+#define MAP_FLAG_UDP (1<<3)
+#define MAP_FLAGS (MAP_FLAG_SRC|MAP_FLAG_DST|MAP_FLAG_TCP|MAP_FLAG_UDP)
+
+#define COUNTER_SHIFT 6
+
+#define MAP_NAME_PORTS filter_ports
+#define MAP_NAME_IPV4 filter_ipv4
+#define MAP_NAME_IPV6 filter_ipv6
+#define MAP_NAME_ETHERNET filter_ethernet
+
+#include "xdp/xdp_stats_kern_user.h"
+
+#endif
diff --git a/xdp-filter/extract_features.sh b/xdp-filter/extract_features.sh
new file mode 100644
index 0000000..9cfb5e9
--- /dev/null
+++ b/xdp-filter/extract_features.sh
@@ -0,0 +1,37 @@
+#!/bin/sh
+
+cat<<EOF
+#ifndef PROG_FEATURES_H
+#define PROG_FEATURES_H
+struct prog_feature {
+ char *prog_name;
+ __u32 features;
+};
+
+static struct prog_feature prog_features[] = {
+EOF
+
+for f in $*; do
+ featstring=$(readelf -x features $f 2>/dev/null)
+ [ "$?" -ne "0" ] && continue
+
+ found=0
+ for w in $featstring; do
+ if [ "$w" = "0x00000000" ]; then
+ found=1
+ else
+ if [ "$found" -eq "1" ]; then
+ feats=$w
+ break
+ fi
+ fi
+ done
+
+ echo " {\"$f\", 0x$feats},"
+done
+
+cat<<EOF
+ {}
+};
+#endif
+EOF
diff --git a/xdp-filter/tests/common.py b/xdp-filter/tests/common.py
new file mode 100644
index 0000000..4848f31
--- /dev/null
+++ b/xdp-filter/tests/common.py
@@ -0,0 +1,56 @@
+import os
+import subprocess
+
+from xdp_test_harness.xdp_case import XDPCase, usingCustomLoader
+from xdp_test_harness.utils import XDPFlag
+
+
+XDP_FILTER = os.environ.get("XDP_FILTER", "xdp-filter")
+
+
+def get_mode_string(xdp_mode: XDPFlag):
+ if xdp_mode == XDPFlag.SKB_MODE:
+ return "skb"
+ if xdp_mode == XDPFlag.DRV_MODE:
+ return "native"
+ if xdp_mode == XDPFlag.HW_MODE:
+ return "hw"
+ return None
+
+
+@usingCustomLoader
+class Base(XDPCase):
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+
+ cls.src_port = 60001
+ cls.dst_port = 60002
+ cls.to_send = cls.generate_default_packets(
+ src_port=cls.src_port, dst_port=cls.dst_port)
+ cls.to_send6 = cls.generate_default_packets(
+ src_port=cls.src_port, dst_port=cls.dst_port, use_inet6=True)
+
+ def arrived(self, packets, result):
+ self.assertPacketsIn(packets, result.captured_local)
+ for i in result.captured_remote:
+ self.assertPacketContainerEmpty(i)
+
+ def not_arrived(self, packets, result):
+ self.assertPacketsNotIn(packets, result.captured_local)
+ for i in result.captured_remote:
+ self.assertPacketContainerEmpty(i)
+
+ def setUp(self):
+ subprocess.check_output([
+ XDP_FILTER, "load",
+ self.get_contexts().get_local_main().iface,
+ "--mode", get_mode_string(
+ self.get_contexts().get_local_main().xdp_mode
+ )
+ ], stderr=subprocess.STDOUT)
+
+ def tearDown(self):
+ subprocess.check_output([
+ XDP_FILTER, "unload", "--all"
+ ], stderr=subprocess.STDOUT)
diff --git a/xdp-filter/tests/test-xdp-filter.sh b/xdp-filter/tests/test-xdp-filter.sh
new file mode 100644
index 0000000..1d24c31
--- /dev/null
+++ b/xdp-filter/tests/test-xdp-filter.sh
@@ -0,0 +1,391 @@
+XDP_LOADER=${XDP_LOADER:-./xdp-loader}
+XDP_FILTER=${XDP_FILTER:-./xdp-filter}
+ALL_TESTS="test_load test_print test_output_remove test_ports_allow test_ports_deny test_ipv6_allow test_ipv6_deny test_ipv4_allow test_ipv4_deny test_ether_allow test_ether_deny test_python_basic test_python_slow"
+
+try_feat()
+{
+ local output
+
+ feat=$1
+ prog=$2
+ shift 2
+
+ output=$($XDP_FILTER load $NS --features $feat "$@" -v 2>&1)
+ ret=$?
+ if [ "$ret" -ne "0" ]; then
+ return $ret
+ fi
+ echo "$output"
+ regex="Found prog '$prog'"
+ if ! [[ $output =~ $regex ]]; then
+ echo
+ echo "Couldn't find '$regex' in output for feat $feat" >&2
+ return 1
+ fi
+ check_run $XDP_FILTER unload $NS -v
+}
+
+test_load()
+{
+
+ declare -a FEATS=(tcp udp ipv4 ipv6 ethernet all)
+ declare -a PROGS_D=(xdpfilt_dny_tcp.o xdpfilt_dny_udp.o xdpfilt_dny_ip.o xdpfilt_dny_ip.o xdpfilt_dny_eth.o xdpfilt_dny_all.o)
+ declare -a PROGS_A=(xdpfilt_alw_tcp.o xdpfilt_alw_udp.o xdpfilt_alw_ip.o xdpfilt_alw_ip.o xdpfilt_alw_eth.o xdpfilt_alw_all.o)
+ local len=${#FEATS[@]}
+
+ for (( i=0; i<$len; i++ )); do
+ if ! try_feat ${FEATS[$i]} ${PROGS_A[$i]}; then
+ return 1
+ fi
+ if ! try_feat ${FEATS[$i]} ${PROGS_A[$i]} --mode skb; then
+ return 1
+ fi
+ if ! try_feat ${FEATS[$i]} ${PROGS_D[$i]} --policy deny; then
+ return 1
+ fi
+ if ! try_feat ${FEATS[$i]} ${PROGS_D[$i]} --policy deny --mode skb; then
+ return 1
+ fi
+ done
+
+ if [ -d /sys/fs/bpf/xdp-filter ]; then
+ die "/sys/fs/bpf/xdp-filter still exists!"
+ fi
+}
+
+check_packet()
+{
+ local filter="$1"
+ local command="$2"
+ local expect="$3"
+ echo "Checking command '$command' filter '$filter'"
+ PID=$(start_background tcpdump --immediate-mode -epni $NS "$filter")
+ echo "Started listener as $PID"
+ ns_exec bash -c "$command"
+ sleep 1
+ output=$(stop_background $PID)
+ echo "$output"
+
+ if [[ "$expect" == "OK" ]]; then
+ regex="[1-9] packets? captured"
+ else
+ regex="0 packets captured"
+ fi
+
+ if [[ "$output" =~ $regex ]]; then
+ echo "Packet check $expect SUCCESS"
+ return 0
+ else
+ echo "Packet check $expect FAILURE"
+ exit 1
+ fi
+}
+
+check_port()
+{
+ local type=$1
+ local port=$2
+ local expect=$3
+ echo "$type port $port $expect"
+ [[ "$type" == "tcp" ]] && command="nc -w 1 -z $OUTSIDE_IP6 $port"
+ [[ "$type" == "udp" ]] && command="echo test | nc -w 1 -u $OUTSIDE_IP6 $port"
+
+ check_packet "$type dst port $port" "$command" $expect
+}
+
+test_ports_allow()
+{
+ local TEST_PORT=10000
+
+ # default allow mode
+ check_run $XDP_FILTER load -f udp,tcp $NS -v
+ check_port tcp $TEST_PORT OK
+ check_port udp $TEST_PORT OK
+ check_run $XDP_FILTER port $TEST_PORT -v
+ check_port tcp $TEST_PORT FAIL
+ check_port tcp $[TEST_PORT+1] OK
+ check_port udp $TEST_PORT FAIL
+ check_port udp $[TEST_PORT+1] OK
+ check_run $XDP_FILTER port -r $TEST_PORT -v
+ check_port tcp $TEST_PORT OK
+ check_port udp $TEST_PORT OK
+ check_run $XDP_FILTER unload $NS -v
+}
+
+test_ports_deny()
+{
+ local TEST_PORT=10000
+ # default deny mode
+ check_run $XDP_FILTER load -p deny -f udp,tcp $NS -v
+ check_port tcp $TEST_PORT FAIL
+ check_port udp $TEST_PORT FAIL
+ check_run $XDP_FILTER port $TEST_PORT -v
+ check_port tcp $TEST_PORT OK
+ check_port tcp $[TEST_PORT+1] FAIL
+ check_port udp $TEST_PORT OK
+ check_port udp $[TEST_PORT+1] FAIL
+ check_run $XDP_FILTER port -r $TEST_PORT -v
+ check_port tcp $TEST_PORT FAIL
+ check_port udp $TEST_PORT FAIL
+ check_run $XDP_FILTER unload $NS -v
+}
+
+check_ping6()
+{
+ check_packet "dst $OUTSIDE_IP6" "$PING6 -c 1 $OUTSIDE_IP6" $1
+}
+
+test_ipv6_allow()
+{
+ check_ping6 OK
+ check_run $XDP_FILTER load -f ipv6 $NS -v
+ check_run $XDP_FILTER ip $OUTSIDE_IP6
+ check_ping6 FAIL
+ check_run $XDP_FILTER ip -r $OUTSIDE_IP6
+ check_ping6 OK
+ check_run $XDP_FILTER ip -m src $INSIDE_IP6
+ check_ping6 FAIL
+ check_run $XDP_FILTER ip -m src -r $INSIDE_IP6
+ check_ping6 OK
+ check_run $XDP_FILTER unload $NS -v
+}
+
+test_ipv6_deny()
+{
+ check_ping6 OK
+ check_run $XDP_FILTER load -p deny -f ipv6 $NS -v
+ check_run $XDP_FILTER ip $OUTSIDE_IP6
+ check_ping6 OK
+ check_run $XDP_FILTER ip -r $OUTSIDE_IP6
+ check_ping6 FAIL
+ check_run $XDP_FILTER ip -m src $INSIDE_IP6
+ check_ping6 OK
+ check_run $XDP_FILTER ip -m src -r $INSIDE_IP6
+ check_ping6 FAIL
+ check_run $XDP_FILTER unload $NS -v
+}
+
+check_ping4()
+{
+ check_packet "dst $OUTSIDE_IP4" "ping -c 1 $OUTSIDE_IP4" $1
+}
+
+test_ipv4_allow()
+{
+ check_ping4 OK
+ check_run $XDP_FILTER load -f ipv4 $NS -v
+ check_run $XDP_FILTER ip $OUTSIDE_IP4
+ check_ping4 FAIL
+ check_run $XDP_FILTER ip -r $OUTSIDE_IP4
+ check_ping4 OK
+ check_run $XDP_FILTER ip -m src $INSIDE_IP4
+ check_ping4 FAIL
+ check_run $XDP_FILTER ip -m src -r $INSIDE_IP4
+ check_ping4 OK
+ check_run $XDP_FILTER unload $NS -v
+}
+
+test_ipv4_deny()
+{
+ check_ping4 OK
+ check_run $XDP_FILTER load -p deny -f ipv4 $NS -v
+ check_run $XDP_FILTER ip $OUTSIDE_IP4
+ check_ping4 OK
+ check_run $XDP_FILTER ip -r $OUTSIDE_IP4
+ check_ping4 FAIL
+ check_run $XDP_FILTER ip -m src $INSIDE_IP4
+ check_ping4 OK
+ check_run $XDP_FILTER ip -m src -r $INSIDE_IP4
+ check_ping4 FAIL
+ check_run $XDP_FILTER unload $NS -v
+}
+
+test_ether_allow()
+{
+ check_ping6 OK
+ check_run $XDP_FILTER load -f ethernet $NS -v
+ check_run $XDP_FILTER ether $OUTSIDE_MAC
+ check_ping6 FAIL
+ check_run $XDP_FILTER ether -r $OUTSIDE_MAC
+ check_ping6 OK
+ check_run $XDP_FILTER ether -m src $INSIDE_MAC
+ check_ping6 FAIL
+ check_run $XDP_FILTER ether -m src -r $INSIDE_MAC
+ check_ping6 OK
+ check_run $XDP_FILTER unload $NS -v
+}
+
+test_ether_deny()
+{
+ check_ping6 OK
+ check_run $XDP_FILTER load -p deny -f ethernet $NS -v
+ check_run $XDP_FILTER ether $OUTSIDE_MAC
+ check_ping6 OK
+ check_run $XDP_FILTER ether -r $OUTSIDE_MAC
+ check_ping6 FAIL
+ check_run $XDP_FILTER ether -m src $INSIDE_MAC
+ check_ping6 OK
+ check_run $XDP_FILTER ether -m src -r $INSIDE_MAC
+ check_ping6 FAIL
+ check_run $XDP_FILTER unload $NS -v
+}
+
+check_status()
+{
+ local match
+ local output
+ match="$1"
+ output=$($XDP_FILTER status)
+
+ if echo "$output" | grep -q $match; then
+ echo "Output check for $match SUCCESS"
+ return 0
+ else
+ echo "Output check for $match FAILURE"
+ echo "Output: $output"
+ exit 1
+ fi
+}
+
+check_status_no_match()
+{
+ local match
+ local output
+ match="$1"
+ output=$($XDP_FILTER status)
+
+ if echo "$output" | grep -q $match; then
+ echo "Output check for no $match FAILURE"
+ echo "Output: $output"
+ exit 1
+ else
+ echo "Output check for no $match SUCCESS"
+ return 0
+ fi
+}
+
+test_print()
+{
+ check_run $XDP_FILTER load $NS -v
+ check_run $XDP_FILTER ether aa:bb:cc:dd:ee:ff
+ check_status "aa:bb:cc:dd:ee:ff"
+ check_run $XDP_FILTER ip 1.2.3.4
+ check_status "1.2.3.4"
+ check_run $XDP_FILTER ip aa::bb
+ check_status "aa::bb"
+ check_run $XDP_FILTER port 100
+ check_status "100.*dst,tcp,udp"
+ check_run $XDP_FILTER unload $NS -v
+}
+
+check_port_removal_from_all()
+{
+ local command_options=$1
+ local expected_output=$2
+
+ local TEST_PORT=54321
+
+ check_run $XDP_FILTER port $TEST_PORT -p tcp,udp -m src,dst
+ check_status "$TEST_PORT.*src,dst,tcp,udp"
+
+ check_run $XDP_FILTER port $TEST_PORT $command_options -r
+ if [[ -z "$expected_output" ]]; then
+ check_status_no_match "$TEST_PORT"
+ else
+ check_status "$TEST_PORT.*$expected_output"
+ fi
+}
+
+test_output_remove()
+{
+ check_run $XDP_FILTER load $NS -v
+
+ # Remove only one mode/proto.
+ check_port_removal_from_all "-m src" "dst,tcp,udp"
+ check_port_removal_from_all "-m dst" "src,tcp,udp"
+ check_port_removal_from_all "-p udp" "src,dst,tcp"
+ check_port_removal_from_all "-p tcp" "src,dst,udp"
+
+ # Remove one from each.
+ check_port_removal_from_all "-m src -p udp" "dst,tcp"
+ check_port_removal_from_all "-m src -p tcp" "dst,udp"
+ check_port_removal_from_all "-m dst -p udp" "src,tcp"
+ check_port_removal_from_all "-m dst -p tcp" "src,udp"
+
+ # Remove everything.
+ check_port_removal_from_all "" ""
+ check_port_removal_from_all "-m src,dst" ""
+ check_port_removal_from_all "-p tcp,udp" ""
+ check_port_removal_from_all "-m src,dst -p tcp,udp" ""
+
+
+ check_run $XDP_FILTER unload $NS -v
+}
+
+get_python()
+{
+ if [[ -z "${PYTHON:-}" ]]; then
+ local -a possible=(python3 python)
+ local -a available
+
+ local found=0
+ for i in "${possible[@]}"; do
+ PYTHON=$(which $i)
+ if [[ $? -eq 0 ]]; then
+ found=1
+ break
+ fi
+ done
+ if [[ found -eq 0 ]]; then
+ return 1
+ fi
+ fi
+
+ $PYTHON -c "import xdp_test_harness" &> /dev/null
+ if [[ $? -ne 0 ]]; then
+ # Libraries are not installed.
+ return 1
+ fi
+
+ echo "$PYTHON"
+}
+
+run_python_test()
+{
+ local module="$1"
+ local module_path
+ local python
+
+ module_path="$(realpath --relative-to=. "$TOOL_TESTS_DIR" | sed "s/\//./g")"
+ if [[ $? -ne 0 ]] || [[ $module_path == "." ]]; then
+ return "$SKIPPED_TEST"
+ fi
+
+ python="$(get_python)"
+ if [[ $? -ne 0 ]]; then
+ return "$SKIPPED_TEST"
+ fi
+
+ $python -m xdp_test_harness.runner client "$module_path"."$module"
+ if [[ $? -ne 0 ]]; then
+ return 1
+ fi
+
+ return 0
+}
+
+test_python_basic()
+{
+ run_python_test test_basic
+}
+
+test_python_slow()
+{
+ run_python_test test_slow
+}
+
+cleanup_tests()
+{
+ $XDP_FILTER unload $NS >/dev/null 2>&1
+ $XDP_LOADER unload $NS --all >/dev/null 2>&1
+}
diff --git a/xdp-filter/tests/test_basic.py b/xdp-filter/tests/test_basic.py
new file mode 100644
index 0000000..b0e72e3
--- /dev/null
+++ b/xdp-filter/tests/test_basic.py
@@ -0,0 +1,279 @@
+import subprocess
+import os
+import signal
+
+import unittest
+import scapy
+from scapy.all import (Ether, Packet, IP, IPv6, Raw,
+ UDP, TCP, IPv6ExtHdrRouting)
+
+from xdp_test_harness.xdp_case import XDPCase, usingCustomLoader
+from xdp_test_harness.utils import XDPFlag
+
+from . common import XDP_FILTER, Base, get_mode_string
+
+
+@usingCustomLoader
+class LoadUnload(XDPCase):
+ def setUp(self):
+ self.msg = "WARNING: All tests that follow will likely provide false result.\n"
+
+ def run_wrap(self, cmd):
+ r = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ self.msg += "command: '" + str(cmd) + "'\n"
+ self.msg += "stdout: '" + r.stdout.decode().strip() + "'\n"
+ if r.stderr is not None:
+ self.msg += "stderr: '" + r.stderr.decode().strip() + "'\n"
+ self.msg += "\n"
+ return r.returncode == 0
+
+ def load(self, mode=None):
+ return self.run_wrap([
+ XDP_FILTER, "load",
+ self.get_contexts().get_local_main().iface,
+ "--verbose",
+ "--mode", get_mode_string(
+ mode if mode else self.get_contexts().get_local_main().xdp_mode
+ )
+ ])
+
+ def unload(self):
+ return self.run_wrap([
+ XDP_FILTER, "unload",
+ self.get_contexts().get_local_main().iface,
+ "--verbose"
+ ])
+
+ def test_load_once(self):
+ self.assertFalse(self.unload(), self.msg)
+ self.assertTrue(self.load(), self.msg)
+ self.assertTrue(self.unload(), self.msg)
+ self.assertFalse(self.unload(), self.msg)
+
+ def test_load_twice(self):
+ self.assertFalse(self.unload(), self.msg)
+ self.assertTrue(self.load(), self.msg)
+ self.assertFalse(self.load(), self.msg)
+ self.assertTrue(self.unload(), self.msg)
+ self.assertFalse(self.unload(), self.msg)
+
+ def test_load_hw(self):
+ self.assertFalse(self.unload(), self.msg)
+ self.load(mode=XDPFlag.HW_MODE), self.msg
+ self.unload(), self.msg
+ self.assertFalse(self.unload(), self.msg)
+
+
+class DirectBase:
+ def drop_generic(self, address, target, use_inet6=False):
+ to_send = self.to_send6 if use_inet6 else self.to_send
+
+ self.arrived(to_send, self.send_packets(to_send))
+
+ subprocess.run([XDP_FILTER, target, address,
+ "--mode", self.get_mode()])
+
+ self.not_arrived(to_send, self.send_packets(to_send))
+
+ subprocess.run([XDP_FILTER, target, address,
+ "--mode", self.get_mode(),
+ "--remove"])
+
+ self.arrived(to_send, self.send_packets(to_send))
+
+ def test_none_specified(self):
+ self.arrived(self.to_send, self.send_packets(self.to_send))
+
+ def test_ether(self):
+ self.drop_generic(self.get_device().ether, "ether")
+
+ def test_ip(self):
+ self.drop_generic(self.get_device().inet, "ip")
+
+ def test_port(self):
+ self.drop_generic(str(self.get_port()), "port")
+
+ @unittest.skipIf(XDPCase.get_contexts().get_local_main().inet6 is None or
+ XDPCase.get_contexts().get_remote_main().inet6 is None,
+ "no inet6 address available")
+ def test_ipv6(self):
+ self.drop_generic(self.get_device().inet6, "ip", use_inet6=True)
+
+
+class BaseSrc:
+ def get_device(self):
+ return self.get_contexts().get_remote_main()
+
+ def get_port(self):
+ return self.src_port
+
+ def get_mode(self):
+ return "src"
+
+
+class BaseDst:
+ def get_device(self):
+ return self.get_contexts().get_local_main()
+
+ def get_port(self):
+ return self.dst_port
+
+ def get_mode(self):
+ return "dst"
+
+
+class BaseInvert:
+ def setUp(self):
+ subprocess.run([
+ XDP_FILTER, "load",
+ "--policy", "deny",
+ self.get_contexts().get_local_main().iface,
+ "--mode", get_mode_string(
+ self.get_contexts().get_local_main().xdp_mode
+ )
+ ])
+
+ arrived = Base.not_arrived
+ not_arrived = Base.arrived
+
+
+class DirectDropSrc(Base, DirectBase, BaseSrc):
+ pass
+
+
+class DirectPassSrc(Base, DirectBase, BaseSrc, BaseInvert):
+ pass
+
+
+class DirectDropDst(Base, DirectBase, BaseDst):
+ pass
+
+
+class DirectPassDst(Base, DirectBase, BaseDst, BaseInvert):
+ pass
+
+
+class IPv6ExtensionHeader(Base):
+ def generic(self, extensions):
+ packets = [Ether() /
+ IPv6() / extensions /
+ UDP(dport=55555)] * 5
+
+ self.arrived(packets, self.send_packets(packets))
+
+ subprocess.run([XDP_FILTER,
+ "port", "55555",
+ "--mode", "dst"])
+ self.not_arrived(packets, self.send_packets(packets))
+
+ subprocess.run([XDP_FILTER,
+ "port", "55555",
+ "--mode", "dst",
+ "--remove"])
+ self.arrived(packets, self.send_packets(packets))
+
+ def test_routing(self):
+ self.generic(scapy.layers.inet6.IPv6ExtHdrRouting())
+
+ def test_hop_by_hop(self):
+ self.generic(scapy.layers.inet6.IPv6ExtHdrHopByHop())
+
+ def test_destination_options(self):
+ self.generic(scapy.layers.inet6.IPv6ExtHdrDestOpt())
+
+ def test_fragment(self):
+ self.generic(scapy.layers.inet6.IPv6ExtHdrFragment())
+
+
+class IPv4ToIPv6Mapping(Base):
+ def setUp(self):
+ super().setUp()
+
+ inet = self.get_contexts().get_local_main().inet
+
+ self.address_explicit = "::ffff:" + inet
+
+ inet6_split = [format(int(i), "02x") for i in inet.split(".")]
+ self.address_converted = "::ffff:" + \
+ inet6_split[0] + inet6_split[1] + ":" + \
+ inet6_split[2] + inet6_split[3]
+
+ self.packets = self.generate_default_packets(
+ dst_inet=self.address_explicit, use_inet6=True)
+ self.packets += self.generate_default_packets(
+ dst_inet=self.address_converted, use_inet6=True)
+
+ def test_filter_explicit_address(self):
+ self.arrived(self.packets, self.send_packets(self.packets))
+
+ subprocess.run([XDP_FILTER,
+ "ip", self.address_explicit,
+ "--mode", "dst"])
+ self.not_arrived(self.packets, self.send_packets(self.packets))
+
+ subprocess.run([XDP_FILTER,
+ "ip", self.address_explicit,
+ "--mode", "dst",
+ "--remove"])
+ self.arrived(self.packets, self.send_packets(self.packets))
+
+ def test_filter_converted_address(self):
+ self.arrived(self.packets, self.send_packets(self.packets))
+
+ subprocess.run([XDP_FILTER,
+ "ip", self.address_converted,
+ "--mode", "dst"])
+ self.not_arrived(self.packets, self.send_packets(self.packets))
+
+ subprocess.run([XDP_FILTER,
+ "ip", self.address_converted,
+ "--mode", "dst",
+ "--remove"])
+ self.arrived(self.packets, self.send_packets(self.packets))
+
+
+class Status(Base):
+ def setUp(self):
+ pass
+
+ def load(self, features):
+ return subprocess.run([
+ XDP_FILTER, "load",
+ self.get_contexts().get_local_main().iface,
+ "--mode", get_mode_string(
+ self.get_contexts().get_local_main().xdp_mode
+ ),
+ "--features", features,
+ ])
+
+ def get_status(self):
+ return subprocess.run(
+ [XDP_FILTER, "status"], capture_output=True
+ ).stdout.decode()
+
+ def test_ethernet_feature(self):
+ self.load("ethernet")
+ self.check_status("ether", self.get_contexts().get_local_main().ether)
+
+ def test_ipv4_feature(self):
+ self.load("ipv4")
+ self.check_status("ip", self.get_contexts().get_local_main().inet)
+
+ def test_udp_feature(self):
+ self.load("udp")
+ self.check_status("port", str(self.dst_port))
+
+ def test_all_features(self):
+ self.load("all")
+ self.check_status("ether", self.get_contexts().get_local_main().ether)
+ self.check_status("ip", self.get_contexts().get_local_main().inet)
+ self.check_status("port", str(self.dst_port))
+
+ def check_status(self, subcommand, address):
+ self.assertEqual(self.get_status().find(address), -1)
+
+ subprocess.run([XDP_FILTER, subcommand, address])
+ self.assertNotEqual(self.get_status().find(address), -1)
+
+ subprocess.run([XDP_FILTER, subcommand, address, "--remove"])
+ self.assertEqual(self.get_status().find(address), -1)
diff --git a/xdp-filter/tests/test_slow.py b/xdp-filter/tests/test_slow.py
new file mode 100644
index 0000000..3a1c91f
--- /dev/null
+++ b/xdp-filter/tests/test_slow.py
@@ -0,0 +1,127 @@
+import subprocess
+import os
+import signal
+
+import unittest
+from scapy.all import (Ether, Packet, IP, IPv6, Raw,
+ UDP, TCP, IPv6ExtHdrRouting)
+
+from xdp_test_harness.xdp_case import XDPCase, usingCustomLoader
+from xdp_test_harness.utils import XDPFlag
+
+from . common import Base, XDP_FILTER, get_mode_string
+
+
+class ManyAddresses(Base):
+ def format_number(self, number,
+ delimiter, format_string,
+ part_size, parts_amount):
+ splitted = []
+
+ while number > 0:
+ splitted.append(int(number % (1 << part_size)))
+ number = number >> part_size
+
+ assert(len(splitted) <= parts_amount)
+ if (len(splitted) < parts_amount):
+ splitted += [0] * (parts_amount - len(splitted))
+
+ splitted.reverse()
+
+ return delimiter.join(format(s, format_string) for s in splitted)
+
+ def generate_addresses(self,
+ delimiter, format_string, parts_amount, full_size):
+ AMOUNT = 257
+
+ bits = parts_amount * full_size
+
+ for gen_number in range(0, (1 << bits) - 1, int((1 << bits) / AMOUNT)):
+ yield self.format_number(gen_number, delimiter,
+ format_string, parts_amount, full_size)
+
+ def filter_addresses(self, name,
+ delimiter, format_string, parts_amount, full_size):
+ summed = 0
+ for address in self.generate_addresses(delimiter, format_string,
+ parts_amount, full_size):
+ summed += 1
+ subprocess.run([XDP_FILTER, name, address, "--mode", "dst"])
+
+ output = subprocess.check_output([XDP_FILTER, "status"])
+ # Each address is on a separate line.
+ self.assertGreaterEqual(len(output.splitlines()), summed)
+
+ def get_invalid_address(self, name,
+ delimiter, format_string,
+ parts_amount, full_size):
+ """
+ Try to add addresses to xdp-filter,
+ return address that does not get added.
+ """
+
+ last_length = subprocess.check_output([XDP_FILTER, "status"])
+ for address in self.generate_addresses(delimiter, format_string,
+ parts_amount, full_size):
+ new_length = subprocess.check_output(
+ [XDP_FILTER, name, address, "--mode", "dst", "--status"])
+
+ if new_length == last_length:
+ return address
+ last_length = new_length
+
+ return None
+
+ def test_ip_arrive(self):
+ missing = self.get_invalid_address("ip", ".", "d", 8, 4)
+
+ if missing is None:
+ return
+
+ to_send = self.generate_default_packets(dst_inet=missing)
+ res = self.send_packets(to_send)
+ self.not_arrived(to_send, res)
+
+ def test_ether_arrive(self):
+ missing = self.get_invalid_address("ether", ":", "02x", 8, 6)
+
+ if missing is None:
+ return
+
+ to_send = self.generate_default_packets(dst_ether=missing)
+ res = self.send_packets(to_send)
+ self.not_arrived(to_send, res)
+
+ def test_port_arrive(self):
+ missing = self.get_invalid_address("port", "", "d", 16, 1)
+
+ if missing is None:
+ return
+
+ to_send = self.generate_default_packets(dst_port=missing)
+ res = self.send_packets(to_send)
+ self.not_arrived(to_send, res)
+
+ def test_ip_status(self):
+ self.filter_addresses("ip", ".", "d", 8, 4)
+
+ def test_port_status(self):
+ self.filter_addresses("port", "", "d", 16, 1)
+
+ def test_ether_status(self):
+ self.filter_addresses("ether", ":", "02x", 8, 6)
+
+
+class ManyAddressesInverted(ManyAddresses):
+ def setUp(self):
+ subprocess.run([
+ XDP_FILTER, "load",
+ "--policy", "deny",
+ self.get_contexts().get_local_main().iface,
+ "--mode", get_mode_string(
+ self.get_contexts().get_local_main().xdp_mode
+ )
+ ])
+
+ arrived = Base.not_arrived
+ not_arrived = Base.arrived
diff --git a/xdp-filter/xdp-filter.8 b/xdp-filter/xdp-filter.8
new file mode 100644
index 0000000..c2db9b5
--- /dev/null
+++ b/xdp-filter/xdp-filter.8
@@ -0,0 +1,371 @@
+.TH "xdp-filter" "8" "SEPTEMBER 5, 2022" "V1.3.1" "A simple XDP-powered packet filter"
+
+.SH "NAME"
+xdp-filter \- a simple XDP-powered packet filter
+.SH "SYNOPSIS"
+.PP
+XDP-filter is a packet filtering utility powered by XDP. It is deliberately
+simple and so does not have the same matching capabilities as, e.g., netfilter.
+Instead, thanks to XDP, it can achieve very high drop rates: tens of millions of
+packets per second on a single CPU core.
+
+.SS "Running xdp-filter"
+.PP
+The syntax for running xdp-filter is:
+
+.RS
+.nf
+\fCxdp-filter COMMAND [options]
+
+Where COMMAND can be one of:
+ load - load xdp-filter on an interface
+ unload - unload xdp-filter from an interface
+ port - add a port to the filter list
+ ip - add an IP address to the filter list
+ ether - add an Ethernet MAC address to the filter list
+ status - show current xdp-filter status
+ poll - poll statistics output
+ help - show the list of available commands
+\fP
+.fi
+.RE
+
+.PP
+Each command, and its options are explained below. Or use \fIxdp\-filter COMMAND
+\-\-help\fP to see the options for each command.
+
+.SH "The LOAD command"
+.PP
+To use \fIxdp\-filter\fP, it must first be loaded onto an interface. This is
+accomplished with the \fIload\fP command, which takes the name of the interface as a
+parameter, and optionally allows specifying the features that should be
+included. By default all features are loaded, but de-selecting some features can
+speed up the packet matching, and increase performance by a substantial amount.
+
+.PP
+The syntax for the \fIload\fP command is:
+
+.PP
+\fIxdp\-filter load [options] <ifname>\fP
+
+.PP
+Where \fI<ifname>\fP is the name of the interface to load \fIxdp\-filter\fP onto, and
+must be specified. The supported options are:
+
+.SS "-m, --mode <mode>"
+.PP
+Specifies which mode to load the XDP program to be loaded in. The valid values
+are 'native', which is the default in-driver XDP mode, 'skb', which causes the
+so-called \fIskb mode\fP (also known as \fIgeneric XDP\fP) to be used, or 'hw' which
+causes the program to be offloaded to the hardware.
+
+.SS "-p, --policy <policy>"
+.PP
+This sets the policy \fIxdp\-filter\fP applies to packets \fBnot\fP matched by any of the
+filter rules. The default is \fIallow\fP, in which packets not matching any rules
+are allowed to pass. The other option is \fIdeny\fP, in which \fBall\fP packets are
+dropped \fBexcept\fP those matched by the filter options.
+
+.PP
+\fIxdp\-filter\fP cannot be loaded simultaneously in \fIdeny\fP and \fIallow\fP policy modes
+on the system. Note that loading \fIxdp\-filter\fP in \fIdeny\fP mode will drop all
+traffic on the interface until suitable allow rules are installed, so some care
+is needed to avoid being locked out of a remote system.
+
+.SS "-f, --features <feats>"
+.PP
+Use this option to select which features to include when loaded \fIxdp\-filter\fP.
+The default is to load all available features. So select individual features
+specify one or more of these:
+
+.IP \(bu 4
+\fBtcp\fP: Support filtering on TCP port number
+.IP \(bu 4
+\fBudp\fP: Support filtering on UDP port number
+.IP \(bu 4
+\fBipv6\fP: Support filtering on IPv6 addresses
+.IP \(bu 4
+\fBipv4\fP: Support filtering on IPv4 addresses
+.IP \(bu 4
+\fBethernet\fP: Support filtering on Ethernet MAC addresses
+
+.PP
+Specify multiple features by separating them with a comma. E.g.: \fItcp,udp,ipv6\fP.
+
+.SS "-v, --verbose"
+.PP
+Enable debug logging. Specify twice for even more verbosity.
+
+.SS "-h, --help"
+.PP
+Display a summary of the available options
+
+.SH "The UNLOAD command"
+.PP
+The \fIunload\fP command unloads \fIxdp\-filter\fP from one (or all) interfaces, and
+cleans up the program state.
+
+.PP
+The syntax for the \fIload\fP command is:
+
+.PP
+\fIxdp\-filter unload [options] <ifname>\fP
+
+.PP
+Where \fI<ifname>\fP is the name of the interface to unload \fIxdp\-filter\fP from, and
+must be specified unless the \fB--all\fP option is used. The supported options are:
+
+.SS "-a, --all"
+.PP
+Specify this option to remove \fIxdp\-filter\fP from all interfaces it was loaded
+onto. If this option is specified, no \fI<ifname>\fP is needed.
+
+.PP
+This option can also be used to clean up all \fIxdp\-filter\fP state if the XDP
+program(s) were unloaded by other means.
+
+.SS "-k, --keep-maps"
+.PP
+Specify this option to prevent \fIxdp\-filter\fP from clearing its map state. By
+default, all BPF maps no longer needed by any loaded program are removed.
+However, this will also remove the contents of the maps (the filtering rules),
+so this option can be used to keep the maps around so the rules persist until
+\fIxdp\-filter\fP is loaded again.
+
+.SS "-v, --verbose"
+.PP
+Enable debug logging. Specify twice for even more verbosity.
+
+.SS "-h, --help"
+.PP
+Display a summary of the available options
+
+.SH "The PORT command"
+.PP
+Use the \fIport\fP command to add a TCP or UDP port to the \fIxdp\-filter\fP match list.
+For this to work, \fIxdp\-filter\fP must be loaded with either the \fBudp\fP or the \fBtcp\fP
+feature (or both) on at least one interface.
+
+.PP
+The syntax for the \fIport\fP command is:
+
+.PP
+\fIxdp\-filter port [options] <port>\fP
+
+.PP
+Where \fI<port>\fP is the port number to add (or remove if the \fB--remove\fP is
+specified). The supported options are:
+
+.SS "-r, --remove"
+.PP
+Remove the port instead of adding it.
+
+.SS "-m, --mode <mode>"
+.PP
+Select filtering mode. Valid options are \fBsrc\fP and \fBdst\fP, both of which may be
+specified as \fIsrc,dst\fP. If \fBsrc\fP is specified, the port number will added as a
+\fIsource port\fP match, while if \fBdst\fP is specified, the port number will be added
+as a \fIdestination port\fP match. If both are specified, a packet will be matched
+if \fBeither\fP its source or destination port is the specified port number.
+
+.SS "-p, --proto <proto>"
+.PP
+Specify one (or both) of \fBudp\fP and/or \fBtcp\fP to match UDP or TCP ports,
+respectively.
+
+.SS "-s, --status"
+.PP
+If this option is specified, the current list of matched ports will be printed
+after inserting the port number. Otherwise, nothing will be printed.
+
+.SS "-v, --verbose"
+.PP
+Enable debug logging. Specify twice for even more verbosity.
+
+.SS "-h, --help"
+.PP
+Display a summary of the available options
+
+
+.SH "The IP command"
+.PP
+Use the \fIip\fP command to add an IPv6 or an IPv4 address to the \fIxdp\-filter\fP match
+list.
+
+.PP
+The syntax for the \fIip\fP command is:
+
+.PP
+\fIxdp\-filter ip [options] <ip>\fP
+
+.PP
+Where \fI<ip>\fP is the IP address to add (or remove if the \fB--remove\fP is
+specified). Either IPv4 or IPv6 addresses can be specified, but \fIxdp\-filter\fP
+must be loaded with the corresponding features (\fBipv4\fP and \fBipv6\fP,
+respectively). The supported options are:
+
+.SS "-r, --remove"
+.PP
+Remove the IP address instead of adding it.
+
+.SS "-m, --mode <mode>"
+.PP
+Select filtering mode. Valid options are \fBsrc\fP and \fBdst\fP, both of which may be
+specified as \fIsrc,dst\fP. If \fBsrc\fP is specified, the IP address will added as a
+\fIsource IP\fP match, while if \fBdst\fP is specified, the IP address will be added
+as a \fIdestination IP\fP match. If both are specified, a packet will be matched
+if \fBeither\fP its source or destination IP is the specified IP address.
+
+.SS "-s, --status"
+.PP
+If this option is specified, the current list of matched ips will be printed
+after inserting the IP address. Otherwise, nothing will be printed.
+
+.SS "-v, --verbose"
+.PP
+Enable debug logging. Specify twice for even more verbosity.
+
+.SS "-h, --help"
+.PP
+Display a summary of the available options
+
+.SH "The ETHER command"
+.PP
+Use the \fIether\fP command to add an Ethernet MAC address to the \fIxdp\-filter\fP match
+list. For this to work, \fIxdp\-filter\fP must be loaded with either the \fBethernet\fP
+feature on at least one interface.
+
+.PP
+The syntax for the \fIether\fP command is:
+
+.PP
+\fIxdp\-filter ether [options] <addr>\fP
+
+.PP
+Where \fI<addr>\fP is the MAC address to add (or remove if the \fB--remove\fP is
+specified). The supported options are:
+
+.SS "-r, --remove"
+.PP
+Remove the MAC address instead of adding it.
+
+.SS "-m, --mode <mode>"
+.PP
+Select filtering mode. Valid options are \fBsrc\fP and \fBdst\fP, both of which may be
+specified as \fIsrc,dst\fP. If \fBsrc\fP is specified, the MAC address will added as a
+\fIsource MAC\fP match, while if \fBdst\fP is specified, the MAC address will be added
+as a \fIdestination MAC\fP match. If both are specified, a packet will be matched
+if \fBeither\fP its source or destination MAC is the specified MAC address.
+
+.SS "-s, --status"
+.PP
+If this option is specified, the current list of matched ips will be printed
+after inserting the MAC address. Otherwise, nothing will be printed.
+
+.SS "-v, --verbose"
+.PP
+Enable debug logging. Specify twice for even more verbosity.
+
+.SS "-h, --help"
+.PP
+Display a summary of the available options
+
+.SH "The STATUS command"
+.PP
+The \fIstatus\fP command prints the current status of \fIxdp\-filter\fP: Which interfaces
+it is loaded on, the current list of rules, and some statistics for how many
+packets have been processed in total, and how many times each rule has been hit.
+
+.PP
+The syntax for the \fIstatus\fP command is:
+
+.PP
+\fIxdp\-filter status [options]\fP
+
+.PP
+Where the supported options are:
+
+.SS "-v, --verbose"
+.PP
+Enable debug logging. Specify twice for even more verbosity.
+
+.SS "-h, --help"
+.PP
+Display a summary of the available options
+
+.SH "The POLL command"
+.PP
+The \fIpoll\fP command periodically polls the \fIxdp\-filter\fP statistics map and prints
+out the total number of packets and bytes processed by \fIxdp\-filter\fP, as well as
+the number in the last polling interval, converted to packets (and bytes) per
+second. This can be used to inspect the performance of \fIxdp\-filter\fP, and to
+compare the performance of the different feature sets selectable by the \fIload\fP
+parameter.
+
+.PP
+The syntax for the \fIpoll\fP command is:
+
+.PP
+\fIxdp\-filter poll [options]\fP
+
+.PP
+Where the supported options are:
+
+.SS "-i, --interval <interval>"
+.PP
+The polling interval, in milliseconds. Defaults to 1000 (1 second).
+
+.SS "-v, --verbose"
+.PP
+Enable debug logging. Specify twice for even more verbosity.
+
+.SS "-h, --help"
+.PP
+Display a summary of the available options
+
+.SH "Examples"
+.PP
+To filter all packets arriving on port 80 on eth0, issue the
+following commands:
+
+.RS
+.nf
+\fC# xdp-filter load eth0 -f tcp,udp
+# xdp-filter port 80
+\fP
+.fi
+.RE
+
+.PP
+To filter all packets \fBexcept\fP those from IP address fc00:dead:cafe::1 issue the
+following commands (careful, this can lock you out of remote access!):
+
+.RS
+.nf
+\fC# xdp-filter load eth0 -f ipv6 -p deny
+# xdp-filter ip fc00:dead:cafe::1 -m src
+\fP
+.fi
+.RE
+
+.PP
+To allow packets from \fBeither\fP IP fc00:dead:cafe::1 \fBor\fP arriving on port 22,
+issue the following (careful, this can lock you out of remote access!):
+
+.RS
+.nf
+\fC# xdp-filter load eth0 -f ipv6,tcp -p deny
+# xdp-filter port 22
+# xdp-filter ip fc00:dead:cafe::1 -m src
+\fP
+.fi
+.RE
+
+.SH "BUGS"
+.PP
+Please report any bugs on Github: \fIhttps://github.com/xdp-project/xdp-tools/issues\fP
+
+.SH "AUTHOR"
+.PP
+xdp-filter was written by Toke Høiland-Jørgensen and Jesper Dangaard Brouer.
+This man page was written by Toke Høiland-Jørgensen.
diff --git a/xdp-filter/xdp-filter.c b/xdp-filter/xdp-filter.c
new file mode 100644
index 0000000..0396a1a
--- /dev/null
+++ b/xdp-filter/xdp-filter.c
@@ -0,0 +1,1097 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <inttypes.h>
+
+#include <bpf/bpf.h>
+#include <bpf/libbpf.h>
+#include <xdp/libxdp.h>
+#include <arpa/inet.h>
+
+#include <linux/if_ether.h>
+
+#include "params.h"
+#include "logging.h"
+#include "util.h"
+#include "stats.h"
+#include "common_kern_user.h"
+#include "prog_features.h"
+
+#define NEED_RLIMIT (20 * 1024 * 1024) /* 10 Mbyte */
+#define PROG_NAME "xdp-filter"
+
+struct flag_val map_flags_all[] = {
+ {"src", MAP_FLAG_SRC},
+ {"dst", MAP_FLAG_DST},
+ {"tcp", MAP_FLAG_TCP},
+ {"udp", MAP_FLAG_UDP},
+ {}
+};
+
+struct flag_val map_flags_srcdst[] = {
+ {"src", MAP_FLAG_SRC},
+ {"dst", MAP_FLAG_DST},
+ {}
+};
+
+struct flag_val map_flags_tcpudp[] = {
+ {"tcp", MAP_FLAG_TCP},
+ {"udp", MAP_FLAG_UDP},
+ {}
+};
+
+static char *find_prog_file(__u32 features)
+{
+ struct prog_feature *feat;
+
+ if (!features)
+ return NULL;
+
+ for (feat = prog_features; feat->prog_name; feat++) {
+ if ((ntohl(feat->features) & features) == features)
+ return strdup(feat->prog_name);
+ }
+ return NULL;
+}
+
+static __u32 find_features(const char *progname)
+{
+ struct prog_feature *feat;
+
+ for (feat = prog_features; feat->prog_name; feat++) {
+ if (is_prefix(progname, feat->prog_name))
+ return ntohl(feat->features);
+ }
+ return 0;
+}
+
+static int map_get_counter_flags(int fd, void *key, __u64 *counter, __u8 *flags)
+{
+ /* For percpu maps, userspace gets a value per possible CPU */
+ int nr_cpus = libbpf_num_possible_cpus();
+ __u64 sum_ctr = 0;
+ int i, err = 0;
+ __u64 *values;
+
+ if (nr_cpus < 0)
+ return nr_cpus;
+
+ values = calloc(nr_cpus, sizeof(*values));
+ if (!values)
+ return -ENOMEM;
+
+ if ((bpf_map_lookup_elem(fd, key, values)) != 0) {
+ err = -ENOENT;
+ goto out;
+ }
+
+ /* Sum values from each CPU */
+ for (i = 0; i < nr_cpus; i++) {
+ __u8 flg = values[i] & MAP_FLAGS;
+
+ if (!flg) {
+ err = -ENOENT; /* not set */
+ goto out;
+ }
+ *flags = flg;
+ sum_ctr += values[i] >> COUNTER_SHIFT;
+ }
+ *counter = sum_ctr;
+
+out:
+ free(values);
+ return err;
+}
+
+static int map_set_flags(int fd, void *key, __u8 flags, bool delete_empty)
+{
+ /* For percpu maps, userspace gets a value per possible CPU */
+ int nr_cpus = libbpf_num_possible_cpus();
+ __u64 *values;
+ int i, err;
+
+ if (nr_cpus < 0)
+ return nr_cpus;
+
+ values = calloc(nr_cpus, sizeof(*values));
+ if (!values)
+ return -ENOMEM;
+
+ if (bpf_map_lookup_elem(fd, key, values) != 0) {
+ memset(values, 0, sizeof(*values) * nr_cpus);
+ } else if (!flags && delete_empty) {
+ pr_debug("Deleting empty map value from flags %u\n", flags);
+
+ err = bpf_map_delete_elem(fd, key);
+ if (err) {
+ err = -errno;
+ pr_warn("Couldn't delete value from state map: %s\n",
+ strerror(-err));
+ }
+ goto out;
+ }
+
+ for (i = 0; i < nr_cpus; i++)
+ values[i] = flags ? (values[i] & ~MAP_FLAGS) | (flags & MAP_FLAGS) : 0;
+
+ pr_debug("Setting new map value %" PRIu64 " from flags %u\n",
+ (uint64_t)values[0], flags);
+
+ err = bpf_map_update_elem(fd, key, values, 0);
+ if (err) {
+ err = -errno;
+ if (err == -E2BIG)
+ pr_warn("Couldn't add entry: state map is full\n");
+ else
+ pr_warn("Unable to update state map: %s\n", strerror(-err));
+ }
+
+out:
+ free(values);
+ return err;
+}
+
+static int get_iface_features(__unused const struct iface *iface,
+ struct xdp_program *prog,
+ __unused enum xdp_attach_mode mode, void *arg)
+{
+ __u32 *all_feats = arg;
+
+ *all_feats |= find_features(xdp_program__name(prog));
+ return 0;
+}
+
+static int get_used_features(const char *pin_root_path, __u32 *feats)
+{
+ __u32 all_feats = 0;
+ int err;
+
+ err = iterate_pinned_programs(pin_root_path, get_iface_features,
+ &all_feats);
+ if (err && err != -ENOENT)
+ return err;
+
+ *feats = all_feats;
+ return 0;
+}
+
+static const struct loadopt {
+ bool help;
+ struct iface iface;
+ unsigned int features;
+ enum xdp_attach_mode mode;
+ unsigned int policy_mode;
+} defaults_load = {
+ .features = FEAT_ALL,
+ .mode = XDP_MODE_NATIVE,
+ .policy_mode = FEAT_ALLOW,
+};
+
+struct flag_val load_features[] = {
+ {"tcp", FEAT_TCP},
+ {"udp", FEAT_UDP},
+ {"ipv6", FEAT_IPV6},
+ {"ipv4", FEAT_IPV4},
+ {"ethernet", FEAT_ETHERNET},
+ {"all", FEAT_ALL},
+ {}
+};
+
+struct flag_val print_features[] = {
+ {"tcp", FEAT_TCP},
+ {"udp", FEAT_UDP},
+ {"ipv6", FEAT_IPV6},
+ {"ipv4", FEAT_IPV4},
+ {"ethernet", FEAT_ETHERNET},
+ {"allow", FEAT_ALLOW},
+ {"deny", FEAT_DENY},
+ {}
+};
+
+struct enum_val xdp_modes[] = {
+ {"native", XDP_MODE_NATIVE},
+ {"skb", XDP_MODE_SKB},
+ {"hw", XDP_MODE_HW},
+ {NULL, 0}
+};
+
+struct enum_val policy_modes[] = {
+ {"allow", FEAT_ALLOW},
+ {"deny", FEAT_DENY},
+ {NULL, 0}
+};
+
+static struct prog_option load_options[] = {
+ DEFINE_OPTION("mode", OPT_ENUM, struct loadopt, mode,
+ .short_opt = 'm',
+ .typearg = xdp_modes,
+ .metavar = "<mode>",
+ .help = "Load XDP program in <mode>; default native"),
+ DEFINE_OPTION("policy", OPT_ENUM, struct loadopt, policy_mode,
+ .short_opt = 'p',
+ .typearg = policy_modes,
+ .metavar = "<policy>",
+ .help = "Policy for unmatched packets; default allow"),
+ DEFINE_OPTION("dev", OPT_IFNAME, struct loadopt, iface,
+ .positional = true,
+ .metavar = "<ifname>",
+ .required = true,
+ .help = "Load on device <ifname>"),
+ DEFINE_OPTION("features", OPT_FLAGS, struct loadopt, features,
+ .short_opt = 'f',
+ .metavar = "<feats>",
+ .typearg = load_features,
+ .help = "Features to enable; default all"),
+ END_OPTIONS
+};
+
+int do_load(const void *cfg, const char *pin_root_path)
+{
+ char errmsg[STRERR_BUFSIZE], featbuf[100];
+ const struct loadopt *opt = cfg;
+ struct xdp_program *p = NULL;
+ int err = EXIT_SUCCESS;
+ unsigned int features;
+ __u32 used_feats;
+ char *filename;
+ DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts,
+ .pin_root_path = pin_root_path);
+ DECLARE_LIBXDP_OPTS(xdp_program_opts, xdp_opts, 0);
+
+ if (opt->mode == XDP_MODE_HW) {
+ pr_warn("xdp-filter does not support offloading.\n");
+ return EXIT_FAILURE;
+ }
+
+ err = get_used_features(pin_root_path, &used_feats);
+ if (err) {
+ pr_warn("Error getting list of loaded programs: %s\n",
+ strerror(-err));
+ return err;
+ }
+
+ features = opt->features;
+ if (opt->policy_mode == FEAT_DENY && used_feats & FEAT_ALLOW) {
+ pr_warn("xdp-filter is already loaded in allow policy mode. "
+ "Unload before loading in deny mode.\n");
+ return EXIT_FAILURE;
+ } else if (opt->policy_mode == FEAT_ALLOW && used_feats & FEAT_DENY) {
+ pr_warn("xdp-filter is already loaded in deny policy mode. "
+ "Unload before loading in allow mode.\n");
+ return EXIT_FAILURE;
+ }
+ features |= opt->policy_mode;
+
+ err = get_pinned_program(&opt->iface, pin_root_path, NULL, &p);
+ if (!err) {
+ pr_warn("xdp-filter is already loaded on %s\n", opt->iface.ifname);
+ xdp_program__close(p);
+ return EXIT_FAILURE;
+ }
+
+ print_flags(featbuf, sizeof(featbuf), print_features, features);
+ pr_debug("Looking for eBPF program with features %s\n", featbuf);
+
+ filename = find_prog_file(features);
+ if (!filename) {
+ pr_warn("Couldn't find an eBPF program with the requested feature set!\n");
+ return EXIT_FAILURE;
+ }
+
+ pr_debug("Found prog '%s' matching feature set to be loaded on interface '%s'.\n",
+ filename, opt->iface.ifname);
+
+ /* libbpf spits out a lot of unhelpful error messages while loading.
+ * Silence the logging so we can provide our own messages instead; this
+ * is a noop if verbose logging is enabled.
+ */
+ silence_libbpf_logging();
+
+retry:
+ xdp_opts.find_filename = filename;
+ xdp_opts.opts = &opts;
+ /* prog_name is NULL, so choose the first program in object */
+ p = xdp_program__create(&xdp_opts);
+ err = libxdp_get_error(p);
+ if (err) {
+ if (err == -EPERM && !double_rlimit())
+ goto retry;
+
+ libxdp_strerror(err, errmsg, sizeof(errmsg));
+ pr_warn("Couldn't load BPF program: %s(%d)\n", errmsg, err);
+ p = NULL;
+ goto out;
+ }
+
+ err = attach_xdp_program(p, &opt->iface, opt->mode, pin_root_path);
+ if (err) {
+ if (err == -EPERM && !double_rlimit()) {
+ xdp_program__close(p);
+ goto retry;
+ }
+
+ libxdp_strerror(err, errmsg, sizeof(errmsg));
+ pr_warn("Couldn't attach XDP program on iface '%s': %s(%d)\n",
+ opt->iface.ifname, errmsg, err);
+ goto out;
+ }
+
+out:
+ if (p)
+ xdp_program__close(p);
+ free(filename);
+ return err;
+}
+
+static int remove_unused_maps(const char *pin_root_path, __u32 features)
+{
+ int dir_fd, err = 0;
+
+ dir_fd = open(pin_root_path, O_DIRECTORY);
+ if (dir_fd < 0) {
+ if (errno == ENOENT)
+ return 0;
+ err = -errno;
+ pr_warn("Unable to open pin directory %s: %s\n",
+ pin_root_path, strerror(-err));
+ goto out;
+ }
+
+ if (!(features & (FEAT_TCP | FEAT_UDP))) {
+ err = unlink_pinned_map(dir_fd, textify(MAP_NAME_PORTS));
+ if (err)
+ goto out;
+ }
+
+ if (!(features & FEAT_IPV4)) {
+ err = unlink_pinned_map(dir_fd, textify(MAP_NAME_IPV4));
+ if (err)
+ goto out;
+ }
+
+ if (!(features & FEAT_IPV6)) {
+ err = unlink_pinned_map(dir_fd, textify(MAP_NAME_IPV6));
+ if (err)
+ goto out;
+ }
+
+ if (!(features & FEAT_ETHERNET)) {
+ err = unlink_pinned_map(dir_fd, textify(MAP_NAME_ETHERNET));
+ if (err)
+ goto out;
+ }
+
+ if (!features) {
+ char buf[PATH_MAX];
+
+ err = unlink_pinned_map(dir_fd, textify(XDP_STATS_MAP_NAME));
+ if (err)
+ goto out;
+
+ close(dir_fd);
+ dir_fd = -1;
+
+ err = try_snprintf(buf, sizeof(buf), "%s/%s", pin_root_path, "programs");
+ if (err)
+ goto out;
+
+ pr_debug("Removing program directory %s\n", buf);
+ err = rmdir(buf);
+ if (err) {
+ err = -errno;
+ pr_warn("Unable to rmdir: %s\n", strerror(-err));
+ goto out;
+ }
+
+ pr_debug("Removing pinning directory %s\n", pin_root_path);
+ err = rmdir(pin_root_path);
+ if (err) {
+ err = -errno;
+ pr_warn("Unable to rmdir: %s\n", strerror(-err));
+ goto out;
+ }
+ }
+
+out:
+ if (dir_fd >= 0)
+ close(dir_fd);
+
+ return err;
+}
+
+static int remove_iface_program(const struct iface *iface,
+ struct xdp_program *prog,
+ enum xdp_attach_mode mode, void *arg)
+{
+ char errmsg[STRERR_BUFSIZE], buf[100];
+ char *pin_root_path = arg;
+ __u32 feats;
+ int err;
+
+ feats = find_features(xdp_program__name(prog));
+ if (!feats) {
+ pr_warn("Unrecognised XDP program on interface %s. Not removing.\n",
+ iface->ifname);
+ return -ENOENT;
+ }
+
+ print_flags(buf, sizeof(buf), print_features, feats);
+ pr_debug("Removing XDP program with features %s from iface %s\n",
+ buf, iface->ifname);
+
+ err = detach_xdp_program(prog, iface, mode, pin_root_path);
+ if (err) {
+ libxdp_strerror(err, errmsg, sizeof(errmsg));
+ pr_warn("Removing XDP program on iface %s failed (%d): %s\n",
+ iface->ifname, -err, errmsg);
+ }
+
+ return err;
+}
+
+
+static const struct unloadopt {
+ bool all;
+ bool keep;
+ struct iface iface;
+} defaults_unload = {};
+
+static struct prog_option unload_options[] = {
+ DEFINE_OPTION("dev", OPT_IFNAME, struct unloadopt, iface,
+ .positional = true,
+ .metavar = "<ifname>",
+ .help = "Unload from device <ifname>"),
+ DEFINE_OPTION("all", OPT_BOOL, struct unloadopt, all,
+ .short_opt = 'a',
+ .help = "Unload from all interfaces"),
+ DEFINE_OPTION("keep-maps", OPT_BOOL, struct unloadopt, keep,
+ .short_opt = 'k',
+ .help = "Don't destroy unused maps after unloading"),
+ END_OPTIONS
+};
+
+int do_unload(const void *cfg, const char *pin_root_path)
+{
+ const struct unloadopt *opt = cfg;
+ enum xdp_attach_mode mode;
+ struct xdp_program *prog;
+ int err = EXIT_SUCCESS;
+ char buf[100];
+ __u32 feats;
+ DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts,
+ .pin_root_path = pin_root_path);
+
+ if (opt->all) {
+ pr_debug("Removing xdp-filter from all interfaces\n");
+ err = iterate_pinned_programs(pin_root_path,
+ remove_iface_program,
+ (void *)pin_root_path);
+ if (err && err != -ENOENT)
+ goto out;
+ goto clean_maps;
+ }
+
+ if (!opt->iface.ifindex) {
+ pr_warn("Must specify ifname or --all\n");
+ err = EXIT_FAILURE;
+ goto out;
+ }
+
+ err = get_pinned_program(&opt->iface, pin_root_path, &mode, &prog);
+ if (err) {
+ pr_warn("xdp-filter is not loaded on %s\n", opt->iface.ifname);
+ err = EXIT_FAILURE;
+ goto out;
+ }
+
+ err = remove_iface_program(&opt->iface, prog, mode,
+ (void *)pin_root_path);
+ if (err)
+ goto out;
+
+clean_maps:
+ if (opt->keep) {
+ pr_debug("Not removing pinned maps because of --keep-maps option\n");
+ goto out;
+ }
+
+ pr_debug("Checking map usage and removing unused maps\n");
+ err = get_used_features(pin_root_path, &feats);
+ if (err)
+ goto out;
+
+ print_flags(buf, sizeof(buf), print_features, feats);
+ pr_debug("Features still being used: %s\n", feats ? buf : "none");
+
+ err = remove_unused_maps(pin_root_path, feats);
+ if (err)
+ goto out;
+
+out:
+ return err;
+}
+
+int print_ports(int map_fd)
+{
+ __u32 map_key = -1, prev_key = 0;
+ int err;
+
+ printf("Filtered ports:\n");
+ printf(" %-40s Mode Hit counter\n", "");
+ FOR_EACH_MAP_KEY (err, map_fd, map_key, prev_key) {
+ char buf[100];
+ __u64 counter;
+ __u8 flags = 0;
+
+ err = map_get_counter_flags(map_fd, &map_key, &counter, &flags);
+ if (err == -ENOENT)
+ continue;
+ else if (err)
+ return err;
+
+ print_flags(buf, sizeof(buf), map_flags_all, flags);
+ printf(" %-40u %-15s %" PRIu64 "\n", ntohs(map_key), buf,
+ (uint64_t)counter);
+ }
+ return 0;
+}
+
+static const struct portopt {
+ unsigned int mode;
+ unsigned int proto;
+ __u16 port;
+ bool print_status;
+ bool remove;
+} defaults_port = {};
+
+static struct prog_option port_options[] = {
+ DEFINE_OPTION("port", OPT_U16, struct portopt, port,
+ .positional = true,
+ .metavar = "<port>",
+ .required = true,
+ .help = "Port to add or remove"),
+ DEFINE_OPTION("remove", OPT_BOOL, struct portopt, remove,
+ .short_opt = 'r',
+ .help = "Remove port instead of adding"),
+ DEFINE_OPTION("mode", OPT_FLAGS, struct portopt, mode,
+ .short_opt = 'm',
+ .metavar = "<mode>",
+ .typearg = map_flags_srcdst,
+ .help = "Filter mode; default dst"),
+ DEFINE_OPTION("proto", OPT_FLAGS, struct portopt, proto,
+ .short_opt = 'p',
+ .metavar = "<proto>",
+ .typearg = map_flags_tcpudp,
+ .help = "Protocol to filter; default tcp,udp"),
+ DEFINE_OPTION("status", OPT_BOOL, struct portopt, print_status,
+ .short_opt = 's',
+ .help = "Print status of filtered ports after changing"),
+ END_OPTIONS
+};
+
+
+int do_port(const void *cfg, const char *pin_root_path)
+{
+ int map_fd = -1, err = EXIT_SUCCESS;
+ char modestr[100], protostr[100];
+ const struct portopt *opt = cfg;
+ unsigned int proto = opt->proto;
+ unsigned int mode = opt->mode;
+ struct bpf_map_info info = {};
+ __u8 flags = 0;
+ __u64 counter;
+ __u32 map_key;
+
+ map_fd = get_pinned_map_fd(pin_root_path, textify(MAP_NAME_PORTS), &info);
+ if (map_fd < 0) {
+ pr_warn("Couldn't find port filter map; is xdp-filter loaded "
+ "with the right features (udp and/or tcp)?\n");
+ err = EXIT_FAILURE;
+ goto out;
+ }
+ pr_debug("Found map with fd %d for map id %d\n", map_fd, info.id);
+
+ map_key = htons(opt->port);
+
+ err = map_get_counter_flags(map_fd, &map_key, &counter, &flags);
+ if (err && err != -ENOENT)
+ goto out;
+
+ if (opt->remove) {
+ if (mode == 0 && proto == 0) {
+ mode = MAP_FLAG_SRC | MAP_FLAG_DST;
+ proto = MAP_FLAG_TCP | MAP_FLAG_UDP;
+ }
+
+ flags &= ~(mode | proto);
+ } else {
+ if (mode == 0)
+ mode = MAP_FLAG_DST;
+ if (proto == 0)
+ proto = MAP_FLAG_TCP | MAP_FLAG_UDP;
+
+ flags |= mode | proto;
+ }
+
+ print_flags(modestr, sizeof(modestr), map_flags_srcdst, mode);
+ print_flags(protostr, sizeof(protostr), map_flags_tcpudp, proto);
+ pr_debug("%s %s port %u mode %s\n", opt->remove ? "Removing" : "Adding",
+ protostr, opt->port, modestr);
+
+ if (!(flags & (MAP_FLAG_DST | MAP_FLAG_SRC)) ||
+ !(flags & (MAP_FLAG_TCP | MAP_FLAG_UDP)))
+ flags = 0;
+
+ err = map_set_flags(map_fd, &map_key, flags, false);
+ if (err)
+ goto out;
+
+ if (opt->print_status) {
+ err = print_ports(map_fd);
+ if (err)
+ goto out;
+ }
+
+out:
+ if (map_fd >= 0)
+ close(map_fd);
+ return err;
+}
+
+int __print_ips(int map_fd, int af)
+{
+ struct ip_addr map_key = { .af = af }, prev_key = {};
+ int err;
+
+ FOR_EACH_MAP_KEY (err, map_fd, map_key.addr, prev_key.addr) {
+ char flagbuf[100], addrbuf[100];
+ __u8 flags = 0;
+ __u64 counter;
+
+ err = map_get_counter_flags(map_fd, &map_key.addr, &counter, &flags);
+ if (err == -ENOENT)
+ continue;
+ else if (err)
+ return err;
+
+ print_flags(flagbuf, sizeof(flagbuf), map_flags_srcdst, flags);
+ print_addr(addrbuf, sizeof(addrbuf), &map_key);
+ printf(" %-40s %-15s %" PRIu64 "\n", addrbuf, flagbuf,
+ (uint64_t)counter);
+ }
+
+ return 0;
+}
+
+int print_ips()
+{
+ int map_fd4 = -1, map_fd6 = -1;
+ char pin_root_path[PATH_MAX];
+ int err = 0;
+
+ err = get_bpf_root_dir(pin_root_path, sizeof(pin_root_path), PROG_NAME,
+ true);
+ if (err)
+ goto out;
+
+ map_fd6 = get_pinned_map_fd(pin_root_path, textify(MAP_NAME_IPV6), NULL);
+ map_fd4 = get_pinned_map_fd(pin_root_path, textify(MAP_NAME_IPV4), NULL);
+ if (map_fd4 < 0 && map_fd6 < 0) {
+ err = -ENOENT;
+ goto out;
+ }
+
+ printf("Filtered IP addresses:\n");
+ printf(" %-40s Mode Hit counter\n", "");
+
+ if (map_fd6 >= 0) {
+ err = __print_ips(map_fd6, AF_INET6);
+ if (err)
+ goto out;
+ }
+
+ if (map_fd4 >= 0)
+ err = __print_ips(map_fd4, AF_INET);
+
+out:
+ if (map_fd4 >= 0)
+ close(map_fd4);
+ if (map_fd6 >= 0)
+ close(map_fd6);
+ return err;
+}
+
+
+static int __do_address(const char *pin_root_path,
+ const char *map_name, const char *feat_name,
+ void *map_key, bool remove, int mode)
+{
+ int map_fd = -1, err = 0;
+ __u8 flags = 0;
+ __u64 counter;
+
+ map_fd = get_pinned_map_fd(pin_root_path, map_name, NULL);
+ if (map_fd < 0) {
+ pr_warn("Couldn't find filter map; is xdp-filter loaded "
+ "with the %s feature?\n", feat_name);
+ err = -ENOENT;
+ goto out;
+ }
+
+ err = map_get_counter_flags(map_fd, map_key, &counter, &flags);
+ if (err && err != -ENOENT)
+ goto out;
+
+ if (remove)
+ flags &= ~mode;
+ else
+ flags |= mode;
+
+ err = map_set_flags(map_fd, map_key, flags, true);
+ if (err)
+ goto out;
+
+out:
+ return err ?: map_fd;
+}
+
+static const struct ipopt {
+ unsigned int mode;
+ struct ip_addr addr;
+ bool print_status;
+ bool remove;
+} defaults_ip = {
+ .mode = MAP_FLAG_DST,
+};
+
+static struct prog_option ip_options[] = {
+ DEFINE_OPTION("addr", OPT_IPADDR, struct ipopt, addr,
+ .positional = true,
+ .metavar = "<addr>",
+ .required = true,
+ .help = "Address to add or remove"),
+ DEFINE_OPTION("remove", OPT_BOOL, struct ipopt, remove,
+ .short_opt = 'r',
+ .help = "Remove address instead of adding"),
+ DEFINE_OPTION("mode", OPT_FLAGS, struct ipopt, mode,
+ .short_opt = 'm',
+ .metavar = "<mode>",
+ .typearg = map_flags_srcdst,
+ .help = "Filter mode; default dst"),
+ DEFINE_OPTION("status", OPT_BOOL, struct ipopt, print_status,
+ .short_opt = 's',
+ .help = "Print status of filtered addresses after changing"),
+ END_OPTIONS
+};
+
+static int do_ip(const void *cfg, const char *pin_root_path)
+{
+ int map_fd = -1, err = EXIT_SUCCESS;
+ char modestr[100], addrstr[100];
+ const struct ipopt *opt = cfg;
+ struct ip_addr addr = opt->addr;
+ bool v6;
+
+ print_flags(modestr, sizeof(modestr), map_flags_srcdst, opt->mode);
+ print_addr(addrstr, sizeof(addrstr), &opt->addr);
+ pr_debug("%s addr %s mode %s\n", opt->remove ? "Removing" : "Adding",
+ addrstr, modestr);
+
+ v6 = (opt->addr.af == AF_INET6);
+
+ map_fd = __do_address(pin_root_path,
+ v6 ? textify(MAP_NAME_IPV6) : textify(MAP_NAME_IPV4),
+ v6 ? "ipv6" : "ipv4",
+ &addr.addr, opt->remove, opt->mode);
+ if (map_fd < 0) {
+ err = map_fd;
+ goto out;
+ }
+
+ if (opt->print_status) {
+ err = print_ips();
+ if (err)
+ goto out;
+ }
+
+out:
+ if (map_fd >= 0)
+ close(map_fd);
+ return err;
+}
+
+int print_ethers(int map_fd)
+{
+ struct mac_addr map_key = {}, prev_key = {};
+ int err;
+
+ printf("Filtered MAC addresses:\n");
+ printf(" %-40s Mode Hit counter\n", "");
+ FOR_EACH_MAP_KEY (err, map_fd, map_key, prev_key) {
+ char modebuf[100], addrbuf[100];
+ __u8 flags = 0;
+ __u64 counter;
+
+ err = map_get_counter_flags(map_fd, &map_key, &counter, &flags);
+ if (err == -ENOENT)
+ continue;
+ else if (err)
+ return err;
+
+ print_flags(modebuf, sizeof(modebuf), map_flags_srcdst, flags);
+ print_macaddr(addrbuf, sizeof(addrbuf), &map_key);
+ printf(" %-40s %-15s %" PRIu64 "\n", addrbuf, modebuf,
+ (uint64_t)counter);
+ }
+ return 0;
+}
+
+static const struct etheropt {
+ unsigned int mode;
+ struct mac_addr addr;
+ bool print_status;
+ bool remove;
+} defaults_ether = {
+ .mode = MAP_FLAG_DST,
+};
+
+static struct prog_option ether_options[] = {
+ DEFINE_OPTION("addr", OPT_MACADDR, struct etheropt, addr,
+ .positional = true,
+ .metavar = "<addr>",
+ .required = true,
+ .help = "Address to add or remove"),
+ DEFINE_OPTION("remove", OPT_BOOL, struct etheropt, remove,
+ .short_opt = 'r',
+ .help = "Remove address instead of adding"),
+ DEFINE_OPTION("mode", OPT_FLAGS, struct etheropt, mode,
+ .short_opt = 'm',
+ .metavar = "<mode>",
+ .typearg = map_flags_srcdst,
+ .help = "Filter mode; default dst"),
+ DEFINE_OPTION("status", OPT_BOOL, struct etheropt, print_status,
+ .short_opt = 's',
+ .help = "Print status of filtered addresses after changing"),
+ END_OPTIONS
+};
+
+static int do_ether(const void *cfg, const char *pin_root_path)
+{
+ int err = EXIT_SUCCESS, map_fd = -1;
+ const struct etheropt *opt = cfg;
+ struct mac_addr addr = opt->addr;
+ char modestr[100], addrstr[100];
+
+ print_flags(modestr, sizeof(modestr), map_flags_srcdst, opt->mode);
+ print_macaddr(addrstr, sizeof(addrstr), &opt->addr);
+ pr_debug("%s addr %s mode %s\n", opt->remove ? "Removing" : "Adding",
+ addrstr, modestr);
+
+ map_fd = __do_address(pin_root_path, textify(MAP_NAME_ETHERNET),
+ "ethernet", &addr.addr, opt->remove, opt->mode);
+ if (map_fd < 0) {
+ err = map_fd;
+ goto out;
+ }
+
+ if (opt->print_status) {
+ err = print_ethers(map_fd);
+ if (err)
+ goto out;
+ }
+
+out:
+ if (map_fd >= 0)
+ close(map_fd);
+ return err;
+}
+
+static struct prog_option status_options[] = { END_OPTIONS };
+
+int print_iface_status(const struct iface *iface, struct xdp_program *prog,
+ enum xdp_attach_mode mode, __unused void *arg)
+{
+ __u32 feat = 0;
+ int err;
+ printf("%s\n", xdp_program__name(prog));
+
+ err = get_iface_features(iface, prog, XDP_MODE_UNSPEC, &feat);
+ if (err)
+ return err;
+
+ if (feat) {
+ char featbuf[100];
+ char namebuf[100];
+
+ print_flags(featbuf, sizeof(featbuf), print_features, feat);
+ snprintf(namebuf, sizeof(namebuf), "%s (%s mode)", iface->ifname,
+ get_enum_name(xdp_modes, mode));
+ printf(" %-40s %s\n", namebuf, featbuf);
+ }
+ return 0;
+}
+
+int do_status(__unused const void *cfg, const char *pin_root_path)
+{
+ int err = EXIT_SUCCESS, map_fd = -1;
+ struct bpf_map_info info = {};
+ struct stats_record rec = {};
+
+ map_fd = get_pinned_map_fd(pin_root_path, textify(XDP_STATS_MAP_NAME), &info);
+ if (map_fd < 0) {
+ err = map_fd;
+ pr_warn("Couldn't find stats map. Maybe xdp-filter is not loaded?\n");
+ goto out;
+ }
+ rec.stats[XDP_DROP].enabled = true;
+ rec.stats[XDP_PASS].enabled = true;
+ rec.stats[XDP_ABORTED].enabled = true;
+
+ err = stats_collect(map_fd, info.type, &rec);
+ if (err)
+ goto out;
+
+ printf("CURRENT XDP-FILTER STATUS:\n\n");
+ printf("Aggregate per-action statistics:\n");
+ err = stats_print_one(&rec);
+ if (err)
+ goto out;
+ printf("\n");
+
+ printf("Loaded on interfaces:\n");
+ printf(" %-40s Enabled features\n", "");
+
+ err = iterate_pinned_programs(pin_root_path, print_iface_status,
+ NULL);
+ if (err)
+ goto out;
+ printf("\n");
+
+ map_fd = get_pinned_map_fd(pin_root_path, textify(MAP_NAME_PORTS), NULL);
+ if (map_fd >= 0) {
+ err = print_ports(map_fd);
+ if (err)
+ goto out;
+ printf("\n");
+ close(map_fd);
+ map_fd = -1;
+ }
+
+ err = print_ips();
+ if (err && err != -ENOENT)
+ goto out;
+
+ printf("\n");
+
+ map_fd = get_pinned_map_fd(pin_root_path, textify(MAP_NAME_ETHERNET), NULL);
+ if (map_fd >= 0) {
+ err = print_ethers(map_fd);
+ if (err)
+ goto out;
+ }
+
+ printf("\n");
+
+out:
+ if (map_fd >= 0)
+ close(map_fd);
+ return err;
+}
+
+static const struct pollopt {
+ __u32 interval;
+} defaults_poll = { .interval = 1000 };
+
+static struct prog_option poll_options[] = {
+ DEFINE_OPTION("interval", OPT_U32, struct pollopt, interval,
+ .short_opt = 'i',
+ .metavar = "<interval>",
+ .help = "Polling interval in milliseconds (default 1000)"),
+ END_OPTIONS
+};
+
+int do_poll(const void *cfg, const char *pin_root_path)
+{
+ int err = 0, map_fd = -1;
+ const struct pollopt *opt = cfg;
+ bool exit = false;
+
+ if (!opt->interval) {
+ err = -EINVAL;
+ pr_warn("Can't use a polling interval of 0\n");
+ goto out;
+ }
+
+ map_fd = get_pinned_map_fd(pin_root_path, textify(XDP_STATS_MAP_NAME), NULL);
+ if (map_fd < 0) {
+ err = map_fd;
+ pr_warn("Couldn't find stats map. Maybe xdp-filter is not loaded?\n");
+ goto out;
+ }
+
+ prog_lock_release(0);
+ err = stats_poll(map_fd, opt->interval, &exit,
+ pin_root_path, textify(XDP_STATS_MAP_NAME));
+ if (err) {
+ pr_warn("Error polling statistics: %s\n", strerror(-err));
+ goto out;
+ }
+
+out:
+ return err ? EXIT_FAILURE : EXIT_SUCCESS;
+}
+
+int do_help(__unused const void *cfg, __unused const char *pin_root_path)
+{
+ fprintf(stderr,
+ "Usage: xdp-filter COMMAND [options]\n"
+ "\n"
+ "COMMAND can be one of:\n"
+ " load - load xdp-filter on an interface\n"
+ " unload - unload xdp-filter from an interface\n"
+ " port - add a port to the filter list\n"
+ " ip - add an IP address to the filter list\n"
+ " ether - add an Ethernet MAC address to the filter list\n"
+ " status - show current xdp-filter status\n"
+ " poll - poll statistics output\n"
+ " help - show this help message\n"
+ "\n"
+ "Use 'xdp-filter COMMAND --help' to see options for each command\n");
+ return -1;
+}
+
+static const struct prog_command cmds[] = {
+ DEFINE_COMMAND(load, "Load xdp-filter on an interface"),
+ DEFINE_COMMAND(unload, "Unload xdp-filter from an interface"),
+ DEFINE_COMMAND(port, "Add or remove ports from xdp-filter"),
+ DEFINE_COMMAND(ip, "Add or remove IP addresses from xdp-filter"),
+ DEFINE_COMMAND(ether, "Add or remove MAC addresses from xdp-filter"),
+ DEFINE_COMMAND(poll, "Poll xdp-filter statistics"),
+ DEFINE_COMMAND_NODEF(status, "Show xdp-filter status"),
+ { .name = "help", .func = do_help, .no_cfg = true },
+ END_COMMANDS
+};
+
+union all_opts {
+ struct loadopt load;
+ struct unloadopt unload;
+ struct portopt port;
+ struct ipopt ip;
+ struct etheropt ether;
+ struct pollopt poll;
+};
+
+int main(int argc, char **argv)
+{
+ if (argc > 1)
+ return dispatch_commands(argv[1], argc - 1, argv + 1, cmds,
+ sizeof(union all_opts), PROG_NAME, true);
+
+ return do_help(NULL, NULL);
+}
diff --git a/xdp-filter/xdpfilt_alw_all.c b/xdp-filter/xdpfilt_alw_all.c
new file mode 100644
index 0000000..5d1588b
--- /dev/null
+++ b/xdp-filter/xdpfilt_alw_all.c
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#define FILT_MODE_ALLOW
+#define FILT_MODE_ETHERNET
+#define FILT_MODE_IPV4
+#define FILT_MODE_IPV6
+#define FILT_MODE_UDP
+#define FILT_MODE_TCP
+#define FUNCNAME xdpfilt_alw_all
+#include "xdpfilt_prog.h"
diff --git a/xdp-filter/xdpfilt_alw_eth.c b/xdp-filter/xdpfilt_alw_eth.c
new file mode 100644
index 0000000..62327bd
--- /dev/null
+++ b/xdp-filter/xdpfilt_alw_eth.c
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#define FILT_MODE_ALLOW
+#define FILT_MODE_ETHERNET
+#undef FILT_MODE_IPV4
+#undef FILT_MODE_IPV6
+#undef FILT_MODE_UDP
+#undef FILT_MODE_TCP
+#define FUNCNAME xdpfilt_alw_eth
+#include "xdpfilt_prog.h"
diff --git a/xdp-filter/xdpfilt_alw_ip.c b/xdp-filter/xdpfilt_alw_ip.c
new file mode 100644
index 0000000..3b7f72f
--- /dev/null
+++ b/xdp-filter/xdpfilt_alw_ip.c
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#define FILT_MODE_ALLOW
+#undef FILT_MODE_ETHERNET
+#define FILT_MODE_IPV4
+#define FILT_MODE_IPV6
+#undef FILT_MODE_UDP
+#undef FILT_MODE_TCP
+#define FUNCNAME xdpfilt_alw_ip
+#include "xdpfilt_prog.h"
diff --git a/xdp-filter/xdpfilt_alw_tcp.c b/xdp-filter/xdpfilt_alw_tcp.c
new file mode 100644
index 0000000..2c32ec3
--- /dev/null
+++ b/xdp-filter/xdpfilt_alw_tcp.c
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#define FILT_MODE_ALLOW
+#undef FILT_MODE_ETHERNET
+#undef FILT_MODE_IPV4
+#undef FILT_MODE_IPV6
+#undef FILT_MODE_UDP
+#define FILT_MODE_TCP
+#define FUNCNAME xdpfilt_alw_tcp
+#include "xdpfilt_prog.h"
diff --git a/xdp-filter/xdpfilt_alw_udp.c b/xdp-filter/xdpfilt_alw_udp.c
new file mode 100644
index 0000000..d9b7cf3
--- /dev/null
+++ b/xdp-filter/xdpfilt_alw_udp.c
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#define FILT_MODE_ALLOW
+#undef FILT_MODE_ETHERNET
+#undef FILT_MODE_IPV4
+#undef FILT_MODE_IPV6
+#define FILT_MODE_UDP
+#undef FILT_MODE_TCP
+#define FUNCNAME xdpfilt_alw_udp
+#include "xdpfilt_prog.h"
diff --git a/xdp-filter/xdpfilt_dny_all.c b/xdp-filter/xdpfilt_dny_all.c
new file mode 100644
index 0000000..05e006a
--- /dev/null
+++ b/xdp-filter/xdpfilt_dny_all.c
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#define FILT_MODE_DENY
+#define FILT_MODE_ETHERNET
+#define FILT_MODE_IPV4
+#define FILT_MODE_IPV6
+#define FILT_MODE_UDP
+#define FILT_MODE_TCP
+#define FUNCNAME xdpfilt_dny_all
+#include "xdpfilt_prog.h"
diff --git a/xdp-filter/xdpfilt_dny_eth.c b/xdp-filter/xdpfilt_dny_eth.c
new file mode 100644
index 0000000..237715c
--- /dev/null
+++ b/xdp-filter/xdpfilt_dny_eth.c
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#define FILT_MODE_DENY
+#define FILT_MODE_ETHERNET
+#undef FILT_MODE_IPV4
+#undef FILT_MODE_IPV6
+#undef FILT_MODE_UDP
+#undef FILT_MODE_TCP
+#define FUNCNAME xdpfilt_dny_eth
+#include "xdpfilt_prog.h"
diff --git a/xdp-filter/xdpfilt_dny_ip.c b/xdp-filter/xdpfilt_dny_ip.c
new file mode 100644
index 0000000..94447ea
--- /dev/null
+++ b/xdp-filter/xdpfilt_dny_ip.c
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#define FILT_MODE_DENY
+#undef FILT_MODE_ETHERNET
+#define FILT_MODE_IPV4
+#define FILT_MODE_IPV6
+#undef FILT_MODE_UDP
+#undef FILT_MODE_TCP
+#define FUNCNAME xdpfilt_dny_ip
+#include "xdpfilt_prog.h"
diff --git a/xdp-filter/xdpfilt_dny_tcp.c b/xdp-filter/xdpfilt_dny_tcp.c
new file mode 100644
index 0000000..d8a78b4
--- /dev/null
+++ b/xdp-filter/xdpfilt_dny_tcp.c
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#define FILT_MODE_DENY
+#undef FILT_MODE_ETHERNET
+#undef FILT_MODE_IPV4
+#undef FILT_MODE_IPV6
+#undef FILT_MODE_UDP
+#define FILT_MODE_TCP
+#define FUNCNAME xdpfilt_dny_tcp
+#include "xdpfilt_prog.h"
diff --git a/xdp-filter/xdpfilt_dny_udp.c b/xdp-filter/xdpfilt_dny_udp.c
new file mode 100644
index 0000000..2393232
--- /dev/null
+++ b/xdp-filter/xdpfilt_dny_udp.c
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#define FILT_MODE_DENY
+#undef FILT_MODE_ETHERNET
+#undef FILT_MODE_IPV4
+#undef FILT_MODE_IPV6
+#define FILT_MODE_UDP
+#undef FILT_MODE_TCP
+#define FUNCNAME xdpfilt_dny_udp
+#include "xdpfilt_prog.h"
diff --git a/xdp-filter/xdpfilt_prog.h b/xdp-filter/xdpfilt_prog.h
new file mode 100644
index 0000000..fbc781e
--- /dev/null
+++ b/xdp-filter/xdpfilt_prog.h
@@ -0,0 +1,257 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/* XDP filter program fragment. This header file contains the full-featured
+ * program, split up with ifdefs. The actual program files xdpfilt_*.c
+ * include this file with different #defines to create the
+ * different eBPF program sections that include only the needed features.
+ */
+
+#ifndef __XDPFILT_PROG_H
+#define __XDPFILT_PROG_H
+
+#include <linux/bpf.h>
+#include <linux/in.h>
+#include <bpf/bpf_helpers.h>
+#include <xdp/xdp_helpers.h>
+
+#include "common_kern_user.h"
+
+/* Defines xdp_stats_map */
+#include "xdp/xdp_stats_kern.h"
+#include "xdp/parsing_helpers.h"
+
+#ifdef FILT_MODE_DENY
+#define VERDICT_HIT XDP_PASS
+#define VERDICT_MISS XDP_DROP
+#define FEATURE_OPMODE FEAT_DENY
+#else
+#define VERDICT_HIT XDP_DROP
+#define VERDICT_MISS XDP_PASS
+#define FEATURE_OPMODE FEAT_ALLOW
+#endif
+
+#define CHECK_RET(ret) \
+ do { \
+ if ((ret) < 0) { \
+ action = XDP_ABORTED; \
+ goto out; \
+ } \
+ } while (0)
+
+#define CHECK_VERDICT(type, param) \
+ do { \
+ if ((action = lookup_verdict_##type(param)) != VERDICT_MISS) \
+ goto out; \
+ } while (0)
+
+#define CHECK_MAP(map, key, mask) \
+ do { \
+ __u64 *value; \
+ value = bpf_map_lookup_elem(map, key); \
+ if ((value) && (*(value) & (mask)) == (mask)) { \
+ *value += (1 << COUNTER_SHIFT); \
+ return VERDICT_HIT; \
+ } \
+ } while (0)
+
+#if defined(FILT_MODE_TCP) || defined(FILT_MODE_UDP)
+struct {
+ __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
+ __uint(max_entries, 65536);
+ __type(key, __u32);
+ __type(value, __u64);
+ __uint(pinning, LIBBPF_PIN_BY_NAME);
+} MAP_NAME_PORTS SEC(".maps");
+
+#ifdef FILT_MODE_TCP
+static int __always_inline lookup_verdict_tcp(struct tcphdr *tcphdr)
+{
+ __u32 key;
+
+ key = tcphdr->dest;
+ CHECK_MAP(&filter_ports, &key, MAP_FLAG_DST | MAP_FLAG_TCP);
+ key = tcphdr->source;
+ CHECK_MAP(&filter_ports, &key, MAP_FLAG_SRC | MAP_FLAG_TCP);
+ return VERDICT_MISS;
+}
+#define FEATURE_TCP FEAT_TCP
+#else
+#define FEATURE_TCP 0
+#endif
+
+#ifdef FILT_MODE_UDP
+static int __always_inline lookup_verdict_udp(struct udphdr *udphdr)
+{
+ __u32 key;
+
+ key = udphdr->dest;
+ CHECK_MAP(&filter_ports, &key, MAP_FLAG_DST | MAP_FLAG_UDP);
+ key = udphdr->source;
+ CHECK_MAP(&filter_ports, &key, MAP_FLAG_SRC | MAP_FLAG_UDP);
+ return VERDICT_MISS;
+}
+#define FEATURE_UDP FEAT_UDP
+#else
+#define FEATURE_UDP 0
+#endif
+
+#else
+#define FEATURE_UDP 0
+#define FEATURE_TCP 0
+#endif /* TCP || UDP */
+
+#ifdef FILT_MODE_IPV4
+struct {
+ __uint(type, BPF_MAP_TYPE_PERCPU_HASH);
+ __uint(max_entries, 10000);
+ __type(key, __u32);
+ __type(value, __u64);
+ __uint(pinning, LIBBPF_PIN_BY_NAME);
+} MAP_NAME_IPV4 SEC(".maps");
+
+static int __always_inline lookup_verdict_ipv4(struct iphdr *iphdr)
+{
+ __u32 addr;
+ addr = iphdr->daddr;
+ CHECK_MAP(&filter_ipv4, &addr, MAP_FLAG_DST);
+ addr = iphdr->saddr;
+ CHECK_MAP(&filter_ipv4, &addr, MAP_FLAG_SRC);
+ return VERDICT_MISS;
+}
+
+#define CHECK_VERDICT_IPV4(param) CHECK_VERDICT(ipv4, param)
+#define FEATURE_IPV4 FEAT_IPV4
+#else
+#define FEATURE_IPV4 0
+#define CHECK_VERDICT_IPV4(param)
+#endif /* FILT_MODE_IPV4 */
+
+#ifdef FILT_MODE_IPV6
+struct {
+ __uint(type, BPF_MAP_TYPE_PERCPU_HASH);
+ __uint(max_entries, 10000);
+ __type(key, struct in6_addr);
+ __type(value, __u64);
+ __uint(pinning, LIBBPF_PIN_BY_NAME);
+} MAP_NAME_IPV6 SEC(".maps");
+
+static int __always_inline lookup_verdict_ipv6(struct ipv6hdr *ipv6hdr)
+{
+ struct in6_addr addr;
+
+ addr = ipv6hdr->daddr;
+ CHECK_MAP(&filter_ipv6, &addr, MAP_FLAG_DST);
+ addr = ipv6hdr->saddr;
+ CHECK_MAP(&filter_ipv6, &addr, MAP_FLAG_SRC);
+ return VERDICT_MISS;
+}
+
+#define CHECK_VERDICT_IPV6(param) CHECK_VERDICT(ipv6, param)
+#define FEATURE_IPV6 FEAT_IPV6
+#else
+#define FEATURE_IPV6 0
+#define CHECK_VERDICT_IPV6(param)
+#endif /* FILT_MODE_IPV6 */
+
+#ifdef FILT_MODE_ETHERNET
+struct ethaddr {
+ __u8 addr[ETH_ALEN];
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_PERCPU_HASH);
+ __uint(max_entries, 10000);
+ __type(key, struct ethaddr);
+ __type(value, __u64);
+ __uint(pinning, LIBBPF_PIN_BY_NAME);
+} MAP_NAME_ETHERNET SEC(".maps");
+
+static int __always_inline lookup_verdict_ethernet(struct ethhdr *eth)
+{
+ struct ethaddr addr = {};
+
+ __builtin_memcpy(&addr, eth->h_dest, sizeof(addr));
+ CHECK_MAP(&filter_ethernet, &addr, MAP_FLAG_DST);
+ __builtin_memcpy(&addr, eth->h_source, sizeof(addr));
+ CHECK_MAP(&filter_ethernet, &addr, MAP_FLAG_SRC);
+ return VERDICT_MISS;
+}
+
+#define CHECK_VERDICT_ETHERNET(param) CHECK_VERDICT(ethernet, param)
+#define FEATURE_ETHERNET FEAT_ETHERNET
+#else
+#define FEATURE_ETHERNET 0
+#define CHECK_VERDICT_ETHERNET(param)
+#endif /* FILT_MODE_ETHERNET */
+
+#ifndef FUNCNAME
+#define FUNCNAME xdp_filt_unknown
+#endif
+
+struct {
+ __uint(priority, 10);
+ __uint(XDP_PASS, 1);
+} XDP_RUN_CONFIG(FUNCNAME);
+
+SEC("xdp")
+int FUNCNAME(struct xdp_md *ctx)
+{
+ void *data_end = (void *)(long)ctx->data_end;
+ void *data = (void *)(long)ctx->data;
+ __u32 action = VERDICT_MISS; /* Default action */
+ struct hdr_cursor nh;
+ struct ethhdr *eth;
+ int eth_type;
+
+ nh.pos = data;
+ eth_type = parse_ethhdr(&nh, data_end, &eth);
+ CHECK_RET(eth_type);
+ CHECK_VERDICT_ETHERNET(eth);
+
+#if defined(FILT_MODE_IPV4) || defined(FILT_MODE_IPV6) || \
+ defined(FILT_MODE_TCP) || defined(FILT_MODE_UDP)
+ struct iphdr *iphdr;
+ struct ipv6hdr *ipv6hdr;
+ int ip_type;
+ if (eth_type == bpf_htons(ETH_P_IP)) {
+ ip_type = parse_iphdr(&nh, data_end, &iphdr);
+ CHECK_RET(ip_type);
+
+ CHECK_VERDICT_IPV4(iphdr);
+ } else if (eth_type == bpf_htons(ETH_P_IPV6)) {
+ ip_type = parse_ip6hdr(&nh, data_end, &ipv6hdr);
+ CHECK_RET(ip_type);
+
+ CHECK_VERDICT_IPV6(ipv6hdr);
+ } else {
+ goto out;
+ }
+
+#ifdef FILT_MODE_UDP
+ struct udphdr *udphdr;
+ if (ip_type == IPPROTO_UDP) {
+ CHECK_RET(parse_udphdr(&nh, data_end, &udphdr));
+ CHECK_VERDICT(udp, udphdr);
+ }
+#endif /* FILT_MODE_UDP */
+
+#ifdef FILT_MODE_TCP
+ struct tcphdr *tcphdr;
+ if (ip_type == IPPROTO_TCP) {
+ CHECK_RET(parse_tcphdr(&nh, data_end, &tcphdr));
+ CHECK_VERDICT(tcp, tcphdr);
+ }
+#endif /* FILT_MODE_TCP*/
+#endif /* FILT_MODE_{IPV4,IPV6,TCP,UDP} */
+out:
+ return xdp_stats_record_action(ctx, action);
+}
+
+char _license[] SEC("license") = "GPL";
+__u32 _features SEC("features") = (FEATURE_ETHERNET | FEATURE_IPV4 |
+ FEATURE_IPV6 | FEATURE_UDP | FEATURE_TCP |
+ FEATURE_OPMODE);
+
+#else
+#error "Multiple includes of xdpfilt_prog.h"
+#endif // include guard