xref: /freebsd/sys/net/dummymbuf.c (revision dd3603749cb7f20a628f04d595b105962b21a3d2)
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/if_private.h>
41 #include <net/vnet.h>
42 #include <net/pfil.h>
43 
44 static int validate_rules(const char *);
45 
46 /*
47  * Separate sysctl sub-tree
48  */
49 
50 SYSCTL_NODE(_net, OID_AUTO, dummymbuf, 0, NULL,
51     "Dummy mbuf sysctl");
52 
53 /*
54  * Rules
55  */
56 
57 static MALLOC_DEFINE(M_DUMMYMBUF_RULES, "dummymbuf_rules",
58     "dummymbuf rules string buffer");
59 
60 #define RULES_MAXLEN		1024
61 VNET_DEFINE_STATIC(char *,	dmb_rules) = NULL;
62 #define V_dmb_rules		VNET(dmb_rules)
63 
64 VNET_DEFINE_STATIC(struct sx,	dmb_rules_lock);
65 #define V_dmb_rules_lock	VNET(dmb_rules_lock)
66 
67 #define DMB_RULES_SLOCK()	sx_slock(&V_dmb_rules_lock)
68 #define DMB_RULES_SUNLOCK()	sx_sunlock(&V_dmb_rules_lock)
69 #define DMB_RULES_XLOCK()	sx_xlock(&V_dmb_rules_lock)
70 #define DMB_RULES_XUNLOCK()	sx_xunlock(&V_dmb_rules_lock)
71 #define DMB_RULES_LOCK_ASSERT()	sx_assert(&V_dmb_rules_lock, SA_LOCKED)
72 
73 static int
74 dmb_sysctl_handle_rules(SYSCTL_HANDLER_ARGS)
75 {
76 	int error = 0;
77 	char empty = '\0';
78 	char **rulesp = (char **)arg1;
79 
80 	if (req->newptr == NULL) {
81 		/* read only */
82 		DMB_RULES_SLOCK();
83 		arg1 = *rulesp;
84 		if (arg1 == NULL) {
85 			arg1 = &empty;
86 			arg2 = 0;
87 		}
88 		error = sysctl_handle_string(oidp, arg1, arg2, req);
89 		DMB_RULES_SUNLOCK();
90 	} else {
91 		/* read and write */
92 		DMB_RULES_XLOCK();
93 		arg1 = malloc(arg2, M_DUMMYMBUF_RULES, M_WAITOK | M_ZERO);
94 		error = sysctl_handle_string(oidp, arg1, arg2, req);
95 		if (error == 0 && (error = validate_rules(arg1)) == 0) {
96 			free(*rulesp, M_DUMMYMBUF_RULES);
97 			*rulesp = arg1;
98 			arg1 = NULL;
99 		}
100 		free(arg1, M_DUMMYMBUF_RULES);
101 		DMB_RULES_XUNLOCK();
102 	}
103 
104 	return (error);
105 }
106 
107 SYSCTL_PROC(_net_dummymbuf, OID_AUTO, rules,
108     CTLTYPE_STRING | CTLFLAG_MPSAFE | CTLFLAG_RW | CTLFLAG_VNET,
109     &VNET_NAME(dmb_rules), RULES_MAXLEN, dmb_sysctl_handle_rules, "A",
110     "{inet|inet6|ethernet} {in|out} <ifname> <opname>[ <opargs>]; ...;");
111 
112 /*
113  * Statistics
114  */
115 
116 VNET_DEFINE_STATIC(counter_u64_t,	dmb_hits);
117 #define V_dmb_hits			VNET(dmb_hits)
118 SYSCTL_PROC(_net_dummymbuf, OID_AUTO, hits,
119     CTLTYPE_U64 | CTLFLAG_MPSAFE | CTLFLAG_STATS | CTLFLAG_RW | CTLFLAG_VNET,
120     &VNET_NAME(dmb_hits), 0, sysctl_handle_counter_u64,
121     "QU", "Number of times a rule has been applied");
122 
123 /*
124  * pfil(9) context
125  */
126 
127 #ifdef INET
128 VNET_DEFINE_STATIC(pfil_hook_t,		dmb_pfil_inet_hook);
129 #define V_dmb_pfil_inet_hook		VNET(dmb_pfil_inet_hook)
130 #endif
131 
132 #ifdef INET6
133 VNET_DEFINE_STATIC(pfil_hook_t,		dmb_pfil_inet6_hook);
134 #define V_dmb_pfil_inet6_hook		VNET(dmb_pfil_inet6_hook)
135 #endif
136 
137 VNET_DEFINE_STATIC(pfil_hook_t,		dmb_pfil_ethernet_hook);
138 #define V_dmb_pfil_ethernet_hook	VNET(dmb_pfil_ethernet_hook)
139 
140 /*
141  * Logging
142  */
143 
144 #define FEEDBACK_RULE(rule, msg)					\
145 	printf("dummymbuf: %s: %.*s\n",					\
146 	    (msg),							\
147 	    (rule).syntax_len, (rule).syntax_begin			\
148 	)
149 
150 #define FEEDBACK_PFIL(pfil_type, pfil_flags, ifp, rule, msg)		\
151 	printf("dummymbuf: %s %b %s: %s: %.*s\n",			\
152 	    ((pfil_type) == PFIL_TYPE_IP4 ?	 "PFIL_TYPE_IP4" :	\
153 	     (pfil_type) == PFIL_TYPE_IP6 ?	 "PFIL_TYPE_IP6" :	\
154 	     (pfil_type) == PFIL_TYPE_ETHERNET ? "PFIL_TYPE_ETHERNET" :	\
155 						 "PFIL_TYPE_UNKNOWN"),	\
156 	    (pfil_flags), "\20\21PFIL_IN\22PFIL_OUT",			\
157 	    (ifp)->if_xname,						\
158 	    (msg),							\
159 	    (rule).syntax_len, (rule).syntax_begin			\
160 	)
161 
162 /*
163  * Internals
164  */
165 
166 struct rule;
167 typedef struct mbuf * (*op_t)(struct mbuf *, struct rule *);
168 struct rule {
169 	const char	*syntax_begin;
170 	int		 syntax_len;
171 	int		 pfil_type;
172 	int		 pfil_dir;
173 	char		 ifname[IFNAMSIZ];
174 	op_t		 op;
175 	const char	*opargs;
176 };
177 
178 static struct mbuf *
179 dmb_m_pull_head(struct mbuf *m, struct rule *rule)
180 {
181 	struct mbuf *n;
182 	int count;
183 
184 	count = (int)strtol(rule->opargs, NULL, 10);
185 	if (count < 0 || count > MCLBYTES)
186 		goto bad;
187 
188 	if (!(m->m_flags & M_PKTHDR))
189 		goto bad;
190 	if (m->m_pkthdr.len <= 0)
191 		return (m);
192 	if (count > m->m_pkthdr.len)
193 		count = m->m_pkthdr.len;
194 
195 	if ((n = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR)) == NULL)
196 		goto bad;
197 
198 	m_move_pkthdr(n, m);
199 	m_copydata(m, 0, count, n->m_ext.ext_buf);
200 	n->m_len = count;
201 
202 	m_adj(m, count);
203 	n->m_next = m;
204 
205 	return (n);
206 
207 bad:
208 	m_freem(m);
209 	return (NULL);
210 }
211 
212 static struct mbuf *
213 dmb_m_enlarge(struct mbuf *m, struct rule *rule)
214 {
215 	struct mbuf *n;
216 	int size;
217 
218 	size = (int)strtol(rule->opargs, NULL, 10);
219 	if (size < 0 || size > MJUM16BYTES)
220 		goto bad;
221 
222 	if (!(m->m_flags & M_PKTHDR))
223 		goto bad;
224 	if (m->m_pkthdr.len <= 0)
225 		return (m);
226 
227 	if ((n = m_get3(size, M_NOWAIT, MT_DATA, M_PKTHDR)) == NULL)
228 		goto bad;
229 
230 	m_move_pkthdr(n, m);
231 	m_copydata(m, 0, m->m_pkthdr.len, n->m_ext.ext_buf);
232 	n->m_len = m->m_pkthdr.len;
233 
234 	n->m_next = m;
235 
236 	return (n);
237 
238 bad:
239 	m_freem(m);
240 	return (NULL);
241 }
242 
243 static bool
244 read_rule(const char **cur, struct rule *rule, bool *eof)
245 {
246 	/* {inet|inet6|ethernet} {in|out} <ifname> <opname>[ <opargs>]; */
247 
248 	rule->syntax_begin = NULL;
249 	rule->syntax_len = 0;
250 
251 	if (*cur == NULL)
252 		return (false);
253 
254 	/* syntax_begin */
255 	while (**cur == ' ')
256 		(*cur)++;
257 	rule->syntax_begin = *cur;
258 	rule->syntax_len = strlen(rule->syntax_begin);
259 
260 	/* syntax_len */
261 	char *delim = strchr(*cur, ';');
262 	if (delim == NULL)
263 		return (false);
264 	rule->syntax_len = (int)(delim - *cur + 1);
265 
266 	/* pfil_type */
267 	if (strstr(*cur, "inet6") == *cur) {
268 		rule->pfil_type = PFIL_TYPE_IP6;
269 		*cur += strlen("inet6");
270 	} else if (strstr(*cur, "inet") == *cur) {
271 		rule->pfil_type = PFIL_TYPE_IP4;
272 		*cur += strlen("inet");
273 	} else if (strstr(*cur, "ethernet")) {
274 		rule->pfil_type = PFIL_TYPE_ETHERNET;
275 		*cur += strlen("ethernet");
276 	} else {
277 		return (false);
278 	}
279 	while (**cur == ' ')
280 		(*cur)++;
281 
282 	/* pfil_dir */
283 	if (strstr(*cur, "in") == *cur) {
284 		rule->pfil_dir = PFIL_IN;
285 		*cur += strlen("in");
286 	} else if (strstr(*cur, "out") == *cur) {
287 		rule->pfil_dir = PFIL_OUT;
288 		*cur += strlen("out");
289 	} else {
290 		return (false);
291 	}
292 	while (**cur == ' ')
293 		(*cur)++;
294 
295 	/* ifname */
296 	char *sp = strchr(*cur, ' ');
297 	if (sp == NULL || sp > delim)
298 		return (false);
299 	size_t len = sp - *cur;
300 	if (len >= sizeof(rule->ifname))
301 		return (false);
302 	strncpy(rule->ifname, *cur, len);
303 	rule->ifname[len] = 0;
304 	*cur = sp;
305 	while (**cur == ' ')
306 		(*cur)++;
307 
308 	/* opname */
309 	if (strstr(*cur, "pull-head") == *cur) {
310 		rule->op = dmb_m_pull_head;
311 		*cur += strlen("pull-head");
312 	} else if (strstr(*cur, "enlarge") == *cur) {
313 		rule->op = dmb_m_enlarge;
314 		*cur += strlen("enlarge");
315 	} else {
316 		return (false);
317 	}
318 	while (**cur == ' ')
319 		(*cur)++;
320 
321 	/* opargs */
322 	if (*cur > delim)
323 		return (false);
324 	rule->opargs = *cur;
325 
326 	/* the next rule & eof */
327 	*cur = delim + 1;
328 	while (**cur == ' ')
329 		(*cur)++;
330 	*eof = strlen(*cur) == 0;
331 
332 	return (true);
333 }
334 
335 static int
336 validate_rules(const char *rules)
337 {
338 	const char *cursor = rules;
339 	bool parsed;
340 	struct rule rule;
341 	bool eof = false;
342 
343 	DMB_RULES_LOCK_ASSERT();
344 
345 	while (!eof && (parsed = read_rule(&cursor, &rule, &eof))) {
346 		/* noop */
347 	}
348 
349 	if (!parsed) {
350 		FEEDBACK_RULE(rule, "rule parsing failed");
351 		return (EINVAL);
352 	}
353 
354 	return (0);
355 }
356 
357 static pfil_return_t
358 dmb_pfil_mbuf_chk(int pfil_type, struct mbuf **mp, struct ifnet *ifp,
359     int flags, void *ruleset, void *unused)
360 {
361 	struct mbuf *m = *mp;
362 	const char *cursor;
363 	bool parsed;
364 	struct rule rule;
365 	bool eof = false;
366 
367 	DMB_RULES_SLOCK();
368 	cursor = V_dmb_rules;
369 	while (!eof && (parsed = read_rule(&cursor, &rule, &eof))) {
370 		if (rule.pfil_type == pfil_type &&
371 		    rule.pfil_dir == (flags & rule.pfil_dir)  &&
372 		    strcmp(rule.ifname, ifp->if_xname) == 0) {
373 			m = rule.op(m, &rule);
374 			if (m == NULL) {
375 				FEEDBACK_PFIL(pfil_type, flags, ifp, rule,
376 				    "mbuf operation failed");
377 				break;
378 			}
379 			counter_u64_add(V_dmb_hits, 1);
380 		}
381 	}
382 	if (!parsed) {
383 		FEEDBACK_PFIL(pfil_type, flags, ifp, rule,
384 		    "rule parsing failed");
385 		m_freem(m);
386 		m = NULL;
387 	}
388 	DMB_RULES_SUNLOCK();
389 
390 	if (m == NULL) {
391 		*mp = NULL;
392 		return (PFIL_DROPPED);
393 	}
394 	if (m != *mp) {
395 		*mp = m;
396 		return (PFIL_REALLOCED);
397 	}
398 
399 	return (PFIL_PASS);
400 }
401 
402 #ifdef INET
403 static pfil_return_t
404 dmb_pfil_inet_mbuf_chk(struct mbuf **mp, struct ifnet *ifp, int flags,
405     void *ruleset, struct inpcb *inp)
406 {
407 	return (dmb_pfil_mbuf_chk(PFIL_TYPE_IP4, mp, ifp, flags,
408 	    ruleset, inp));
409 }
410 #endif
411 
412 #ifdef INET6
413 static pfil_return_t
414 dmb_pfil_inet6_mbuf_chk(struct mbuf **mp, struct ifnet *ifp, int flags,
415     void *ruleset, struct inpcb *inp)
416 {
417 	return (dmb_pfil_mbuf_chk(PFIL_TYPE_IP6, mp, ifp, flags,
418 	    ruleset, inp));
419 }
420 #endif
421 
422 static pfil_return_t
423 dmb_pfil_ethernet_mbuf_chk(struct mbuf **mp, struct ifnet *ifp, int flags,
424     void *ruleset, struct inpcb *inp)
425 {
426 	return (dmb_pfil_mbuf_chk(PFIL_TYPE_ETHERNET, mp, ifp, flags,
427 	    ruleset, inp));
428 }
429 
430 static void
431 dmb_pfil_init(void)
432 {
433 	struct pfil_hook_args pha = {
434 		.pa_version = PFIL_VERSION,
435 		.pa_modname = "dummymbuf",
436 		.pa_flags = PFIL_IN | PFIL_OUT,
437 	};
438 
439 #ifdef INET
440 	pha.pa_type = PFIL_TYPE_IP4;
441 	pha.pa_mbuf_chk = dmb_pfil_inet_mbuf_chk;
442 	pha.pa_rulname = "inet";
443 	V_dmb_pfil_inet_hook = pfil_add_hook(&pha);
444 #endif
445 
446 #ifdef INET6
447 	pha.pa_type = PFIL_TYPE_IP6;
448 	pha.pa_mbuf_chk = dmb_pfil_inet6_mbuf_chk;
449 	pha.pa_rulname = "inet6";
450 	V_dmb_pfil_inet6_hook = pfil_add_hook(&pha);
451 #endif
452 
453 	pha.pa_type = PFIL_TYPE_ETHERNET;
454 	pha.pa_mbuf_chk = dmb_pfil_ethernet_mbuf_chk;
455 	pha.pa_rulname = "ethernet";
456 	V_dmb_pfil_ethernet_hook = pfil_add_hook(&pha);
457 }
458 
459 static void
460 dmb_pfil_uninit(void)
461 {
462 #ifdef INET
463 	pfil_remove_hook(V_dmb_pfil_inet_hook);
464 #endif
465 
466 #ifdef INET6
467 	pfil_remove_hook(V_dmb_pfil_inet6_hook);
468 #endif
469 
470 	pfil_remove_hook(V_dmb_pfil_ethernet_hook);
471 }
472 
473 static void
474 vnet_dmb_init(const void *unused __unused)
475 {
476 	sx_init(&V_dmb_rules_lock, "dummymbuf rules");
477 	V_dmb_hits = counter_u64_alloc(M_WAITOK);
478 	dmb_pfil_init();
479 }
480 VNET_SYSINIT(vnet_dmb_init, SI_SUB_PROTO_PFIL, SI_ORDER_ANY,
481     vnet_dmb_init, NULL);
482 
483 static void
484 vnet_dmb_uninit(const void *unused __unused)
485 {
486 	dmb_pfil_uninit();
487 	counter_u64_free(V_dmb_hits);
488 	sx_destroy(&V_dmb_rules_lock);
489 	free(V_dmb_rules, M_DUMMYMBUF_RULES);
490 }
491 VNET_SYSUNINIT(vnet_dmb_uninit, SI_SUB_PROTO_PFIL, SI_ORDER_ANY,
492     vnet_dmb_uninit, NULL);
493 
494 static int
495 dmb_modevent(module_t mod __unused, int event, void *arg __unused)
496 {
497 	int error = 0;
498 
499 	switch (event) {
500 	case MOD_LOAD:
501 	case MOD_UNLOAD:
502 		break;
503 	default:
504 		error = EOPNOTSUPP;
505 		break;
506 	}
507 
508 	return (error);
509 }
510 
511 static moduledata_t dmb_mod = {
512 	"dummymbuf",
513 	dmb_modevent,
514 	NULL
515 };
516 
517 DECLARE_MODULE(dummymbuf, dmb_mod, SI_SUB_PROTO_PFIL, SI_ORDER_ANY);
518 MODULE_VERSION(dummymbuf, 1);
519