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