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í
commit faa0b2a848
17 changed files with 1186 additions and 1 deletions

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_ */