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