1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2015-2019 Yandex LLC 5 * Copyright (c) 2015 Alexander V. Chernikov <melifaro@FreeBSD.org> 6 * Copyright (c) 2015-2019 Andrey V. Elsukov <ae@FreeBSD.org> 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 #include <sys/param.h> 31 #include <sys/systm.h> 32 #include <sys/counter.h> 33 #include <sys/errno.h> 34 #include <sys/kernel.h> 35 #include <sys/lock.h> 36 #include <sys/malloc.h> 37 #include <sys/mbuf.h> 38 #include <sys/module.h> 39 #include <sys/rmlock.h> 40 #include <sys/rwlock.h> 41 #include <sys/socket.h> 42 #include <sys/sockopt.h> 43 #include <sys/queue.h> 44 #include <sys/syslog.h> 45 #include <sys/sysctl.h> 46 47 #include <net/if.h> 48 #include <net/if_var.h> 49 #include <net/pfil.h> 50 #include <net/route.h> 51 #include <net/vnet.h> 52 53 #include <netinet/in.h> 54 #include <netinet/ip_var.h> 55 #include <netinet/ip_fw.h> 56 #include <netinet6/in6_var.h> 57 #include <netinet6/ip6_var.h> 58 #include <netinet6/ip_fw_nat64.h> 59 60 #include <netpfil/ipfw/ip_fw_private.h> 61 62 #include "nat64stl.h" 63 64 VNET_DEFINE(uint16_t, nat64stl_eid) = 0; 65 66 static struct nat64stl_cfg *nat64stl_alloc_config(const char *name, 67 uint8_t set); 68 static void nat64stl_free_config(struct nat64stl_cfg *cfg); 69 static struct nat64stl_cfg *nat64stl_find(struct namedobj_instance *ni, 70 const char *name, uint8_t set); 71 72 static struct nat64stl_cfg * 73 nat64stl_alloc_config(const char *name, uint8_t set) 74 { 75 struct nat64stl_cfg *cfg; 76 77 cfg = malloc(sizeof(struct nat64stl_cfg), M_IPFW, M_WAITOK | M_ZERO); 78 COUNTER_ARRAY_ALLOC(cfg->base.stats.cnt, NAT64STATS, M_WAITOK); 79 cfg->no.name = cfg->name; 80 cfg->no.etlv = IPFW_TLV_NAT64STL_NAME; 81 cfg->no.set = set; 82 strlcpy(cfg->name, name, sizeof(cfg->name)); 83 return (cfg); 84 } 85 86 static void 87 nat64stl_free_config(struct nat64stl_cfg *cfg) 88 { 89 90 COUNTER_ARRAY_FREE(cfg->base.stats.cnt, NAT64STATS); 91 free(cfg, M_IPFW); 92 } 93 94 static void 95 nat64stl_export_config(struct ip_fw_chain *ch, struct nat64stl_cfg *cfg, 96 ipfw_nat64stl_cfg *uc) 97 { 98 struct named_object *no; 99 100 uc->prefix6 = cfg->base.plat_prefix; 101 uc->plen6 = cfg->base.plat_plen; 102 uc->flags = cfg->base.flags & NAT64STL_FLAGSMASK; 103 uc->set = cfg->no.set; 104 strlcpy(uc->name, cfg->no.name, sizeof(uc->name)); 105 106 no = ipfw_objhash_lookup_table_kidx(ch, cfg->map64); 107 ipfw_export_obj_ntlv(no, &uc->ntlv6); 108 no = ipfw_objhash_lookup_table_kidx(ch, cfg->map46); 109 ipfw_export_obj_ntlv(no, &uc->ntlv4); 110 } 111 112 struct nat64stl_dump_arg { 113 struct ip_fw_chain *ch; 114 struct sockopt_data *sd; 115 }; 116 117 static int 118 export_config_cb(struct namedobj_instance *ni, struct named_object *no, 119 void *arg) 120 { 121 struct nat64stl_dump_arg *da = (struct nat64stl_dump_arg *)arg; 122 ipfw_nat64stl_cfg *uc; 123 124 uc = (ipfw_nat64stl_cfg *)ipfw_get_sopt_space(da->sd, sizeof(*uc)); 125 nat64stl_export_config(da->ch, (struct nat64stl_cfg *)no, uc); 126 return (0); 127 } 128 129 static struct nat64stl_cfg * 130 nat64stl_find(struct namedobj_instance *ni, const char *name, uint8_t set) 131 { 132 struct nat64stl_cfg *cfg; 133 134 cfg = (struct nat64stl_cfg *)ipfw_objhash_lookup_name_type(ni, set, 135 IPFW_TLV_NAT64STL_NAME, name); 136 137 return (cfg); 138 } 139 140 static int 141 nat64stl_create_internal(struct ip_fw_chain *ch, struct nat64stl_cfg *cfg, 142 ipfw_nat64stl_cfg *i) 143 { 144 145 IPFW_UH_WLOCK_ASSERT(ch); 146 147 if (ipfw_objhash_alloc_idx(CHAIN_TO_SRV(ch), &cfg->no.kidx) != 0) 148 return (ENOSPC); 149 cfg->base.flags |= NAT64STL_KIDX; 150 151 if (ipfw_ref_table(ch, &i->ntlv4, &cfg->map46) != 0) 152 return (EINVAL); 153 cfg->base.flags |= NAT64STL_46T; 154 155 if (ipfw_ref_table(ch, &i->ntlv6, &cfg->map64) != 0) 156 return (EINVAL); 157 cfg->base.flags |= NAT64STL_64T; 158 159 ipfw_objhash_add(CHAIN_TO_SRV(ch), &cfg->no); 160 161 return (0); 162 } 163 164 /* 165 * Creates new nat64 instance. 166 * Data layout (v0)(current): 167 * Request: [ ipfw_obj_lheader ipfw_nat64stl_cfg ] 168 * 169 * Returns 0 on success 170 */ 171 static int 172 nat64stl_create(struct ip_fw_chain *ch, ip_fw3_opheader *op3, 173 struct sockopt_data *sd) 174 { 175 ipfw_obj_lheader *olh; 176 ipfw_nat64stl_cfg *uc; 177 struct namedobj_instance *ni; 178 struct nat64stl_cfg *cfg; 179 int error; 180 181 if (sd->valsize != sizeof(*olh) + sizeof(*uc)) 182 return (EINVAL); 183 184 olh = (ipfw_obj_lheader *)sd->kbuf; 185 uc = (ipfw_nat64stl_cfg *)(olh + 1); 186 187 if (ipfw_check_object_name_generic(uc->name) != 0) 188 return (EINVAL); 189 if (uc->set >= IPFW_MAX_SETS || 190 nat64_check_prefix6(&uc->prefix6, uc->plen6) != 0) 191 return (EINVAL); 192 193 /* XXX: check types of tables */ 194 195 ni = CHAIN_TO_SRV(ch); 196 error = 0; 197 198 IPFW_UH_RLOCK(ch); 199 if (nat64stl_find(ni, uc->name, uc->set) != NULL) { 200 IPFW_UH_RUNLOCK(ch); 201 return (EEXIST); 202 } 203 IPFW_UH_RUNLOCK(ch); 204 205 cfg = nat64stl_alloc_config(uc->name, uc->set); 206 cfg->base.plat_prefix = uc->prefix6; 207 cfg->base.plat_plen = uc->plen6; 208 cfg->base.flags = (uc->flags & NAT64STL_FLAGSMASK) | NAT64_PLATPFX; 209 if (IN6_IS_ADDR_WKPFX(&cfg->base.plat_prefix)) 210 cfg->base.flags |= NAT64_WKPFX; 211 212 IPFW_UH_WLOCK(ch); 213 214 if (nat64stl_find(ni, uc->name, uc->set) != NULL) { 215 IPFW_UH_WUNLOCK(ch); 216 nat64stl_free_config(cfg); 217 return (EEXIST); 218 } 219 error = nat64stl_create_internal(ch, cfg, uc); 220 if (error == 0) { 221 /* Okay, let's link data */ 222 SRV_OBJECT(ch, cfg->no.kidx) = cfg; 223 IPFW_UH_WUNLOCK(ch); 224 return (0); 225 } 226 227 if (cfg->base.flags & NAT64STL_KIDX) 228 ipfw_objhash_free_idx(ni, cfg->no.kidx); 229 if (cfg->base.flags & NAT64STL_46T) 230 ipfw_unref_table(ch, cfg->map46); 231 if (cfg->base.flags & NAT64STL_64T) 232 ipfw_unref_table(ch, cfg->map64); 233 234 IPFW_UH_WUNLOCK(ch); 235 nat64stl_free_config(cfg); 236 return (error); 237 } 238 239 /* 240 * Change existing nat64stl instance configuration. 241 * Data layout (v0)(current): 242 * Request: [ ipfw_obj_header ipfw_nat64stl_cfg ] 243 * Reply: [ ipfw_obj_header ipfw_nat64stl_cfg ] 244 * 245 * Returns 0 on success 246 */ 247 static int 248 nat64stl_config(struct ip_fw_chain *ch, ip_fw3_opheader *op, 249 struct sockopt_data *sd) 250 { 251 ipfw_obj_header *oh; 252 ipfw_nat64stl_cfg *uc; 253 struct nat64stl_cfg *cfg; 254 struct namedobj_instance *ni; 255 256 if (sd->valsize != sizeof(*oh) + sizeof(*uc)) 257 return (EINVAL); 258 259 oh = (ipfw_obj_header *)ipfw_get_sopt_space(sd, 260 sizeof(*oh) + sizeof(*uc)); 261 uc = (ipfw_nat64stl_cfg *)(oh + 1); 262 263 if (ipfw_check_object_name_generic(oh->ntlv.name) != 0 || 264 oh->ntlv.set >= IPFW_MAX_SETS) 265 return (EINVAL); 266 267 ni = CHAIN_TO_SRV(ch); 268 if (sd->sopt->sopt_dir == SOPT_GET) { 269 IPFW_UH_RLOCK(ch); 270 cfg = nat64stl_find(ni, oh->ntlv.name, oh->ntlv.set); 271 if (cfg == NULL) { 272 IPFW_UH_RUNLOCK(ch); 273 return (EEXIST); 274 } 275 nat64stl_export_config(ch, cfg, uc); 276 IPFW_UH_RUNLOCK(ch); 277 return (0); 278 } 279 280 IPFW_UH_WLOCK(ch); 281 cfg = nat64stl_find(ni, oh->ntlv.name, oh->ntlv.set); 282 if (cfg == NULL) { 283 IPFW_UH_WUNLOCK(ch); 284 return (EEXIST); 285 } 286 287 /* 288 * For now allow to change only following values: 289 * flags. 290 */ 291 cfg->base.flags &= ~NAT64STL_FLAGSMASK; 292 cfg->base.flags |= uc->flags & NAT64STL_FLAGSMASK; 293 294 IPFW_UH_WUNLOCK(ch); 295 return (0); 296 } 297 298 static void 299 nat64stl_detach_config(struct ip_fw_chain *ch, struct nat64stl_cfg *cfg) 300 { 301 302 IPFW_UH_WLOCK_ASSERT(ch); 303 304 ipfw_objhash_del(CHAIN_TO_SRV(ch), &cfg->no); 305 ipfw_objhash_free_idx(CHAIN_TO_SRV(ch), cfg->no.kidx); 306 ipfw_unref_table(ch, cfg->map46); 307 ipfw_unref_table(ch, cfg->map64); 308 } 309 310 /* 311 * Destroys nat64 instance. 312 * Data layout (v0)(current): 313 * Request: [ ipfw_obj_header ] 314 * 315 * Returns 0 on success 316 */ 317 static int 318 nat64stl_destroy(struct ip_fw_chain *ch, ip_fw3_opheader *op3, 319 struct sockopt_data *sd) 320 { 321 ipfw_obj_header *oh; 322 struct nat64stl_cfg *cfg; 323 324 if (sd->valsize != sizeof(*oh)) 325 return (EINVAL); 326 327 oh = (ipfw_obj_header *)sd->kbuf; 328 if (ipfw_check_object_name_generic(oh->ntlv.name) != 0) 329 return (EINVAL); 330 331 IPFW_UH_WLOCK(ch); 332 cfg = nat64stl_find(CHAIN_TO_SRV(ch), oh->ntlv.name, oh->ntlv.set); 333 if (cfg == NULL) { 334 IPFW_UH_WUNLOCK(ch); 335 return (ESRCH); 336 } 337 if (cfg->no.refcnt > 0) { 338 IPFW_UH_WUNLOCK(ch); 339 return (EBUSY); 340 } 341 342 ipfw_reset_eaction_instance(ch, V_nat64stl_eid, cfg->no.kidx); 343 SRV_OBJECT(ch, cfg->no.kidx) = NULL; 344 nat64stl_detach_config(ch, cfg); 345 IPFW_UH_WUNLOCK(ch); 346 347 nat64stl_free_config(cfg); 348 return (0); 349 } 350 351 /* 352 * Lists all nat64stl instances currently available in kernel. 353 * Data layout (v0)(current): 354 * Request: [ ipfw_obj_lheader ] 355 * Reply: [ ipfw_obj_lheader ipfw_nat64stl_cfg x N ] 356 * 357 * Returns 0 on success 358 */ 359 static int 360 nat64stl_list(struct ip_fw_chain *ch, ip_fw3_opheader *op3, 361 struct sockopt_data *sd) 362 { 363 ipfw_obj_lheader *olh; 364 struct nat64stl_dump_arg da; 365 366 /* Check minimum header size */ 367 if (sd->valsize < sizeof(ipfw_obj_lheader)) 368 return (EINVAL); 369 370 olh = (ipfw_obj_lheader *)ipfw_get_sopt_header(sd, sizeof(*olh)); 371 372 IPFW_UH_RLOCK(ch); 373 olh->count = ipfw_objhash_count_type(CHAIN_TO_SRV(ch), 374 IPFW_TLV_NAT64STL_NAME); 375 olh->objsize = sizeof(ipfw_nat64stl_cfg); 376 olh->size = sizeof(*olh) + olh->count * olh->objsize; 377 378 if (sd->valsize < olh->size) { 379 IPFW_UH_RUNLOCK(ch); 380 return (ENOMEM); 381 } 382 memset(&da, 0, sizeof(da)); 383 da.ch = ch; 384 da.sd = sd; 385 ipfw_objhash_foreach_type(CHAIN_TO_SRV(ch), export_config_cb, 386 &da, IPFW_TLV_NAT64STL_NAME); 387 IPFW_UH_RUNLOCK(ch); 388 389 return (0); 390 } 391 392 #define __COPY_STAT_FIELD(_cfg, _stats, _field) \ 393 (_stats)->_field = NAT64STAT_FETCH(&(_cfg)->base.stats, _field) 394 static void 395 export_stats(struct ip_fw_chain *ch, struct nat64stl_cfg *cfg, 396 struct ipfw_nat64stl_stats *stats) 397 { 398 399 __COPY_STAT_FIELD(cfg, stats, opcnt64); 400 __COPY_STAT_FIELD(cfg, stats, opcnt46); 401 __COPY_STAT_FIELD(cfg, stats, ofrags); 402 __COPY_STAT_FIELD(cfg, stats, ifrags); 403 __COPY_STAT_FIELD(cfg, stats, oerrors); 404 __COPY_STAT_FIELD(cfg, stats, noroute4); 405 __COPY_STAT_FIELD(cfg, stats, noroute6); 406 __COPY_STAT_FIELD(cfg, stats, noproto); 407 __COPY_STAT_FIELD(cfg, stats, nomem); 408 __COPY_STAT_FIELD(cfg, stats, dropped); 409 } 410 411 /* 412 * Get nat64stl statistics. 413 * Data layout (v0)(current): 414 * Request: [ ipfw_obj_header ] 415 * Reply: [ ipfw_obj_header ipfw_obj_ctlv [ uint64_t x N ]] 416 * 417 * Returns 0 on success 418 */ 419 static int 420 nat64stl_stats(struct ip_fw_chain *ch, ip_fw3_opheader *op, 421 struct sockopt_data *sd) 422 { 423 struct ipfw_nat64stl_stats stats; 424 struct nat64stl_cfg *cfg; 425 ipfw_obj_header *oh; 426 ipfw_obj_ctlv *ctlv; 427 size_t sz; 428 429 sz = sizeof(ipfw_obj_header) + sizeof(ipfw_obj_ctlv) + sizeof(stats); 430 if (sd->valsize % sizeof(uint64_t)) 431 return (EINVAL); 432 if (sd->valsize < sz) 433 return (ENOMEM); 434 oh = (ipfw_obj_header *)ipfw_get_sopt_header(sd, sz); 435 if (oh == NULL) 436 return (EINVAL); 437 memset(&stats, 0, sizeof(stats)); 438 439 IPFW_UH_RLOCK(ch); 440 cfg = nat64stl_find(CHAIN_TO_SRV(ch), oh->ntlv.name, oh->ntlv.set); 441 if (cfg == NULL) { 442 IPFW_UH_RUNLOCK(ch); 443 return (ESRCH); 444 } 445 export_stats(ch, cfg, &stats); 446 IPFW_UH_RUNLOCK(ch); 447 448 ctlv = (ipfw_obj_ctlv *)(oh + 1); 449 memset(ctlv, 0, sizeof(*ctlv)); 450 ctlv->head.type = IPFW_TLV_COUNTERS; 451 ctlv->head.length = sz - sizeof(ipfw_obj_header); 452 ctlv->count = sizeof(stats) / sizeof(uint64_t); 453 ctlv->objsize = sizeof(uint64_t); 454 ctlv->version = IPFW_NAT64_VERSION; 455 memcpy(ctlv + 1, &stats, sizeof(stats)); 456 return (0); 457 } 458 459 /* 460 * Reset nat64stl statistics. 461 * Data layout (v0)(current): 462 * Request: [ ipfw_obj_header ] 463 * 464 * Returns 0 on success 465 */ 466 static int 467 nat64stl_reset_stats(struct ip_fw_chain *ch, ip_fw3_opheader *op, 468 struct sockopt_data *sd) 469 { 470 struct nat64stl_cfg *cfg; 471 ipfw_obj_header *oh; 472 473 if (sd->valsize != sizeof(*oh)) 474 return (EINVAL); 475 oh = (ipfw_obj_header *)sd->kbuf; 476 if (ipfw_check_object_name_generic(oh->ntlv.name) != 0 || 477 oh->ntlv.set >= IPFW_MAX_SETS) 478 return (EINVAL); 479 480 IPFW_UH_WLOCK(ch); 481 cfg = nat64stl_find(CHAIN_TO_SRV(ch), oh->ntlv.name, oh->ntlv.set); 482 if (cfg == NULL) { 483 IPFW_UH_WUNLOCK(ch); 484 return (ESRCH); 485 } 486 COUNTER_ARRAY_ZERO(cfg->base.stats.cnt, NAT64STATS); 487 IPFW_UH_WUNLOCK(ch); 488 return (0); 489 } 490 491 static struct ipfw_sopt_handler scodes[] = { 492 { IP_FW_NAT64STL_CREATE, 0, HDIR_SET, nat64stl_create }, 493 { IP_FW_NAT64STL_DESTROY,0, HDIR_SET, nat64stl_destroy }, 494 { IP_FW_NAT64STL_CONFIG, 0, HDIR_BOTH, nat64stl_config }, 495 { IP_FW_NAT64STL_LIST, 0, HDIR_GET, nat64stl_list }, 496 { IP_FW_NAT64STL_STATS, 0, HDIR_GET, nat64stl_stats }, 497 { IP_FW_NAT64STL_RESET_STATS,0, HDIR_SET, nat64stl_reset_stats }, 498 }; 499 500 static int 501 nat64stl_classify(ipfw_insn *cmd, uint16_t *puidx, uint8_t *ptype) 502 { 503 ipfw_insn *icmd; 504 505 icmd = cmd - 1; 506 if (icmd->opcode != O_EXTERNAL_ACTION || 507 icmd->arg1 != V_nat64stl_eid) 508 return (1); 509 510 *puidx = cmd->arg1; 511 *ptype = 0; 512 return (0); 513 } 514 515 static void 516 nat64stl_update_arg1(ipfw_insn *cmd, uint16_t idx) 517 { 518 519 cmd->arg1 = idx; 520 } 521 522 static int 523 nat64stl_findbyname(struct ip_fw_chain *ch, struct tid_info *ti, 524 struct named_object **pno) 525 { 526 int err; 527 528 err = ipfw_objhash_find_type(CHAIN_TO_SRV(ch), ti, 529 IPFW_TLV_NAT64STL_NAME, pno); 530 return (err); 531 } 532 533 static struct named_object * 534 nat64stl_findbykidx(struct ip_fw_chain *ch, uint16_t idx) 535 { 536 struct namedobj_instance *ni; 537 struct named_object *no; 538 539 IPFW_UH_WLOCK_ASSERT(ch); 540 ni = CHAIN_TO_SRV(ch); 541 no = ipfw_objhash_lookup_kidx(ni, idx); 542 KASSERT(no != NULL, ("NAT with index %d not found", idx)); 543 544 return (no); 545 } 546 547 static int 548 nat64stl_manage_sets(struct ip_fw_chain *ch, uint16_t set, uint8_t new_set, 549 enum ipfw_sets_cmd cmd) 550 { 551 552 return (ipfw_obj_manage_sets(CHAIN_TO_SRV(ch), IPFW_TLV_NAT64STL_NAME, 553 set, new_set, cmd)); 554 } 555 556 static struct opcode_obj_rewrite opcodes[] = { 557 { 558 .opcode = O_EXTERNAL_INSTANCE, 559 .etlv = IPFW_TLV_EACTION /* just show it isn't table */, 560 .classifier = nat64stl_classify, 561 .update = nat64stl_update_arg1, 562 .find_byname = nat64stl_findbyname, 563 .find_bykidx = nat64stl_findbykidx, 564 .manage_sets = nat64stl_manage_sets, 565 }, 566 }; 567 568 static int 569 destroy_config_cb(struct namedobj_instance *ni, struct named_object *no, 570 void *arg) 571 { 572 struct nat64stl_cfg *cfg; 573 struct ip_fw_chain *ch; 574 575 ch = (struct ip_fw_chain *)arg; 576 cfg = (struct nat64stl_cfg *)SRV_OBJECT(ch, no->kidx); 577 SRV_OBJECT(ch, no->kidx) = NULL; 578 nat64stl_detach_config(ch, cfg); 579 nat64stl_free_config(cfg); 580 return (0); 581 } 582 583 int 584 nat64stl_init(struct ip_fw_chain *ch, int first) 585 { 586 587 V_nat64stl_eid = ipfw_add_eaction(ch, ipfw_nat64stl, "nat64stl"); 588 if (V_nat64stl_eid == 0) 589 return (ENXIO); 590 IPFW_ADD_SOPT_HANDLER(first, scodes); 591 IPFW_ADD_OBJ_REWRITER(first, opcodes); 592 return (0); 593 } 594 595 void 596 nat64stl_uninit(struct ip_fw_chain *ch, int last) 597 { 598 599 IPFW_DEL_OBJ_REWRITER(last, opcodes); 600 IPFW_DEL_SOPT_HANDLER(last, scodes); 601 ipfw_del_eaction(ch, V_nat64stl_eid); 602 /* 603 * Since we already have deregistered external action, 604 * our named objects become unaccessible via rules, because 605 * all rules were truncated by ipfw_del_eaction(). 606 * So, we can unlink and destroy our named objects without holding 607 * IPFW_WLOCK(). 608 */ 609 IPFW_UH_WLOCK(ch); 610 ipfw_objhash_foreach_type(CHAIN_TO_SRV(ch), destroy_config_cb, ch, 611 IPFW_TLV_NAT64STL_NAME); 612 V_nat64stl_eid = 0; 613 IPFW_UH_WUNLOCK(ch); 614 } 615