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