xref: /freebsd/sys/net/dummymbuf.c (revision 5a5c2279813012882e59aa7bb51d50c5baba3b1e)
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 
227 	/* syntax_len */
228 	char *delim = strchr(*cur, ';');
229 	if (delim == NULL)
230 		return (false);
231 	rule->syntax_len = (int)(delim - *cur + 1);
232 
233 	/* pfil_type */
234 	if (strstr(*cur, "inet6") == *cur) {
235 		rule->pfil_type = PFIL_TYPE_IP6;
236 		*cur += strlen("inet6");
237 	} else if (strstr(*cur, "inet") == *cur) {
238 		rule->pfil_type = PFIL_TYPE_IP4;
239 		*cur += strlen("inet");
240 	} else if (strstr(*cur, "ethernet")) {
241 		rule->pfil_type = PFIL_TYPE_ETHERNET;
242 		*cur += strlen("ethernet");
243 	} else {
244 		return (false);
245 	}
246 	while (**cur == ' ')
247 		(*cur)++;
248 
249 	/* pfil_dir */
250 	if (strstr(*cur, "in") == *cur) {
251 		rule->pfil_dir = PFIL_IN;
252 		*cur += strlen("in");
253 	} else if (strstr(*cur, "out") == *cur) {
254 		rule->pfil_dir = PFIL_OUT;
255 		*cur += strlen("out");
256 	} else {
257 		return (false);
258 	}
259 	while (**cur == ' ')
260 		(*cur)++;
261 
262 	/* ifname */
263 	char *sp = strchr(*cur, ' ');
264 	if (sp == NULL || sp > delim)
265 		return (false);
266 	size_t len = sp - *cur;
267 	if (len >= sizeof(rule->ifname))
268 		return (false);
269 	strncpy(rule->ifname, *cur, len);
270 	rule->ifname[len] = 0;
271 	*cur = sp;
272 	while (**cur == ' ')
273 		(*cur)++;
274 
275 	/* opname */
276 	if (strstr(*cur, "pull-head") == *cur) {
277 		rule->op = dmb_m_pull_head;
278 		*cur += strlen("pull-head");
279 	} else {
280 		return (false);
281 	}
282 	while (**cur == ' ')
283 		(*cur)++;
284 
285 	/* opargs */
286 	if (*cur > delim)
287 		return (false);
288 	rule->opargs = *cur;
289 
290 	/* the next rule & eof */
291 	*cur = delim + 1;
292 	while (**cur == ' ')
293 		(*cur)++;
294 	*eof = strlen(*cur) == 0;
295 
296 	return (true);
297 }
298 
299 static int
300 validate_rules(const char *rules)
301 {
302 	const char *cursor = rules;
303 	bool parsed;
304 	struct rule rule;
305 	bool eof = false;
306 
307 	DMB_RULES_LOCK_ASSERT();
308 
309 	while (!eof && (parsed = read_rule(&cursor, &rule, &eof))) {
310 		/* noop */
311 	}
312 
313 	if (!parsed) {
314 		FEEDBACK_RULE(rule, "rule parsing failed");
315 		return (EINVAL);
316 	}
317 
318 	return (0);
319 }
320 
321 static pfil_return_t
322 dmb_pfil_mbuf_chk(int pfil_type, struct mbuf **mp, struct ifnet *ifp,
323     int flags, void *ruleset, void *unused)
324 {
325 	struct mbuf *m = *mp;
326 	const char *cursor;
327 	bool parsed;
328 	struct rule rule;
329 	bool eof = false;
330 
331 	DMB_RULES_SLOCK();
332 	cursor = V_dmb_rules;
333 	while (!eof && (parsed = read_rule(&cursor, &rule, &eof))) {
334 		if (rule.pfil_type == pfil_type &&
335 		    rule.pfil_dir == (flags & rule.pfil_dir)  &&
336 		    strcmp(rule.ifname, ifp->if_xname) == 0) {
337 			m = rule.op(m, &rule);
338 			if (m == NULL) {
339 				FEEDBACK_PFIL(pfil_type, flags, ifp, rule,
340 				    "mbuf operation failed");
341 				break;
342 			}
343 			counter_u64_add(V_dmb_hits, 1);
344 		}
345 	}
346 	if (!parsed) {
347 		FEEDBACK_PFIL(pfil_type, flags, ifp, rule,
348 		    "rule parsing failed");
349 		m_freem(m);
350 		m = NULL;
351 	}
352 	DMB_RULES_SUNLOCK();
353 
354 	if (m == NULL) {
355 		*mp = NULL;
356 		return (PFIL_DROPPED);
357 	}
358 	if (m != *mp) {
359 		*mp = m;
360 		return (PFIL_REALLOCED);
361 	}
362 
363 	return (PFIL_PASS);
364 }
365 
366 #ifdef INET
367 static pfil_return_t
368 dmb_pfil_inet_mbuf_chk(struct mbuf **mp, struct ifnet *ifp, int flags,
369     void *ruleset, struct inpcb *inp)
370 {
371 	return (dmb_pfil_mbuf_chk(PFIL_TYPE_IP4, mp, ifp, flags,
372 	    ruleset, inp));
373 }
374 #endif
375 
376 #ifdef INET6
377 static pfil_return_t
378 dmb_pfil_inet6_mbuf_chk(struct mbuf **mp, struct ifnet *ifp, int flags,
379     void *ruleset, struct inpcb *inp)
380 {
381 	return (dmb_pfil_mbuf_chk(PFIL_TYPE_IP6, mp, ifp, flags,
382 	    ruleset, inp));
383 }
384 #endif
385 
386 static pfil_return_t
387 dmb_pfil_ethernet_mbuf_chk(struct mbuf **mp, struct ifnet *ifp, int flags,
388     void *ruleset, struct inpcb *inp)
389 {
390 	return (dmb_pfil_mbuf_chk(PFIL_TYPE_ETHERNET, mp, ifp, flags,
391 	    ruleset, inp));
392 }
393 
394 static void
395 dmb_pfil_init(void)
396 {
397 	struct pfil_hook_args pha = {
398 		.pa_version = PFIL_VERSION,
399 		.pa_modname = "dummymbuf",
400 		.pa_flags = PFIL_IN | PFIL_OUT,
401 	};
402 
403 #ifdef INET
404 	pha.pa_type = PFIL_TYPE_IP4;
405 	pha.pa_mbuf_chk = dmb_pfil_inet_mbuf_chk;
406 	pha.pa_rulname = "inet";
407 	V_dmb_pfil_inet_hook = pfil_add_hook(&pha);
408 #endif
409 
410 #ifdef INET6
411 	pha.pa_type = PFIL_TYPE_IP6;
412 	pha.pa_mbuf_chk = dmb_pfil_inet6_mbuf_chk;
413 	pha.pa_rulname = "inet6";
414 	V_dmb_pfil_inet6_hook = pfil_add_hook(&pha);
415 #endif
416 
417 	pha.pa_type = PFIL_TYPE_ETHERNET;
418 	pha.pa_mbuf_chk = dmb_pfil_ethernet_mbuf_chk;
419 	pha.pa_rulname = "ethernet";
420 	V_dmb_pfil_ethernet_hook = pfil_add_hook(&pha);
421 }
422 
423 static void
424 dmb_pfil_uninit(void)
425 {
426 #ifdef INET
427 	pfil_remove_hook(V_dmb_pfil_inet_hook);
428 #endif
429 
430 #ifdef INET6
431 	pfil_remove_hook(V_dmb_pfil_inet6_hook);
432 #endif
433 
434 	pfil_remove_hook(V_dmb_pfil_ethernet_hook);
435 }
436 
437 static void
438 vnet_dmb_init(void *unused __unused)
439 {
440 	sx_init(&V_dmb_rules_lock, "dummymbuf rules");
441 	V_dmb_hits = counter_u64_alloc(M_WAITOK);
442 	dmb_pfil_init();
443 }
444 VNET_SYSINIT(vnet_dmb_init, SI_SUB_PROTO_PFIL, SI_ORDER_ANY,
445     vnet_dmb_init, NULL);
446 
447 static void
448 vnet_dmb_uninit(void *unused __unused)
449 {
450 	dmb_pfil_uninit();
451 	counter_u64_free(V_dmb_hits);
452 	sx_destroy(&V_dmb_rules_lock);
453 	free(V_dmb_rules, M_DUMMYMBUF_RULES);
454 }
455 VNET_SYSUNINIT(vnet_dmb_uninit, SI_SUB_PROTO_PFIL, SI_ORDER_ANY,
456     vnet_dmb_uninit, NULL);
457 
458 static int
459 dmb_modevent(module_t mod __unused, int event, void *arg __unused)
460 {
461 	int error = 0;
462 
463 	switch (event) {
464 	case MOD_LOAD:
465 	case MOD_UNLOAD:
466 		break;
467 	default:
468 		error = EOPNOTSUPP;
469 		break;
470 	}
471 
472 	return (error);
473 }
474 
475 static moduledata_t dmb_mod = {
476 	"dummymbuf",
477 	dmb_modevent,
478 	NULL
479 };
480 
481 DECLARE_MODULE(dummymbuf, dmb_mod, SI_SUB_PROTO_PFIL, SI_ORDER_ANY);
482 MODULE_VERSION(dummymbuf, 1);
483