samples: net: pkt_filter: Add a sample to demo packet filtering

Add a network packet filtering sample to show how the packet
filtering can be used.

Signed-off-by: Jukka Rissanen <jukka.rissanen@nordicsemi.no>
This commit is contained in:
Jukka Rissanen 2025-04-11 14:54:29 +03:00 committed by Benjamin Cabé
commit 3064c7b11d
7 changed files with 365 additions and 0 deletions

View file

@ -0,0 +1,10 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(pkt_filter)
target_sources(app PRIVATE src/main.c)
include(${ZEPHYR_BASE}/samples/net/common/common.cmake)

View file

@ -0,0 +1,7 @@
# Private config options for pkt_filter sample app
# Copyright (c) 2025 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
source "samples/net/common/Kconfig"
source "Kconfig.zephyr"

View file

@ -0,0 +1,120 @@
.. zephyr:code-sample:: net-pkt-filter
:name: Network packet filter
:relevant-api: net_pkt_filter
Install network packet filter hooks.
Overview
********
This sample shows how to set network packet filters from a user application.
The source code for this sample application can be found at:
:zephyr_file:`samples/net/pkt_filter`.
Requirements
************
- :ref:`networking_with_host`
Building and Running
********************
A good way to run this sample application is with QEMU or native_sim board
as described in :ref:`networking_with_host`.
For demo purposes, the VLAN support needs to be enabled in host side like this.
Execute these commands in a terminal window:
.. code-block:: console
$ cd tools/net-tools
$ ./net-setup.sh -c zeth-vlan.conf
Then follow these steps to build the network packet filter sample application for
either ``qemu_x86`` or ``native_sim`` boards:
.. zephyr-app-commands::
:zephyr-app: samples/net/pkt_filter
:board: <board to use>
:conf: "prj.conf overlay-vlan.conf"
:goals: build
:compact:
In this example, we enable VLAN support with these settings:
The VLAN overlay configuration file :zephyr_file:`samples/net/pkt_filter/overlay-vlan.conf`
creates two virtual LAN networks with these settings:
- VLAN tag 100: IPv4 198.51.100.1 and IPv6 2001:db8:100::1
- VLAN tag 200: IPv4 203.0.113.1 and IPv6 2001:db8:200::1
In network shell, you can monitor the network packet filters:
.. code-block:: console
uart:~$ net filter
Rule Type Verdict Tests
[ 1] recv OK 3 eth vlan type[0x0800],size max[200],iface[2]
[ 2] recv OK 3 eth vlan type[0x0800],size min[100],iface[3]
[ 3] recv OK 1 iface[1]
[ 4] recv OK 2 eth vlan type[0x0806],iface[2]
[ 5] recv OK 2 eth vlan type[0x0806],iface[3]
[ 6] recv DROP 0
The above sample application network packet filter rules can be interpreted
like this:
* Rule 1: Allow IPv4 (Ethernet type 0x0800) packets with max size 200 bytes
to network interface 2 which is the first VLAN interface.
* Rule 2: Allow IPv4 packets with min size 100 bytes to network interface 3
which is the second VLAN interface.
* Rule 3: Allow all incoming traffic to Ethernet interface 1
* Rule 4: Allow ARP packets (Ethernet type 0x0806) to VLAN interface 2
* Rule 5: Allow ARP packets (Ethernet type 0x0806) to VLAN interface 3
* Rule 6: Drop all other packets. This also means that IPv6 packets are
dropped.
The network statistics can be used to see that the packets are dropped.
Use ``net stats`` command to monitor statistics.
You can verify the rules from network shell:
.. code-block:: console
uart:~$ net ping 2001:db8:100::2 -c 2
PING 2001:db8:100::2
Ping timeout
uart:~$ net stats 2
Interface 0x8089c6c (Virtual) [2]
==================================
IPv6 recv 0 sent 3 drop 0 forwarded 0
IPv6 ND recv 0 sent 7 drop 1
IPv6 MLD recv 0 sent 0 drop 0
ICMP recv 0 sent 3 drop 0
...
Filter drop rx 10 tx 0
Bytes received 320
Bytes sent 660
Processing err 10
uart:~$ net ping 198.51.100.2 -c 1
PING 198.51.100.2
28 bytes from 198.51.100.2 to 198.51.100.1: icmp_seq=1 ttl=64 time=100 ms
uart:~$ net ping 198.51.100.2 -c 1 -s 201
PING 198.51.100.2
Ping timeout
uart:~$ net ping 203.0.113.2 -c 1
PING 203.0.113.2
Ping timeout
uart:~$ net ping 203.0.113.2 -c 1 -s 101
PING 203.0.113.2
125 bytes from 203.0.113.2 to 203.0.113.1: icmp_seq=1 ttl=64 time=20 ms

