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 bool 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 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 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 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 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 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 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 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 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 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 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