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