xref: /freebsd/sys/kern/kern_hhook.c (revision bb15ca603fa442c72dde3f3cb8b46db6970e3950)
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$");
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 static MALLOC_DEFINE(M_HHOOK, "hhook", "Helper hooks are linked off hhook_head lists");
62 
63 LIST_HEAD(hhookheadhead, hhook_head);
64 VNET_DEFINE(struct hhookheadhead, hhook_head_list);
65 #define	V_hhook_head_list VNET(hhook_head_list)
66 
67 static struct mtx hhook_head_list_lock;
68 MTX_SYSINIT(hhookheadlistlock, &hhook_head_list_lock, "hhook_head list lock",
69     MTX_DEF);
70 
71 /* Private function prototypes. */
72 static 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  */
88 void
89 hhook_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  */
124 int
125 hhook_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  */
169 int
170 hhook_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  */
189 int
190 hhook_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  */
215 int
216 hhook_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  */
234 int
235 hhook_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 	else
270 		refcount_init(&tmphhh->hhh_refcount, 0);
271 
272 	if (flags & HHOOK_HEADISINVNET) {
273 		tmphhh->hhh_flags |= HHH_ISINVNET;
274 		HHHLIST_LOCK();
275 		LIST_INSERT_HEAD(&V_hhook_head_list, tmphhh, hhh_next);
276 		HHHLIST_UNLOCK();
277 	} else {
278 		/* XXXLAS: Add tmphhh to the non-virtualised list. */
279 	}
280 
281 	*hhh = tmphhh;
282 
283 	return (0);
284 }
285 
286 static void
287 hhook_head_destroy(struct hhook_head *hhh)
288 {
289 	struct hhook *tmp, *tmp2;
290 
291 	HHHLIST_LOCK_ASSERT();
292 
293 	LIST_REMOVE(hhh, hhh_next);
294 	HHH_WLOCK(hhh);
295 	STAILQ_FOREACH_SAFE(tmp, &hhh->hhh_hooks, hhk_next, tmp2)
296 		free(tmp, M_HHOOK);
297 	HHH_WUNLOCK(hhh);
298 	HHH_LOCK_DESTROY(hhh);
299 	free(hhh, M_HHOOK);
300 }
301 
302 /*
303  * Remove a helper hook point.
304  */
305 int
306 hhook_head_deregister(struct hhook_head *hhh)
307 {
308 	int error;
309 
310 	error = 0;
311 
312 	HHHLIST_LOCK();
313 	if (hhh == NULL)
314 		error = ENOENT;
315 	else if (hhh->hhh_refcount > 1)
316 		error = EBUSY;
317 	else
318 		hhook_head_destroy(hhh);
319 	HHHLIST_UNLOCK();
320 
321 	return (error);
322 }
323 
324 /*
325  * Remove a helper hook point via a hhook_head lookup.
326  */
327 int
328 hhook_head_deregister_lookup(int32_t hhook_type, int32_t hhook_id)
329 {
330 	struct hhook_head *hhh;
331 	int error;
332 
333 	hhh = hhook_head_get(hhook_type, hhook_id);
334 	error = hhook_head_deregister(hhh);
335 
336 	if (error == EBUSY)
337 		hhook_head_release(hhh);
338 
339 	return (error);
340 }
341 
342 /*
343  * Lookup and return the hhook_head struct associated with the specified type
344  * and id, or NULL if not found. If found, the hhook_head's refcount is bumped.
345  */
346 struct hhook_head *
347 hhook_head_get(int32_t hhook_type, int32_t hhook_id)
348 {
349 	struct hhook_head *hhh;
350 
351 	/* XXXLAS: Pick hhook_head_list based on hhook_head flags. */
352 	HHHLIST_LOCK();
353 	LIST_FOREACH(hhh, &V_hhook_head_list, hhh_next) {
354 		if (hhh->hhh_type == hhook_type && hhh->hhh_id == hhook_id) {
355 			refcount_acquire(&hhh->hhh_refcount);
356 			break;
357 		}
358 	}
359 	HHHLIST_UNLOCK();
360 
361 	return (hhh);
362 }
363 
364 void
365 hhook_head_release(struct hhook_head *hhh)
366 {
367 
368 	refcount_release(&hhh->hhh_refcount);
369 }
370 
371 /*
372  * Check the hhook_head private flags and return the appropriate public
373  * representation of the flag to the caller. The function is implemented in a
374  * way that allows us to cope with other subsystems becoming virtualised in the
375  * future.
376  */
377 uint32_t
378 hhook_head_is_virtualised(struct hhook_head *hhh)
379 {
380 	uint32_t ret;
381 
382 	ret = 0;
383 
384 	if (hhh != NULL) {
385 		if (hhh->hhh_flags & HHH_ISINVNET)
386 			ret = HHOOK_HEADISINVNET;
387 	}
388 
389 	return (ret);
390 }
391 
392 uint32_t
393 hhook_head_is_virtualised_lookup(int32_t hook_type, int32_t hook_id)
394 {
395 	struct hhook_head *hhh;
396 	uint32_t ret;
397 
398 	hhh = hhook_head_get(hook_type, hook_id);
399 
400 	if (hhh == NULL)
401 		return (0);
402 
403 	ret = hhook_head_is_virtualised(hhh);
404 	hhook_head_release(hhh);
405 
406 	return (ret);
407 }
408 
409 /*
410  * Vnet created and being initialised.
411  */
412 static void
413 hhook_vnet_init(const void *unused __unused)
414 {
415 
416 	LIST_INIT(&V_hhook_head_list);
417 }
418 
419 /*
420  * Vnet being torn down and destroyed.
421  */
422 static void
423 hhook_vnet_uninit(const void *unused __unused)
424 {
425 	struct hhook_head *hhh, *tmphhh;
426 
427 	/*
428 	 * If subsystems which export helper hook points use the hhook KPI
429 	 * correctly, the loop below should have no work to do because the
430 	 * subsystem should have already called hhook_head_deregister().
431 	 */
432 	HHHLIST_LOCK();
433 	LIST_FOREACH_SAFE(hhh, &V_hhook_head_list, hhh_next, tmphhh) {
434 		printf("%s: hhook_head type=%d, id=%d cleanup required\n",
435 		    __func__, hhh->hhh_type, hhh->hhh_id);
436 		hhook_head_destroy(hhh);
437 	}
438 	HHHLIST_UNLOCK();
439 }
440 
441 
442 /*
443  * When a vnet is created and being initialised, init the V_hhook_head_list.
444  */
445 VNET_SYSINIT(hhook_vnet_init, SI_SUB_PROTO_BEGIN, SI_ORDER_FIRST,
446     hhook_vnet_init, NULL);
447 
448 /*
449  * The hhook KPI provides a mechanism for subsystems which export helper hook
450  * points to clean up on vnet tear down, but in case the KPI is misused,
451  * provide a function to clean up and free memory for a vnet being destroyed.
452  */
453 VNET_SYSUNINIT(hhook_vnet_uninit, SI_SUB_PROTO_BEGIN, SI_ORDER_FIRST,
454     hhook_vnet_uninit, NULL);
455