Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/sys/netpfil/ipfw/ip_fw_eaction.c
39482 views
1
/*-
2
* SPDX-License-Identifier: BSD-2-Clause
3
*
4
* Copyright (c) 2016-2025 Yandex LLC
5
* Copyright (c) 2016-2025 Andrey V. Elsukov <[email protected]>
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
465