xref: /netbsd-src/sys/net/pfil.c (revision 82d56013d7b633d116a93943de88e08335357a7c)
1 /*	$NetBSD: pfil.c,v 1.39 2020/06/22 16:39:56 maxv Exp $	*/
2 
3 /*
4  * Copyright (c) 2013 Mindaugas Rasiukevicius <rmind at NetBSD org>
5  * Copyright (c) 1996 Matthew R. Green
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
22  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29 
30 #include <sys/cdefs.h>
31 __KERNEL_RCSID(0, "$NetBSD: pfil.c,v 1.39 2020/06/22 16:39:56 maxv Exp $");
32 
33 #if defined(_KERNEL_OPT)
34 #include "opt_net_mpsafe.h"
35 #endif
36 
37 #include <sys/param.h>
38 #include <sys/systm.h>
39 #include <sys/queue.h>
40 #include <sys/kmem.h>
41 #include <sys/psref.h>
42 
43 #include <net/if.h>
44 #include <net/pfil.h>
45 
46 #define	MAX_HOOKS	8
47 
48 /* Func is either pfil_func_t or pfil_ifunc_t. */
49 typedef void		(*pfil_polyfunc_t)(void);
50 
51 typedef struct {
52 	pfil_polyfunc_t pfil_func;
53 	void *		pfil_arg;
54 } pfil_hook_t;
55 
56 typedef struct {
57 	pfil_hook_t	hooks[MAX_HOOKS];
58 	u_int		nhooks;
59 	struct psref_target psref;
60 } pfil_list_t;
61 
62 typedef struct {
63 	pfil_list_t	*active;	/* lists[0] or lists[1] */
64 	pfil_list_t	lists[2];
65 } pfil_listset_t;
66 
67 CTASSERT(PFIL_IN == 1);
68 CTASSERT(PFIL_OUT == 2);
69 
70 struct pfil_head {
71 	pfil_listset_t	ph_in;
72 	pfil_listset_t	ph_out;
73 	pfil_listset_t	ph_ifaddr;
74 	pfil_listset_t	ph_ifevent;
75 	int		ph_type;
76 	void *		ph_key;
77 	LIST_ENTRY(pfil_head) ph_list;
78 };
79 
80 static const int pfil_flag_cases[] = {
81 	PFIL_IN, PFIL_OUT
82 };
83 
84 static LIST_HEAD(, pfil_head) pfil_head_list __read_mostly =
85     LIST_HEAD_INITIALIZER(&pfil_head_list);
86 
87 static kmutex_t pfil_mtx __cacheline_aligned;
88 static struct psref_class *pfil_psref_class __read_mostly;
89 #ifdef NET_MPSAFE
90 static pserialize_t pfil_psz;
91 #endif
92 
93 void
94 pfil_init(void)
95 {
96 	mutex_init(&pfil_mtx, MUTEX_DEFAULT, IPL_NONE);
97 #ifdef NET_MPSAFE
98 	pfil_psz = pserialize_create();
99 #endif
100 	pfil_psref_class = psref_class_create("pfil", IPL_SOFTNET);
101 }
102 
103 static inline void
104 pfil_listset_init(pfil_listset_t *pflistset)
105 {
106 	pflistset->active = &pflistset->lists[0];
107 	psref_target_init(&pflistset->active->psref, pfil_psref_class);
108 }
109 
110 /*
111  * pfil_head_create: create and register a packet filter head.
112  */
113 pfil_head_t *
114 pfil_head_create(int type, void *key)
115 {
116 	pfil_head_t *ph;
117 
118 	if (pfil_head_get(type, key)) {
119 		return NULL;
120 	}
121 	ph = kmem_zalloc(sizeof(pfil_head_t), KM_SLEEP);
122 	ph->ph_type = type;
123 	ph->ph_key = key;
124 
125 	pfil_listset_init(&ph->ph_in);
126 	pfil_listset_init(&ph->ph_out);
127 	pfil_listset_init(&ph->ph_ifaddr);
128 	pfil_listset_init(&ph->ph_ifevent);
129 
130 	LIST_INSERT_HEAD(&pfil_head_list, ph, ph_list);
131 	return ph;
132 }
133 
134 /*
135  * pfil_head_destroy: remove and destroy a packet filter head.
136  */
137 void
138 pfil_head_destroy(pfil_head_t *pfh)
139 {
140 	LIST_REMOVE(pfh, ph_list);
141 
142 	psref_target_destroy(&pfh->ph_in.active->psref, pfil_psref_class);
143 	psref_target_destroy(&pfh->ph_out.active->psref, pfil_psref_class);
144 	psref_target_destroy(&pfh->ph_ifaddr.active->psref, pfil_psref_class);
145 	psref_target_destroy(&pfh->ph_ifevent.active->psref, pfil_psref_class);
146 
147 	kmem_free(pfh, sizeof(pfil_head_t));
148 }
149 
150 /*
151  * pfil_head_get: returns the packer filter head for a given key.
152  */
153 pfil_head_t *
154 pfil_head_get(int type, void *key)
155 {
156 	pfil_head_t *ph;
157 
158 	LIST_FOREACH(ph, &pfil_head_list, ph_list) {
159 		if (ph->ph_type == type && ph->ph_key == key)
160 			break;
161 	}
162 	return ph;
163 }
164 
165 static pfil_listset_t *
166 pfil_hook_get(int dir, pfil_head_t *ph)
167 {
168 	switch (dir) {
169 	case PFIL_IN:
170 		return &ph->ph_in;
171 	case PFIL_OUT:
172 		return &ph->ph_out;
173 	case PFIL_IFADDR:
174 		return &ph->ph_ifaddr;
175 	case PFIL_IFNET:
176 		return &ph->ph_ifevent;
177 	}
178 	return NULL;
179 }
180 
181 static int
182 pfil_list_add(pfil_listset_t *phlistset, pfil_polyfunc_t func, void *arg,
183               int flags)
184 {
185 	u_int nhooks;
186 	pfil_list_t *newlist, *oldlist;
187 	pfil_hook_t *pfh;
188 
189 	mutex_enter(&pfil_mtx);
190 
191 	/* Check if we have a free slot. */
192 	nhooks = phlistset->active->nhooks;
193 	if (nhooks == MAX_HOOKS) {
194 		mutex_exit(&pfil_mtx);
195 		return ENOSPC;
196 	}
197 	KASSERT(nhooks < MAX_HOOKS);
198 
199 	if (phlistset->active == &phlistset->lists[0]) {
200 		oldlist = &phlistset->lists[0];
201 		newlist = &phlistset->lists[1];
202 	} else{
203 		oldlist = &phlistset->lists[1];
204 		newlist = &phlistset->lists[0];
205 	}
206 
207 	/* Make sure the hook is not already added. */
208 	for (u_int i = 0; i < nhooks; i++) {
209 		pfh = &oldlist->hooks[i];
210 		if (pfh->pfil_func == func && pfh->pfil_arg == arg) {
211 			mutex_exit(&pfil_mtx);
212 			return EEXIST;
213 		}
214 	}
215 
216 	/* create new pfil_list_t copied from old */
217 	memcpy(newlist, oldlist, sizeof(pfil_list_t));
218 	psref_target_init(&newlist->psref, pfil_psref_class);
219 
220 	/*
221 	 * Finally, add the hook.  Note: for PFIL_IN we insert the hooks in
222 	 * reverse order of the PFIL_OUT so that the same path is followed
223 	 * in or out of the kernel.
224 	 */
225 	if (flags & PFIL_IN) {
226 		/* XXX: May want to revisit this later; */
227 		size_t len = sizeof(pfil_hook_t) * nhooks;
228 		pfh = &newlist->hooks[0];
229 		memmove(&newlist->hooks[1], pfh, len);
230 	} else {
231 		pfh = &newlist->hooks[nhooks];
232 	}
233 	newlist->nhooks++;
234 
235 	pfh->pfil_func = func;
236 	pfh->pfil_arg  = arg;
237 
238 	/* switch from oldlist to newlist */
239 	atomic_store_release(&phlistset->active, newlist);
240 #ifdef NET_MPSAFE
241 	pserialize_perform(pfil_psz);
242 #endif
243 	mutex_exit(&pfil_mtx);
244 
245 	/* Wait for all readers */
246 #ifdef NET_MPSAFE
247 	psref_target_destroy(&oldlist->psref, pfil_psref_class);
248 #endif
249 
250 	return 0;
251 }
252 
253 /*
254  * pfil_add_hook: add a function (hook) to the packet filter head.
255  * The possible flags are:
256  *
257  *	PFIL_IN		call on incoming packets
258  *	PFIL_OUT	call on outgoing packets
259  *	PFIL_ALL	call on all of the above
260  */
261 int
262 pfil_add_hook(pfil_func_t func, void *arg, int flags, pfil_head_t *ph)
263 {
264 	int error = 0;
265 
266 	KASSERT(func != NULL);
267 	KASSERT((flags & ~PFIL_ALL) == 0);
268 
269 	for (u_int i = 0; i < __arraycount(pfil_flag_cases); i++) {
270 		const int fcase = pfil_flag_cases[i];
271 		pfil_listset_t *phlistset;
272 
273 		if ((flags & fcase) == 0) {
274 			continue;
275 		}
276 		phlistset = pfil_hook_get(fcase, ph);
277 		error = pfil_list_add(phlistset, (pfil_polyfunc_t)func, arg,
278 		    flags);
279 		if (error && (error != EEXIST))
280 			break;
281 	}
282 	if (error && (error != EEXIST)) {
283 		pfil_remove_hook(func, arg, flags, ph);
284 	}
285 	return error;
286 }
287 
288 /*
289  * pfil_add_ihook: add an interface-event function (hook) to the packet
290  * filter head.  The possible flags are:
291  *
292  *	PFIL_IFADDR	call on interface reconfig (cmd is ioctl #)
293  *	PFIL_IFNET	call on interface attach/detach (cmd is PFIL_IFNET_*)
294  */
295 int
296 pfil_add_ihook(pfil_ifunc_t func, void *arg, int flags, pfil_head_t *ph)
297 {
298 	pfil_listset_t *phlistset;
299 
300 	KASSERT(func != NULL);
301 	KASSERT(flags == PFIL_IFADDR || flags == PFIL_IFNET);
302 
303 	phlistset = pfil_hook_get(flags, ph);
304 	return pfil_list_add(phlistset, (pfil_polyfunc_t)func, arg, flags);
305 }
306 
307 /*
308  * pfil_list_remove: remove the hook from a specified list.
309  */
310 static int
311 pfil_list_remove(pfil_listset_t *phlistset, pfil_polyfunc_t func, void *arg)
312 {
313 	u_int nhooks;
314 	pfil_list_t *oldlist, *newlist;
315 
316 	mutex_enter(&pfil_mtx);
317 
318 	/* create new pfil_list_t copied from old */
319 	if (phlistset->active == &phlistset->lists[0]) {
320 		oldlist = &phlistset->lists[0];
321 		newlist = &phlistset->lists[1];
322 	} else{
323 		oldlist = &phlistset->lists[1];
324 		newlist = &phlistset->lists[0];
325 	}
326 	memcpy(newlist, oldlist, sizeof(*newlist));
327 	psref_target_init(&newlist->psref, pfil_psref_class);
328 
329 	nhooks = newlist->nhooks;
330 	for (u_int i = 0; i < nhooks; i++) {
331 		pfil_hook_t *last, *pfh = &newlist->hooks[i];
332 
333 		if (pfh->pfil_func != func || pfh->pfil_arg != arg) {
334 			continue;
335 		}
336 		if ((last = &newlist->hooks[nhooks - 1]) != pfh) {
337 			memcpy(pfh, last, sizeof(pfil_hook_t));
338 		}
339 		newlist->nhooks--;
340 
341 		/* switch from oldlist to newlist */
342 		atomic_store_release(&phlistset->active, newlist);
343 #ifdef NET_MPSAFE
344 		pserialize_perform(pfil_psz);
345 #endif
346 		mutex_exit(&pfil_mtx);
347 
348 		/* Wait for all readers */
349 #ifdef NET_MPSAFE
350 		psref_target_destroy(&oldlist->psref, pfil_psref_class);
351 #endif
352 
353 		return 0;
354 	}
355 	mutex_exit(&pfil_mtx);
356 	return ENOENT;
357 }
358 
359 /*
360  * pfil_remove_hook: remove the hook from the packet filter head.
361  */
362 int
363 pfil_remove_hook(pfil_func_t func, void *arg, int flags, pfil_head_t *ph)
364 {
365 	KASSERT((flags & ~PFIL_ALL) == 0);
366 
367 	for (u_int i = 0; i < __arraycount(pfil_flag_cases); i++) {
368 		const int fcase = pfil_flag_cases[i];
369 		pfil_listset_t *pflistset;
370 
371 		if ((flags & fcase) == 0) {
372 			continue;
373 		}
374 		pflistset = pfil_hook_get(fcase, ph);
375 		(void)pfil_list_remove(pflistset, (pfil_polyfunc_t)func, arg);
376 	}
377 	return 0;
378 }
379 
380 int
381 pfil_remove_ihook(pfil_ifunc_t func, void *arg, int flags, pfil_head_t *ph)
382 {
383 	pfil_listset_t *pflistset;
384 
385 	KASSERT(flags == PFIL_IFADDR || flags == PFIL_IFNET);
386 	pflistset = pfil_hook_get(flags, ph);
387 	(void)pfil_list_remove(pflistset, (pfil_polyfunc_t)func, arg);
388 	return 0;
389 }
390 
391 /*
392  * pfil_run_hooks: run the specified packet filter hooks.
393  */
394 int
395 pfil_run_hooks(pfil_head_t *ph, struct mbuf **mp, ifnet_t *ifp, int dir)
396 {
397 	struct mbuf *m = mp ? *mp : NULL;
398 	pfil_listset_t *phlistset;
399 	pfil_list_t *phlist;
400 	struct psref psref;
401 	int s, bound;
402 	int ret = 0;
403 
404 	KASSERT(dir == PFIL_IN || dir == PFIL_OUT);
405 
406 	if (ph == NULL) {
407 		return ret;
408 	}
409 
410 	if (__predict_false((phlistset = pfil_hook_get(dir, ph)) == NULL)) {
411 		return ret;
412 	}
413 
414 	bound = curlwp_bind();
415 	s = pserialize_read_enter();
416 	phlist = atomic_load_consume(&phlistset->active);
417 	psref_acquire(&psref, &phlist->psref, pfil_psref_class);
418 	pserialize_read_exit(s);
419 	for (u_int i = 0; i < phlist->nhooks; i++) {
420 		pfil_hook_t *pfh = &phlist->hooks[i];
421 		pfil_func_t func = (pfil_func_t)pfh->pfil_func;
422 
423 		ret = (*func)(pfh->pfil_arg, &m, ifp, dir);
424 		if (m == NULL || ret)
425 			break;
426 	}
427 	psref_release(&psref, &phlist->psref, pfil_psref_class);
428 	curlwp_bindx(bound);
429 
430 	if (mp) {
431 		*mp = m;
432 	}
433 	return ret;
434 }
435 
436 static void
437 pfil_run_arg(pfil_listset_t *phlistset, u_long cmd, void *arg)
438 {
439 	pfil_list_t *phlist;
440 	struct psref psref;
441 	int s, bound;
442 
443 	bound = curlwp_bind();
444 	s = pserialize_read_enter();
445 	phlist = atomic_load_consume(&phlistset->active);
446 	psref_acquire(&psref, &phlist->psref, pfil_psref_class);
447 	pserialize_read_exit(s);
448 	for (u_int i = 0; i < phlist->nhooks; i++) {
449 		pfil_hook_t *pfh = &phlist->hooks[i];
450 		pfil_ifunc_t func = (pfil_ifunc_t)pfh->pfil_func;
451 		(*func)(pfh->pfil_arg, cmd, arg);
452 	}
453 	psref_release(&psref, &phlist->psref, pfil_psref_class);
454 	curlwp_bindx(bound);
455 }
456 
457 void
458 pfil_run_addrhooks(pfil_head_t *ph, u_long cmd, struct ifaddr *ifa)
459 {
460 	pfil_run_arg(&ph->ph_ifaddr, cmd, ifa);
461 }
462 
463 void
464 pfil_run_ifhooks(pfil_head_t *ph, u_long cmd, struct ifnet *ifp)
465 {
466 	pfil_run_arg(&ph->ph_ifevent, cmd, ifp);
467 }
468