kern_hhook.c revision 216615
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, made possible in
8 * part by grants from the FreeBSD Foundation and Cisco University Research
9 * 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 216615 2010-12-21 13:45:29Z 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
61MALLOC_DECLARE(M_HHOOK);
62MALLOC_DEFINE(M_HHOOK, "hhook", "Helper hooks are linked off hhook_head lists");
63
64LIST_HEAD(hhookheadhead, hhook_head);
65VNET_DEFINE(struct hhookheadhead, hhook_head_list);
66#define	V_hhook_head_list VNET(hhook_head_list)
67
68static struct mtx hhook_head_list_lock;
69MTX_SYSINIT(hhookheadlistlock, &hhook_head_list_lock, "hhook_head list lock",
70    MTX_DEF);
71
72/* Private function prototypes. */
73static void hhook_head_destroy(struct hhook_head *hhh);
74
75#define	HHHLIST_LOCK() mtx_lock(&hhook_head_list_lock)
76#define	HHHLIST_UNLOCK() mtx_unlock(&hhook_head_list_lock)
77#define	HHHLIST_LOCK_ASSERT() mtx_assert(&hhook_head_list_lock, MA_OWNED)
78
79#define	HHH_LOCK_INIT(hhh) rm_init(&(hhh)->hhh_lock, "hhook_head rm lock")
80#define	HHH_LOCK_DESTROY(hhh) rm_destroy(&(hhh)->hhh_lock)
81#define	HHH_WLOCK(hhh) rm_wlock(&(hhh)->hhh_lock)
82#define	HHH_WUNLOCK(hhh) rm_wunlock(&(hhh)->hhh_lock)
83#define	HHH_RLOCK(hhh, rmpt) rm_rlock(&(hhh)->hhh_lock, (rmpt))
84#define	HHH_RUNLOCK(hhh, rmpt) rm_runlock(&(hhh)->hhh_lock, (rmpt))
85
86/*
87 * Run all helper hook functions for a given hook point.
88 */
89void
90hhook_run_hooks(struct hhook_head *hhh, void *ctx_data, struct osd *hosd)
91{
92	struct hhook *hhk;
93	void *hdata;
94	struct rm_priotracker rmpt;
95
96	KASSERT(hhh->hhh_refcount > 0, ("hhook_head %p refcount is 0", hhh));
97
98	HHH_RLOCK(hhh, &rmpt);
99	STAILQ_FOREACH(hhk, &hhh->hhh_hooks, hhk_next) {
100		if (hhk->hhk_helper->h_flags & HELPER_NEEDS_OSD) {
101			hdata = osd_get(OSD_KHELP, hosd, hhk->hhk_helper->h_id);
102			if (hdata == NULL)
103				continue;
104		} else
105			hdata = NULL;
106
107		/*
108		 * XXXLAS: We currently ignore the int returned by the hook,
109		 * but will likely want to handle it in future to allow hhook to
110		 * be used like pfil and effect changes at the hhook calling
111		 * site e.g. we could define a new hook type of HHOOK_TYPE_PFIL
112		 * and standardise what particular return values mean and set
113		 * the context data to pass exactly the same information as pfil
114		 * hooks currently receive, thus replicating pfil with hhook.
115		 */
116		hhk->hhk_func(hhh->hhh_type, hhh->hhh_id, hhk->hhk_udata,
117		    ctx_data, hdata, hosd);
118	}
119	HHH_RUNLOCK(hhh, &rmpt);
120}
121
122/*
123 * Register a new helper hook function with a helper hook point.
124 */
125int
126hhook_add_hook(struct hhook_head *hhh, struct hookinfo *hki, uint32_t flags)
127{
128	struct hhook *hhk, *tmp;
129	int error;
130
131	error = 0;
132
133	if (hhh == NULL)
134		return (ENOENT);
135
136	hhk = malloc(sizeof(struct hhook), M_HHOOK,
137	    M_ZERO | ((flags & HHOOK_WAITOK) ? M_WAITOK : M_NOWAIT));
138
139	if (hhk == NULL)
140		return (ENOMEM);
141
142	hhk->hhk_helper = hki->hook_helper;
143	hhk->hhk_func = hki->hook_func;
144	hhk->hhk_udata = hki->hook_udata;
145
146	HHH_WLOCK(hhh);
147	STAILQ_FOREACH(tmp, &hhh->hhh_hooks, hhk_next) {
148		if (tmp->hhk_func == hki->hook_func &&
149		    tmp->hhk_udata == hki->hook_udata) {
150			/* The helper hook function is already registered. */
151			error = EEXIST;
152			break;
153		}
154	}
155
156	if (!error) {
157		STAILQ_INSERT_TAIL(&hhh->hhh_hooks, hhk, hhk_next);
158		hhh->hhh_nhooks++;
159	}
160	else
161		free(hhk, M_HHOOK);
162
163	HHH_WUNLOCK(hhh);
164
165	return (error);
166}
167
168/*
169 * Lookup a helper hook point and register a new helper hook function with it.
170 */
171int
172hhook_add_hook_lookup(struct hookinfo *hki, uint32_t flags)
173{
174	struct hhook_head *hhh;
175	int error;
176
177	hhh = hhook_head_get(hki->hook_type, hki->hook_id);
178
179	if (hhh == NULL)
180		return (ENOENT);
181
182	error = hhook_add_hook(hhh, hki, flags);
183	hhook_head_release(hhh);
184
185	return (error);
186}
187
188/*
189 * Remove a helper hook function from a helper hook point.
190 */
191int
192hhook_remove_hook(struct hhook_head *hhh, struct hookinfo *hki)
193{
194	struct hhook *tmp;
195
196	if (hhh == NULL)
197		return (ENOENT);
198
199	HHH_WLOCK(hhh);
200	STAILQ_FOREACH(tmp, &hhh->hhh_hooks, hhk_next) {
201		if (tmp->hhk_func == hki->hook_func &&
202		    tmp->hhk_udata == hki->hook_udata) {
203			STAILQ_REMOVE(&hhh->hhh_hooks, tmp, hhook, hhk_next);
204			free(tmp, M_HHOOK);
205			hhh->hhh_nhooks--;
206			break;
207		}
208	}
209	HHH_WUNLOCK(hhh);
210
211	return (0);
212}
213
214/*
215 * Lookup a helper hook point and remove a helper hook function from it.
216 */
217int
218hhook_remove_hook_lookup(struct hookinfo *hki)
219{
220	struct hhook_head *hhh;
221
222	hhh = hhook_head_get(hki->hook_type, hki->hook_id);
223
224	if (hhh == NULL)
225		return (ENOENT);
226
227	hhook_remove_hook(hhh, hki);
228	hhook_head_release(hhh);
229
230	return (0);
231}
232
233/*
234 * Register a new helper hook point.
235 */
236int
237hhook_head_register(int32_t hhook_type, int32_t hhook_id, struct hhook_head **hhh,
238    uint32_t flags)
239{
240	struct hhook_head *tmphhh;
241
242	tmphhh = hhook_head_get(hhook_type, hhook_id);
243
244	if (tmphhh != NULL) {
245		/* Hook point previously registered. */
246		hhook_head_release(tmphhh);
247		return (EEXIST);
248	}
249
250	/* XXXLAS: Need to implement support for non-virtualised hooks. */
251	if ((flags & HHOOK_HEADISINVNET) == 0) {
252		printf("%s: only vnet-style virtualised hooks can be used\n",
253		    __func__);
254		return (EINVAL);
255	}
256
257	tmphhh = malloc(sizeof(struct hhook_head), M_HHOOK,
258	    M_ZERO | ((flags & HHOOK_WAITOK) ? M_WAITOK : M_NOWAIT));
259
260	if (tmphhh == NULL)
261		return (ENOMEM);
262
263	tmphhh->hhh_type = hhook_type;
264	tmphhh->hhh_id = hhook_id;
265	tmphhh->hhh_nhooks = 0;
266	STAILQ_INIT(&tmphhh->hhh_hooks);
267	HHH_LOCK_INIT(tmphhh);
268
269	if (hhh != NULL)
270		refcount_init(&tmphhh->hhh_refcount, 1);
271	else
272		refcount_init(&tmphhh->hhh_refcount, 0);
273
274	if (flags & HHOOK_HEADISINVNET) {
275		tmphhh->hhh_flags |= HHH_ISINVNET;
276		HHHLIST_LOCK();
277		LIST_INSERT_HEAD(&V_hhook_head_list, tmphhh, hhh_next);
278		HHHLIST_UNLOCK();
279	} else {
280		/* XXXLAS: Add tmphhh to the non-virtualised list. */
281	}
282
283	*hhh = tmphhh;
284
285	return (0);
286}
287
288static void
289hhook_head_destroy(struct hhook_head *hhh)
290{
291	struct hhook *tmp, *tmp2;
292
293	HHHLIST_LOCK_ASSERT();
294
295	LIST_REMOVE(hhh, hhh_next);
296	HHH_WLOCK(hhh);
297	STAILQ_FOREACH_SAFE(tmp, &hhh->hhh_hooks, hhk_next, tmp2)
298		free(tmp, M_HHOOK);
299	HHH_WUNLOCK(hhh);
300	HHH_LOCK_DESTROY(hhh);
301	free(hhh, M_HHOOK);
302}
303
304/*
305 * Remove a helper hook point.
306 */
307int
308hhook_head_deregister(struct hhook_head *hhh)
309{
310	int error;
311
312	error = 0;
313
314	HHHLIST_LOCK();
315	if (hhh == NULL)
316		error = ENOENT;
317	else if (hhh->hhh_refcount > 1)
318		error = EBUSY;
319	else
320		hhook_head_destroy(hhh);
321	HHHLIST_UNLOCK();
322
323	return (error);
324}
325
326/*
327 * Remove a helper hook point via a hhook_head lookup.
328 */
329int
330hhook_head_deregister_lookup(int32_t hhook_type, int32_t hhook_id)
331{
332	struct hhook_head *hhh;
333	int error;
334
335	error = 0;
336	hhh = hhook_head_get(hhook_type, hhook_id);
337	error = hhook_head_deregister(hhh);
338
339	if (error == EBUSY)
340		hhook_head_release(hhh);
341
342	return (error);
343}
344
345/*
346 * Lookup and return the hhook_head struct associated with the specified type
347 * and id, or NULL if not found. If found, the hhook_head's refcount is bumped.
348 */
349struct hhook_head *
350hhook_head_get(int32_t hhook_type, int32_t hhook_id)
351{
352	struct hhook_head *hhh;
353
354	/* XXXLAS: Pick hhook_head_list based on hhook_head flags. */
355	HHHLIST_LOCK();
356	LIST_FOREACH(hhh, &V_hhook_head_list, hhh_next) {
357		if (hhh->hhh_type == hhook_type && hhh->hhh_id == hhook_id) {
358			refcount_acquire(&hhh->hhh_refcount);
359			HHHLIST_UNLOCK();
360			return (hhh);
361		}
362	}
363	HHHLIST_UNLOCK();
364
365	return (NULL);
366}
367
368void
369hhook_head_release(struct hhook_head *hhh)
370{
371
372	refcount_release(&hhh->hhh_refcount);
373}
374
375/*
376 * Check the hhook_head private flags and return the appropriate public
377 * representation of the flag to the caller. The function is implemented in a
378 * way that allows us to cope with other subsystems becoming virtualised in the
379 * future.
380 */
381uint32_t
382hhook_head_is_virtualised(struct hhook_head *hhh)
383{
384	uint32_t ret;
385
386	if (hhh == NULL)
387		return (0);
388
389	if (hhh->hhh_flags & HHH_ISINVNET)
390		ret = HHOOK_HEADISINVNET;
391
392	return (ret);
393}
394
395uint32_t
396hhook_head_is_virtualised_lookup(int32_t hook_type, int32_t hook_id)
397{
398	struct hhook_head *hhh;
399	uint32_t ret;
400
401	hhh = hhook_head_get(hook_type, hook_id);
402
403	if (hhh == NULL)
404		return (0);
405
406	ret = hhook_head_is_virtualised(hhh);
407	hhook_head_release(hhh);
408
409	return (ret);
410}
411
412/*
413 * Vnet created and being initialised.
414 */
415static void
416hhook_vnet_init(const void *unused __unused)
417{
418
419	LIST_INIT(&V_hhook_head_list);
420}
421
422/*
423 * Vnet being torn down and destroyed.
424 */
425static void
426hhook_vnet_uninit(const void *unused __unused)
427{
428	struct hhook_head *hhh, *tmphhh;
429
430	/*
431	 * If subsystems which export helper hook points use the hhook KPI
432	 * correctly, the loop below should have no work to do because the
433	 * subsystem should have already called hhook_head_deregister().
434	 */
435	HHHLIST_LOCK();
436	LIST_FOREACH_SAFE(hhh, &V_hhook_head_list, hhh_next, tmphhh) {
437		printf("%s: hhook_head type=%d, id=%d cleanup required\n",
438		    __func__, hhh->hhh_type, hhh->hhh_id);
439		hhook_head_destroy(hhh);
440	}
441	HHHLIST_UNLOCK();
442}
443
444
445/*
446 * When a vnet is created and being initialised, init the V_hhook_head_list.
447 */
448VNET_SYSINIT(hhook_vnet_init, SI_SUB_PROTO_BEGIN, SI_ORDER_FIRST,
449    hhook_vnet_init, NULL);
450
451/*
452 * The hhook KPI provides a mechanism for subsystems which export helper hook
453 * points to clean up on vnet tear down, but in case the KPI is misused,
454 * provide a function to clean up and free memory for a vnet being destroyed.
455 */
456VNET_SYSUNINIT(hhook_vnet_uninit, SI_SUB_PROTO_BEGIN, SI_ORDER_FIRST,
457    hhook_vnet_uninit, NULL);
458