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