diff options
Diffstat (limited to 'sys/net/dummymbuf.c')
| -rw-r--r-- | sys/net/dummymbuf.c | 518 |
1 files changed, 518 insertions, 0 deletions
diff --git a/sys/net/dummymbuf.c b/sys/net/dummymbuf.c new file mode 100644 index 000000000000..e88087e1572f --- /dev/null +++ b/sys/net/dummymbuf.c @@ -0,0 +1,518 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2024 Igor Ostapenko <pm@igoro.pro> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "opt_inet.h" +#include "opt_inet6.h" + +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/mbuf.h> +#include <sys/module.h> +#include <sys/socket.h> +#include <sys/sysctl.h> + +#include <net/if.h> +#include <net/if_var.h> +#include <net/if_private.h> +#include <net/vnet.h> +#include <net/pfil.h> + +static int validate_rules(const char *); + +/* + * Separate sysctl sub-tree + */ + +SYSCTL_NODE(_net, OID_AUTO, dummymbuf, 0, NULL, + "Dummy mbuf sysctl"); + +/* + * Rules + */ + +static MALLOC_DEFINE(M_DUMMYMBUF_RULES, "dummymbuf_rules", + "dummymbuf rules string buffer"); + +#define RULES_MAXLEN 1024 +VNET_DEFINE_STATIC(char *, dmb_rules) = NULL; +#define V_dmb_rules VNET(dmb_rules) + +VNET_DEFINE_STATIC(struct sx, dmb_rules_lock); +#define V_dmb_rules_lock VNET(dmb_rules_lock) + +#define DMB_RULES_SLOCK() sx_slock(&V_dmb_rules_lock) +#define DMB_RULES_SUNLOCK() sx_sunlock(&V_dmb_rules_lock) +#define DMB_RULES_XLOCK() sx_xlock(&V_dmb_rules_lock) +#define DMB_RULES_XUNLOCK() sx_xunlock(&V_dmb_rules_lock) +#define DMB_RULES_LOCK_ASSERT() sx_assert(&V_dmb_rules_lock, SA_LOCKED) + +static int +dmb_sysctl_handle_rules(SYSCTL_HANDLER_ARGS) +{ + int error = 0; + char empty = '\0'; + char **rulesp = (char **)arg1; + + if (req->newptr == NULL) { + /* read only */ + DMB_RULES_SLOCK(); + arg1 = *rulesp; + if (arg1 == NULL) { + arg1 = ∅ + arg2 = 0; + } + error = sysctl_handle_string(oidp, arg1, arg2, req); + DMB_RULES_SUNLOCK(); + } else { + /* read and write */ + DMB_RULES_XLOCK(); + arg1 = malloc(arg2, M_DUMMYMBUF_RULES, M_WAITOK | M_ZERO); + error = sysctl_handle_string(oidp, arg1, arg2, req); + if (error == 0 && (error = validate_rules(arg1)) == 0) { + free(*rulesp, M_DUMMYMBUF_RULES); + *rulesp = arg1; + arg1 = NULL; + } + free(arg1, M_DUMMYMBUF_RULES); + DMB_RULES_XUNLOCK(); + } + + return (error); +} + +SYSCTL_PROC(_net_dummymbuf, OID_AUTO, rules, + CTLTYPE_STRING | CTLFLAG_MPSAFE | CTLFLAG_RW | CTLFLAG_VNET, + &VNET_NAME(dmb_rules), RULES_MAXLEN, dmb_sysctl_handle_rules, "A", + "{inet|inet6|ethernet} {in|out} <ifname> <opname>[ <opargs>]; ...;"); + +/* + * Statistics + */ + +VNET_DEFINE_STATIC(counter_u64_t, dmb_hits); +#define V_dmb_hits VNET(dmb_hits) +SYSCTL_PROC(_net_dummymbuf, OID_AUTO, hits, + CTLTYPE_U64 | CTLFLAG_MPSAFE | CTLFLAG_STATS | CTLFLAG_RW | CTLFLAG_VNET, + &VNET_NAME(dmb_hits), 0, sysctl_handle_counter_u64, + "QU", "Number of times a rule has been applied"); + +/* + * pfil(9) context + */ + +#ifdef INET +VNET_DEFINE_STATIC(pfil_hook_t, dmb_pfil_inet_hook); +#define V_dmb_pfil_inet_hook VNET(dmb_pfil_inet_hook) +#endif + +#ifdef INET6 +VNET_DEFINE_STATIC(pfil_hook_t, dmb_pfil_inet6_hook); +#define V_dmb_pfil_inet6_hook VNET(dmb_pfil_inet6_hook) +#endif + +VNET_DEFINE_STATIC(pfil_hook_t, dmb_pfil_ethernet_hook); +#define V_dmb_pfil_ethernet_hook VNET(dmb_pfil_ethernet_hook) + +/* + * Logging + */ + +#define FEEDBACK_RULE(rule, msg) \ + printf("dummymbuf: %s: %.*s\n", \ + (msg), \ + (rule).syntax_len, (rule).syntax_begin \ + ) + +#define FEEDBACK_PFIL(pfil_type, pfil_flags, ifp, rule, msg) \ + printf("dummymbuf: %s %b %s: %s: %.*s\n", \ + ((pfil_type) == PFIL_TYPE_IP4 ? "PFIL_TYPE_IP4" : \ + (pfil_type) == PFIL_TYPE_IP6 ? "PFIL_TYPE_IP6" : \ + (pfil_type) == PFIL_TYPE_ETHERNET ? "PFIL_TYPE_ETHERNET" : \ + "PFIL_TYPE_UNKNOWN"), \ + (pfil_flags), "\20\21PFIL_IN\22PFIL_OUT", \ + (ifp)->if_xname, \ + (msg), \ + (rule).syntax_len, (rule).syntax_begin \ + ) + +/* + * Internals + */ + +struct rule; +typedef struct mbuf * (*op_t)(struct mbuf *, struct rule *); +struct rule { + const char *syntax_begin; + int syntax_len; + int pfil_type; + int pfil_dir; + char ifname[IFNAMSIZ]; + op_t op; + const char *opargs; +}; + +static struct mbuf * +dmb_m_pull_head(struct mbuf *m, struct rule *rule) +{ + struct mbuf *n; + int count; + + count = (int)strtol(rule->opargs, NULL, 10); + if (count < 0 || count > MCLBYTES) + goto bad; + + if (!(m->m_flags & M_PKTHDR)) + goto bad; + if (m->m_pkthdr.len <= 0) + return (m); + if (count > m->m_pkthdr.len) + count = m->m_pkthdr.len; + + if ((n = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR)) == NULL) + goto bad; + + m_move_pkthdr(n, m); + m_copydata(m, 0, count, n->m_ext.ext_buf); + n->m_len = count; + + m_adj(m, count); + n->m_next = m; + + return (n); + +bad: + m_freem(m); + return (NULL); +} + +static struct mbuf * +dmb_m_enlarge(struct mbuf *m, struct rule *rule) +{ + struct mbuf *n; + int size; + + size = (int)strtol(rule->opargs, NULL, 10); + if (size < 0 || size > MJUM16BYTES) + goto bad; + + if (!(m->m_flags & M_PKTHDR)) + goto bad; + if (m->m_pkthdr.len <= 0) + return (m); + + if ((n = m_get3(size, M_NOWAIT, MT_DATA, M_PKTHDR)) == NULL) + goto bad; + + m_move_pkthdr(n, m); + m_copydata(m, 0, m->m_pkthdr.len, n->m_ext.ext_buf); + n->m_len = m->m_pkthdr.len; + + n->m_next = m; + + return (n); + +bad: + m_freem(m); + return (NULL); +} + +static bool +read_rule(const char **cur, struct rule *rule, bool *eof) +{ + /* {inet|inet6|ethernet} {in|out} <ifname> <opname>[ <opargs>]; */ + + rule->syntax_begin = NULL; + rule->syntax_len = 0; + + if (*cur == NULL) + return (false); + + /* syntax_begin */ + while (**cur == ' ') + (*cur)++; + rule->syntax_begin = *cur; + rule->syntax_len = strlen(rule->syntax_begin); + + /* syntax_len */ + char *delim = strchr(*cur, ';'); + if (delim == NULL) + return (false); + rule->syntax_len = (int)(delim - *cur + 1); + + /* pfil_type */ + if (strstr(*cur, "inet6") == *cur) { + rule->pfil_type = PFIL_TYPE_IP6; + *cur += strlen("inet6"); + } else if (strstr(*cur, "inet") == *cur) { + rule->pfil_type = PFIL_TYPE_IP4; + *cur += strlen("inet"); + } else if (strstr(*cur, "ethernet")) { + rule->pfil_type = PFIL_TYPE_ETHERNET; + *cur += strlen("ethernet"); + } else { + return (false); + } + while (**cur == ' ') + (*cur)++; + + /* pfil_dir */ + if (strstr(*cur, "in") == *cur) { + rule->pfil_dir = PFIL_IN; + *cur += strlen("in"); + } else if (strstr(*cur, "out") == *cur) { + rule->pfil_dir = PFIL_OUT; + *cur += strlen("out"); + } else { + return (false); + } + while (**cur == ' ') + (*cur)++; + + /* ifname */ + char *sp = strchr(*cur, ' '); + if (sp == NULL || sp > delim) + return (false); + size_t len = sp - *cur; + if (len >= sizeof(rule->ifname)) + return (false); + strncpy(rule->ifname, *cur, len); + rule->ifname[len] = 0; + *cur = sp; + while (**cur == ' ') + (*cur)++; + + /* opname */ + if (strstr(*cur, "pull-head") == *cur) { + rule->op = dmb_m_pull_head; + *cur += strlen("pull-head"); + } else if (strstr(*cur, "enlarge") == *cur) { + rule->op = dmb_m_enlarge; + *cur += strlen("enlarge"); + } else { + return (false); + } + while (**cur == ' ') + (*cur)++; + + /* opargs */ + if (*cur > delim) + return (false); + rule->opargs = *cur; + + /* the next rule & eof */ + *cur = delim + 1; + while (**cur == ' ') + (*cur)++; + *eof = strlen(*cur) == 0; + + return (true); +} + +static int +validate_rules(const char *rules) +{ + const char *cursor = rules; + bool parsed; + struct rule rule; + bool eof = false; + + DMB_RULES_LOCK_ASSERT(); + + while (!eof && (parsed = read_rule(&cursor, &rule, &eof))) { + /* noop */ + } + + if (!parsed) { + FEEDBACK_RULE(rule, "rule parsing failed"); + return (EINVAL); + } + + return (0); +} + +static pfil_return_t +dmb_pfil_mbuf_chk(int pfil_type, struct mbuf **mp, struct ifnet *ifp, + int flags, void *ruleset, void *unused) +{ + struct mbuf *m = *mp; + const char *cursor; + bool parsed; + struct rule rule; + bool eof = false; + + DMB_RULES_SLOCK(); + cursor = V_dmb_rules; + while (!eof && (parsed = read_rule(&cursor, &rule, &eof))) { + if (rule.pfil_type == pfil_type && + rule.pfil_dir == (flags & rule.pfil_dir) && + strcmp(rule.ifname, ifp->if_xname) == 0) { + m = rule.op(m, &rule); + if (m == NULL) { + FEEDBACK_PFIL(pfil_type, flags, ifp, rule, + "mbuf operation failed"); + break; + } + counter_u64_add(V_dmb_hits, 1); + } + } + if (!parsed) { + FEEDBACK_PFIL(pfil_type, flags, ifp, rule, + "rule parsing failed"); + m_freem(m); + m = NULL; + } + DMB_RULES_SUNLOCK(); + + if (m == NULL) { + *mp = NULL; + return (PFIL_DROPPED); + } + if (m != *mp) { + *mp = m; + return (PFIL_REALLOCED); + } + + return (PFIL_PASS); +} + +#ifdef INET +static pfil_return_t +dmb_pfil_inet_mbuf_chk(struct mbuf **mp, struct ifnet *ifp, int flags, + void *ruleset, struct inpcb *inp) +{ + return (dmb_pfil_mbuf_chk(PFIL_TYPE_IP4, mp, ifp, flags, + ruleset, inp)); +} +#endif + +#ifdef INET6 +static pfil_return_t +dmb_pfil_inet6_mbuf_chk(struct mbuf **mp, struct ifnet *ifp, int flags, + void *ruleset, struct inpcb *inp) +{ + return (dmb_pfil_mbuf_chk(PFIL_TYPE_IP6, mp, ifp, flags, + ruleset, inp)); +} +#endif + +static pfil_return_t +dmb_pfil_ethernet_mbuf_chk(struct mbuf **mp, struct ifnet *ifp, int flags, + void *ruleset, struct inpcb *inp) +{ + return (dmb_pfil_mbuf_chk(PFIL_TYPE_ETHERNET, mp, ifp, flags, + ruleset, inp)); +} + +static void +dmb_pfil_init(void) +{ + struct pfil_hook_args pha = { + .pa_version = PFIL_VERSION, + .pa_modname = "dummymbuf", + .pa_flags = PFIL_IN | PFIL_OUT, + }; + +#ifdef INET + pha.pa_type = PFIL_TYPE_IP4; + pha.pa_mbuf_chk = dmb_pfil_inet_mbuf_chk; + pha.pa_rulname = "inet"; + V_dmb_pfil_inet_hook = pfil_add_hook(&pha); +#endif + +#ifdef INET6 + pha.pa_type = PFIL_TYPE_IP6; + pha.pa_mbuf_chk = dmb_pfil_inet6_mbuf_chk; + pha.pa_rulname = "inet6"; + V_dmb_pfil_inet6_hook = pfil_add_hook(&pha); +#endif + + pha.pa_type = PFIL_TYPE_ETHERNET; + pha.pa_mbuf_chk = dmb_pfil_ethernet_mbuf_chk; + pha.pa_rulname = "ethernet"; + V_dmb_pfil_ethernet_hook = pfil_add_hook(&pha); +} + +static void +dmb_pfil_uninit(void) +{ +#ifdef INET + pfil_remove_hook(V_dmb_pfil_inet_hook); +#endif + +#ifdef INET6 + pfil_remove_hook(V_dmb_pfil_inet6_hook); +#endif + + pfil_remove_hook(V_dmb_pfil_ethernet_hook); +} + +static void +vnet_dmb_init(const void *unused __unused) +{ + sx_init(&V_dmb_rules_lock, "dummymbuf rules"); + V_dmb_hits = counter_u64_alloc(M_WAITOK); + dmb_pfil_init(); +} +VNET_SYSINIT(vnet_dmb_init, SI_SUB_PROTO_PFIL, SI_ORDER_ANY, + vnet_dmb_init, NULL); + +static void +vnet_dmb_uninit(const void *unused __unused) +{ + dmb_pfil_uninit(); + counter_u64_free(V_dmb_hits); + sx_destroy(&V_dmb_rules_lock); + free(V_dmb_rules, M_DUMMYMBUF_RULES); +} +VNET_SYSUNINIT(vnet_dmb_uninit, SI_SUB_PROTO_PFIL, SI_ORDER_ANY, + vnet_dmb_uninit, NULL); + +static int +dmb_modevent(module_t mod __unused, int event, void *arg __unused) +{ + int error = 0; + + switch (event) { + case MOD_LOAD: + case MOD_UNLOAD: + break; + default: + error = EOPNOTSUPP; + break; + } + + return (error); +} + +static moduledata_t dmb_mod = { + "dummymbuf", + dmb_modevent, + NULL +}; + +DECLARE_MODULE(dummymbuf, dmb_mod, SI_SUB_PROTO_PFIL, SI_ORDER_ANY); +MODULE_VERSION(dummymbuf, 1); |