View file

@ -0,0 +1,23 @@
CONFIG_NET_VLAN=y
# Allow useful interface assigned to VLAN interface
CONFIG_NET_INTERFACE_NAME_LEN=15
# We have one non-vlan interface and two VLAN interfaces
CONFIG_NET_VLAN_COUNT=2
# There will be three network interfaces so allocate enough IPv4 and IPv6 configs.
CONFIG_NET_IF_MAX_IPV4_COUNT=3
CONFIG_NET_IF_MAX_IPV6_COUNT=3
# First ethernet interface will use these settings
CONFIG_NET_CONFIG_MY_IPV6_ADDR="2001:db8::1"
CONFIG_NET_CONFIG_PEER_IPV6_ADDR="2001:db8::2"
CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1"
CONFIG_NET_CONFIG_PEER_IPV4_ADDR="192.0.2.2"
# First VLAN interface will have these settings
CONFIG_NET_SAMPLE_COMMON_VLAN_SETUP_1="100;2001:db8:100::1/64,198.51.100.1/24"
# Second VLAN interface will have these settings
CONFIG_NET_SAMPLE_COMMON_VLAN_SETUP_2="200;2001:db8:200::1/64,203.0.113.1/24"

View file

@ -0,0 +1,53 @@
# Generic network options
CONFIG_NETWORKING=y
CONFIG_NET_LOG=y
CONFIG_NET_IPV6=y
CONFIG_NET_IPV4=y
CONFIG_NET_DHCPV4=n
CONFIG_NET_UDP=y
CONFIG_NET_TCP=y
# Enable packet filtering
CONFIG_NET_PKT_FILTER=y
CONFIG_NET_PKT_FILTER_IPV4_HOOK=y
CONFIG_NET_PKT_FILTER_IPV6_HOOK=y
CONFIG_NET_PKT_FILTER_LOCAL_IN_HOOK=y
# Network statistics options
CONFIG_NET_STATISTICS=y
CONFIG_NET_STATISTICS_USER_API=y
CONFIG_NET_STATISTICS_PER_INTERFACE=y
CONFIG_NET_STATISTICS_ETHERNET=y
CONFIG_TEST_RANDOM_GENERATOR=y
# Network packet configuration
CONFIG_NET_PKT_RX_COUNT=32
CONFIG_NET_PKT_TX_COUNT=32
CONFIG_NET_BUF_RX_COUNT=32
CONFIG_NET_BUF_TX_COUNT=32
# IP address configuration
CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=5
CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=5
CONFIG_NET_IF_UNICAST_IPV4_ADDR_COUNT=1
CONFIG_NET_MAX_CONTEXTS=10
CONFIG_INIT_STACKS=y
CONFIG_PRINTK=y
CONFIG_NET_SHELL=y
# Application configuration
CONFIG_NET_CONFIG_NEED_IPV6=y
CONFIG_NET_CONFIG_NEED_IPV4=y
CONFIG_NET_CONFIG_SETTINGS=y
# IP address configuration
CONFIG_NET_CONFIG_MY_IPV6_ADDR="2001:db8::1"
CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1"
CONFIG_NET_CONFIG_PEER_IPV6_ADDR="2001:db8::2"
CONFIG_NET_CONFIG_PEER_IPV4_ADDR="192.0.2.2"
# Logging
CONFIG_LOG=y
CONFIG_LOG_BUFFER_SIZE=4096

View file

@ -0,0 +1,14 @@
sample:
description: Network packet filtering functionality
name: Network packet filter sample app
tests:
sample.net.pkt_filter:
harness: net
min_ram: 64
tags:
- net
- statistics
- net_pkt_filter
depends_on: netif
integration_platforms:
- native_sim

View file

