net: introduce a network packet filter framework

This provides the infrastructure to create network packet filter rules
and to apply them to the RX and TX packet paths. Rules are made of
simple condition tests that can be linked together, creating a facility
similarly to the Linux iptables functionality.

A couple of generic and Ethernet-specific condition tests are also
provided.

Additional tests can be easily created on top of this.

Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
This commit is contained in:
Nicolas Pitre 2021-08-02 15:43:32 -04:00 committed by Carles Cufí
parent 44585b7fc5
commit faa0b2a848
17 changed files with 1186 additions and 1 deletions

View file

@ -568,6 +568,7 @@
/include/net/coap*.h @rlubos
/include/net/lwm2m*.h @rlubos
/include/net/mqtt.h @rlubos
/include/net/net_pkt_filter.h @npitre
/include/posix/ @pfalcon
/include/pm/pm.h @nashif @ceolin
/include/drivers/ptp_clock.h @tbursztyka
@ -719,6 +720,7 @@
/subsys/net/lib/tls_credentials/ @rlubos
/subsys/net/l2/ @rlubos @tbursztyka
/subsys/net/l2/canbus/ @alexanderwachter
/subsys/net/pkt_filter/ @npitre
/subsys/net/*/openthread/ @rlubos
/subsys/pm/ @nashif @ceolin
/subsys/random/ @dleach02
@ -760,6 +762,7 @@
/tests/net/lib/http_header_fields/ @rlubos @tbursztyka
/tests/net/lib/mqtt_packet/ @rlubos
/tests/net/lib/coap/ @rlubos
/tests/net/npf/ @npitre
/tests/net/socket/socketpair/ @cfriedt
/tests/net/socket/ @rlubos @tbursztyka @pfalcon
/tests/subsys/debug/coredump/ @dcpleung

View file

@ -0,0 +1,96 @@
.. _net_pkt_filter_interface:
Network Packet Filtering
########################
.. contents::
:local:
:depth: 2
Overview
********
The Network Packet Filtering facility provides the infrastructure to
construct custom rules for accepting and/or denying packet transmission
and reception. This can be used to create a basic firewall, control
network traffic, etc.
The :kconfig:`CONFIG_NET_PKT_FILTER` must be set in order to enable the
relevant APIs.
Both the transmission and reception paths may have a list of filter rules.
Each rule is made of a set of conditions and a packet outcome. Every packet
is subjected to the conditions attached to a rule. When all the conditions
for a given rule are true then the packet outcome is immediately determined
as specified by the current rule and no more rules are considered. If one
condition is false then the next rule in the list is considered.
Packet outcome is either ``NET_OK`` to accept the packet or ``NET_DROP`` to
drop it.
A rule is represented by a :c:struct:`npf_rule` object. It can be inserted to,
appended to or removed from a rule list contained in a
:c:struct:`npf_rule_list` object using :c:func:`npf_insert_rule()`,
:c:func:`npf_append_rule()`, and :c:func:`npf_remove_rule()`.
Currently, two such rule lists exist: ``npf_send_rules`` for outgoing packets,
and ``npf_recv_rules`` for incoming packets.
If a filter rule list is empty then ``NET_OK`` is assumed. If a non-empty
rule list runs to the end then ``NET_DROP`` is assumed. However it is
recommended to always terminate a non-empty rule list with an explicit
default termination rule, either ``npf_default_ok`` or ``npf_default_drop``.
Rule conditions are represented by a :c:struct:`npf_test`. This structure
can be embedded into a larger structure when a specific condition requires
extra test data. It is up to the test function for such conditions to
retrieve the outer structure from the provided ``npf_test`` structure pointer.
Convenience macros are provided in :zephyr_file:`include/net/net_pkt_filter.h`
to statically define condition instances for various conditions, and
:c:macro:`NPF_RULE()` to create a rule instance to tie them.
Examples
********
Here's an example usage:
.. code-block:: c
static NPF_SIZE_MAX(maxsize_200, 200);
static NPF_ETH_TYPE_MATCH(ip_packet, NET_ETH_PTYPE_IP);
static NPF_RULE(small_ip_pkt, NET_OK, ip_packet, maxsize_200);
void install_my_filter(void)
{
npf_insert_recv_rule(&npf_default_drop);
npf_insert_recv_rule(&small_ip_pkt);
}
The above would accept IP packets that are 200 bytes or smaller, and drop
all other packets.
Another (less efficient) way to achieve the same result could be:
.. code-block:: c
static NPF_SIZE_MIN(minsize_201, 201);
static NPF_ETH_TYPE_UNMATCH(not_ip_packet, NET_ETH_PTYPE_IP);
static NPF_RULE(reject_big_pkts, NET_DROP, minsize_201);
static NPF_RULE(reject_non_ip, NET_DROP, not_ip_packet);
void install_my_filter(void) {
npf_append_recv_rule(&reject_big_pkts);
npf_append_recv_rule(&reject_non_ip);
npf_append_recv_rule(&npf_default_ok);
}
API Reference
*************
.. doxygengroup:: net_pkt_filter
.. doxygengroup:: npf_basic_cond
.. doxygengroup:: npf_eth_cond

View file

@ -16,4 +16,5 @@ Network System Management
net_linkaddr.rst
ethernet_mgmt.rst
traffic-class.rst
net_pkt_filter.rst
net_shell.rst

View file

@ -1212,6 +1212,29 @@ static inline bool net_pkt_is_being_overwritten(struct net_pkt *pkt)
return pkt->overwrite;
}
#ifdef CONFIG_NET_PKT_FILTER
bool net_pkt_filter_send_ok(struct net_pkt *pkt);
bool net_pkt_filter_recv_ok(struct net_pkt *pkt);
#else
static inline bool net_pkt_filter_send_ok(struct net_pkt *pkt)
{
ARG_UNUSED(pkt);
return true;
}
static inline bool net_pkt_filter_recv_ok(struct net_pkt *pkt)
{
ARG_UNUSED(pkt);
return true;
}
#endif /* CONFIG_NET_PKT_FILTER */
/* @endcond */
/**

View file

@ -0,0 +1,428 @@
/** @file
* @brief Network packet filtering public header file
*
* The network packet filtering provides a mechanism for deciding the fate
* of an incoming or outgoing packet based on a set of basic rules.
*/
/*
* Copyright (c) 2021 BayLibre SAS
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_NET_PKT_FILTER_H_
#define ZEPHYR_INCLUDE_NET_PKT_FILTER_H_
#include <limits.h>
#include <stdbool.h>
#include <sys/slist.h>
#include <net/net_core.h>
#include <net/ethernet.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Network Packet Filter API
* @defgroup net_pkt_filter Network Packet Filter API
* @ingroup networking
* @{
*/
/** @cond INTERNAL_HIDDEN */
struct npf_test;
typedef bool (npf_test_fn_t)(struct npf_test *test, struct net_pkt *pkt);
/** @endcond */
/** @brief common filter test structure to be embedded into larger structures */
struct npf_test {
npf_test_fn_t *fn; /*< packet condition test function */
};
/** @brief filter rule structure */
struct npf_rule {
sys_snode_t node;
enum net_verdict result; /*< result if all tests pass */
uint32_t nb_tests; /*< number of tests in this rule */
struct npf_test *tests[]; /*< pointers to @ref npf_test instances */
};
/** @brief Default rule list termination for accepting a packet */
extern struct npf_rule npf_default_ok;
/** @brief Default rule list termination for rejecting a packet */
extern struct npf_rule npf_default_drop;
/** @brief rule set for a given test location */
struct npf_rule_list {
sys_slist_t rule_head;
struct k_spinlock lock;
};
/** @brief rule list applied to outgoing packets */
extern struct npf_rule_list npf_send_rules;
/** @brief rule list applied to incoming packets */
extern struct npf_rule_list npf_recv_rules;
/**
* @brief Insert a rule at the front of given rule list
*
* @param rules the affected rule list
* @param rule the rule to be inserted
*/
void npf_insert_rule(struct npf_rule_list *rules, struct npf_rule *rule);
/**
* @brief Append a rule at the end of given rule list
*
* @param rules the affected rule list
* @param rule the rule to be appended
*/
void npf_append_rule(struct npf_rule_list *rules, struct npf_rule *rule);
/**
* @brief Remove a rule from the given rule list
*
* @param rules the affected rule list
* @param rule the rule to be removed
* @retval true if given rule was found in the rule list and removed
*/
bool npf_remove_rule(struct npf_rule_list *rules, struct npf_rule *rule);
/**
* @brief Remove all rules from the given rule list
*
* @param rules the affected rule list
* @retval true if at least one rule was removed from the rule list
*/
bool npf_remove_all_rules(struct npf_rule_list *rules);
/* convenience shortcuts */
#define npf_insert_send_rule(rule) npf_insert_rule(&npf_send_rules, rule)
#define npf_insert_recv_rule(rule) npf_insert_rule(&npf_recv_rules, rule)
#define npf_append_send_rule(rule) npf_append_rule(&npf_send_rules, rule)
#define npf_append_recv_rule(rule) npf_append_rule(&npf_recv_rules, rule)
#define npf_remove_send_rule(rule) npf_remove_rule(&npf_send_rules, rule)
#define npf_remove_recv_rule(rule) npf_remove_rule(&npf_recv_rules, rule)
#define npf_remove_all_send_rules() npf_remove_all_rules(&npf_send_rules)
#define npf_remove_all_recv_rules() npf_remove_all_rules(&npf_recv_rules)
/**
* @brief Statically define one packet filter rule
*
* This creates a rule from a variable amount of filter conditions.
* This rule can then be inserted or appended to the rule list for a given
* network packet path.
*
* Example:
*
* @code{.c}
*
* static NPF_SIZE_MAX(maxsize_200, 200);
* static NPF_ETH_TYPE_MATCH(ip_packet, NET_ETH_PTYPE_IP);
*
* static NPF_RULE(small_ip_pkt, NET_OK, ip_packet, maxsize_200);
*
* void install_my_filter(void)
* {
* npf_insert_recv_rule(&npf_default_drop);
* npf_insert_recv_rule(&small_ip_pkt);
* }
*
* @endcode
*
* The above would accept IP packets that are 200 bytes or smaller, and drop
* all other packets.
*
* Another (less efficient) way to create the same result could be:
*
* @code{.c}
*
* static NPF_SIZE_MIN(minsize_201, 201);
* static NPF_ETH_TYPE_UNMATCH(not_ip_packet, NET_ETH_PTYPE_IP);
*
* static NPF_RULE(reject_big_pkts, NET_DROP, minsize_201);
* static NPF_RULE(reject_non_ip, NET_DROP, not_ip_packet);
*
* void install_my_filter(void) {
* npf_append_recv_rule(&reject_big_pkts);
* npf_append_recv_rule(&reject_non_ip);
* npf_append_recv_rule(&npf_default_ok);
* }
*
* @endcode
*
* The first rule in the list for which all conditions are true determines
* the fate of the packet. If one condition is false then the next rule in
* the list is evaluated.
*
* @param _name Name for this rule.
* @param _result Fate of the packet if all conditions are true, either
* <tt>NET_OK</tt> or <tt>NET_DROP</tt>.
* @param ... List of conditions for this rule.
*/
#define NPF_RULE(_name, _result, ...) \
struct npf_rule _name = { \
.result = (_result), \
.nb_tests = NUM_VA_ARGS_LESS_1(__VA_ARGS__) + 1, \
.tests = { FOR_EACH(Z_NPF_TEST_ADDR, (,), __VA_ARGS__) }, \
}
#define Z_NPF_TEST_ADDR(arg) &arg.test
/** @} */
/**
* @defgroup npf_basic_cond Basic Filter Conditions
* @ingroup net_pkt_filter
* @{
*/
/** @cond INTERNAL_HIDDEN */
struct npf_test_iface {
struct npf_test test;
struct net_if *iface;
};
extern npf_test_fn_t npf_iface_match;
extern npf_test_fn_t npf_iface_unmatch;
extern npf_test_fn_t npf_orig_iface_match;
extern npf_test_fn_t npf_orig_iface_unmatch;
/** @endcond */
/**
* @brief Statically define an "interface match" packet filter condition
*
* @param _name Name of the condition
* @param _iface Interface to match
*/
#define NPF_IFACE_MATCH(_name, _iface) \
struct npf_test_iface _name = { \
.iface = (_iface), \
.test.fn = npf_iface_match, \
}
/**
* @brief Statically define an "interface unmatch" packet filter condition
*
* @param _name Name of the condition
* @param _iface Interface to exclude
*/
#define NPF_IFACE_UNMATCH(_name, _iface) \
struct npf_test_iface _name = { \
.iface = (_iface), \
.test.fn = npf_iface_unmatch, \
}
/**
* @brief Statically define an "orig interface match" packet filter condition
*
* @param _name Name of the condition
* @param _iface Interface to match
*/
#define NPF_ORIG_IFACE_MATCH(_name, _iface) \
struct npf_test_iface _name = { \
.iface = (_iface), \
.test.fn = npf_orig_iface_match, \
}
/**
* @brief Statically define an "orig interface unmatch" packet filter condition
*
* @param _name Name of the condition
* @param _iface Interface to exclude
*/
#define NPF_ORIG_IFACE_UNMATCH(_name, _iface) \
struct npf_test_iface _name = { \
.iface = (_iface), \
.test.fn = npf_orig_iface_unmatch, \
}
/** @cond INTERNAL_HIDDEN */
struct npf_test_size_bounds {
struct npf_test test;
size_t min;
size_t max;
};
extern npf_test_fn_t npf_size_inbounds;
/** @endcond */
/**
* @brief Statically define a "data minimum size" packet filter condition
*
* @param _name Name of the condition
* @param _size Lower bound of the packet's data size
*/
#define NPF_SIZE_MIN(_name, _size) \
struct npf_test_size_bounds _name = { \
.min = (_size), \
.max = SIZE_MAX, \
.test.fn = npf_size_inbounds, \
}
/**
* @brief Statically define a "data maximum size" packet filter condition
*
* @param _name Name of the condition
* @param _size Higher bound of the packet's data size
*/
#define NPF_SIZE_MAX(_name, _size) \
struct npf_test_size_bounds _name = { \
.min = 0, \
.max = (_size), \
.test.fn = npf_size_inbounds, \
}
/**
* @brief Statically define a "data bounded size" packet filter condition
*
* @param _name Name of the condition
* @param _min_size Lower bound of the packet's data size
* @param _max_size Higher bound of the packet's data size
*/
#define NPF_SIZE_BOUNDS(_name, _min_size, _max_size) \
struct npf_test_size_bounds _name = { \
.min = (_min_size), \
.max = (_max_size), \
.test.fn = npf_size_inbounds, \
}
/** @} */
/**
* @defgroup npf_eth_cond Ethernet Filter Conditions
* @ingroup net_pkt_filter
* @{
*/
/** @cond INTERNAL_HIDDEN */
struct npf_test_eth_addr {
struct npf_test test;
unsigned int nb_addresses;
struct net_eth_addr *addresses;
};
extern npf_test_fn_t npf_eth_src_addr_match;
extern npf_test_fn_t npf_eth_src_addr_unmatch;
extern npf_test_fn_t npf_eth_dst_addr_match;
extern npf_test_fn_t npf_eth_dst_addr_unmatch;
/** @endcond */
/**
* @brief Statically define a "source address match" packet filter condition
*
* This tests if the packet source address matches any of the Ethernet
* addresses contained in the provided set.
*
* @param _name Name of the condition
* @param _addr_array Array of <tt>struct net_eth_addr</tt> items to test against
*/
#define NPF_ETH_SRC_ADDR_MATCH(_name, _addr_array) \
struct npf_test_eth_addr _name = { \
.addresses = (_addr_array), \
.nb_addresses = ARRAY_SIZE(_addr_array), \
.test.fn = npf_eth_src_addr_match, \
}
/**
* @brief Statically define a "source address unmatch" packet filter condition
*
* This tests if the packet source address matches none of the Ethernet
* addresses contained in the provided set.
*
* @param _name Name of the condition
* @param _addr_array Array of <tt>struct net_eth_addr</tt> items to test against
*/
#define NPF_ETH_SRC_ADDR_UNMATCH(_name, _addr_array) \
struct npf_test_eth_addr _name = { \
.addresses = (_addr_array), \
.nb_addresses = ARRAY_SIZE(_addr_array), \
.test.fn = npf_eth_src_addr_unmatch, \
}
/**
* @brief Statically define a "destination address match" packet filter condition
*
* This tests if the packet destination address matches any of the Ethernet
* addresses contained in the provided set.
*
* @param _name Name of the condition
* @param _addr_array Array of <tt>struct net_eth_addr</tt> items to test against
*/
#define NPF_ETH_DST_ADDR_MATCH(_name, _addr_array) \
struct npf_test_eth_addr _name = { \
.addresses = (_addr_array), \
.nb_addresses = ARRAY_SIZE(_addr_array), \
.test.fn = npf_eth_dst_addr_match, \
}
/**
* @brief Statically define a "destination address unmatch" packet filter condition
*
* This tests if the packet destination address matches none of the Ethernet
* addresses contained in the provided set.
*
* @param _name Name of the condition
* @param _addr_array Array of <tt>struct net_eth_addr</tt> items to test against
*/
#define NPF_ETH_DST_ADDR_UNMATCH(_name, _addr_array) \
struct npf_test_eth_addr _name = { \
.addresses = (_addr_array), \
.nb_addresses = ARRAY_SIZE(_addr_array), \
.test.fn = npf_eth_dst_addr_unmatch, \
}
/** @cond INTERNAL_HIDDEN */
struct npf_test_eth_type {
struct npf_test test;
uint16_t type; /* type in network order */
};
extern npf_test_fn_t npf_eth_type_match;
extern npf_test_fn_t npf_eth_type_unmatch;
/** @endcond */
/**
* @brief Statically define an "Ethernet type match" packet filter condition
*
* @param _name Name of the condition
* @param _type Ethernet type to match
*/
#define NPF_ETH_TYPE_MATCH(_name, _type) \
struct npf_test_eth_type _name = { \
.type = htons(_type), \
.test.fn = npf_eth_type_match, \
}
/**
* @brief Statically define an "Ethernet type unmatch" packet filter condition
*
* @param _name Name of the condition
* @param _type Ethernet type to exclude
*/
#define NPF_ETH_TYPE_UNMATCH(_name, _type) \
struct npf_test_eth_type _name = { \
.type = htons(_type), \
.test.fn = npf_eth_type_unmatch, \
}
/** @} */
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_INCLUDE_NET_PKT_FILTER_H_ */

