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 static int validate_rules(const char *); 44 45 /* 46 * Separate sysctl sub-tree 47 */ 48 49 SYSCTL_NODE(_net, OID_AUTO, dummymbuf, 0, NULL, 50 "Dummy mbuf sysctl"); 51 52 /* 53 * Rules 54 */ 55 56 static MALLOC_DEFINE(M_DUMMYMBUF_RULES, "dummymbuf_rules", 57 "dummymbuf rules string buffer"); 58 59 #define RULES_MAXLEN 1024 60 VNET_DEFINE_STATIC(char *, dmb_rules) = NULL; 61 #define V_dmb_rules VNET(dmb_rules) 62 63 VNET_DEFINE_STATIC(struct sx, dmb_rules_lock); 64 #define V_dmb_rules_lock VNET(dmb_rules_lock) 65 66 #define DMB_RULES_SLOCK() sx_slock(&V_dmb_rules_lock) 67 #define DMB_RULES_SUNLOCK() sx_sunlock(&V_dmb_rules_lock) 68 #define DMB_RULES_XLOCK() sx_xlock(&V_dmb_rules_lock) 69 #define DMB_RULES_XUNLOCK() sx_xunlock(&V_dmb_rules_lock) 70 #define DMB_RULES_LOCK_ASSERT() sx_assert(&V_dmb_rules_lock, SA_LOCKED) 71 72 static int 73 dmb_sysctl_handle_rules(SYSCTL_HANDLER_ARGS) 74 { 75 int error = 0; 76 char empty = '\0'; 77 char **rulesp = (char **)arg1; 78 79 if (req->newptr == NULL) { 80 /* read only */ 81 DMB_RULES_SLOCK(); 82 arg1 = *rulesp; 83 if (arg1 == NULL) { 84 arg1 = ∅ 85 arg2 = 0; 86 } 87 error = sysctl_handle_string(oidp, arg1, arg2, req); 88 DMB_RULES_SUNLOCK(); 89 } else { 90 /* read and write */ 91 DMB_RULES_XLOCK(); 92 arg1 = malloc(arg2, M_DUMMYMBUF_RULES, M_WAITOK | M_ZERO); 93 error = sysctl_handle_string(oidp, arg1, arg2, req); 94 if (error == 0 && (error = validate_rules(arg1)) == 0) { 95 free(*rulesp, M_DUMMYMBUF_RULES); 96 *rulesp = arg1; 97 arg1 = NULL; 98 } 99 free(arg1, M_DUMMYMBUF_RULES); 100 DMB_RULES_XUNLOCK(); 101 } 102 103 return (error); 104 } 105 106 SYSCTL_PROC(_net_dummymbuf, OID_AUTO, rules, 107 CTLTYPE_STRING | CTLFLAG_MPSAFE | CTLFLAG_RW | CTLFLAG_VNET, 108 &VNET_NAME(dmb_rules), RULES_MAXLEN, dmb_sysctl_handle_rules, "A", 109 "{inet|inet6|ethernet} {in|out} <ifname> <opname>[ <opargs>]; ...;"); 110 111 /* 112 * Statistics 113 */ 114 115 VNET_DEFINE_STATIC(counter_u64_t, dmb_hits); 116 #define V_dmb_hits VNET(dmb_hits) 117 SYSCTL_PROC(_net_dummymbuf, OID_AUTO, hits, 118 CTLTYPE_U64 | CTLFLAG_MPSAFE | CTLFLAG_STATS | CTLFLAG_RW | CTLFLAG_VNET, 119 &VNET_NAME(dmb_hits), 0, sysctl_handle_counter_u64, 120 "QU", "Number of times a rule has been applied"); 121 122 /* 123 * pfil(9) context 124 */ 125 126 #ifdef INET 127 VNET_DEFINE_STATIC(pfil_hook_t, dmb_pfil_inet_hook); 128 #define V_dmb_pfil_inet_hook VNET(dmb_pfil_inet_hook) 129 #endif 130 131 #ifdef INET6 132 VNET_DEFINE_STATIC(pfil_hook_t, dmb_pfil_inet6_hook); 133 #define V_dmb_pfil_inet6_hook VNET(dmb_pfil_inet6_hook) 134 #endif 135 136 VNET_DEFINE_STATIC(pfil_hook_t, dmb_pfil_ethernet_hook); 137 #define V_dmb_pfil_ethernet_hook VNET(dmb_pfil_ethernet_hook) 138 139 /* 140 * Logging 141 */ 142 143 #define FEEDBACK_RULE(rule, msg) \ 144 printf("dummymbuf: %s: %.*s\n", \ 145 (msg), \ 146 (rule).syntax_len, (rule).syntax_begin \ 147 ) 148 149 #define FEEDBACK_PFIL(pfil_type, pfil_flags, ifp, rule, msg) \ 150 printf("dummymbuf: %s %b %s: %s: %.*s\n", \ 151 ((pfil_type) == PFIL_TYPE_IP4 ? "PFIL_TYPE_IP4" : \ 152 (pfil_type) == PFIL_TYPE_IP6 ? "PFIL_TYPE_IP6" : \ 153 (pfil_type) == PFIL_TYPE_ETHERNET ? "PFIL_TYPE_ETHERNET" : \ 154 "PFIL_TYPE_UNKNOWN"), \ 155 (pfil_flags), "\20\21PFIL_IN\22PFIL_OUT", \ 156 (ifp)->if_xname, \ 157 (msg), \ 158 (rule).syntax_len, (rule).syntax_begin \ 159 ) 160 161 /* 162 * Internals 163 */ 164 165 struct rule; 166 typedef struct mbuf * (*op_t)(struct mbuf *, struct rule *); 167 struct rule { 168 const char *syntax_begin; 169 int syntax_len; 170 int pfil_type; 171 int pfil_dir; 172 char ifname[IFNAMSIZ]; 173 op_t op; 174 const char *opargs; 175 }; 176 177 static struct mbuf * 178 dmb_m_pull_head(struct mbuf *m, struct rule *rule) 179 { 180 struct mbuf *n; 181 int count; 182 183 count = (int)strtol(rule->opargs, NULL, 10); 184 if (count < 0 || count > MCLBYTES) 185 goto bad; 186 187 if (!(m->m_flags & M_PKTHDR)) 188 goto bad; 189 if (m->m_pkthdr.len <= 0) 190 return (m); 191 if (count > m->m_pkthdr.len) 192 count = m->m_pkthdr.len; 193 194 if ((n = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR)) == NULL) 195 goto bad; 196 197 m_move_pkthdr(n, m); 198 m_copydata(m, 0, count, n->m_ext.ext_buf); 199 n->m_len = count; 200 201 m_adj(m, count); 202 n->m_next = m; 203 204 return (n); 205 206 bad: 207 m_freem(m); 208 return (NULL); 209 } 210 211 static bool 212 read_rule(const char **cur, struct rule *rule, bool *eof) 213 { 214 /* {inet|inet6|ethernet} {in|out} <ifname> <opname>[ <opargs>]; */ 215 216 rule->syntax_begin = NULL; 217 rule->syntax_len = 0; 218 219 if (*cur == NULL) 220 return (false); 221 222 /* syntax_begin */ 223 while (**cur == ' ') 224 (*cur)++; 225 rule->syntax_begin = *cur; 226 227 /* syntax_len */ 228 char *delim = strchr(*cur, ';'); 229 if (delim == NULL) 230 return (false); 231 rule->syntax_len = (int)(delim - *cur + 1); 232 233 /* pfil_type */ 234 if (strstr(*cur, "inet6") == *cur) { 235 rule->pfil_type = PFIL_TYPE_IP6; 236 *cur += strlen("inet6"); 237 } else if (strstr(*cur, "inet") == *cur) { 238 rule->pfil_type = PFIL_TYPE_IP4; 239 *cur += strlen("inet"); 240 } else if (strstr(*cur, "ethernet")) { 241 rule->pfil_type = PFIL_TYPE_ETHERNET; 242 *cur += strlen("ethernet"); 243 } else { 244 return (false); 245 } 246 while (**cur == ' ') 247 (*cur)++; 248 249 /* pfil_dir */ 250 if (strstr(*cur, "in") == *cur) { 251 rule->pfil_dir = PFIL_IN; 252 *cur += strlen("in"); 253 } else if (strstr(*cur, "out") == *cur) { 254 rule->pfil_dir = PFIL_OUT; 255 *cur += strlen("out"); 256 } else { 257 return (false); 258 } 259 while (**cur == ' ') 260 (*cur)++; 261 262 /* ifname */ 263 char *sp = strchr(*cur, ' '); 264 if (sp == NULL || sp > delim) 265 return (false); 266 size_t len = sp - *cur; 267 if (len >= sizeof(rule->ifname)) 268 return (false); 269 strncpy(rule->ifname, *cur, len); 270 rule->ifname[len] = 0; 271 *cur = sp; 272 while (**cur == ' ') 273 (*cur)++; 274 275 /* opname */ 276 if (strstr(*cur, "pull-head") == *cur) { 277 rule->op = dmb_m_pull_head; 278 *cur += strlen("pull-head"); 279 } else { 280 return (false); 281 } 282 while (**cur == ' ') 283 (*cur)++; 284 285 /* opargs */ 286 if (*cur > delim) 287 return (false); 288 rule->opargs = *cur; 289 290 /* the next rule & eof */ 291 *cur = delim + 1; 292 while (**cur == ' ') 293 (*cur)++; 294 *eof = strlen(*cur) == 0; 295 296 return (true); 297 } 298 299 static int 300 validate_rules(const char *rules) 301 { 302 const char *cursor = rules; 303 bool parsed; 304 struct rule rule; 305 bool eof = false; 306 307 DMB_RULES_LOCK_ASSERT(); 308 309 while (!eof && (parsed = read_rule(&cursor, &rule, &eof))) { 310 /* noop */ 311 } 312 313 if (!parsed) { 314 FEEDBACK_RULE(rule, "rule parsing failed"); 315 return (EINVAL); 316 } 317 318 return (0); 319 } 320 321 static pfil_return_t 322 dmb_pfil_mbuf_chk(int pfil_type, struct mbuf **mp, struct ifnet *ifp, 323 int flags, void *ruleset, void *unused) 324 { 325 struct mbuf *m = *mp; 326 const char *cursor; 327 bool parsed; 328 struct rule rule; 329 bool eof = false; 330 331 DMB_RULES_SLOCK(); 332 cursor = V_dmb_rules; 333 while (!eof && (parsed = read_rule(&cursor, &rule, &eof))) { 334 if (rule.pfil_type == pfil_type && 335 rule.pfil_dir == (flags & rule.pfil_dir) && 336 strcmp(rule.ifname, ifp->if_xname) == 0) { 337 m = rule.op(m, &rule); 338 if (m == NULL) { 339 FEEDBACK_PFIL(pfil_type, flags, ifp, rule, 340 "mbuf operation failed"); 341 break; 342 } 343 counter_u64_add(V_dmb_hits, 1); 344 } 345 } 346 if (!parsed) { 347 FEEDBACK_PFIL(pfil_type, flags, ifp, rule, 348 "rule parsing failed"); 349 m_freem(m); 350 m = NULL; 351 } 352 DMB_RULES_SUNLOCK(); 353 354 if (m == NULL) { 355 *mp = NULL; 356 return (PFIL_DROPPED); 357 } 358 if (m != *mp) { 359 *mp = m; 360 return (PFIL_REALLOCED); 361 } 362 363 return (PFIL_PASS); 364 } 365 366 #ifdef INET 367 static pfil_return_t 368 dmb_pfil_inet_mbuf_chk(struct mbuf **mp, struct ifnet *ifp, int flags, 369 void *ruleset, struct inpcb *inp) 370 { 371 return (dmb_pfil_mbuf_chk(PFIL_TYPE_IP4, mp, ifp, flags, 372 ruleset, inp)); 373 } 374 #endif 375 376 #ifdef INET6 377 static pfil_return_t 378 dmb_pfil_inet6_mbuf_chk(struct mbuf **mp, struct ifnet *ifp, int flags, 379 void *ruleset, struct inpcb *inp) 380 { 381 return (dmb_pfil_mbuf_chk(PFIL_TYPE_IP6, mp, ifp, flags, 382 ruleset, inp)); 383 } 384 #endif 385 386 static pfil_return_t 387 dmb_pfil_ethernet_mbuf_chk(struct mbuf **mp, struct ifnet *ifp, int flags, 388 void *ruleset, struct inpcb *inp) 389 { 390 return (dmb_pfil_mbuf_chk(PFIL_TYPE_ETHERNET, mp, ifp, flags, 391 ruleset, inp)); 392 } 393 394 static void 395 dmb_pfil_init(void) 396 { 397 struct pfil_hook_args pha = { 398 .pa_version = PFIL_VERSION, 399 .pa_modname = "dummymbuf", 400 .pa_flags = PFIL_IN | PFIL_OUT, 401 }; 402 403 #ifdef INET 404 pha.pa_type = PFIL_TYPE_IP4; 405 pha.pa_mbuf_chk = dmb_pfil_inet_mbuf_chk; 406 pha.pa_rulname = "inet"; 407 V_dmb_pfil_inet_hook = pfil_add_hook(&pha); 408 #endif 409 410 #ifdef INET6 411 pha.pa_type = PFIL_TYPE_IP6; 412 pha.pa_mbuf_chk = dmb_pfil_inet6_mbuf_chk; 413 pha.pa_rulname = "inet6"; 414 V_dmb_pfil_inet6_hook = pfil_add_hook(&pha); 415 #endif 416 417 pha.pa_type = PFIL_TYPE_ETHERNET; 418 pha.pa_mbuf_chk = dmb_pfil_ethernet_mbuf_chk; 419 pha.pa_rulname = "ethernet"; 420 V_dmb_pfil_ethernet_hook = pfil_add_hook(&pha); 421 } 422 423 static void 424 dmb_pfil_uninit(void) 425 { 426 #ifdef INET 427 pfil_remove_hook(V_dmb_pfil_inet_hook); 428 #endif 429 430 #ifdef INET6 431 pfil_remove_hook(V_dmb_pfil_inet6_hook); 432 #endif 433 434 pfil_remove_hook(V_dmb_pfil_ethernet_hook); 435 } 436 437 static void 438 vnet_dmb_init(void *unused __unused) 439 { 440 sx_init(&V_dmb_rules_lock, "dummymbuf rules"); 441 V_dmb_hits = counter_u64_alloc(M_WAITOK); 442 dmb_pfil_init(); 443 } 444 VNET_SYSINIT(vnet_dmb_init, SI_SUB_PROTO_PFIL, SI_ORDER_ANY, 445 vnet_dmb_init, NULL); 446 447 static void 448 vnet_dmb_uninit(void *unused __unused) 449 { 450 dmb_pfil_uninit(); 451 counter_u64_free(V_dmb_hits); 452 sx_destroy(&V_dmb_rules_lock); 453 free(V_dmb_rules, M_DUMMYMBUF_RULES); 454 } 455 VNET_SYSUNINIT(vnet_dmb_uninit, SI_SUB_PROTO_PFIL, SI_ORDER_ANY, 456 vnet_dmb_uninit, NULL); 457 458 static int 459 dmb_modevent(module_t mod __unused, int event, void *arg __unused) 460 { 461 int error = 0; 462 463 switch (event) { 464 case MOD_LOAD: 465 case MOD_UNLOAD: 466 break; 467 default: 468 error = EOPNOTSUPP; 469 break; 470 } 471 472 return (error); 473 } 474 475 static moduledata_t dmb_mod = { 476 "dummymbuf", 477 dmb_modevent, 478 NULL 479 }; 480 481 DECLARE_MODULE(dummymbuf, dmb_mod, SI_SUB_PROTO_PFIL, SI_ORDER_ANY); 482 MODULE_VERSION(dummymbuf, 1); 483