@ -0,0 +1,138 @@
/*
* Copyright (c) 2025 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(net_stats_sample, LOG_LEVEL_DBG);
#include <zephyr/kernel.h>
#include <errno.h>
#include <zephyr/net/net_core.h>
#include <zephyr/net/net_if.h>
#include <zephyr/net/ethernet.h>
#include <zephyr/net/net_pkt_filter.h>
#include "net_sample_common.h"
#define MAX_INTERFACES 3
static struct net_if *interfaces[MAX_INTERFACES];
/* Interface match rule. The interface value is set at runtime in init_app() */
static NPF_IFACE_MATCH(match_iface_vlan1, NULL);
static NPF_IFACE_MATCH(match_iface_vlan2, NULL);
static NPF_IFACE_MATCH(match_iface_eth, NULL);
/* Allow all traffic to Ethernet interface, drop VLAN traffic except
* couple of exceptions for IPv4 and IPv6
*/
static NPF_RULE(eth_iface_rule, NET_OK, match_iface_eth);
/* Match max size rule */
static NPF_SIZE_MAX(maxsize_200, 200);
static NPF_ETH_VLAN_TYPE_MATCH(ipv4_packet1, NET_ETH_PTYPE_IP);
static NPF_RULE(small_ipv4_pkt, NET_OK, ipv4_packet1, maxsize_200,
match_iface_vlan1);
/* Match min size rule */
static NPF_SIZE_MIN(minsize_100, 100);
static NPF_ETH_VLAN_TYPE_MATCH(ipv4_packet2, NET_ETH_PTYPE_IP);
static NPF_RULE(large_ipv4_pkt, NET_OK, ipv4_packet2, minsize_100,
match_iface_vlan2);
/* Allow ARP for VLAN interface */
static NPF_ETH_VLAN_TYPE_MATCH(arp_packet, NET_ETH_PTYPE_ARP);
static NPF_RULE(arp_pkt_vlan1, NET_OK, arp_packet, match_iface_vlan1);
static NPF_RULE(arp_pkt_vlan2, NET_OK, arp_packet, match_iface_vlan2);
static void iface_cb(struct net_if *iface, void *user_data)
{
int count = 0;
ARG_UNUSED(user_data);
ARRAY_FOR_EACH(interfaces, i) {
if (interfaces[i] == NULL) {
interfaces[i] = iface;
return;
}
count++;
}
LOG_ERR("Too many interfaces %d (max is %d)", count, MAX_INTERFACES);
}
static void init_app(void)
{
struct net_if *iface, *eth = NULL, *vlan1 = NULL, *vlan2 = NULL;
int found = 0;
ARRAY_FOR_EACH(interfaces, i) {
if (interfaces[i] == NULL) {
continue;
}
iface = interfaces[i];
if (net_eth_is_vlan_interface(iface)) {
if (vlan1 == NULL) {
vlan1 = iface;
} else if (vlan2 == NULL) {
vlan2 = iface;
}
} else if (net_if_l2(iface) == &NET_L2_GET_NAME(ETHERNET)) {
eth = iface;
} else {
continue;
}
found++;
}
if (found == 0) {
LOG_ERR("No interfaces found");
return;
}
match_iface_vlan1.iface = vlan1;
match_iface_vlan2.iface = vlan2;
match_iface_eth.iface = eth;
/* The sample will setup the Ethernet interface and two VLAN
* optional interfaces (if VLAN is enabled).
* We allow all traffic to the Ethernet interface, but have
* filters for the VLAN interfaces.
*/
/* We allow small IPv4 packets to the VLAN interface 1 */
npf_append_recv_rule(&small_ipv4_pkt);
/* We allow large IPv4 packets to the VLAN interface 2 */
npf_append_recv_rule(&large_ipv4_pkt);
/* We allow all traffic to the Ethernet interface */
npf_append_recv_rule(&eth_iface_rule);
/* We allow ARP traffic to the VLAN 1 interface */
npf_append_recv_rule(&arp_pkt_vlan1);
/* We allow ARP traffic to the VLAN 2 interface */
npf_append_recv_rule(&arp_pkt_vlan2);
/* The remaining packets that do not match are dropped */
npf_append_recv_rule(&npf_default_drop);
}
int main(void)
{
net_if_foreach(iface_cb, interfaces);
init_vlan();
init_app();
return 0;
}