blob: bb2eb9f085ba7a39d7e8337240a2b06d9a3c32ac [file] [log] [blame]
/*
* Copyright (c) 2021 BayLibre SAS
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(npf_base, CONFIG_NET_PKT_FILTER_LOG_LEVEL);
#include <zephyr/net/net_core.h>
#include <zephyr/net/net_pkt_filter.h>
#include <zephyr/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 = { },
};
#ifdef CONFIG_NET_PKT_FILTER_LOCAL_IN_HOOK
struct npf_rule_list npf_local_in_recv_rules = {
.rule_head = SYS_SLIST_STATIC_INIT(&local_in_recv_rules.rule_head),
.lock = { },
};
#endif /* CONFIG_NET_PKT_FILTER_LOCAL_IN_HOOK */
#ifdef CONFIG_NET_PKT_FILTER_IPV4_HOOK
struct npf_rule_list npf_ipv4_recv_rules = {
.rule_head = SYS_SLIST_STATIC_INIT(&ipv4_recv_rules.rule_head),
.lock = { },
};
#endif /* CONFIG_NET_PKT_FILTER_IPV4_HOOK */
#ifdef CONFIG_NET_PKT_FILTER_IPV6_HOOK
struct npf_rule_list npf_ipv6_recv_rules = {
.rule_head = SYS_SLIST_STATIC_INIT(&ipv6_recv_rules.rule_head),
.lock = { },
};
#endif /* CONFIG_NET_PKT_FILTER_IPV6_HOOK */
/*
* Helper function
*/
static struct npf_rule_list *get_ip_rules(uint8_t pf)
{
switch (pf) {
case PF_INET:
#ifdef CONFIG_NET_PKT_FILTER_IPV4_HOOK
return &npf_ipv4_recv_rules;
#endif
break;
case PF_INET6:
#ifdef CONFIG_NET_PKT_FILTER_IPV6_HOOK
return &npf_ipv6_recv_rules;
#endif
break;
default:
return NULL;
}
return NULL;
}
/*
* 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;
}
#ifdef CONFIG_NET_PKT_FILTER_LOCAL_IN_HOOK
bool net_pkt_filter_local_in_recv_ok(struct net_pkt *pkt)
{
enum net_verdict result = lock_evaluate(&npf_local_in_recv_rules, pkt);
return result == NET_OK;
}
#endif /* CONFIG_NET_PKT_FILTER_LOCAL_IN_HOOK */
#if defined(CONFIG_NET_PKT_FILTER_IPV4_HOOK) || defined(CONFIG_NET_PKT_FILTER_IPV6_HOOK)
bool net_pkt_filter_ip_recv_ok(struct net_pkt *pkt)
{
struct npf_rule_list *rules = get_ip_rules(net_pkt_family(pkt));
if (!rules) {
NET_DBG("no rules");
return true;
}
enum net_verdict result = lock_evaluate(rules, pkt);
return result == NET_OK;
}
#endif /* CONFIG_NET_PKT_FILTER_IPV4_HOOK || CONFIG_NET_PKT_FILTER_IPV6_HOOK */
/*
* 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;
}
bool npf_ip_src_addr_match(struct npf_test *test, struct net_pkt *pkt)
{
struct npf_test_ip *test_ip =
CONTAINER_OF(test, struct npf_test_ip, test);
uint8_t pkt_family = net_pkt_family(pkt);
for (uint32_t ip_it = 0; ip_it < test_ip->ipaddr_num; ip_it++) {
if (IS_ENABLED(CONFIG_NET_IPV4) && pkt_family == AF_INET) {
struct in_addr *addr = (struct in_addr *)NET_IPV4_HDR(pkt)->src;
if (net_ipv4_addr_cmp(addr, &((struct in_addr *)test_ip->ipaddr)[ip_it])) {
return true;
}
} else if (IS_ENABLED(CONFIG_NET_IPV6) && pkt_family == AF_INET6) {
struct in6_addr *addr = (struct in6_addr *)NET_IPV6_HDR(pkt)->src;
if (net_ipv6_addr_cmp(addr, &((struct in6_addr *)test_ip->ipaddr)[ip_it])) {
return true;
}
}
}
return false;
}
bool npf_ip_src_addr_unmatch(struct npf_test *test, struct net_pkt *pkt)
{
return !npf_ip_src_addr_match(test, pkt);
}