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