xref: /freebsd/sys/kern/kern_hhook.c (revision aa0a1e58)
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$");
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 
54 struct hhook {
55 	hhook_func_t		hhk_func;
56 	struct helper		*hhk_helper;
57 	void			*hhk_udata;
58 	STAILQ_ENTRY(hhook)	hhk_next;
59 };
60 
61 MALLOC_DECLARE(M_HHOOK);
62 MALLOC_DEFINE(M_HHOOK, "hhook", "Helper hooks are linked off hhook_head lists");
63 
64 LIST_HEAD(hhookheadhead, hhook_head);
65 VNET_DEFINE(struct hhookheadhead, hhook_head_list);
66 #define	V_hhook_head_list VNET(hhook_head_list)
67 
68 static struct mtx hhook_head_list_lock;
69 MTX_SYSINIT(hhookheadlistlock, &hhook_head_list_lock, "hhook_head list lock",
70     MTX_DEF);
71 
72 /* Private function prototypes. */
73 static 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  */
89 void
90 hhook_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  */
125 int
126 hhook_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 	} else
160 		free(hhk, M_HHOOK);
161 
162 	HHH_WUNLOCK(hhh);
163 
164 	return (error);
165 }
166 
167 /*
168  * Lookup a helper hook point and register a new helper hook function with it.
169  */
170 int
171 hhook_add_hook_lookup(struct hookinfo *hki, uint32_t flags)
172 {
173 	struct hhook_head *hhh;
174 	int error;
175 
176 	hhh = hhook_head_get(hki->hook_type, hki->hook_id);
177 
178 	if (hhh == NULL)
179 		return (ENOENT);
180 
181 	error = hhook_add_hook(hhh, hki, flags);
182 	hhook_head_release(hhh);
183 
184 	return (error);
185 }
186 
187 /*
188  * Remove a helper hook function from a helper hook point.
189  */
190 int
191 hhook_remove_hook(struct hhook_head *hhh, struct hookinfo *hki)
192 {
193 	struct hhook *tmp;
194 
195 	if (hhh == NULL)
196 		return (ENOENT);
197 
198 	HHH_WLOCK(hhh);
199 	STAILQ_FOREACH(tmp, &hhh->hhh_hooks, hhk_next) {
200 		if (tmp->hhk_func == hki->hook_func &&
201 		    tmp->hhk_udata == hki->hook_udata) {
202 			STAILQ_REMOVE(&hhh->hhh_hooks, tmp, hhook, hhk_next);
203 			free(tmp, M_HHOOK);
204 			hhh->hhh_nhooks--;
205 			break;
206 		}
207 	}
208 	HHH_WUNLOCK(hhh);
209 
210 	return (0);
211 }
212 
213 /*
214  * Lookup a helper hook point and remove a helper hook function from it.
215  */
216 int
217 hhook_remove_hook_lookup(struct hookinfo *hki)
218 {
219 	struct hhook_head *hhh;
220 
221 	hhh = hhook_head_get(hki->hook_type, hki->hook_id);
222 
223 	if (hhh == NULL)
224 		return (ENOENT);
225 
226 	hhook_remove_hook(hhh, hki);
227 	hhook_head_release(hhh);
228 
229 	return (0);
230 }
231 
232 /*
233  * Register a new helper hook point.
234  */
235 int
236 hhook_head_register(int32_t hhook_type, int32_t hhook_id, struct hhook_head **hhh,
237     uint32_t flags)
238 {
239 	struct hhook_head *tmphhh;
240 
241 	tmphhh = hhook_head_get(hhook_type, hhook_id);
242 
243 	if (tmphhh != NULL) {
244 		/* Hook point previously registered. */
245 		hhook_head_release(tmphhh);
246 		return (EEXIST);
247 	}
248 
249 	/* XXXLAS: Need to implement support for non-virtualised hooks. */
250 	if ((flags & HHOOK_HEADISINVNET) == 0) {
251 		printf("%s: only vnet-style virtualised hooks can be used\n",
252 		    __func__);
253 		return (EINVAL);
254 	}
255 
256 	tmphhh = malloc(sizeof(struct hhook_head), M_HHOOK,
257 	    M_ZERO | ((flags & HHOOK_WAITOK) ? M_WAITOK : M_NOWAIT));
258 
259 	if (tmphhh == NULL)
260 		return (ENOMEM);
261 
262 	tmphhh->hhh_type = hhook_type;
263 	tmphhh->hhh_id = hhook_id;
264 	tmphhh->hhh_nhooks = 0;
265 	STAILQ_INIT(&tmphhh->hhh_hooks);
266 	HHH_LOCK_INIT(tmphhh);
267 
268 	if (hhh != NULL)
269 		refcount_init(&tmphhh->hhh_refcount, 1);
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 	*hhh = tmphhh;
283 
284 	return (0);
285 }
286 
287 static void
288 hhook_head_destroy(struct hhook_head *hhh)
289 {
290 	struct hhook *tmp, *tmp2;
291 
292 	HHHLIST_LOCK_ASSERT();
293 
294 	LIST_REMOVE(hhh, hhh_next);
295 	HHH_WLOCK(hhh);
296 	STAILQ_FOREACH_SAFE(tmp, &hhh->hhh_hooks, hhk_next, tmp2)
297 		free(tmp, M_HHOOK);
298 	HHH_WUNLOCK(hhh);
299 	HHH_LOCK_DESTROY(hhh);
300 	free(hhh, M_HHOOK);
301 }
302 
303 /*
304  * Remove a helper hook point.
305  */
306 int
307 hhook_head_deregister(struct hhook_head *hhh)
308 {
309 	int error;
310 
311 	error = 0;
312 
313 	HHHLIST_LOCK();
314 	if (hhh == NULL)
315 		error = ENOENT;
316 	else if (hhh->hhh_refcount > 1)
317 		error = EBUSY;
318 	else
319 		hhook_head_destroy(hhh);
320 	HHHLIST_UNLOCK();
321 
322 	return (error);
323 }
324 
325 /*
326  * Remove a helper hook point via a hhook_head lookup.
327  */
328 int
329 hhook_head_deregister_lookup(int32_t hhook_type, int32_t hhook_id)
330 {
331 	struct hhook_head *hhh;
332 	int error;
333 
334 	hhh = hhook_head_get(hhook_type, hhook_id);
335 	error = hhook_head_deregister(hhh);
336 
337 	if (error == EBUSY)
338 		hhook_head_release(hhh);
339 
340 	return (error);
341 }
342 
343 /*
344  * Lookup and return the hhook_head struct associated with the specified type
345  * and id, or NULL if not found. If found, the hhook_head's refcount is bumped.
346  */
347 struct hhook_head *
348 hhook_head_get(int32_t hhook_type, int32_t hhook_id)
349 {
350 	struct hhook_head *hhh;
351 
352 	/* XXXLAS: Pick hhook_head_list based on hhook_head flags. */
353 	HHHLIST_LOCK();
354 	LIST_FOREACH(hhh, &V_hhook_head_list, hhh_next) {
355 		if (hhh->hhh_type == hhook_type && hhh->hhh_id == hhook_id) {
356 			refcount_acquire(&hhh->hhh_refcount);
357 			break;
358 		}
359 	}
360 	HHHLIST_UNLOCK();
361 
362 	return (hhh);
363 }
364 
365 void
366 hhook_head_release(struct hhook_head *hhh)
367 {
368 
369 	refcount_release(&hhh->hhh_refcount);
370 }
371 
372 /*
373  * Check the hhook_head private flags and return the appropriate public
374  * representation of the flag to the caller. The function is implemented in a
375  * way that allows us to cope with other subsystems becoming virtualised in the
376  * future.
377  */
378 uint32_t
379 hhook_head_is_virtualised(struct hhook_head *hhh)
380 {
381 	uint32_t ret;
382 
383 	ret = 0;
384 
385 	if (hhh != NULL) {
386 		if (hhh->hhh_flags & HHH_ISINVNET)
387 			ret = HHOOK_HEADISINVNET;
388 	}
389 
390 	return (ret);
391 }
392 
393 uint32_t
394 hhook_head_is_virtualised_lookup(int32_t hook_type, int32_t hook_id)
395 {
396 	struct hhook_head *hhh;
397 	uint32_t ret;
398 
399 	hhh = hhook_head_get(hook_type, hook_id);
400 
401 	if (hhh == NULL)
402 		return (0);
403 
404 	ret = hhook_head_is_virtualised(hhh);
405 	hhook_head_release(hhh);
406 
407 	return (ret);
408 }
409 
410 /*
411  * Vnet created and being initialised.
412  */
413 static void
414 hhook_vnet_init(const void *unused __unused)
415 {
416 
417 	LIST_INIT(&V_hhook_head_list);
418 }
419 
420 /*
421  * Vnet being torn down and destroyed.
422  */
423 static void
424 hhook_vnet_uninit(const void *unused __unused)
425 {
426 	struct hhook_head *hhh, *tmphhh;
427 
428 	/*
429 	 * If subsystems which export helper hook points use the hhook KPI
430 	 * correctly, the loop below should have no work to do because the
431 	 * subsystem should have already called hhook_head_deregister().
432 	 */
433 	HHHLIST_LOCK();
434 	LIST_FOREACH_SAFE(hhh, &V_hhook_head_list, hhh_next, tmphhh) {
435 		printf("%s: hhook_head type=%d, id=%d cleanup required\n",
436 		    __func__, hhh->hhh_type, hhh->hhh_id);
437 		hhook_head_destroy(hhh);
438 	}
439 	HHHLIST_UNLOCK();
440 }
441 
442 
443 /*
444  * When a vnet is created and being initialised, init the V_hhook_head_list.
445  */
446 VNET_SYSINIT(hhook_vnet_init, SI_SUB_PROTO_BEGIN, SI_ORDER_FIRST,
447     hhook_vnet_init, NULL);
448 
449 /*
450  * The hhook KPI provides a mechanism for subsystems which export helper hook
451  * points to clean up on vnet tear down, but in case the KPI is misused,
452  * provide a function to clean up and free memory for a vnet being destroyed.
453  */
454 VNET_SYSUNINIT(hhook_vnet_uninit, SI_SUB_PROTO_BEGIN, SI_ORDER_FIRST,
455     hhook_vnet_uninit, NULL);
456