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