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