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
default_eaction(struct ip_fw_chain * ch,struct ip_fw_args * args,ipfw_insn * cmd,int * done)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
eaction_classify(ipfw_insn * cmd0,uint32_t * puidx,uint8_t * ptype)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
eaction_update(ipfw_insn * cmd0,uint32_t idx)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
eaction_findbyname(struct ip_fw_chain * ch,struct tid_info * ti,struct named_object ** pno)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 *
eaction_findbykidx(struct ip_fw_chain * ch,uint32_t idx)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
create_eaction_obj(struct ip_fw_chain * ch,ipfw_eaction_t handler,const char * name,uint32_t * eaction_id)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
destroy_eaction_obj(struct ip_fw_chain * ch,struct named_object * no)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
reset_eaction_rules(struct ip_fw_chain * ch,uint32_t eaction_id,uint32_t instance_id,bool reset_rules)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
ipfw_eaction_init(struct ip_fw_chain * ch,int first)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
ipfw_eaction_uninit(struct ip_fw_chain * ch,int last)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
ipfw_add_eaction(struct ip_fw_chain * ch,ipfw_eaction_t handler,const char * name)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
ipfw_del_eaction(struct ip_fw_chain * ch,uint32_t eaction_id)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
ipfw_reset_eaction(struct ip_fw_chain * ch,struct ip_fw * rule,uint32_t eaction_id,uint32_t default_id,uint32_t instance_id)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
ipfw_reset_eaction_instance(struct ip_fw_chain * ch,uint32_t eaction_id,uint32_t kidx)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
ipfw_run_eaction(struct ip_fw_chain * ch,struct ip_fw_args * args,ipfw_insn * cmd,int * done)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