ip_fw_nat.c revision 185844
1/*- 2 * Copyright (c) 2008 Paolo Pisati 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 */ 26 27#include <sys/cdefs.h> 28__FBSDID("$FreeBSD: head/sys/netinet/ip_fw_nat.c 185844 2008-12-10 08:22:51Z kmacy $"); 29 30#include <sys/param.h> 31#include <sys/systm.h> 32#include <sys/condvar.h> 33#include <sys/eventhandler.h> 34#include <sys/malloc.h> 35#include <sys/mbuf.h> 36#include <sys/kernel.h> 37#include <sys/lock.h> 38#include <sys/jail.h> 39#include <sys/module.h> 40#include <sys/priv.h> 41#include <sys/proc.h> 42#include <sys/rwlock.h> 43#include <sys/socket.h> 44#include <sys/socketvar.h> 45#include <sys/sysctl.h> 46#include <sys/syslog.h> 47#include <sys/ucred.h> 48#include <sys/vimage.h> 49 50#include <netinet/libalias/alias.h> 51#include <netinet/libalias/alias_local.h> 52 53#define IPFW_INTERNAL /* Access to protected data structures in ip_fw.h. */ 54 55#include <net/if.h> 56#include <netinet/in.h> 57#include <netinet/ip.h> 58#include <netinet/ip_var.h> 59#include <netinet/ip_icmp.h> 60#include <netinet/ip_fw.h> 61#include <netinet/tcp.h> 62#include <netinet/tcp_timer.h> 63#include <netinet/tcp_var.h> 64#include <netinet/tcpip.h> 65#include <netinet/udp.h> 66#include <netinet/udp_var.h> 67 68#include <machine/in_cksum.h> /* XXX for in_cksum */ 69 70MALLOC_DECLARE(M_IPFW); 71 72extern struct ip_fw_chain layer3_chain; 73 74static eventhandler_tag ifaddr_event_tag; 75 76extern ipfw_nat_t *ipfw_nat_ptr; 77extern ipfw_nat_cfg_t *ipfw_nat_cfg_ptr; 78extern ipfw_nat_cfg_t *ipfw_nat_del_ptr; 79extern ipfw_nat_cfg_t *ipfw_nat_get_cfg_ptr; 80extern ipfw_nat_cfg_t *ipfw_nat_get_log_ptr; 81 82static void 83ifaddr_change(void *arg __unused, struct ifnet *ifp) 84{ 85 INIT_VNET_IPFW(curvnet); 86 struct cfg_nat *ptr; 87 struct ifaddr *ifa; 88 89 IPFW_WLOCK(&V_layer3_chain); 90 /* Check every nat entry... */ 91 LIST_FOREACH(ptr, &V_layer3_chain.nat, _next) { 92 /* ...using nic 'ifp->if_xname' as dynamic alias address. */ 93 if (strncmp(ptr->if_name, ifp->if_xname, IF_NAMESIZE) == 0) { 94 IF_ADDR_LOCK(ifp); 95 TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) { 96 if (ifa->ifa_addr == NULL) 97 continue; 98 if (ifa->ifa_addr->sa_family != AF_INET) 99 continue; 100 ptr->ip = ((struct sockaddr_in *) 101 (ifa->ifa_addr))->sin_addr; 102 LibAliasSetAddress(ptr->lib, ptr->ip); 103 } 104 IF_ADDR_LOCK(ifp); 105 } 106 } 107 IPFW_WUNLOCK(&V_layer3_chain); 108} 109 110static void 111flush_nat_ptrs(const int i) 112{ 113 INIT_VNET_IPFW(curvnet); 114 struct ip_fw *rule; 115 116 IPFW_WLOCK_ASSERT(&V_layer3_chain); 117 for (rule = V_layer3_chain.rules; rule; rule = rule->next) { 118 ipfw_insn_nat *cmd = (ipfw_insn_nat *)ACTION_PTR(rule); 119 if (cmd->o.opcode != O_NAT) 120 continue; 121 if (cmd->nat != NULL && cmd->nat->id == i) 122 cmd->nat = NULL; 123 } 124} 125 126#define HOOK_NAT(b, p) do { \ 127 IPFW_WLOCK_ASSERT(&V_layer3_chain); \ 128 LIST_INSERT_HEAD(b, p, _next); \ 129 } while (0) 130 131#define UNHOOK_NAT(p) do { \ 132 IPFW_WLOCK_ASSERT(&V_layer3_chain); \ 133 LIST_REMOVE(p, _next); \ 134 } while (0) 135 136#define HOOK_REDIR(b, p) do { \ 137 LIST_INSERT_HEAD(b, p, _next); \ 138 } while (0) 139 140#define HOOK_SPOOL(b, p) do { \ 141 LIST_INSERT_HEAD(b, p, _next); \ 142 } while (0) 143 144static void 145del_redir_spool_cfg(struct cfg_nat *n, struct redir_chain *head) 146{ 147 struct cfg_redir *r, *tmp_r; 148 struct cfg_spool *s, *tmp_s; 149 int i, num; 150 151 LIST_FOREACH_SAFE(r, head, _next, tmp_r) { 152 num = 1; /* Number of alias_link to delete. */ 153 switch (r->mode) { 154 case REDIR_PORT: 155 num = r->pport_cnt; 156 /* FALLTHROUGH */ 157 case REDIR_ADDR: 158 case REDIR_PROTO: 159 /* Delete all libalias redirect entry. */ 160 for (i = 0; i < num; i++) 161 LibAliasRedirectDelete(n->lib, r->alink[i]); 162 /* Del spool cfg if any. */ 163 LIST_FOREACH_SAFE(s, &r->spool_chain, _next, tmp_s) { 164 LIST_REMOVE(s, _next); 165 free(s, M_IPFW); 166 } 167 free(r->alink, M_IPFW); 168 LIST_REMOVE(r, _next); 169 free(r, M_IPFW); 170 break; 171 default: 172 printf("unknown redirect mode: %u\n", r->mode); 173 /* XXX - panic?!?!? */ 174 break; 175 } 176 } 177} 178 179static int 180add_redir_spool_cfg(char *buf, struct cfg_nat *ptr) 181{ 182 struct cfg_redir *r, *ser_r; 183 struct cfg_spool *s, *ser_s; 184 int cnt, off, i; 185 char *panic_err; 186 187 for (cnt = 0, off = 0; cnt < ptr->redir_cnt; cnt++) { 188 ser_r = (struct cfg_redir *)&buf[off]; 189 r = malloc(SOF_REDIR, M_IPFW, M_WAITOK | M_ZERO); 190 memcpy(r, ser_r, SOF_REDIR); 191 LIST_INIT(&r->spool_chain); 192 off += SOF_REDIR; 193 r->alink = malloc(sizeof(struct alias_link *) * r->pport_cnt, 194 M_IPFW, M_WAITOK | M_ZERO); 195 switch (r->mode) { 196 case REDIR_ADDR: 197 r->alink[0] = LibAliasRedirectAddr(ptr->lib, r->laddr, 198 r->paddr); 199 break; 200 case REDIR_PORT: 201 for (i = 0 ; i < r->pport_cnt; i++) { 202 /* If remotePort is all ports, set it to 0. */ 203 u_short remotePortCopy = r->rport + i; 204 if (r->rport_cnt == 1 && r->rport == 0) 205 remotePortCopy = 0; 206 r->alink[i] = LibAliasRedirectPort(ptr->lib, 207 r->laddr, htons(r->lport + i), r->raddr, 208 htons(remotePortCopy), r->paddr, 209 htons(r->pport + i), r->proto); 210 if (r->alink[i] == NULL) { 211 r->alink[0] = NULL; 212 break; 213 } 214 } 215 break; 216 case REDIR_PROTO: 217 r->alink[0] = LibAliasRedirectProto(ptr->lib ,r->laddr, 218 r->raddr, r->paddr, r->proto); 219 break; 220 default: 221 printf("unknown redirect mode: %u\n", r->mode); 222 break; 223 } 224 if (r->alink[0] == NULL) { 225 panic_err = "LibAliasRedirect* returned NULL"; 226 goto bad; 227 } else /* LSNAT handling. */ 228 for (i = 0; i < r->spool_cnt; i++) { 229 ser_s = (struct cfg_spool *)&buf[off]; 230 s = malloc(SOF_REDIR, M_IPFW, 231 M_WAITOK | M_ZERO); 232 memcpy(s, ser_s, SOF_SPOOL); 233 LibAliasAddServer(ptr->lib, r->alink[0], 234 s->addr, htons(s->port)); 235 off += SOF_SPOOL; 236 /* Hook spool entry. */ 237 HOOK_SPOOL(&r->spool_chain, s); 238 } 239 /* And finally hook this redir entry. */ 240 HOOK_REDIR(&ptr->redir_chain, r); 241 } 242 return (1); 243bad: 244 /* something really bad happened: panic! */ 245 panic("%s\n", panic_err); 246} 247 248static int 249ipfw_nat(struct ip_fw_args *args, struct cfg_nat *t, struct mbuf *m) 250{ 251 struct mbuf *mcl; 252 struct ip *ip; 253 /* XXX - libalias duct tape */ 254 int ldt, retval; 255 char *c; 256 257 ldt = 0; 258 retval = 0; 259 if ((mcl = m_megapullup(m, m->m_pkthdr.len)) == 260 NULL) 261 goto badnat; 262 ip = mtod(mcl, struct ip *); 263 if (args->eh == NULL) { 264 ip->ip_len = htons(ip->ip_len); 265 ip->ip_off = htons(ip->ip_off); 266 } 267 268 /* 269 * XXX - Libalias checksum offload 'duct tape': 270 * 271 * locally generated packets have only 272 * pseudo-header checksum calculated 273 * and libalias will screw it[1], so 274 * mark them for later fix. Moreover 275 * there are cases when libalias 276 * modify tcp packet data[2], mark it 277 * for later fix too. 278 * 279 * [1] libalias was never meant to run 280 * in kernel, so it doesn't have any 281 * knowledge about checksum 282 * offloading, and it expects a packet 283 * with a full internet 284 * checksum. Unfortunately, packets 285 * generated locally will have just the 286 * pseudo header calculated, and when 287 * libalias tries to adjust the 288 * checksum it will actually screw it. 289 * 290 * [2] when libalias modify tcp's data 291 * content, full TCP checksum has to 292 * be recomputed: the problem is that 293 * libalias doesn't have any idea 294 * about checksum offloading To 295 * workaround this, we do not do 296 * checksumming in LibAlias, but only 297 * mark the packets in th_x2 field. If 298 * we receive a marked packet, we 299 * calculate correct checksum for it 300 * aware of offloading. Why such a 301 * terrible hack instead of 302 * recalculating checksum for each 303 * packet? Because the previous 304 * checksum was not checked! 305 * Recalculating checksums for EVERY 306 * packet will hide ALL transmission 307 * errors. Yes, marked packets still 308 * suffer from this problem. But, 309 * sigh, natd(8) has this problem, 310 * too. 311 * 312 * TODO: -make libalias mbuf aware (so 313 * it can handle delayed checksum and tso) 314 */ 315 316 if (mcl->m_pkthdr.rcvif == NULL && 317 mcl->m_pkthdr.csum_flags & 318 CSUM_DELAY_DATA) 319 ldt = 1; 320 321 c = mtod(mcl, char *); 322 if (args->oif == NULL) 323 retval = LibAliasIn(t->lib, c, 324 mcl->m_len + M_TRAILINGSPACE(mcl)); 325 else 326 retval = LibAliasOut(t->lib, c, 327 mcl->m_len + M_TRAILINGSPACE(mcl)); 328 if (retval != PKT_ALIAS_OK && 329 retval != PKT_ALIAS_FOUND_HEADER_FRAGMENT) { 330 /* XXX - should i add some logging? */ 331 m_free(mcl); 332 badnat: 333 args->m = NULL; 334 return (IP_FW_DENY); 335 } 336 mcl->m_pkthdr.len = mcl->m_len = 337 ntohs(ip->ip_len); 338 339 /* 340 * XXX - libalias checksum offload 341 * 'duct tape' (see above) 342 */ 343 344 if ((ip->ip_off & htons(IP_OFFMASK)) == 0 && 345 ip->ip_p == IPPROTO_TCP) { 346 struct tcphdr *th; 347 348 th = (struct tcphdr *)(ip + 1); 349 if (th->th_x2) 350 ldt = 1; 351 } 352 353 if (ldt) { 354 struct tcphdr *th; 355 struct udphdr *uh; 356 u_short cksum; 357 358 ip->ip_len = ntohs(ip->ip_len); 359 cksum = in_pseudo( 360 ip->ip_src.s_addr, 361 ip->ip_dst.s_addr, 362 htons(ip->ip_p + ip->ip_len - (ip->ip_hl << 2)) 363 ); 364 365 switch (ip->ip_p) { 366 case IPPROTO_TCP: 367 th = (struct tcphdr *)(ip + 1); 368 /* 369 * Maybe it was set in 370 * libalias... 371 */ 372 th->th_x2 = 0; 373 th->th_sum = cksum; 374 mcl->m_pkthdr.csum_data = 375 offsetof(struct tcphdr, th_sum); 376 break; 377 case IPPROTO_UDP: 378 uh = (struct udphdr *)(ip + 1); 379 uh->uh_sum = cksum; 380 mcl->m_pkthdr.csum_data = 381 offsetof(struct udphdr, uh_sum); 382 break; 383 } 384 /* 385 * No hw checksum offloading: do it 386 * by ourself. 387 */ 388 if ((mcl->m_pkthdr.csum_flags & 389 CSUM_DELAY_DATA) == 0) { 390 in_delayed_cksum(mcl); 391 mcl->m_pkthdr.csum_flags &= 392 ~CSUM_DELAY_DATA; 393 } 394 ip->ip_len = htons(ip->ip_len); 395 } 396 397 if (args->eh == NULL) { 398 ip->ip_len = ntohs(ip->ip_len); 399 ip->ip_off = ntohs(ip->ip_off); 400 } 401 402 args->m = mcl; 403 return (IP_FW_NAT); 404} 405 406static int 407ipfw_nat_cfg(struct sockopt *sopt) 408{ 409 INIT_VNET_IPFW(curvnet); 410 struct cfg_nat *ptr, *ser_n; 411 char *buf; 412 413 buf = malloc(NAT_BUF_LEN, M_IPFW, M_WAITOK | M_ZERO); 414 sooptcopyin(sopt, buf, NAT_BUF_LEN, 415 sizeof(struct cfg_nat)); 416 ser_n = (struct cfg_nat *)buf; 417 418 /* 419 * Find/create nat rule. 420 */ 421 IPFW_WLOCK(&V_layer3_chain); 422 LOOKUP_NAT(V_layer3_chain, ser_n->id, ptr); 423 if (ptr == NULL) { 424 /* New rule: allocate and init new instance. */ 425 ptr = malloc(sizeof(struct cfg_nat), 426 M_IPFW, M_NOWAIT | M_ZERO); 427 if (ptr == NULL) { 428 IPFW_WUNLOCK(&V_layer3_chain); 429 free(buf, M_IPFW); 430 return (ENOSPC); 431 } 432 ptr->lib = LibAliasInit(NULL); 433 if (ptr->lib == NULL) { 434 IPFW_WUNLOCK(&V_layer3_chain); 435 free(ptr, M_IPFW); 436 free(buf, M_IPFW); 437 return (EINVAL); 438 } 439 LIST_INIT(&ptr->redir_chain); 440 } else { 441 /* Entry already present: temporarly unhook it. */ 442 UNHOOK_NAT(ptr); 443 flush_nat_ptrs(ser_n->id); 444 } 445 IPFW_WUNLOCK(&V_layer3_chain); 446 447 /* 448 * Basic nat configuration. 449 */ 450 ptr->id = ser_n->id; 451 /* 452 * XXX - what if this rule doesn't nat any ip and just 453 * redirect? 454 * do we set aliasaddress to 0.0.0.0? 455 */ 456 ptr->ip = ser_n->ip; 457 ptr->redir_cnt = ser_n->redir_cnt; 458 ptr->mode = ser_n->mode; 459 LibAliasSetMode(ptr->lib, ser_n->mode, ser_n->mode); 460 LibAliasSetAddress(ptr->lib, ptr->ip); 461 memcpy(ptr->if_name, ser_n->if_name, IF_NAMESIZE); 462 463 /* 464 * Redir and LSNAT configuration. 465 */ 466 /* Delete old cfgs. */ 467 del_redir_spool_cfg(ptr, &ptr->redir_chain); 468 /* Add new entries. */ 469 add_redir_spool_cfg(&buf[(sizeof(struct cfg_nat))], ptr); 470 free(buf, M_IPFW); 471 IPFW_WLOCK(&V_layer3_chain); 472 HOOK_NAT(&V_layer3_chain.nat, ptr); 473 IPFW_WUNLOCK(&V_layer3_chain); 474 return (0); 475} 476 477static int 478ipfw_nat_del(struct sockopt *sopt) 479{ 480 INIT_VNET_IPFW(curvnet); 481 struct cfg_nat *ptr; 482 int i; 483 484 sooptcopyin(sopt, &i, sizeof i, sizeof i); 485 IPFW_WLOCK(&V_layer3_chain); 486 LOOKUP_NAT(V_layer3_chain, i, ptr); 487 if (ptr == NULL) { 488 IPFW_WUNLOCK(&V_layer3_chain); 489 return (EINVAL); 490 } 491 UNHOOK_NAT(ptr); 492 flush_nat_ptrs(i); 493 IPFW_WUNLOCK(&V_layer3_chain); 494 del_redir_spool_cfg(ptr, &ptr->redir_chain); 495 LibAliasUninit(ptr->lib); 496 free(ptr, M_IPFW); 497 return (0); 498} 499 500static int 501ipfw_nat_get_cfg(struct sockopt *sopt) 502{ 503 INIT_VNET_IPFW(curvnet); 504 uint8_t *data; 505 struct cfg_nat *n; 506 struct cfg_redir *r; 507 struct cfg_spool *s; 508 int nat_cnt, off; 509 510 nat_cnt = 0; 511 off = sizeof(nat_cnt); 512 513 data = malloc(NAT_BUF_LEN, M_IPFW, M_WAITOK | M_ZERO); 514 IPFW_RLOCK(&V_layer3_chain); 515 /* Serialize all the data. */ 516 LIST_FOREACH(n, &V_layer3_chain.nat, _next) { 517 nat_cnt++; 518 if (off + SOF_NAT < NAT_BUF_LEN) { 519 bcopy(n, &data[off], SOF_NAT); 520 off += SOF_NAT; 521 LIST_FOREACH(r, &n->redir_chain, _next) { 522 if (off + SOF_REDIR < NAT_BUF_LEN) { 523 bcopy(r, &data[off], 524 SOF_REDIR); 525 off += SOF_REDIR; 526 LIST_FOREACH(s, &r->spool_chain, 527 _next) { 528 if (off + SOF_SPOOL < 529 NAT_BUF_LEN) { 530 bcopy(s, &data[off], 531 SOF_SPOOL); 532 off += SOF_SPOOL; 533 } else 534 goto nospace; 535 } 536 } else 537 goto nospace; 538 } 539 } else 540 goto nospace; 541 } 542 bcopy(&nat_cnt, data, sizeof(nat_cnt)); 543 IPFW_RUNLOCK(&V_layer3_chain); 544 sooptcopyout(sopt, data, NAT_BUF_LEN); 545 free(data, M_IPFW); 546 return (0); 547nospace: 548 IPFW_RUNLOCK(&V_layer3_chain); 549 printf("serialized data buffer not big enough:" 550 "please increase NAT_BUF_LEN\n"); 551 free(data, M_IPFW); 552 return (ENOSPC); 553} 554 555static int 556ipfw_nat_get_log(struct sockopt *sopt) 557{ 558 INIT_VNET_IPFW(curvnet); 559 uint8_t *data; 560 struct cfg_nat *ptr; 561 int i, size, cnt, sof; 562 563 data = NULL; 564 sof = LIBALIAS_BUF_SIZE; 565 cnt = 0; 566 567 IPFW_RLOCK(&V_layer3_chain); 568 size = i = 0; 569 LIST_FOREACH(ptr, &V_layer3_chain.nat, _next) { 570 if (ptr->lib->logDesc == NULL) 571 continue; 572 cnt++; 573 size = cnt * (sof + sizeof(int)); 574 data = realloc(data, size, M_IPFW, M_NOWAIT | M_ZERO); 575 if (data == NULL) { 576 IPFW_RUNLOCK(&V_layer3_chain); 577 return (ENOSPC); 578 } 579 bcopy(&ptr->id, &data[i], sizeof(int)); 580 i += sizeof(int); 581 bcopy(ptr->lib->logDesc, &data[i], sof); 582 i += sof; 583 } 584 IPFW_RUNLOCK(&V_layer3_chain); 585 sooptcopyout(sopt, data, size); 586 free(data, M_IPFW); 587 return(0); 588} 589 590static void 591ipfw_nat_init(void) 592{ 593 INIT_VNET_IPFW(curvnet); 594 595 IPFW_WLOCK(&V_layer3_chain); 596 /* init ipfw hooks */ 597 ipfw_nat_ptr = ipfw_nat; 598 ipfw_nat_cfg_ptr = ipfw_nat_cfg; 599 ipfw_nat_del_ptr = ipfw_nat_del; 600 ipfw_nat_get_cfg_ptr = ipfw_nat_get_cfg; 601 ipfw_nat_get_log_ptr = ipfw_nat_get_log; 602 IPFW_WUNLOCK(&V_layer3_chain); 603 V_ifaddr_event_tag = EVENTHANDLER_REGISTER(ifaddr_event, ifaddr_change, 604 NULL, EVENTHANDLER_PRI_ANY); 605} 606 607static void 608ipfw_nat_destroy(void) 609{ 610 INIT_VNET_IPFW(curvnet); 611 struct ip_fw *rule; 612 struct cfg_nat *ptr, *ptr_temp; 613 614 IPFW_WLOCK(&V_layer3_chain); 615 LIST_FOREACH_SAFE(ptr, &V_layer3_chain.nat, _next, ptr_temp) { 616 LIST_REMOVE(ptr, _next); 617 del_redir_spool_cfg(ptr, &ptr->redir_chain); 618 LibAliasUninit(ptr->lib); 619 free(ptr, M_IPFW); 620 } 621 EVENTHANDLER_DEREGISTER(ifaddr_event, V_ifaddr_event_tag); 622 /* flush all nat ptrs */ 623 for (rule = V_layer3_chain.rules; rule; rule = rule->next) { 624 ipfw_insn_nat *cmd = (ipfw_insn_nat *)ACTION_PTR(rule); 625 if (cmd->o.opcode == O_NAT) 626 cmd->nat = NULL; 627 } 628 /* deregister ipfw_nat */ 629 ipfw_nat_ptr = NULL; 630 IPFW_WUNLOCK(&V_layer3_chain); 631} 632 633static int 634ipfw_nat_modevent(module_t mod, int type, void *unused) 635{ 636 int err = 0; 637 638 switch (type) { 639 case MOD_LOAD: 640 ipfw_nat_init(); 641 break; 642 643 case MOD_UNLOAD: 644 ipfw_nat_destroy(); 645 break; 646 647 default: 648 return EOPNOTSUPP; 649 break; 650 } 651 return err; 652} 653 654static moduledata_t ipfw_nat_mod = { 655 "ipfw_nat", 656 ipfw_nat_modevent, 657 0 658}; 659 660DECLARE_MODULE(ipfw_nat, ipfw_nat_mod, SI_SUB_PROTO_IFATTACHDOMAIN, SI_ORDER_ANY); 661MODULE_DEPEND(ipfw_nat, libalias, 1, 1, 1); 662MODULE_DEPEND(ipfw_nat, ipfw, 2, 2, 2); 663MODULE_VERSION(ipfw_nat, 1); 664