1 /*- 2 * Copyright (c) 2010,2013 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 struct hhookheadhead hhook_head_list; 65 VNET_DEFINE(struct hhookheadhead, hhook_vhead_list); 66 #define V_hhook_vhead_list VNET(hhook_vhead_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 /* Protected by hhook_head_list_lock. */ 73 static uint32_t n_hhookheads; 74 75 /* Private function prototypes. */ 76 static void hhook_head_destroy(struct hhook_head *hhh); 77 void khelp_new_hhook_registered(struct hhook_head *hhh, uint32_t flags); 78 79 #define HHHLIST_LOCK() mtx_lock(&hhook_head_list_lock) 80 #define HHHLIST_UNLOCK() mtx_unlock(&hhook_head_list_lock) 81 #define HHHLIST_LOCK_ASSERT() mtx_assert(&hhook_head_list_lock, MA_OWNED) 82 83 #define HHH_LOCK_INIT(hhh) rm_init(&(hhh)->hhh_lock, "hhook_head rm lock") 84 #define HHH_LOCK_DESTROY(hhh) rm_destroy(&(hhh)->hhh_lock) 85 #define HHH_WLOCK(hhh) rm_wlock(&(hhh)->hhh_lock) 86 #define HHH_WUNLOCK(hhh) rm_wunlock(&(hhh)->hhh_lock) 87 #define HHH_RLOCK(hhh, rmpt) rm_rlock(&(hhh)->hhh_lock, (rmpt)) 88 #define HHH_RUNLOCK(hhh, rmpt) rm_runlock(&(hhh)->hhh_lock, (rmpt)) 89 90 /* 91 * Run all helper hook functions for a given hook point. 92 */ 93 void 94 hhook_run_hooks(struct hhook_head *hhh, void *ctx_data, struct osd *hosd) 95 { 96 struct hhook *hhk; 97 void *hdata; 98 struct rm_priotracker rmpt; 99 100 KASSERT(hhh->hhh_refcount > 0, ("hhook_head %p refcount is 0", hhh)); 101 102 HHH_RLOCK(hhh, &rmpt); 103 STAILQ_FOREACH(hhk, &hhh->hhh_hooks, hhk_next) { 104 if (hhk->hhk_helper != NULL && 105 hhk->hhk_helper->h_flags & HELPER_NEEDS_OSD) { 106 hdata = osd_get(OSD_KHELP, hosd, hhk->hhk_helper->h_id); 107 if (hdata == NULL) 108 continue; 109 } else 110 hdata = NULL; 111 112 /* 113 * XXXLAS: We currently ignore the int returned by the hook, 114 * but will likely want to handle it in future to allow hhook to 115 * be used like pfil and effect changes at the hhook calling 116 * site e.g. we could define a new hook type of HHOOK_TYPE_PFIL 117 * and standardise what particular return values mean and set 118 * the context data to pass exactly the same information as pfil 119 * hooks currently receive, thus replicating pfil with hhook. 120 */ 121 hhk->hhk_func(hhh->hhh_type, hhh->hhh_id, hhk->hhk_udata, 122 ctx_data, hdata, hosd); 123 } 124 HHH_RUNLOCK(hhh, &rmpt); 125 } 126 127 /* 128 * Register a new helper hook function with a helper hook point. 129 */ 130 int 131 hhook_add_hook(struct hhook_head *hhh, struct hookinfo *hki, uint32_t flags) 132 { 133 struct hhook *hhk, *tmp; 134 int error; 135 136 error = 0; 137 138 if (hhh == NULL) 139 return (ENOENT); 140 141 hhk = malloc(sizeof(struct hhook), M_HHOOK, 142 M_ZERO | ((flags & HHOOK_WAITOK) ? M_WAITOK : M_NOWAIT)); 143 144 if (hhk == NULL) 145 return (ENOMEM); 146 147 hhk->hhk_helper = hki->hook_helper; 148 hhk->hhk_func = hki->hook_func; 149 hhk->hhk_udata = hki->hook_udata; 150 151 HHH_WLOCK(hhh); 152 STAILQ_FOREACH(tmp, &hhh->hhh_hooks, hhk_next) { 153 if (tmp->hhk_func == hki->hook_func && 154 tmp->hhk_udata == hki->hook_udata) { 155 /* The helper hook function is already registered. */ 156 error = EEXIST; 157 break; 158 } 159 } 160 161 if (!error) { 162 STAILQ_INSERT_TAIL(&hhh->hhh_hooks, hhk, hhk_next); 163 hhh->hhh_nhooks++; 164 } else 165 free(hhk, M_HHOOK); 166 167 HHH_WUNLOCK(hhh); 168 169 return (error); 170 } 171 172 /* 173 * Register a helper hook function with a helper hook point (including all 174 * virtual instances of the hook point if it is virtualised). 175 * 176 * The logic is unfortunately far more complex than for 177 * hhook_remove_hook_lookup() because hhook_add_hook() can call malloc() with 178 * M_WAITOK and thus we cannot call hhook_add_hook() with the 179 * hhook_head_list_lock held. 180 * 181 * The logic assembles an array of hhook_head structs that correspond to the 182 * helper hook point being hooked and bumps the refcount on each (all done with 183 * the hhook_head_list_lock held). The hhook_head_list_lock is then dropped, and 184 * hhook_add_hook() is called and the refcount dropped for each hhook_head 185 * struct in the array. 186 */ 187 int 188 hhook_add_hook_lookup(struct hookinfo *hki, uint32_t flags) 189 { 190 struct hhook_head **heads_to_hook, *hhh; 191 int error, i, n_heads_to_hook; 192 193 tryagain: 194 error = i = 0; 195 /* 196 * Accessing n_hhookheads without hhook_head_list_lock held opens up a 197 * race with hhook_head_register() which we are unlikely to lose, but 198 * nonetheless have to cope with - hence the complex goto logic. 199 */ 200 n_heads_to_hook = n_hhookheads; 201 heads_to_hook = malloc(n_heads_to_hook * sizeof(struct hhook_head *), 202 M_HHOOK, flags & HHOOK_WAITOK ? M_WAITOK : M_NOWAIT); 203 if (heads_to_hook == NULL) 204 return (ENOMEM); 205 206 HHHLIST_LOCK(); 207 LIST_FOREACH(hhh, &hhook_head_list, hhh_next) { 208 if (hhh->hhh_type == hki->hook_type && 209 hhh->hhh_id == hki->hook_id) { 210 if (i < n_heads_to_hook) { 211 heads_to_hook[i] = hhh; 212 refcount_acquire(&heads_to_hook[i]->hhh_refcount); 213 i++; 214 } else { 215 /* 216 * We raced with hhook_head_register() which 217 * inserted a hhook_head that we need to hook 218 * but did not malloc space for. Abort this run 219 * and try again. 220 */ 221 for (i--; i >= 0; i--) 222 refcount_release(&heads_to_hook[i]->hhh_refcount); 223 free(heads_to_hook, M_HHOOK); 224 HHHLIST_UNLOCK(); 225 goto tryagain; 226 } 227 } 228 } 229 HHHLIST_UNLOCK(); 230 231 for (i--; i >= 0; i--) { 232 if (!error) 233 error = hhook_add_hook(heads_to_hook[i], hki, flags); 234 refcount_release(&heads_to_hook[i]->hhh_refcount); 235 } 236 237 free(heads_to_hook, M_HHOOK); 238 239 return (error); 240 } 241 242 /* 243 * Remove a helper hook function from a helper hook point. 244 */ 245 int 246 hhook_remove_hook(struct hhook_head *hhh, struct hookinfo *hki) 247 { 248 struct hhook *tmp; 249 250 if (hhh == NULL) 251 return (ENOENT); 252 253 HHH_WLOCK(hhh); 254 STAILQ_FOREACH(tmp, &hhh->hhh_hooks, hhk_next) { 255 if (tmp->hhk_func == hki->hook_func && 256 tmp->hhk_udata == hki->hook_udata) { 257 STAILQ_REMOVE(&hhh->hhh_hooks, tmp, hhook, hhk_next); 258 free(tmp, M_HHOOK); 259 hhh->hhh_nhooks--; 260 break; 261 } 262 } 263 HHH_WUNLOCK(hhh); 264 265 return (0); 266 } 267 268 /* 269 * Remove a helper hook function from a helper hook point (including all 270 * virtual instances of the hook point if it is virtualised). 271 */ 272 int 273 hhook_remove_hook_lookup(struct hookinfo *hki) 274 { 275 struct hhook_head *hhh; 276 277 HHHLIST_LOCK(); 278 LIST_FOREACH(hhh, &hhook_head_list, hhh_next) { 279 if (hhh->hhh_type == hki->hook_type && 280 hhh->hhh_id == hki->hook_id) 281 hhook_remove_hook(hhh, hki); 282 } 283 HHHLIST_UNLOCK(); 284 285 return (0); 286 } 287 288 /* 289 * Register a new helper hook point. 290 */ 291 int 292 hhook_head_register(int32_t hhook_type, int32_t hhook_id, struct hhook_head **hhh, 293 uint32_t flags) 294 { 295 struct hhook_head *tmphhh; 296 297 tmphhh = hhook_head_get(hhook_type, hhook_id); 298 299 if (tmphhh != NULL) { 300 /* Hook point previously registered. */ 301 hhook_head_release(tmphhh); 302 return (EEXIST); 303 } 304 305 tmphhh = malloc(sizeof(struct hhook_head), M_HHOOK, 306 M_ZERO | ((flags & HHOOK_WAITOK) ? M_WAITOK : M_NOWAIT)); 307 308 if (tmphhh == NULL) 309 return (ENOMEM); 310 311 tmphhh->hhh_type = hhook_type; 312 tmphhh->hhh_id = hhook_id; 313 tmphhh->hhh_nhooks = 0; 314 STAILQ_INIT(&tmphhh->hhh_hooks); 315 HHH_LOCK_INIT(tmphhh); 316 refcount_init(&tmphhh->hhh_refcount, 1); 317 318 HHHLIST_LOCK(); 319 if (flags & HHOOK_HEADISINVNET) { 320 tmphhh->hhh_flags |= HHH_ISINVNET; 321 #ifdef VIMAGE 322 KASSERT(curvnet != NULL, ("curvnet is NULL")); 323 tmphhh->hhh_vid = (uintptr_t)curvnet; 324 LIST_INSERT_HEAD(&V_hhook_vhead_list, tmphhh, hhh_vnext); 325 #endif 326 } 327 LIST_INSERT_HEAD(&hhook_head_list, tmphhh, hhh_next); 328 n_hhookheads++; 329 HHHLIST_UNLOCK(); 330 331 khelp_new_hhook_registered(tmphhh, flags); 332 333 if (hhh != NULL) 334 *hhh = tmphhh; 335 else 336 refcount_release(&tmphhh->hhh_refcount); 337 338 return (0); 339 } 340 341 static void 342 hhook_head_destroy(struct hhook_head *hhh) 343 { 344 struct hhook *tmp, *tmp2; 345 346 HHHLIST_LOCK_ASSERT(); 347 KASSERT(n_hhookheads > 0, ("n_hhookheads should be > 0")); 348 349 LIST_REMOVE(hhh, hhh_next); 350 #ifdef VIMAGE 351 if (hhook_head_is_virtualised(hhh) == HHOOK_HEADISINVNET) 352 LIST_REMOVE(hhh, hhh_vnext); 353 #endif 354 HHH_WLOCK(hhh); 355 STAILQ_FOREACH_SAFE(tmp, &hhh->hhh_hooks, hhk_next, tmp2) 356 free(tmp, M_HHOOK); 357 HHH_WUNLOCK(hhh); 358 HHH_LOCK_DESTROY(hhh); 359 free(hhh, M_HHOOK); 360 n_hhookheads--; 361 } 362 363 /* 364 * Remove a helper hook point. 365 */ 366 int 367 hhook_head_deregister(struct hhook_head *hhh) 368 { 369 int error; 370 371 error = 0; 372 373 HHHLIST_LOCK(); 374 if (hhh == NULL) 375 error = ENOENT; 376 else if (hhh->hhh_refcount > 1) 377 error = EBUSY; 378 else 379 hhook_head_destroy(hhh); 380 HHHLIST_UNLOCK(); 381 382 return (error); 383 } 384 385 /* 386 * Remove a helper hook point via a hhook_head lookup. 387 */ 388 int 389 hhook_head_deregister_lookup(int32_t hhook_type, int32_t hhook_id) 390 { 391 struct hhook_head *hhh; 392 int error; 393 394 hhh = hhook_head_get(hhook_type, hhook_id); 395 error = hhook_head_deregister(hhh); 396 397 if (error == EBUSY) 398 hhook_head_release(hhh); 399 400 return (error); 401 } 402 403 /* 404 * Lookup and return the hhook_head struct associated with the specified type 405 * and id, or NULL if not found. If found, the hhook_head's refcount is bumped. 406 */ 407 struct hhook_head * 408 hhook_head_get(int32_t hhook_type, int32_t hhook_id) 409 { 410 struct hhook_head *hhh; 411 412 HHHLIST_LOCK(); 413 LIST_FOREACH(hhh, &hhook_head_list, hhh_next) { 414 if (hhh->hhh_type == hhook_type && hhh->hhh_id == hhook_id) { 415 #ifdef VIMAGE 416 if (hhook_head_is_virtualised(hhh) == 417 HHOOK_HEADISINVNET) { 418 KASSERT(curvnet != NULL, ("curvnet is NULL")); 419 if (hhh->hhh_vid != (uintptr_t)curvnet) 420 continue; 421 } 422 #endif 423 refcount_acquire(&hhh->hhh_refcount); 424 break; 425 } 426 } 427 HHHLIST_UNLOCK(); 428 429 return (hhh); 430 } 431 432 void 433 hhook_head_release(struct hhook_head *hhh) 434 { 435 436 refcount_release(&hhh->hhh_refcount); 437 } 438 439 /* 440 * Check the hhook_head private flags and return the appropriate public 441 * representation of the flag to the caller. The function is implemented in a 442 * way that allows us to cope with other subsystems becoming virtualised in the 443 * future. 444 */ 445 uint32_t 446 hhook_head_is_virtualised(struct hhook_head *hhh) 447 { 448 uint32_t ret; 449 450 ret = 0; 451 452 if (hhh != NULL) { 453 if (hhh->hhh_flags & HHH_ISINVNET) 454 ret = HHOOK_HEADISINVNET; 455 } 456 457 return (ret); 458 } 459 460 uint32_t 461 hhook_head_is_virtualised_lookup(int32_t hook_type, int32_t hook_id) 462 { 463 struct hhook_head *hhh; 464 uint32_t ret; 465 466 hhh = hhook_head_get(hook_type, hook_id); 467 468 if (hhh == NULL) 469 return (0); 470 471 ret = hhook_head_is_virtualised(hhh); 472 hhook_head_release(hhh); 473 474 return (ret); 475 } 476 477 /* 478 * Vnet created and being initialised. 479 */ 480 static void 481 hhook_vnet_init(const void *unused __unused) 482 { 483 484 LIST_INIT(&V_hhook_vhead_list); 485 } 486 487 /* 488 * Vnet being torn down and destroyed. 489 */ 490 static void 491 hhook_vnet_uninit(const void *unused __unused) 492 { 493 struct hhook_head *hhh, *tmphhh; 494 495 /* 496 * If subsystems which export helper hook points use the hhook KPI 497 * correctly, the loop below should have no work to do because the 498 * subsystem should have already called hhook_head_deregister(). 499 */ 500 HHHLIST_LOCK(); 501 LIST_FOREACH_SAFE(hhh, &V_hhook_vhead_list, hhh_vnext, tmphhh) { 502 printf("%s: hhook_head type=%d, id=%d cleanup required\n", 503 __func__, hhh->hhh_type, hhh->hhh_id); 504 hhook_head_destroy(hhh); 505 } 506 HHHLIST_UNLOCK(); 507 } 508 509 510 /* 511 * When a vnet is created and being initialised, init the V_hhook_vhead_list. 512 */ 513 VNET_SYSINIT(hhook_vnet_init, SI_SUB_INIT_IF, SI_ORDER_FIRST, 514 hhook_vnet_init, NULL); 515 516 /* 517 * The hhook KPI provides a mechanism for subsystems which export helper hook 518 * points to clean up on vnet tear down, but in case the KPI is misused, 519 * provide a function to clean up and free memory for a vnet being destroyed. 520 */ 521 VNET_SYSUNINIT(hhook_vnet_uninit, SI_SUB_INIT_IF, SI_ORDER_FIRST, 522 hhook_vnet_uninit, NULL); 523