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