diff --git a/CODEOWNERS b/CODEOWNERS index ed5d9f347c3..7f47cf34155 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -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 diff --git a/doc/reference/networking/net_pkt_filter.rst b/doc/reference/networking/net_pkt_filter.rst new file mode 100644 index 00000000000..415e0c1dbd4 --- /dev/null +++ b/doc/reference/networking/net_pkt_filter.rst @@ -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 diff --git a/doc/reference/networking/system_mgmt.rst b/doc/reference/networking/system_mgmt.rst index 8a3236ae067..59aab16d3b7 100644 --- a/doc/reference/networking/system_mgmt.rst +++ b/doc/reference/networking/system_mgmt.rst @@ -16,4 +16,5 @@ Network System Management net_linkaddr.rst ethernet_mgmt.rst traffic-class.rst + net_pkt_filter.rst net_shell.rst diff --git a/include/net/net_pkt.h b/include/net/net_pkt.h index 91ba8d6ff28..05f9d9151d5 100644 --- a/include/net/net_pkt.h +++ b/include/net/net_pkt.h @@ -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 */ /** diff --git a/include/net/net_pkt_filter.h b/include/net/net_pkt_filter.h new file mode 100644 index 00000000000..23900e2a811 --- /dev/null +++ b/include/net/net_pkt_filter.h @@ -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 +#include +#include +#include +#include + +#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 + * NET_OK or NET_DROP. + * @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 struct net_eth_addr 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 struct net_eth_addr 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 struct net_eth_addr 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 struct net_eth_addr 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_ */ diff --git a/subsys/net/CMakeLists.txt b/subsys/net/CMakeLists.txt index 642132e4f95..bbc680f74e7 100644 --- a/subsys/net/CMakeLists.txt +++ b/subsys/net/CMakeLists.txt @@ -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) diff --git a/subsys/net/Kconfig b/subsys/net/Kconfig index d871d18c47b..4977195049a 100644 --- a/subsys/net/Kconfig +++ b/subsys/net/Kconfig @@ -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 diff --git a/subsys/net/ip/net_core.c b/subsys/net/ip/net_core.c index 9cc57f47821..62633fa236f 100644 --- a/subsys/net/ip/net_core.c +++ b/subsys/net/ip/net_core.c @@ -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; } diff --git a/subsys/net/ip/net_if.c b/subsys/net/ip/net_if.c index 9ba2bf9d92d..1d25a750f43 100644 --- a/subsys/net/ip/net_if.c +++ b/subsys/net/ip/net_if.c @@ -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); diff --git a/subsys/net/pkt_filter/CMakeLists.txt b/subsys/net/pkt_filter/CMakeLists.txt new file mode 100644 index 00000000000..445eb47548f --- /dev/null +++ b/subsys/net/pkt_filter/CMakeLists.txt @@ -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() diff --git a/subsys/net/pkt_filter/Kconfig b/subsys/net/pkt_filter/Kconfig new file mode 100644 index 00000000000..9943c47af59 --- /dev/null +++ b/subsys/net/pkt_filter/Kconfig @@ -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 diff --git a/subsys/net/pkt_filter/base.c b/subsys/net/pkt_filter/base.c new file mode 100644 index 00000000000..c15effba85f --- /dev/null +++ b/subsys/net/pkt_filter/base.c @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2021 BayLibre SAS + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +LOG_MODULE_REGISTER(npf_base, CONFIG_NET_PKT_FILTER_LOG_LEVEL); + +#include +#include +#include + +/* + * 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; +} diff --git a/subsys/net/pkt_filter/ethernet.c b/subsys/net/pkt_filter/ethernet.c new file mode 100644 index 00000000000..cca8a404370 --- /dev/null +++ b/subsys/net/pkt_filter/ethernet.c @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2021 BayLibre SAS + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +LOG_MODULE_REGISTER(npf_ethernet, CONFIG_NET_PKT_FILTER_LOG_LEVEL); + +#include +#include + +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, ð_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, ð_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); +} diff --git a/tests/net/npf/CMakeLists.txt b/tests/net/npf/CMakeLists.txt new file mode 100644 index 00000000000..1e23f15ae19 --- /dev/null +++ b/tests/net/npf/CMakeLists.txt @@ -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}) diff --git a/tests/net/npf/prj.conf b/tests/net/npf/prj.conf new file mode 100644 index 00000000000..9cd08c2360e --- /dev/null +++ b/tests/net/npf/prj.conf @@ -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 diff --git a/tests/net/npf/src/main.c b/tests/net/npf/src/main.c new file mode 100644 index 00000000000..a177bd9c0b1 --- /dev/null +++ b/tests/net/npf/src/main.c @@ -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 +LOG_MODULE_REGISTER(npf_test, NET_LOG_LEVEL); + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#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, ð_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); +} diff --git a/tests/net/npf/testcase.yaml b/tests/net/npf/testcase.yaml new file mode 100644 index 00000000000..2b22d1a9711 --- /dev/null +++ b/tests/net/npf/testcase.yaml @@ -0,0 +1,5 @@ +tests: + net.pkt_filter: + min_ram: 16 + tags: net npf + depends_on: netif