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