xref: /freebsd/sys/net/dummymbuf.c (revision 87b759f0fa1f7554d50ce640c40138512bbded44)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2024 Igor Ostapenko <pm@igoro.pro>
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include "opt_inet.h"
29 #include "opt_inet6.h"
30 
31 #include <sys/param.h>
32 #include <sys/kernel.h>
33 #include <sys/mbuf.h>
34 #include <sys/module.h>
35 #include <sys/socket.h>
36 #include <sys/sysctl.h>
37 
38 #include <net/if.h>
39 #include <net/if_var.h>
40 #include <net/vnet.h>
41 #include <net/pfil.h>
42 
43 static int validate_rules(const char *);
44 
45 /*
46  * Separate sysctl sub-tree
47  */
48 
49 SYSCTL_NODE(_net, OID_AUTO, dummymbuf, 0, NULL,
50     "Dummy mbuf sysctl");
51 
52 /*
53  * Rules
54  */
55 
56 static MALLOC_DEFINE(M_DUMMYMBUF_RULES, "dummymbuf_rules",
57     "dummymbuf rules string buffer");
58 
59 #define RULES_MAXLEN		1024
60 VNET_DEFINE_STATIC(char *,	dmb_rules) = NULL;
61 #define V_dmb_rules		VNET(dmb_rules)
62 
63 VNET_DEFINE_STATIC(struct sx,	dmb_rules_lock);
64 #define V_dmb_rules_lock	VNET(dmb_rules_lock)
65 
66 #define DMB_RULES_SLOCK()	sx_slock(&V_dmb_rules_lock)
67 #define DMB_RULES_SUNLOCK()	sx_sunlock(&V_dmb_rules_lock)
68 #define DMB_RULES_XLOCK()	sx_xlock(&V_dmb_rules_lock)
69 #define DMB_RULES_XUNLOCK()	sx_xunlock(&V_dmb_rules_lock)
70 #define DMB_RULES_LOCK_ASSERT()	sx_assert(&V_dmb_rules_lock, SA_LOCKED)
71 
72 static int
73 dmb_sysctl_handle_rules(SYSCTL_HANDLER_ARGS)
74 {
75 	int error = 0;
76 	char empty = '\0';
77 	char **rulesp = (char **)arg1;
78 
79 	if (req->newptr == NULL) {
80 		/* read only */
81 		DMB_RULES_SLOCK();
82 		arg1 = *rulesp;
83 		if (arg1 == NULL) {
84 			arg1 = &empty;
85 			arg2 = 0;
86 		}
87 		error = sysctl_handle_string(oidp, arg1, arg2, req);
88 		DMB_RULES_SUNLOCK();
89 	} else {
90 		/* read and write */
91 		DMB_RULES_XLOCK();
92 		arg1 = malloc(arg2, M_DUMMYMBUF_RULES, M_WAITOK | M_ZERO);
93 		error = sysctl_handle_string(oidp, arg1, arg2, req);
94 		if (error == 0 && (error = validate_rules(arg1)) == 0) {
95 			free(*rulesp, M_DUMMYMBUF_RULES);
96 			*rulesp = arg1;
97 			arg1 = NULL;
98 		}
99 		free(arg1, M_DUMMYMBUF_RULES);
100 		DMB_RULES_XUNLOCK();
101 	}
102 
103 	return (error);
104 }
105 
106 SYSCTL_PROC(_net_dummymbuf, OID_AUTO, rules,
107     CTLTYPE_STRING | CTLFLAG_MPSAFE | CTLFLAG_RW | CTLFLAG_VNET,
108     &VNET_NAME(dmb_rules), RULES_MAXLEN, dmb_sysctl_handle_rules, "A",
109     "{inet|inet6|ethernet} {in|out} <ifname> <opname>[ <opargs>]; ...;");
110 
111 /*
112  * Statistics
113  */
114 
115 VNET_DEFINE_STATIC(counter_u64_t,	dmb_hits);
116 #define V_dmb_hits			VNET(dmb_hits)
117 SYSCTL_PROC(_net_dummymbuf, OID_AUTO, hits,
118     CTLTYPE_U64 | CTLFLAG_MPSAFE | CTLFLAG_STATS | CTLFLAG_RW | CTLFLAG_VNET,
119     &VNET_NAME(dmb_hits), 0, sysctl_handle_counter_u64,
120     "QU", "Number of times a rule has been applied");
121 
122 /*
123  * pfil(9) context
124  */
125 
126 #ifdef INET
127 VNET_DEFINE_STATIC(pfil_hook_t,		dmb_pfil_inet_hook);
128 #define V_dmb_pfil_inet_hook		VNET(dmb_pfil_inet_hook)
129 #endif
130 
131 #ifdef INET6
132 VNET_DEFINE_STATIC(pfil_hook_t,		dmb_pfil_inet6_hook);
133 #define V_dmb_pfil_inet6_hook		VNET(dmb_pfil_inet6_hook)
134 #endif
135 
136 VNET_DEFINE_STATIC(pfil_hook_t,		dmb_pfil_ethernet_hook);
137 #define V_dmb_pfil_ethernet_hook	VNET(dmb_pfil_ethernet_hook)
138 
139 /*
140  * Logging
141  */
142 
143 #define FEEDBACK_RULE(rule, msg)					\
144 	printf("dummymbuf: %s: %.*s\n",					\
145 	    (msg),							\
146 	    (rule).syntax_len, (rule).syntax_begin			\
147 	)
148 
149 #define FEEDBACK_PFIL(pfil_type, pfil_flags, ifp, rule, msg)		\
150 	printf("dummymbuf: %s %b %s: %s: %.*s\n",			\
151 	    ((pfil_type) == PFIL_TYPE_IP4 ?	 "PFIL_TYPE_IP4" :	\
152 	     (pfil_type) == PFIL_TYPE_IP6 ?	 "PFIL_TYPE_IP6" :	\
153 	     (pfil_type) == PFIL_TYPE_ETHERNET ? "PFIL_TYPE_ETHERNET" :	\
154 						 "PFIL_TYPE_UNKNOWN"),	\
155 	    (pfil_flags), "\20\21PFIL_IN\22PFIL_OUT",			\
156 	    (ifp)->if_xname,						\
157 	    (msg),							\
158 	    (rule).syntax_len, (rule).syntax_begin			\
159 	)
160 
161 /*
162  * Internals
163  */
164 
165 struct rule;
166 typedef struct mbuf * (*op_t)(struct mbuf *, struct rule *);
167 struct rule {
168 	const char	*syntax_begin;
169 	int		 syntax_len;
170 	int		 pfil_type;
171 	int		 pfil_dir;
172 	char		 ifname[IFNAMSIZ];
173 	op_t		 op;
174 	const char	*opargs;
175 };
176 
177 static struct mbuf *
178 dmb_m_pull_head(struct mbuf *m, struct rule *rule)
179 {
180 	struct mbuf *n;
181 	int count;
182 
183 	count = (int)strtol(rule->opargs, NULL, 10);
184 	if (count < 0 || count > MCLBYTES)
185 		goto bad;
186 
187 	if (!(m->m_flags & M_PKTHDR))
188 		goto bad;
189 	if (m->m_pkthdr.len <= 0)
190 		return (m);
191 	if (count > m->m_pkthdr.len)
192 		count = m->m_pkthdr.len;
193 
194 	if ((n = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR)) == NULL)
195 		goto bad;
196 
197 	m_move_pkthdr(n, m);
198 	m_copydata(m, 0, count, n->m_ext.ext_buf);
199 	n->m_len = count;
200 
201 	m_adj(m, count);
202 	n->m_next = m;
203 
204 	return (n);
205 
206 bad:
207 	m_freem(m);
208 	return (NULL);
209 }
210 
211 static bool
212 read_rule(const char **cur, struct rule *rule, bool *eof)
213 {
214 	/* {inet|inet6|ethernet} {in|out} <ifname> <opname>[ <opargs>]; */
215 
216 	rule->syntax_begin = NULL;
217 	rule->syntax_len = 0;
218 
219 	if (*cur == NULL)
220 		return (false);
221 
222 	/* syntax_begin */
223 	while (**cur == ' ')
224 		(*cur)++;
225 	rule->syntax_begin = *cur;
226 	rule->syntax_len = strlen(rule->syntax_begin);
227 
228 	/* syntax_len */
229 	char *delim = strchr(*cur, ';');
230 	if (delim == NULL)
231 		return (false);
232 	rule->syntax_len = (int)(delim - *cur + 1);
233 
234 	/* pfil_type */
235 	if (strstr(*cur, "inet6") == *cur) {
236 		rule->pfil_type = PFIL_TYPE_IP6;
237 		*cur += strlen("inet6");
238 	} else if (strstr(*cur, "inet") == *cur) {
239 		rule->pfil_type = PFIL_TYPE_IP4;
240 		*cur += strlen("inet");
241 	} else if (strstr(*cur, "ethernet")) {
242 		rule->pfil_type = PFIL_TYPE_ETHERNET;
243 		*cur += strlen("ethernet");
244 	} else {
245 		return (false);
246 	}
247 	while (**cur == ' ')
248 		(*cur)++;
249 
250 	/* pfil_dir */
251 	if (strstr(*cur, "in") == *cur) {
252 		rule->pfil_dir = PFIL_IN;
253 		*cur += strlen("in");
254 	} else if (strstr(*cur, "out") == *cur) {
255 		rule->pfil_dir = PFIL_OUT;
256 		*cur += strlen("out");
257 	} else {
258 		return (false);
259 	}
260 	while (**cur == ' ')
261 		(*cur)++;
262 
263 	/* ifname */
264 	char *sp = strchr(*cur, ' ');
265 	if (sp == NULL || sp > delim)
266 		return (false);
267 	size_t len = sp - *cur;
268 	if (len >= sizeof(rule->ifname))
269 		return (false);
270 	strncpy(rule->ifname, *cur, len);
271 	rule->ifname[len] = 0;
272 	*cur = sp;
273 	while (**cur == ' ')
274 		(*cur)++;
275 
276 	/* opname */
277 	if (strstr(*cur, "pull-head") == *cur) {
278 		rule->op = dmb_m_pull_head;
279 		*cur += strlen("pull-head");
280 	} else {
281 		return (false);
282 	}
283 	while (**cur == ' ')
284 		(*cur)++;
285 
286 	/* opargs */
287 	if (*cur > delim)
288 		return (false);
289 	rule->opargs = *cur;
290 
291 	/* the next rule & eof */
292 	*cur = delim + 1;
293 	while (**cur == ' ')
294 		(*cur)++;
295 	*eof = strlen(*cur) == 0;
296 
297 	return (true);
298 }
299 
300 static int
301 validate_rules(const char *rules)
302 {
303 	const char *cursor = rules;
304 	bool parsed;
305 	struct rule rule;
306 	bool eof = false;
307 
308 	DMB_RULES_LOCK_ASSERT();
309 
310 	while (!eof && (parsed = read_rule(&cursor, &rule, &eof))) {
311 		/* noop */
312 	}
313 
314 	if (!parsed) {
315 		FEEDBACK_RULE(rule, "rule parsing failed");
316 		return (EINVAL);
317 	}
318 
319 	return (0);
320 }
321 
322 static pfil_return_t
323 dmb_pfil_mbuf_chk(int pfil_type, struct mbuf **mp, struct ifnet *ifp,
324     int flags, void *ruleset, void *unused)
325 {
326 	struct mbuf *m = *mp;
327 	const char *cursor;
328 	bool parsed;
329 	struct rule rule;
330 	bool eof = false;
331 
332 	DMB_RULES_SLOCK();
333 	cursor = V_dmb_rules;
334 	while (!eof && (parsed = read_rule(&cursor, &rule, &eof))) {
335 		if (rule.pfil_type == pfil_type &&
336 		    rule.pfil_dir == (flags & rule.pfil_dir)  &&
337 		    strcmp(rule.ifname, ifp->if_xname) == 0) {
338 			m = rule.op(m, &rule);
339 			if (m == NULL) {
340 				FEEDBACK_PFIL(pfil_type, flags, ifp, rule,
341 				    "mbuf operation failed");
342 				break;
343 			}
344 			counter_u64_add(V_dmb_hits, 1);
345 		}
346 	}
347 	if (!parsed) {
348 		FEEDBACK_PFIL(pfil_type, flags, ifp, rule,
349 		    "rule parsing failed");
350 		m_freem(m);
351 		m = NULL;
352 	}
353 	DMB_RULES_SUNLOCK();
354 
355 	if (m == NULL) {
356 		*mp = NULL;
357 		return (PFIL_DROPPED);
358 	}
359 	if (m != *mp) {
360 		*mp = m;
361 		return (PFIL_REALLOCED);
362 	}
363 
364 	return (PFIL_PASS);
365 }
366 
367 #ifdef INET
368 static pfil_return_t
369 dmb_pfil_inet_mbuf_chk(struct mbuf **mp, struct ifnet *ifp, int flags,
370     void *ruleset, struct inpcb *inp)
371 {
372 	return (dmb_pfil_mbuf_chk(PFIL_TYPE_IP4, mp, ifp, flags,
373 	    ruleset, inp));
374 }
375 #endif
376 
377 #ifdef INET6
378 static pfil_return_t
379 dmb_pfil_inet6_mbuf_chk(struct mbuf **mp, struct ifnet *ifp, int flags,
380     void *ruleset, struct inpcb *inp)
381 {
382 	return (dmb_pfil_mbuf_chk(PFIL_TYPE_IP6, mp, ifp, flags,
383 	    ruleset, inp));
384 }
385 #endif
386 
387 static pfil_return_t
388 dmb_pfil_ethernet_mbuf_chk(struct mbuf **mp, struct ifnet *ifp, int flags,
389     void *ruleset, struct inpcb *inp)
390 {
391 	return (dmb_pfil_mbuf_chk(PFIL_TYPE_ETHERNET, mp, ifp, flags,
392 	    ruleset, inp));
393 }
394 
395 static void
396 dmb_pfil_init(void)
397 {
398 	struct pfil_hook_args pha = {
399 		.pa_version = PFIL_VERSION,
400 		.pa_modname = "dummymbuf",
401 		.pa_flags = PFIL_IN | PFIL_OUT,
402 	};
403 
404 #ifdef INET
405 	pha.pa_type = PFIL_TYPE_IP4;
406 	pha.pa_mbuf_chk = dmb_pfil_inet_mbuf_chk;
407 	pha.pa_rulname = "inet";
408 	V_dmb_pfil_inet_hook = pfil_add_hook(&pha);
409 #endif
410 
411 #ifdef INET6
412 	pha.pa_type = PFIL_TYPE_IP6;
413 	pha.pa_mbuf_chk = dmb_pfil_inet6_mbuf_chk;
414 	pha.pa_rulname = "inet6";
415 	V_dmb_pfil_inet6_hook = pfil_add_hook(&pha);
416 #endif
417 
418 	pha.pa_type = PFIL_TYPE_ETHERNET;
419 	pha.pa_mbuf_chk = dmb_pfil_ethernet_mbuf_chk;
420 	pha.pa_rulname = "ethernet";
421 	V_dmb_pfil_ethernet_hook = pfil_add_hook(&pha);
422 }
423 
424 static void
425 dmb_pfil_uninit(void)
426 {
427 #ifdef INET
428 	pfil_remove_hook(V_dmb_pfil_inet_hook);
429 #endif
430 
431 #ifdef INET6
432 	pfil_remove_hook(V_dmb_pfil_inet6_hook);
433 #endif
434 
435 	pfil_remove_hook(V_dmb_pfil_ethernet_hook);
436 }
437 
438 static void
439 vnet_dmb_init(void *unused __unused)
440 {
441 	sx_init(&V_dmb_rules_lock, "dummymbuf rules");
442 	V_dmb_hits = counter_u64_alloc(M_WAITOK);
443 	dmb_pfil_init();
444 }
445 VNET_SYSINIT(vnet_dmb_init, SI_SUB_PROTO_PFIL, SI_ORDER_ANY,
446     vnet_dmb_init, NULL);
447 
448 static void
449 vnet_dmb_uninit(void *unused __unused)
450 {
451 	dmb_pfil_uninit();
452 	counter_u64_free(V_dmb_hits);
453 	sx_destroy(&V_dmb_rules_lock);
454 	free(V_dmb_rules, M_DUMMYMBUF_RULES);
455 }
456 VNET_SYSUNINIT(vnet_dmb_uninit, SI_SUB_PROTO_PFIL, SI_ORDER_ANY,
457     vnet_dmb_uninit, NULL);
458 
459 static int
460 dmb_modevent(module_t mod __unused, int event, void *arg __unused)
461 {
462 	int error = 0;
463 
464 	switch (event) {
465 	case MOD_LOAD:
466 	case MOD_UNLOAD:
467 		break;
468 	default:
469 		error = EOPNOTSUPP;
470 		break;
471 	}
472 
473 	return (error);
474 }
475 
476 static moduledata_t dmb_mod = {
477 	"dummymbuf",
478 	dmb_modevent,
479 	NULL
480 };
481 
482 DECLARE_MODULE(dummymbuf, dmb_mod, SI_SUB_PROTO_PFIL, SI_ORDER_ANY);
483 MODULE_VERSION(dummymbuf, 1);
484