View file

@ -6,6 +6,7 @@ zephyr_library_sources_ifdef(CONFIG_NET_HOSTNAME_ENABLE hostname.c)
if(CONFIG_NETWORKING)
add_subdirectory(l2)
add_subdirectory(pkt_filter)
if(CONFIG_NET_RAW_MODE)
zephyr_library_sources(ip/net_pkt.c)

View file

@ -82,6 +82,8 @@ source "subsys/net/l2/Kconfig"
source "subsys/net/ip/Kconfig"
source "subsys/net/pkt_filter/Kconfig"
source "subsys/net/lib/Kconfig"
endif

View file

@ -426,7 +426,12 @@ int net_recv_data(struct net_if *iface, struct net_pkt *pkt)
net_pkt_set_iface(pkt, iface);
net_queue_rx(iface, pkt);
if (!net_pkt_filter_recv_ok(pkt)) {
/* silently drop the packet */
net_pkt_unref(pkt);
} else {
net_queue_rx(iface, pkt);
}
return 0;
}

View file

@ -333,6 +333,12 @@ void net_process_tx_packet(struct net_pkt *pkt)
void net_if_queue_tx(struct net_if *iface, struct net_pkt *pkt)
{
if (!net_pkt_filter_send_ok(pkt)) {
/* silently drop the packet */
net_pkt_unref(pkt);
return;
}
uint8_t prio = net_pkt_priority(pkt);
uint8_t tc = net_tx_priority2tc(prio);

View file

@ -0,0 +1,10 @@
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
if(CONFIG_NET_PKT_FILTER)
zephyr_library_sources(base.c)
zephyr_library_sources_ifdef(CONFIG_NET_L2_ETHERNET ethernet.c)
endif()

View file

@ -0,0 +1,22 @@
# Packet filtering config
# Copyright (c) 2021 BayLibre SAS
# SPDX-License-Identifier: Apache-2.0
menu "Network Packet Filtering"
config NET_PKT_FILTER
bool "Enable network packet filtering"
help
The Network Packet Filtering facility provides the infrastructure
to construct custom rules for accepting and/or denying packet
transmission and reception.
if NET_PKT_FILTER
module = NET_PKT_FILTER
module-dep = NET_LOG
module-str = Log level for packet filtering
module-help = Enables packet filter output debug messages
source "subsys/net/Kconfig.template.log_config.net"
endif # NET_PKT_FILTER
endmenu

View file

@ -0,0 +1,201 @@
/*
* Copyright (c) 2021 BayLibre SAS
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <logging/log.h>
LOG_MODULE_REGISTER(npf_base, CONFIG_NET_PKT_FILTER_LOG_LEVEL);
#include <net/net_core.h>
#include <net/net_pkt_filter.h>
#include <spinlock.h>
/*
* Our actual rule lists for supported test points
*/
struct npf_rule_list npf_send_rules = {
.rule_head = SYS_SLIST_STATIC_INIT(&send_rules.rule_head),
.lock = { },
};
struct npf_rule_list npf_recv_rules = {
.rule_head = SYS_SLIST_STATIC_INIT(&recv_rules.rule_head),
.lock = { },
};
/*
* Rule application
*/
/*
* All tests must be true to return true.
* If no tests then it is true.
*/
static bool apply_tests(struct npf_rule *rule, struct net_pkt *pkt)
{
struct npf_test *test;
unsigned int i;
bool result;
for (i = 0; i < rule->nb_tests; i++) {
test = rule->tests[i];
result = test->fn(test, pkt);
NET_DBG("test %p result %d", test, result);
if (result == false) {
return false;
}
}
return true;
}
/*
* We return the specified result for the first rule whose tests are all true.
*/
static enum net_verdict evaluate(sys_slist_t *rule_head, struct net_pkt *pkt)
{
struct npf_rule *rule;
NET_DBG("rule_head %p on pkt %p", rule_head, pkt);
if (sys_slist_is_empty(rule_head)) {
NET_DBG("no rules");
return NET_OK;
}
SYS_SLIST_FOR_EACH_CONTAINER(rule_head, rule, node) {
if (apply_tests(rule, pkt) == true) {
return rule->result;
}
}
NET_DBG("no matching rules from rule_head %p", rule_head);
return NET_DROP;
}
static enum net_verdict lock_evaluate(struct npf_rule_list *rules, struct net_pkt *pkt)
{
k_spinlock_key_t key = k_spin_lock(&rules->lock);
enum net_verdict result = evaluate(&rules->rule_head, pkt);
k_spin_unlock(&rules->lock, key);
return result;
}
bool net_pkt_filter_send_ok(struct net_pkt *pkt)
{
enum net_verdict result = lock_evaluate(&npf_send_rules, pkt);
return result == NET_OK;
}
bool net_pkt_filter_recv_ok(struct net_pkt *pkt)
{
enum net_verdict result = lock_evaluate(&npf_recv_rules, pkt);
return result == NET_OK;
}
/*
* Rule management
*/
void npf_insert_rule(struct npf_rule_list *rules, struct npf_rule *rule)
{
k_spinlock_key_t key = k_spin_lock(&rules->lock);
NET_DBG("inserting rule %p into %p", rule, rules);
sys_slist_prepend(&rules->rule_head, &rule->node);
k_spin_unlock(&rules->lock, key);
}
void npf_append_rule(struct npf_rule_list *rules, struct npf_rule *rule)
{
__ASSERT(sys_slist_peek_tail(&rules->rule_head) != &npf_default_ok.node, "");
__ASSERT(sys_slist_peek_tail(&rules->rule_head) != &npf_default_drop.node, "");
k_spinlock_key_t key = k_spin_lock(&rules->lock);
NET_DBG("appending rule %p into %p", rule, rules);
sys_slist_append(&rules->rule_head, &rule->node);
k_spin_unlock(&rules->lock, key);
}
bool npf_remove_rule(struct npf_rule_list *rules, struct npf_rule *rule)
{
k_spinlock_key_t key = k_spin_lock(&rules->lock);
bool result = sys_slist_find_and_remove(&rules->rule_head, &rule->node);
k_spin_unlock(&rules->lock, key);
NET_DBG("removing rule %p from %p: %d", rule, rules, result);
return result;
}
bool npf_remove_all_rules(struct npf_rule_list *rules)
{
k_spinlock_key_t key = k_spin_lock(&rules->lock);
bool result = !sys_slist_is_empty(&rules->rule_head);
if (result) {
sys_slist_init(&rules->rule_head);
NET_DBG("removing all rules from %p", rules);
}
k_spin_unlock(&rules->lock, key);
return result;
}
/*
* Default rule list terminations.
*/
struct npf_rule npf_default_ok = {
.result = NET_OK,
};
struct npf_rule npf_default_drop = {
.result = NET_DROP,
};
/*
* Some simple generic conditions
*/
bool npf_iface_match(struct npf_test *test, struct net_pkt *pkt)
{
struct npf_test_iface *test_iface =
CONTAINER_OF(test, struct npf_test_iface, test);
return test_iface->iface == net_pkt_iface(pkt);
}
bool npf_iface_unmatch(struct npf_test *test, struct net_pkt *pkt)
{
return !npf_iface_match(test, pkt);
}
bool npf_orig_iface_match(struct npf_test *test, struct net_pkt *pkt)
{
struct npf_test_iface *test_iface =
CONTAINER_OF(test, struct npf_test_iface, test);
return test_iface->iface == net_pkt_orig_iface(pkt);
}
bool npf_orig_iface_unmatch(struct npf_test *test, struct net_pkt *pkt)
{
return !npf_orig_iface_match(test, pkt);
}
bool npf_size_inbounds(struct npf_test *test, struct net_pkt *pkt)
{
struct npf_test_size_bounds *bounds =
CONTAINER_OF(test, struct npf_test_size_bounds, test);
size_t pkt_size = net_pkt_get_len(pkt);
return pkt_size >= bounds->min && pkt_size <= bounds->max;
}

View file

@ -0,0 +1,67 @@
/*
* Copyright (c) 2021 BayLibre SAS
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <logging/log.h>
LOG_MODULE_REGISTER(npf_ethernet, CONFIG_NET_PKT_FILTER_LOG_LEVEL);
#include <net/ethernet.h>
#include <net/net_pkt_filter.h>
static bool addr_match(struct npf_test *test, struct net_eth_addr *pkt_addr)
{
struct npf_test_eth_addr *test_eth_addr =
CONTAINER_OF(test, struct npf_test_eth_addr, test);
struct net_eth_addr *addr = test_eth_addr->addresses;
unsigned int nb_addr = test_eth_addr->nb_addresses;
while (nb_addr) {
if (memcmp(addr, pkt_addr, sizeof(struct net_eth_addr)) == 0) {
return true;
}
addr++;
nb_addr--;
}
return false;
}
bool npf_eth_src_addr_match(struct npf_test *test, struct net_pkt *pkt)
{
struct net_eth_hdr *eth_hdr = NET_ETH_HDR(pkt);
return addr_match(test, &eth_hdr->src);
}
bool npf_eth_src_addr_unmatch(struct npf_test *test, struct net_pkt *pkt)
{
return !npf_eth_src_addr_match(test, pkt);
}
bool npf_eth_dst_addr_match(struct npf_test *test, struct net_pkt *pkt)
{
struct net_eth_hdr *eth_hdr = NET_ETH_HDR(pkt);
return addr_match(test, &eth_hdr->dst);
}
bool npf_eth_dst_addr_unmatch(struct npf_test *test, struct net_pkt *pkt)
{
return !npf_eth_dst_addr_match(test, pkt);
}
bool npf_eth_type_match(struct npf_test *test, struct net_pkt *pkt)
{
struct npf_test_eth_type *test_eth_type =
CONTAINER_OF(test, struct npf_test_eth_type, test);
struct net_eth_hdr *eth_hdr = NET_ETH_HDR(pkt);
/* note: type_match->type is assumed to be in network order already */
return eth_hdr->type == test_eth_type->type;
}
bool npf_eth_type_unmatch(struct npf_test *test, struct net_pkt *pkt)
{
return !npf_eth_type_match(test, pkt);
}

