xref: /freebsd/sys/netpfil/ipfw/ip_fw_eaction.c (revision 4a77657cbc011ea657ccb079fff6b58b295eccb0)
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