kern_hhook.c revision 251725
1/*-
2 * Copyright (c) 2010 Lawrence Stewart <lstewart@freebsd.org>
3 * Copyright (c) 2010 The FreeBSD Foundation
4 * All rights reserved.
5 *
6 * This software was developed by Lawrence Stewart while studying at the Centre
7 * for Advanced Internet Architectures, Swinburne University of Technology,
8 * made possible in part by grants from the FreeBSD Foundation and Cisco
9 * University Research Program Fund at Community Foundation Silicon Valley.
10 *
11 * Portions of this software were developed at the Centre for Advanced
12 * Internet Architectures, Swinburne University of Technology, Melbourne,
13 * Australia by Lawrence Stewart under sponsorship from the FreeBSD Foundation.
14 *
15 * Redistribution and use in source and binary forms, with or without
16 * modification, are permitted provided that the following conditions
17 * are met:
18 * 1. Redistributions of source code must retain the above copyright
19 *    notice, this list of conditions and the following disclaimer.
20 * 2. Redistributions in binary form must reproduce the above copyright
21 *    notice, this list of conditions and the following disclaimer in the
22 *    documentation and/or other materials provided with the distribution.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
35 */
36
37#include <sys/cdefs.h>
38__FBSDID("$FreeBSD: head/sys/kern/kern_hhook.c 251725 2013-06-14 02:25:40Z lstewart $");
39
40#include <sys/param.h>
41#include <sys/kernel.h>
42#include <sys/hhook.h>
43#include <sys/khelp.h>
44#include <sys/malloc.h>
45#include <sys/module.h>
46#include <sys/module_khelp.h>
47#include <sys/osd.h>
48#include <sys/queue.h>
49#include <sys/refcount.h>
50#include <sys/systm.h>
51
52#include <net/vnet.h>
53
54struct hhook {
55	hhook_func_t		hhk_func;
56	struct helper		*hhk_helper;
57	void			*hhk_udata;
58	STAILQ_ENTRY(hhook)	hhk_next;
59};
60
61static MALLOC_DEFINE(M_HHOOK, "hhook", "Helper hooks are linked off hhook_head lists");
62
63LIST_HEAD(hhookheadhead, hhook_head);
64VNET_DEFINE(struct hhookheadhead, hhook_head_list);
65#define	V_hhook_head_list VNET(hhook_head_list)
66
67static struct mtx hhook_head_list_lock;
68MTX_SYSINIT(hhookheadlistlock, &hhook_head_list_lock, "hhook_head list lock",
69    MTX_DEF);
70
71/* Private function prototypes. */
72static void hhook_head_destroy(struct hhook_head *hhh);
73
74#define	HHHLIST_LOCK() mtx_lock(&hhook_head_list_lock)
75#define	HHHLIST_UNLOCK() mtx_unlock(&hhook_head_list_lock)
76#define	HHHLIST_LOCK_ASSERT() mtx_assert(&hhook_head_list_lock, MA_OWNED)
77
78#define	HHH_LOCK_INIT(hhh) rm_init(&(hhh)->hhh_lock, "hhook_head rm lock")
79#define	HHH_LOCK_DESTROY(hhh) rm_destroy(&(hhh)->hhh_lock)
80#define	HHH_WLOCK(hhh) rm_wlock(&(hhh)->hhh_lock)
81#define	HHH_WUNLOCK(hhh) rm_wunlock(&(hhh)->hhh_lock)
82#define	HHH_RLOCK(hhh, rmpt) rm_rlock(&(hhh)->hhh_lock, (rmpt))
83#define	HHH_RUNLOCK(hhh, rmpt) rm_runlock(&(hhh)->hhh_lock, (rmpt))
84
85/*
86 * Run all helper hook functions for a given hook point.
87 */
88void
89hhook_run_hooks(struct hhook_head *hhh, void *ctx_data, struct osd *hosd)
90{
91	struct hhook *hhk;
92	void *hdata;
93	struct rm_priotracker rmpt;
94
95	KASSERT(hhh->hhh_refcount > 0, ("hhook_head %p refcount is 0", hhh));
96
97	HHH_RLOCK(hhh, &rmpt);
98	STAILQ_FOREACH(hhk, &hhh->hhh_hooks, hhk_next) {
99		if (hhk->hhk_helper->h_flags & HELPER_NEEDS_OSD) {
100			hdata = osd_get(OSD_KHELP, hosd, hhk->hhk_helper->h_id);
101			if (hdata == NULL)
102				continue;
103		} else
104			hdata = NULL;
105
106		/*
107		 * XXXLAS: We currently ignore the int returned by the hook,
108		 * but will likely want to handle it in future to allow hhook to
109		 * be used like pfil and effect changes at the hhook calling
110		 * site e.g. we could define a new hook type of HHOOK_TYPE_PFIL
111		 * and standardise what particular return values mean and set
112		 * the context data to pass exactly the same information as pfil
113		 * hooks currently receive, thus replicating pfil with hhook.
114		 */
115		hhk->hhk_func(hhh->hhh_type, hhh->hhh_id, hhk->hhk_udata,
116		    ctx_data, hdata, hosd);
117	}
118	HHH_RUNLOCK(hhh, &rmpt);
119}
120
121/*
122 * Register a new helper hook function with a helper hook point.
123 */
124int
125hhook_add_hook(struct hhook_head *hhh, struct hookinfo *hki, uint32_t flags)
126{
127	struct hhook *hhk, *tmp;
128	int error;
129
130	error = 0;
131
132	if (hhh == NULL)
133		return (ENOENT);
134
135	hhk = malloc(sizeof(struct hhook), M_HHOOK,
136	    M_ZERO | ((flags & HHOOK_WAITOK) ? M_WAITOK : M_NOWAIT));
137
138	if (hhk == NULL)
139		return (ENOMEM);
140
141	hhk->hhk_helper = hki->hook_helper;
142	hhk->hhk_func = hki->hook_func;
143	hhk->hhk_udata = hki->hook_udata;
144
145	HHH_WLOCK(hhh);
146	STAILQ_FOREACH(tmp, &hhh->hhh_hooks, hhk_next) {
147		if (tmp->hhk_func == hki->hook_func &&
148		    tmp->hhk_udata == hki->hook_udata) {
149			/* The helper hook function is already registered. */
150			error = EEXIST;
151			break;
152		}
153	}
154
155	if (!error) {
156		STAILQ_INSERT_TAIL(&hhh->hhh_hooks, hhk, hhk_next);
157		hhh->hhh_nhooks++;
158	} else
159		free(hhk, M_HHOOK);
160
161	HHH_WUNLOCK(hhh);
162
163	return (error);
164}
165
166/*
167 * Lookup a helper hook point and register a new helper hook function with it.
168 */
169int
170hhook_add_hook_lookup(struct hookinfo *hki, uint32_t flags)
171{
172	struct hhook_head *hhh;
173	int error;
174
175	hhh = hhook_head_get(hki->hook_type, hki->hook_id);
176
177	if (hhh == NULL)
178		return (ENOENT);
179
180	error = hhook_add_hook(hhh, hki, flags);
181	hhook_head_release(hhh);
182
183	return (error);
184}
185
186/*
187 * Remove a helper hook function from a helper hook point.
188 */
189int
190hhook_remove_hook(struct hhook_head *hhh, struct hookinfo *hki)
191{
192	struct hhook *tmp;
193
194	if (hhh == NULL)
195		return (ENOENT);
196
197	HHH_WLOCK(hhh);
198	STAILQ_FOREACH(tmp, &hhh->hhh_hooks, hhk_next) {
199		if (tmp->hhk_func == hki->hook_func &&
200		    tmp->hhk_udata == hki->hook_udata) {
201			STAILQ_REMOVE(&hhh->hhh_hooks, tmp, hhook, hhk_next);
202			free(tmp, M_HHOOK);
203			hhh->hhh_nhooks--;
204			break;
205		}
206	}
207	HHH_WUNLOCK(hhh);
208
209	return (0);
210}
211
212/*
213 * Lookup a helper hook point and remove a helper hook function from it.
214 */
215int
216hhook_remove_hook_lookup(struct hookinfo *hki)
217{
218	struct hhook_head *hhh;
219
220	hhh = hhook_head_get(hki->hook_type, hki->hook_id);
221
222	if (hhh == NULL)
223		return (ENOENT);
224
225	hhook_remove_hook(hhh, hki);
226	hhook_head_release(hhh);
227
228	return (0);
229}
230
231/*
232 * Register a new helper hook point.
233 */
234int
235hhook_head_register(int32_t hhook_type, int32_t hhook_id, struct hhook_head **hhh,
236    uint32_t flags)
237{
238	struct hhook_head *tmphhh;
239
240	tmphhh = hhook_head_get(hhook_type, hhook_id);
241
242	if (tmphhh != NULL) {
243		/* Hook point previously registered. */
244		hhook_head_release(tmphhh);
245		return (EEXIST);
246	}
247
248	/* XXXLAS: Need to implement support for non-virtualised hooks. */
249	if ((flags & HHOOK_HEADISINVNET) == 0) {
250		printf("%s: only vnet-style virtualised hooks can be used\n",
251		    __func__);
252		return (EINVAL);
253	}
254
255	tmphhh = malloc(sizeof(struct hhook_head), M_HHOOK,
256	    M_ZERO | ((flags & HHOOK_WAITOK) ? M_WAITOK : M_NOWAIT));
257
258	if (tmphhh == NULL)
259		return (ENOMEM);
260
261	tmphhh->hhh_type = hhook_type;
262	tmphhh->hhh_id = hhook_id;
263	tmphhh->hhh_nhooks = 0;
264	STAILQ_INIT(&tmphhh->hhh_hooks);
265	HHH_LOCK_INIT(tmphhh);
266
267	if (hhh != NULL) {
268		refcount_init(&tmphhh->hhh_refcount, 1);
269		*hhh = tmphhh;
270	} else
271		refcount_init(&tmphhh->hhh_refcount, 0);
272
273	if (flags & HHOOK_HEADISINVNET) {
274		tmphhh->hhh_flags |= HHH_ISINVNET;
275		HHHLIST_LOCK();
276		LIST_INSERT_HEAD(&V_hhook_head_list, tmphhh, hhh_next);
277		HHHLIST_UNLOCK();
278	} else {
279		/* XXXLAS: Add tmphhh to the non-virtualised list. */
280	}
281
282	return (0);
283}
284
285static void
286hhook_head_destroy(struct hhook_head *hhh)
287{
288	struct hhook *tmp, *tmp2;
289
290	HHHLIST_LOCK_ASSERT();
291
292	LIST_REMOVE(hhh, hhh_next);
293	HHH_WLOCK(hhh);
294	STAILQ_FOREACH_SAFE(tmp, &hhh->hhh_hooks, hhk_next, tmp2)
295		free(tmp, M_HHOOK);
296	HHH_WUNLOCK(hhh);
297	HHH_LOCK_DESTROY(hhh);
298	free(hhh, M_HHOOK);
299}
300
301/*
302 * Remove a helper hook point.
303 */
304int
305hhook_head_deregister(struct hhook_head *hhh)
306{
307	int error;
308
309	error = 0;
310
311	HHHLIST_LOCK();
312	if (hhh == NULL)
313		error = ENOENT;
314	else if (hhh->hhh_refcount > 1)
315		error = EBUSY;
316	else
317		hhook_head_destroy(hhh);
318	HHHLIST_UNLOCK();
319
320	return (error);
321}
322
323/*
324 * Remove a helper hook point via a hhook_head lookup.
325 */
326int
327hhook_head_deregister_lookup(int32_t hhook_type, int32_t hhook_id)
328{
329	struct hhook_head *hhh;
330	int error;
331
332	hhh = hhook_head_get(hhook_type, hhook_id);
333	error = hhook_head_deregister(hhh);
334
335	if (error == EBUSY)
336		hhook_head_release(hhh);
337
338	return (error);
339}
340
341/*
342 * Lookup and return the hhook_head struct associated with the specified type
343 * and id, or NULL if not found. If found, the hhook_head's refcount is bumped.
344 */
345struct hhook_head *
346hhook_head_get(int32_t hhook_type, int32_t hhook_id)
347{
348	struct hhook_head *hhh;
349
350	/* XXXLAS: Pick hhook_head_list based on hhook_head flags. */
351	HHHLIST_LOCK();
352	LIST_FOREACH(hhh, &V_hhook_head_list, hhh_next) {
353		if (hhh->hhh_type == hhook_type && hhh->hhh_id == hhook_id) {
354			refcount_acquire(&hhh->hhh_refcount);
355			break;
356		}
357	}
358	HHHLIST_UNLOCK();
359
360	return (hhh);
361}
362
363void
364hhook_head_release(struct hhook_head *hhh)
365{
366
367	refcount_release(&hhh->hhh_refcount);
368}
369
370/*
371 * Check the hhook_head private flags and return the appropriate public
372 * representation of the flag to the caller. The function is implemented in a
373 * way that allows us to cope with other subsystems becoming virtualised in the
374 * future.
375 */
376uint32_t
377hhook_head_is_virtualised(struct hhook_head *hhh)
378{
379	uint32_t ret;
380
381	ret = 0;
382
383	if (hhh != NULL) {
384		if (hhh->hhh_flags & HHH_ISINVNET)
385			ret = HHOOK_HEADISINVNET;
386	}
387
388	return (ret);
389}
390
391uint32_t
392hhook_head_is_virtualised_lookup(int32_t hook_type, int32_t hook_id)
393{
394	struct hhook_head *hhh;
395	uint32_t ret;
396
397	hhh = hhook_head_get(hook_type, hook_id);
398
399	if (hhh == NULL)
400		return (0);
401
402	ret = hhook_head_is_virtualised(hhh);
403	hhook_head_release(hhh);
404
405	return (ret);
406}
407
408/*
409 * Vnet created and being initialised.
410 */
411static void
412hhook_vnet_init(const void *unused __unused)
413{
414
415	LIST_INIT(&V_hhook_head_list);
416}
417
418/*
419 * Vnet being torn down and destroyed.
420 */
421static void
422hhook_vnet_uninit(const void *unused __unused)
423{
424	struct hhook_head *hhh, *tmphhh;
425
426	/*
427	 * If subsystems which export helper hook points use the hhook KPI
428	 * correctly, the loop below should have no work to do because the
429	 * subsystem should have already called hhook_head_deregister().
430	 */
431	HHHLIST_LOCK();
432	LIST_FOREACH_SAFE(hhh, &V_hhook_head_list, hhh_next, tmphhh) {
433		printf("%s: hhook_head type=%d, id=%d cleanup required\n",
434		    __func__, hhh->hhh_type, hhh->hhh_id);
435		hhook_head_destroy(hhh);
436	}
437	HHHLIST_UNLOCK();
438}
439
440
441/*
442 * When a vnet is created and being initialised, init the V_hhook_head_list.
443 */
444VNET_SYSINIT(hhook_vnet_init, SI_SUB_PROTO_BEGIN, SI_ORDER_FIRST,
445    hhook_vnet_init, NULL);
446
447/*
448 * The hhook KPI provides a mechanism for subsystems which export helper hook
449 * points to clean up on vnet tear down, but in case the KPI is misused,
450 * provide a function to clean up and free memory for a vnet being destroyed.
451 */
452VNET_SYSUNINIT(hhook_vnet_uninit, SI_SUB_PROTO_BEGIN, SI_ORDER_FIRST,
453    hhook_vnet_uninit, NULL);
454