View file

@ -0,0 +1,9 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(npf)
target_include_directories(app PRIVATE ${ZEPHYR_BASE}/subsys/net/ip)
FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources})

13
tests/net/npf/prj.conf Normal file
View file

@ -0,0 +1,13 @@
CONFIG_NETWORKING=y
CONFIG_NET_TEST=y
CONFIG_NET_L2_ETHERNET=y
CONFIG_ENTROPY_GENERATOR=y
CONFIG_TEST_RANDOM_GENERATOR=y
CONFIG_NET_PKT_FILTER=y
CONFIG_NET_LOG=y
CONFIG_NET_PKT_TX_COUNT=10
CONFIG_NET_PKT_RX_COUNT=10
CONFIG_NET_BUF_RX_COUNT=10
CONFIG_NET_BUF_TX_COUNT=10
CONFIG_ZTEST=y
CONFIG_COMPILER_COLOR_DIAGNOSTICS=n

293
tests/net/npf/src/main.c Normal file
View file

@ -0,0 +1,293 @@
/*
* Copyright (c) 2021 BayLibre SAS
*
* SPDX-License-Identifier: Apache-2.0
*/
#define NET_LOG_LEVEL CONFIG_NET_PKT_FILTER_LOG_LEVEL
#include <logging/log.h>
LOG_MODULE_REGISTER(npf_test, NET_LOG_LEVEL);
#include <zephyr/types.h>
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#include <errno.h>
#include <sys/printk.h>
#include <ztest.h>
#include <net/net_if.h>
#include <net/ethernet.h>
#include <net/net_pkt_filter.h>
#if NET_LOG_LEVEL >= LOG_LEVEL_DBG
#define DBG(fmt, ...) printk(fmt, ##__VA_ARGS__)
#else
#define DBG(fmt, ...)
#endif
#define ETH_SRC_ADDR \
(struct net_eth_addr){ { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55 } }
#define ETH_DST_ADDR \
(struct net_eth_addr){ { 0x00, 0x66, 0x77, 0x88, 0x99, 0xaa } }
static const char dummy_data[] =
"The Zephyr Project is a scalable real-time operating system (RTOS) supporting\n"
"multiple hardware architectures, optimized for resource constrained devices,\n"
"and built with security in mind.\n"
"\n"
"The Zephyr OS is based on a small-footprint kernel designed for use on\n"
"resource-constrained systems: from simple embedded environmental sensors and\n"
"LED wearables to sophisticated smart watches and IoT wireless gateways.\n"
"\n"
"The Zephyr kernel supports multiple architectures, including ARM Cortex-M,\n"
"Intel x86, ARC, Nios II, Tensilica Xtensa, and RISC-V, and a large number of\n"
"`supported boards`_.\n";
static struct net_pkt *build_test_pkt(int type, int size, struct net_if *iface)
{
struct net_pkt *pkt;
struct net_eth_hdr eth_hdr;
int ret;
pkt = net_pkt_rx_alloc_with_buffer(iface, size, AF_UNSPEC, 0, K_NO_WAIT);
zassert_not_null(pkt, "");
eth_hdr.src = ETH_SRC_ADDR;
eth_hdr.dst = ETH_DST_ADDR;
eth_hdr.type = htons(type);
ret = net_pkt_write(pkt, &eth_hdr, sizeof(eth_hdr));
zassert_equal(ret, 0, "");
zassert_true(size >= sizeof(eth_hdr), "");
zassert_true((size - sizeof(eth_hdr)) <= sizeof(dummy_data), "");
ret = net_pkt_write(pkt, dummy_data, size - sizeof(eth_hdr));
zassert_equal(ret, 0, "");
DBG("pkt %p: iface %p size %d type 0x%04x\n", pkt, iface, size, type);
return pkt;
}
/*
* Declare some fake interfaces and test their filter conditions.
*/
static int eth_fake_init(const struct device *dev)
{
ARG_UNUSED(dev);
return 0;
}
ETH_NET_DEVICE_INIT(dummy_iface_a, "dummy_a", eth_fake_init, NULL,
NULL, NULL, CONFIG_ETH_INIT_PRIORITY,
NULL, NET_ETH_MTU);
ETH_NET_DEVICE_INIT(dummy_iface_b, "dummy_b", eth_fake_init, NULL,
NULL, NULL, CONFIG_ETH_INIT_PRIORITY,
NULL, NET_ETH_MTU);
#define dummy_iface_a NET_IF_GET_NAME(dummy_iface_a, 0)[0]
#define dummy_iface_b NET_IF_GET_NAME(dummy_iface_b, 0)[0]
static NPF_IFACE_MATCH(match_iface_a, &dummy_iface_a);
static NPF_IFACE_UNMATCH(unmatch_iface_b, &dummy_iface_b);
static NPF_RULE(accept_iface_a, NET_OK, match_iface_a);
static NPF_RULE(accept_all_but_iface_b, NET_OK, unmatch_iface_b);
static void test_npf_iface(void)
{
struct net_pkt *pkt_iface_a, *pkt_iface_b;
pkt_iface_a = build_test_pkt(0, 200, &dummy_iface_a);
pkt_iface_b = build_test_pkt(0, 200, &dummy_iface_b);
/* test with no rules */
zassert_true(net_pkt_filter_recv_ok(pkt_iface_a), "");
zassert_true(net_pkt_filter_recv_ok(pkt_iface_b), "");
/* install rules */
npf_append_recv_rule(&accept_iface_a);
npf_append_recv_rule(&npf_default_drop);
/* test with rules in place */
zassert_true(net_pkt_filter_recv_ok(pkt_iface_a), "");
zassert_false(net_pkt_filter_recv_ok(pkt_iface_b), "");
/* remove first iface rule */
zassert_true(npf_remove_recv_rule(&accept_iface_a), "");
/* fails if removed a second time */
zassert_false(npf_remove_recv_rule(&accept_iface_a), "");
/* test with only default drop rule in place */
zassert_false(net_pkt_filter_recv_ok(pkt_iface_a), "");
zassert_false(net_pkt_filter_recv_ok(pkt_iface_b), "");
/* insert second iface rule */
npf_insert_recv_rule(&accept_all_but_iface_b);
/* test with new rule in place */
zassert_true(net_pkt_filter_recv_ok(pkt_iface_a), "");
zassert_false(net_pkt_filter_recv_ok(pkt_iface_b), "");
/* remove all rules */
zassert_true(npf_remove_recv_rule(&accept_all_but_iface_b), "");
zassert_true(npf_remove_recv_rule(&npf_default_drop), "");
/* should accept any packets again */
zassert_true(net_pkt_filter_recv_ok(pkt_iface_a), "");
zassert_true(net_pkt_filter_recv_ok(pkt_iface_b), "");
net_pkt_unref(pkt_iface_a);
net_pkt_unref(pkt_iface_b);
}
/*
* Example 1 in NPF_RULE() documentation.
*/
static NPF_SIZE_MAX(maxsize_200, 200);
static NPF_ETH_TYPE_MATCH(ip_packet, NET_ETH_PTYPE_IP);
static NPF_RULE(small_ip_pkt, NET_OK, ip_packet, maxsize_200);
static void test_npf_example_common(void)
{
struct net_pkt *pkt;
/* test small IP packet */
pkt = build_test_pkt(NET_ETH_PTYPE_IP, 100, NULL);
zassert_true(net_pkt_filter_recv_ok(pkt), "");
net_pkt_unref(pkt);
/* test "big" IP packet */
pkt = build_test_pkt(NET_ETH_PTYPE_IP, 300, NULL);
zassert_false(net_pkt_filter_recv_ok(pkt), "");
net_pkt_unref(pkt);
/* test "small" non-IP packet */
pkt = build_test_pkt(NET_ETH_PTYPE_ARP, 100, NULL);
zassert_false(net_pkt_filter_recv_ok(pkt), "");
net_pkt_unref(pkt);
/* test "big" non-IP packet */
pkt = build_test_pkt(NET_ETH_PTYPE_ARP, 300, NULL);
zassert_false(net_pkt_filter_recv_ok(pkt), "");
net_pkt_unref(pkt);
}
static void test_npf_example1(void)
{
/* install filter rules */
npf_insert_recv_rule(&npf_default_drop);
npf_insert_recv_rule(&small_ip_pkt);
test_npf_example_common();
/* remove filter rules */
zassert_true(npf_remove_recv_rule(&npf_default_drop), "");
zassert_true(npf_remove_recv_rule(&small_ip_pkt), "");
}
/*
* Example 2 in NPF_RULE() documentation.
*/
static NPF_SIZE_MIN(minsize_201, 201);
static NPF_ETH_TYPE_UNMATCH(not_ip_packet, NET_ETH_PTYPE_IP);
static NPF_RULE(reject_big_pkts, NET_DROP, minsize_201);
static NPF_RULE(reject_non_ip, NET_DROP, not_ip_packet);
static void test_npf_example2(void)
{
/* install filter rules */
npf_append_recv_rule(&reject_big_pkts);
npf_append_recv_rule(&reject_non_ip);
npf_append_recv_rule(&npf_default_ok);
test_npf_example_common();
/* remove filter rules */
zassert_true(npf_remove_all_recv_rules(), "");
zassert_false(npf_remove_all_recv_rules(), "");
}
/*
* Ethernet MAC address filtering
*/
static struct net_eth_addr mac_address_list[4] = {
{ { 0x11, 0x11, 0x11, 0x11, 0x11, 0x11 } },
{ { 0x22, 0x22, 0x22, 0x22, 0x22, 0x22 } },
{ { 0x33, 0x33, 0x33, 0x33, 0x33, 0x33 } },
{ { 0x44, 0x44, 0x44, 0x44, 0x44, 0x44 } },
};
static NPF_ETH_SRC_ADDR_MATCH(matched_src_addr, mac_address_list);
static NPF_ETH_DST_ADDR_MATCH(matched_dst_addr, mac_address_list);
static NPF_ETH_SRC_ADDR_UNMATCH(unmatched_src_addr, mac_address_list);
static NPF_ETH_DST_ADDR_UNMATCH(unmatched_dst_addr, mac_address_list);
static NPF_RULE(accept_matched_src_addr, NET_OK, matched_src_addr);
static NPF_RULE(accept_unmatched_src_addr, NET_OK, unmatched_src_addr);
static NPF_RULE(accept_matched_dst_addr, NET_OK, matched_dst_addr);
static NPF_RULE(accept_unmatched_dst_addr, NET_OK, unmatched_dst_addr);
static void test_npf_eth_mac_address(void)
{
struct net_pkt *pkt = build_test_pkt(NET_ETH_PTYPE_IP, 100, NULL);
/* make sure pkt is initially accepted */
zassert_true(net_pkt_filter_recv_ok(pkt), "");
/* let's test "OK" cases by making "drop" the default */
npf_append_recv_rule(&npf_default_drop);
/* validate missing src address */
npf_insert_recv_rule(&accept_unmatched_src_addr);
npf_insert_recv_rule(&accept_matched_src_addr);
zassert_true(net_pkt_filter_recv_ok(pkt), "");
zassert_true(npf_remove_recv_rule(&accept_unmatched_src_addr), "");
zassert_false(net_pkt_filter_recv_ok(pkt), "");
/* insert known src address in the lot */
mac_address_list[1] = ETH_SRC_ADDR;
zassert_true(net_pkt_filter_recv_ok(pkt), "");
npf_insert_recv_rule(&accept_unmatched_src_addr);
zassert_true(net_pkt_filter_recv_ok(pkt), "");
zassert_true(npf_remove_recv_rule(&accept_matched_src_addr), "");
zassert_false(net_pkt_filter_recv_ok(pkt), "");
zassert_true(npf_remove_recv_rule(&accept_unmatched_src_addr), "");
/* validate missing dst address */
npf_insert_recv_rule(&accept_unmatched_dst_addr);
npf_insert_recv_rule(&accept_matched_dst_addr);
zassert_true(net_pkt_filter_recv_ok(pkt), "");
zassert_true(npf_remove_recv_rule(&accept_unmatched_dst_addr), "");
zassert_false(net_pkt_filter_recv_ok(pkt), "");
/* insert known dst address in the lot */
mac_address_list[2] = ETH_DST_ADDR;
zassert_true(net_pkt_filter_recv_ok(pkt), "");
npf_insert_recv_rule(&accept_unmatched_dst_addr);
zassert_true(net_pkt_filter_recv_ok(pkt), "");
zassert_true(npf_remove_recv_rule(&accept_matched_dst_addr), "");
zassert_false(net_pkt_filter_recv_ok(pkt), "");
zassert_true(npf_remove_recv_rule(&accept_unmatched_dst_addr), "");
}
void test_main(void)
{
ztest_test_suite(net_pkt_filter_test,
ztest_unit_test(test_npf_iface),
ztest_unit_test(test_npf_example1),
ztest_unit_test(test_npf_example2),
ztest_unit_test(test_npf_eth_mac_address));
ztest_run_test_suite(net_pkt_filter_test);
}

View file

@ -0,0 +1,5 @@
tests:
net.pkt_filter:
min_ram: 16
tags: net npf
depends_on: netif