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