1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2016-2025 Yandex LLC 5 * Copyright (c) 2016-2025 Andrey V. Elsukov <ae@FreeBSD.org> 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 #include <sys/param.h> 30 #include <sys/systm.h> 31 #include <sys/malloc.h> 32 #include <sys/kernel.h> 33 #include <sys/hash.h> 34 #include <sys/lock.h> 35 #include <sys/rwlock.h> 36 #include <sys/rmlock.h> 37 #include <sys/socket.h> 38 #include <sys/socketvar.h> 39 #include <sys/queue.h> 40 41 #include <net/if.h> /* ip_fw.h requires IFNAMSIZ */ 42 #include <net/pfil.h> 43 #include <netinet/in.h> 44 #include <netinet/ip_var.h> /* struct ipfw_rule_ref */ 45 #include <netinet/ip_fw.h> 46 47 #include <netpfil/ipfw/ip_fw_private.h> 48 49 #include "opt_ipfw.h" 50 51 /* 52 * External actions support for ipfw. 53 * 54 * This code provides KPI for implementing loadable modules, that 55 * can provide handlers for external action opcodes in the ipfw's 56 * rules. 57 * Module should implement opcode handler with type ipfw_eaction_t. 58 * This handler will be called by ipfw_chk() function when 59 * O_EXTERNAL_ACTION opcode is matched. The handler must return 60 * value used as return value in ipfw_chk(), i.e. IP_FW_PASS, 61 * IP_FW_DENY (see ip_fw_private.h). 62 * Also the last argument must be set by handler. If it is zero, 63 * the search continues to the next rule. If it has non zero value, 64 * the search terminates. 65 * 66 * The module that implements external action should register its 67 * handler and name with ipfw_add_eaction() function. 68 * This function will return eaction_id, that can be used by module. 69 * 70 * It is possible to pass some additional information to external 71 * action handler using O_EXTERNAL_INSTANCE and O_EXTERNAL_DATA opcodes. 72 * Such opcodes should be next after the O_EXTERNAL_ACTION opcode. 73 * For the O_EXTERNAL_INSTANCE opcode the cmd->kidx contains index of named 74 * object related to an instance of external action. 75 * For the O_EXTERNAL_DATA opcode the cmd contains the data that can be used 76 * by external action handler without needing to create named instance. 77 * 78 * In case when eaction module uses named instances, it should register 79 * opcode rewriting routines for O_EXTERNAL_INSTANCE opcode. The 80 * classifier callback can look back into O_EXTERNAL_ACTION opcode (it 81 * must be in the (ipfw_insn *)(cmd - 2)). By kidx from O_EXTERNAL_ACTION 82 * it can deteremine eaction_id and compare it with its own. 83 * The macro IPFW_TLV_EACTION_NAME(eaction_id) can be used to deteremine 84 * the type of named_object related to external action instance. 85 * 86 * On module unload handler should be deregistered with ipfw_del_eaction() 87 * function using known eaction_id. 88 */ 89 90 struct eaction_obj { 91 struct named_object no; 92 ipfw_eaction_t *handler; 93 char name[64]; 94 }; 95 96 #define EACTION_OBJ(ch, cmd) \ 97 ((struct eaction_obj *)SRV_OBJECT((ch), insntod((cmd), kidx)->kidx)) 98 99 #if 0 100 #define EACTION_DEBUG(fmt, ...) do { \ 101 printf("%s: " fmt "\n", __func__, ## __VA_ARGS__); \ 102 } while (0) 103 #else 104 #define EACTION_DEBUG(fmt, ...) 105 #endif 106 107 const char *default_eaction_typename = "drop"; 108 static int 109 default_eaction(struct ip_fw_chain *ch, struct ip_fw_args *args, 110 ipfw_insn *cmd, int *done) 111 { 112 113 *done = 1; /* terminate the search */ 114 return (IP_FW_DENY); 115 } 116 117 /* 118 * Opcode rewriting callbacks. 119 */ 120 static int 121 eaction_classify(ipfw_insn *cmd0, uint32_t *puidx, uint8_t *ptype) 122 { 123 ipfw_insn_kidx *cmd; 124 125 if (F_LEN(cmd0) <= 1) 126 return (EINVAL); 127 128 cmd = insntod(cmd0, kidx); 129 EACTION_DEBUG("opcode %u, kidx %u", cmd0->opcode, cmd->kidx); 130 *puidx = cmd->kidx; 131 *ptype = 0; 132 return (0); 133 } 134 135 static void 136 eaction_update(ipfw_insn *cmd0, uint32_t idx) 137 { 138 ipfw_insn_kidx *cmd; 139 140 cmd = insntod(cmd0, kidx); 141 cmd->kidx = idx; 142 EACTION_DEBUG("opcode %u, kidx -> %u", cmd0->opcode, cmd->kidx); 143 } 144 145 static int 146 eaction_findbyname(struct ip_fw_chain *ch, struct tid_info *ti, 147 struct named_object **pno) 148 { 149 ipfw_obj_ntlv *ntlv; 150 151 if (ti->tlvs == NULL) 152 return (EINVAL); 153 154 /* Search ntlv in the buffer provided by user */ 155 ntlv = ipfw_find_name_tlv_type(ti->tlvs, ti->tlen, ti->uidx, 156 IPFW_TLV_EACTION); 157 if (ntlv == NULL) 158 return (EINVAL); 159 EACTION_DEBUG("name %s, uidx %u, type %u", ntlv->name, 160 ti->uidx, ti->type); 161 /* 162 * Search named object with corresponding name. 163 * Since eaction objects are global - ignore the set value 164 * and use zero instead. 165 */ 166 *pno = ipfw_objhash_lookup_name_type(CHAIN_TO_SRV(ch), 167 0, IPFW_TLV_EACTION, ntlv->name); 168 if (*pno == NULL) 169 return (ESRCH); 170 return (0); 171 } 172 173 static struct named_object * 174 eaction_findbykidx(struct ip_fw_chain *ch, uint32_t idx) 175 { 176 177 EACTION_DEBUG("kidx %u", idx); 178 return (ipfw_objhash_lookup_kidx(CHAIN_TO_SRV(ch), idx)); 179 } 180 181 static struct opcode_obj_rewrite eaction_opcodes[] = { 182 { 183 .opcode = O_EXTERNAL_ACTION, 184 .etlv = IPFW_TLV_EACTION, 185 .classifier = eaction_classify, 186 .update = eaction_update, 187 .find_byname = eaction_findbyname, 188 .find_bykidx = eaction_findbykidx, 189 }, 190 }; 191 192 static int 193 create_eaction_obj(struct ip_fw_chain *ch, ipfw_eaction_t handler, 194 const char *name, uint32_t *eaction_id) 195 { 196 struct namedobj_instance *ni; 197 struct eaction_obj *obj; 198 199 IPFW_UH_UNLOCK_ASSERT(ch); 200 201 ni = CHAIN_TO_SRV(ch); 202 obj = malloc(sizeof(*obj), M_IPFW, M_WAITOK | M_ZERO); 203 obj->no.name = obj->name; 204 obj->no.etlv = IPFW_TLV_EACTION; 205 obj->handler = handler; 206 strlcpy(obj->name, name, sizeof(obj->name)); 207 208 IPFW_UH_WLOCK(ch); 209 if (ipfw_objhash_lookup_name_type(ni, 0, IPFW_TLV_EACTION, 210 name) != NULL) { 211 /* 212 * Object is already created. 213 * We don't allow eactions with the same name. 214 */ 215 IPFW_UH_WUNLOCK(ch); 216 free(obj, M_IPFW); 217 EACTION_DEBUG("External action with typename " 218 "'%s' already exists", name); 219 return (EEXIST); 220 } 221 if (ipfw_objhash_alloc_idx(ni, &obj->no.kidx) != 0) { 222 IPFW_UH_WUNLOCK(ch); 223 free(obj, M_IPFW); 224 EACTION_DEBUG("alloc_idx failed"); 225 return (ENOSPC); 226 } 227 ipfw_objhash_add(ni, &obj->no); 228 IPFW_WLOCK(ch); 229 SRV_OBJECT(ch, obj->no.kidx) = obj; 230 IPFW_WUNLOCK(ch); 231 obj->no.refcnt++; 232 IPFW_UH_WUNLOCK(ch); 233 234 if (eaction_id != NULL) 235 *eaction_id = obj->no.kidx; 236 return (0); 237 } 238 239 static void 240 destroy_eaction_obj(struct ip_fw_chain *ch, struct named_object *no) 241 { 242 struct namedobj_instance *ni; 243 struct eaction_obj *obj; 244 245 IPFW_UH_WLOCK_ASSERT(ch); 246 247 ni = CHAIN_TO_SRV(ch); 248 IPFW_WLOCK(ch); 249 obj = SRV_OBJECT(ch, no->kidx); 250 SRV_OBJECT(ch, no->kidx) = NULL; 251 IPFW_WUNLOCK(ch); 252 ipfw_objhash_del(ni, no); 253 ipfw_objhash_free_idx(ni, no->kidx); 254 free(obj, M_IPFW); 255 } 256 257 /* 258 * Resets all eaction opcodes to default handlers. 259 */ 260 static void 261 reset_eaction_rules(struct ip_fw_chain *ch, uint32_t eaction_id, 262 uint32_t instance_id, bool reset_rules) 263 { 264 struct named_object *no; 265 int i; 266 267 IPFW_UH_WLOCK_ASSERT(ch); 268 269 no = ipfw_objhash_lookup_name_type(CHAIN_TO_SRV(ch), 0, 270 IPFW_TLV_EACTION, default_eaction_typename); 271 if (no == NULL) 272 panic("Default external action handler is not found"); 273 if (eaction_id == no->kidx) 274 panic("Wrong eaction_id"); 275 276 EACTION_DEBUG("Going to replace id %u with %u", eaction_id, no->kidx); 277 IPFW_WLOCK(ch); 278 /* 279 * Reset eaction objects only if it is referenced by rules. 280 * But always reset objects for orphaned dynamic states. 281 */ 282 if (reset_rules) { 283 for (i = 0; i < ch->n_rules; i++) { 284 /* 285 * Refcount on the original object will be just 286 * ignored on destroy. Refcount on default_eaction 287 * will be decremented on rule deletion, thus we 288 * need to reference default_eaction object. 289 */ 290 if (ipfw_reset_eaction(ch, ch->map[i], eaction_id, 291 no->kidx, instance_id) != 0) 292 no->refcnt++; 293 } 294 } 295 /* 296 * Reset eaction opcodes for orphaned dynamic states. 297 * Since parent rules are already deleted, we don't need to 298 * reference named object of default_eaction. 299 */ 300 ipfw_dyn_reset_eaction(ch, eaction_id, no->kidx, instance_id); 301 IPFW_WUNLOCK(ch); 302 } 303 304 /* 305 * Initialize external actions framework. 306 * Create object with default eaction handler "drop". 307 */ 308 int 309 ipfw_eaction_init(struct ip_fw_chain *ch, int first) 310 { 311 int error; 312 313 error = create_eaction_obj(ch, default_eaction, 314 default_eaction_typename, NULL); 315 if (error != 0) 316 return (error); 317 IPFW_ADD_OBJ_REWRITER(first, eaction_opcodes); 318 EACTION_DEBUG("External actions support initialized"); 319 return (0); 320 } 321 322 void 323 ipfw_eaction_uninit(struct ip_fw_chain *ch, int last) 324 { 325 struct namedobj_instance *ni; 326 struct named_object *no; 327 328 ni = CHAIN_TO_SRV(ch); 329 330 IPFW_UH_WLOCK(ch); 331 no = ipfw_objhash_lookup_name_type(ni, 0, IPFW_TLV_EACTION, 332 default_eaction_typename); 333 if (no != NULL) 334 destroy_eaction_obj(ch, no); 335 IPFW_UH_WUNLOCK(ch); 336 IPFW_DEL_OBJ_REWRITER(last, eaction_opcodes); 337 EACTION_DEBUG("External actions support uninitialized"); 338 } 339 340 /* 341 * Registers external action handler to the global array. 342 * On success it returns eaction id, otherwise - zero. 343 */ 344 uint32_t 345 ipfw_add_eaction(struct ip_fw_chain *ch, ipfw_eaction_t handler, 346 const char *name) 347 { 348 uint32_t eaction_id; 349 350 eaction_id = 0; 351 if (ipfw_check_object_name_generic(name) == 0) { 352 create_eaction_obj(ch, handler, name, &eaction_id); 353 EACTION_DEBUG("Registered external action '%s' with id %u", 354 name, eaction_id); 355 } 356 return (eaction_id); 357 } 358 359 /* 360 * Deregisters external action handler with id eaction_id. 361 */ 362 int 363 ipfw_del_eaction(struct ip_fw_chain *ch, uint32_t eaction_id) 364 { 365 struct named_object *no; 366 367 IPFW_UH_WLOCK(ch); 368 no = ipfw_objhash_lookup_kidx(CHAIN_TO_SRV(ch), eaction_id); 369 if (no == NULL || no->etlv != IPFW_TLV_EACTION) { 370 IPFW_UH_WUNLOCK(ch); 371 return (EINVAL); 372 } 373 reset_eaction_rules(ch, eaction_id, 0, (no->refcnt > 1)); 374 EACTION_DEBUG("External action '%s' with id %u unregistered", 375 no->name, eaction_id); 376 destroy_eaction_obj(ch, no); 377 IPFW_UH_WUNLOCK(ch); 378 return (0); 379 } 380 381 int 382 ipfw_reset_eaction(struct ip_fw_chain *ch, struct ip_fw *rule, 383 uint32_t eaction_id, uint32_t default_id, uint32_t instance_id) 384 { 385 ipfw_insn *cmd, *icmd; 386 int l; 387 388 IPFW_UH_WLOCK_ASSERT(ch); 389 IPFW_WLOCK_ASSERT(ch); 390 391 /* 392 * Return if there is not O_EXTERNAL_ACTION or its id is 393 * different. 394 */ 395 cmd = ipfw_get_action(rule); 396 if (cmd->opcode != O_EXTERNAL_ACTION || 397 insntod(cmd, kidx)->kidx != eaction_id) 398 return (0); 399 /* 400 * Check if there is O_EXTERNAL_INSTANCE opcode, we need 401 * to truncate the rule length. 402 * 403 * NOTE: F_LEN(cmd) must be 2 for O_EXTERNAL_ACTION opcode, 404 * and rule length should be enough to keep O_EXTERNAL_INSTANCE 405 * opcode, thus we do check for l > 2. 406 */ 407 l = rule->cmd + rule->cmd_len - cmd; 408 if (l > 2) { 409 MPASS(F_LEN(cmd) == 2); 410 icmd = cmd + F_LEN(cmd); 411 if (icmd->opcode == O_EXTERNAL_INSTANCE && 412 instance_id != 0 && 413 insntod(icmd, kidx)->kidx != instance_id) 414 return (0); 415 /* 416 * Since named_object related to this instance will be 417 * destroyed, truncate the chain of opcodes to remove 418 * the rest of cmd chain just after O_EXTERNAL_ACTION 419 * opcode. 420 */ 421 EACTION_DEBUG("truncate rule %u: len %u -> %u", 422 rule->rulenum, rule->cmd_len, 423 rule->cmd_len - F_LEN(icmd)); 424 rule->cmd_len -= F_LEN(icmd); 425 MPASS(((uint32_t *)icmd - 426 (uint32_t *)rule->cmd) == rule->cmd_len); 427 } 428 429 insntod(cmd, kidx)->kidx = default_id; /* Set to default id */ 430 /* 431 * Return 1 when reset successfully happened. 432 */ 433 return (1); 434 } 435 436 /* 437 * This function should be called before external action instance is 438 * destroyed. It will reset eaction_id to default_id for rules, where 439 * eaction has instance with id == kidx. 440 */ 441 int 442 ipfw_reset_eaction_instance(struct ip_fw_chain *ch, uint32_t eaction_id, 443 uint32_t kidx) 444 { 445 struct named_object *no; 446 447 IPFW_UH_WLOCK_ASSERT(ch); 448 no = ipfw_objhash_lookup_kidx(CHAIN_TO_SRV(ch), eaction_id); 449 if (no == NULL || no->etlv != IPFW_TLV_EACTION) 450 return (EINVAL); 451 452 reset_eaction_rules(ch, eaction_id, kidx, 0); 453 return (0); 454 } 455 456 int 457 ipfw_run_eaction(struct ip_fw_chain *ch, struct ip_fw_args *args, 458 ipfw_insn *cmd, int *done) 459 { 460 461 MPASS(F_LEN(cmd) == 2); 462 return (EACTION_OBJ(ch, cmd)->handler(ch, args, cmd, done)); 463 } 464