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