1 /* $NetBSD: npf_build.c,v 1.24 2013/05/19 20:45:34 rmind Exp $ */ 2 3 /*- 4 * Copyright (c) 2011-2013 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This material is based upon work partially supported by The 8 * NetBSD Foundation under a contract with Mindaugas Rasiukevicius. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 /* 33 * npfctl(8) building of the configuration. 34 */ 35 36 #include <sys/cdefs.h> 37 __RCSID("$NetBSD: npf_build.c,v 1.24 2013/05/19 20:45:34 rmind Exp $"); 38 39 #include <sys/types.h> 40 #include <sys/ioctl.h> 41 42 #include <stdlib.h> 43 #include <inttypes.h> 44 #include <string.h> 45 #include <errno.h> 46 #include <err.h> 47 48 #include "npfctl.h" 49 50 #define MAX_RULE_NESTING 16 51 52 static nl_config_t * npf_conf = NULL; 53 static bool npf_debug = false; 54 static nl_rule_t * the_rule = NULL; 55 56 static nl_rule_t * current_group[MAX_RULE_NESTING]; 57 static unsigned rule_nesting_level = 0; 58 static nl_rule_t * defgroup = NULL; 59 60 void 61 npfctl_config_init(bool debug) 62 { 63 npf_conf = npf_config_create(); 64 if (npf_conf == NULL) { 65 errx(EXIT_FAILURE, "npf_config_create failed"); 66 } 67 npf_debug = debug; 68 memset(current_group, 0, sizeof(current_group)); 69 } 70 71 int 72 npfctl_config_send(int fd, const char *out) 73 { 74 int error; 75 76 if (out) { 77 _npf_config_setsubmit(npf_conf, out); 78 printf("\nSaving to %s\n", out); 79 } 80 if (!defgroup) { 81 errx(EXIT_FAILURE, "default group was not defined"); 82 } 83 npf_rule_insert(npf_conf, NULL, defgroup); 84 error = npf_config_submit(npf_conf, fd); 85 if (error) { 86 nl_error_t ne; 87 _npf_config_error(npf_conf, &ne); 88 npfctl_print_error(&ne); 89 } 90 npf_config_destroy(npf_conf); 91 return error; 92 } 93 94 nl_config_t * 95 npfctl_config_ref(void) 96 { 97 return npf_conf; 98 } 99 100 nl_rule_t * 101 npfctl_rule_ref(void) 102 { 103 return the_rule; 104 } 105 106 unsigned long 107 npfctl_debug_addif(const char *ifname) 108 { 109 char tname[] = "npftest"; 110 const size_t tnamelen = sizeof(tname) - 1; 111 112 if (!npf_debug || strncmp(ifname, tname, tnamelen) != 0) { 113 return 0; 114 } 115 struct ifaddrs ifa = { 116 .ifa_name = __UNCONST(ifname), 117 .ifa_flags = 0 118 }; 119 unsigned long if_idx = atol(ifname + tnamelen) + 1; 120 _npf_debug_addif(npf_conf, &ifa, if_idx); 121 return if_idx; 122 } 123 124 bool 125 npfctl_table_exists_p(const char *id) 126 { 127 return npf_table_exists_p(npf_conf, atoi(id)); 128 } 129 130 static in_port_t 131 npfctl_get_singleport(const npfvar_t *vp) 132 { 133 port_range_t *pr; 134 in_port_t *port; 135 136 if (npfvar_get_count(vp) > 1) { 137 yyerror("multiple ports are not valid"); 138 } 139 pr = npfvar_get_data(vp, NPFVAR_PORT_RANGE, 0); 140 if (pr->pr_start != pr->pr_end) { 141 yyerror("port range is not valid"); 142 } 143 port = &pr->pr_start; 144 return *port; 145 } 146 147 static fam_addr_mask_t * 148 npfctl_get_singlefam(const npfvar_t *vp) 149 { 150 if (npfvar_get_count(vp) > 1) { 151 yyerror("multiple addresses are not valid"); 152 } 153 return npfvar_get_data(vp, NPFVAR_FAM, 0); 154 } 155 156 static bool 157 npfctl_build_fam(nc_ctx_t *nc, sa_family_t family, 158 fam_addr_mask_t *fam, int opts) 159 { 160 /* 161 * If family is specified, address does not match it and the 162 * address is extracted from the interface, then simply ignore. 163 * Otherwise, address of invalid family was passed manually. 164 */ 165 if (family != AF_UNSPEC && family != fam->fam_family) { 166 if (!fam->fam_ifindex) { 167 yyerror("specified address is not of the required " 168 "family %d", family); 169 } 170 return false; 171 } 172 173 /* 174 * Optimise 0.0.0.0/0 case to be NOP. Otherwise, address with 175 * zero mask would never match and therefore is not valid. 176 */ 177 if (fam->fam_mask == 0) { 178 npf_addr_t zero; 179 180 memset(&zero, 0, sizeof(npf_addr_t)); 181 if (memcmp(&fam->fam_addr, &zero, sizeof(npf_addr_t))) { 182 yyerror("filter criterion would never match"); 183 } 184 return false; 185 } 186 187 switch (fam->fam_family) { 188 case AF_INET: 189 npfctl_gennc_v4cidr(nc, opts, 190 &fam->fam_addr, fam->fam_mask); 191 break; 192 case AF_INET6: 193 npfctl_gennc_v6cidr(nc, opts, 194 &fam->fam_addr, fam->fam_mask); 195 break; 196 default: 197 yyerror("family %d is not supported", fam->fam_family); 198 } 199 return true; 200 } 201 202 static void 203 npfctl_build_vars(nc_ctx_t *nc, sa_family_t family, npfvar_t *vars, int opts) 204 { 205 const int type = npfvar_get_type(vars, 0); 206 size_t i; 207 208 npfctl_ncgen_group(nc); 209 for (i = 0; i < npfvar_get_count(vars); i++) { 210 void *data = npfvar_get_data(vars, type, i); 211 assert(data != NULL); 212 213 switch (type) { 214 case NPFVAR_FAM: { 215 fam_addr_mask_t *fam = data; 216 npfctl_build_fam(nc, family, fam, opts); 217 break; 218 } 219 case NPFVAR_PORT_RANGE: { 220 port_range_t *pr = data; 221 if (opts & NC_MATCH_TCP) { 222 npfctl_gennc_ports(nc, opts & ~NC_MATCH_UDP, 223 pr->pr_start, pr->pr_end); 224 } 225 if (opts & NC_MATCH_UDP) { 226 npfctl_gennc_ports(nc, opts & ~NC_MATCH_TCP, 227 pr->pr_start, pr->pr_end); 228 } 229 break; 230 } 231 case NPFVAR_TABLE: { 232 u_int tid = atoi(data); 233 npfctl_gennc_tbl(nc, opts, tid); 234 break; 235 } 236 default: 237 assert(false); 238 } 239 } 240 npfctl_ncgen_endgroup(nc); 241 } 242 243 static int 244 npfctl_build_proto(nc_ctx_t *nc, sa_family_t family, 245 const opt_proto_t *op, bool noaddrs, bool noports) 246 { 247 const npfvar_t *popts = op->op_opts; 248 const int proto = op->op_proto; 249 int pflag = 0; 250 251 switch (proto) { 252 case IPPROTO_TCP: 253 pflag = NC_MATCH_TCP; 254 if (!popts) { 255 break; 256 } 257 assert(npfvar_get_count(popts) == 2); 258 259 /* Build TCP flags block (optional). */ 260 uint8_t *tf, *tf_mask; 261 262 tf = npfvar_get_data(popts, NPFVAR_TCPFLAG, 0); 263 tf_mask = npfvar_get_data(popts, NPFVAR_TCPFLAG, 1); 264 npfctl_gennc_tcpfl(nc, *tf, *tf_mask); 265 noports = false; 266 break; 267 case IPPROTO_UDP: 268 pflag = NC_MATCH_UDP; 269 break; 270 case IPPROTO_ICMP: 271 /* 272 * Build ICMP block. 273 */ 274 if (!noports) { 275 goto invop; 276 } 277 assert(npfvar_get_count(popts) == 2); 278 279 int *icmp_type, *icmp_code; 280 icmp_type = npfvar_get_data(popts, NPFVAR_ICMP, 0); 281 icmp_code = npfvar_get_data(popts, NPFVAR_ICMP, 1); 282 npfctl_gennc_icmp(nc, *icmp_type, *icmp_code); 283 noports = false; 284 break; 285 case IPPROTO_ICMPV6: 286 /* 287 * Build ICMP block. 288 */ 289 if (!noports) { 290 goto invop; 291 } 292 assert(npfvar_get_count(popts) == 2); 293 294 int *icmp6_type, *icmp6_code; 295 icmp6_type = npfvar_get_data(popts, NPFVAR_ICMP6, 0); 296 icmp6_code = npfvar_get_data(popts, NPFVAR_ICMP6, 1); 297 npfctl_gennc_icmp6(nc, *icmp6_type, *icmp6_code); 298 noports = false; 299 break; 300 case -1: 301 pflag = NC_MATCH_TCP | NC_MATCH_UDP; 302 noports = false; 303 break; 304 default: 305 /* 306 * No filter options are supported for other protocols, 307 * only the IP addresses are allowed. 308 */ 309 if (noports) { 310 break; 311 } 312 invop: 313 yyerror("invalid filter options for protocol %d", proto); 314 } 315 316 /* 317 * Build the protocol block, unless other blocks will implicitly 318 * perform the family/protocol checks for us. 319 */ 320 if ((family != AF_UNSPEC && noaddrs) || (proto != -1 && noports)) { 321 uint8_t addrlen; 322 323 switch (family) { 324 case AF_INET: 325 addrlen = sizeof(struct in_addr); 326 break; 327 case AF_INET6: 328 addrlen = sizeof(struct in6_addr); 329 break; 330 default: 331 addrlen = 0; 332 } 333 npfctl_gennc_proto(nc, 334 noaddrs ? addrlen : 0, 335 noports ? proto : 0xff); 336 } 337 return pflag; 338 } 339 340 static bool 341 npfctl_build_ncode(nl_rule_t *rl, sa_family_t family, const opt_proto_t *op, 342 const filt_opts_t *fopts, bool invert) 343 { 344 const addr_port_t *apfrom = &fopts->fo_from; 345 const addr_port_t *apto = &fopts->fo_to; 346 const int proto = op->op_proto; 347 bool noaddrs, noports; 348 nc_ctx_t *nc; 349 void *code; 350 size_t len; 351 352 /* 353 * If none specified, no n-code. 354 */ 355 noaddrs = !apfrom->ap_netaddr && !apto->ap_netaddr; 356 noports = !apfrom->ap_portrange && !apto->ap_portrange; 357 if (family == AF_UNSPEC && proto == -1 && !op->op_opts && 358 noaddrs && noports) 359 return false; 360 361 int srcflag = NC_MATCH_SRC; 362 int dstflag = NC_MATCH_DST; 363 364 if (invert) { 365 srcflag = NC_MATCH_DST; 366 dstflag = NC_MATCH_SRC; 367 } 368 369 nc = npfctl_ncgen_create(); 370 371 /* Build layer 4 protocol blocks. */ 372 int pflag = npfctl_build_proto(nc, family, op, noaddrs, noports); 373 374 /* Build IP address blocks. */ 375 npfctl_build_vars(nc, family, apfrom->ap_netaddr, srcflag); 376 npfctl_build_vars(nc, family, apto->ap_netaddr, dstflag); 377 378 /* Build port-range blocks. */ 379 npfctl_build_vars(nc, family, apfrom->ap_portrange, srcflag | pflag); 380 npfctl_build_vars(nc, family, apto->ap_portrange, dstflag | pflag); 381 382 /* 383 * Complete n-code (destroys the context) and pass to the rule. 384 */ 385 code = npfctl_ncgen_complete(nc, &len); 386 if (npf_debug) { 387 extern char *yytext; 388 extern int yylineno; 389 390 printf("RULE AT LINE %d\n", yylineno - (int)(*yytext == '\n')); 391 npfctl_ncgen_print(code, len); 392 } 393 assert(code && len > 0); 394 395 if (npf_rule_setcode(rl, NPF_CODE_NC, code, len) == -1) { 396 errx(EXIT_FAILURE, "npf_rule_setcode failed"); 397 } 398 free(code); 399 return true; 400 } 401 402 static void 403 npfctl_build_rpcall(nl_rproc_t *rp, const char *name, npfvar_t *args) 404 { 405 npf_extmod_t *extmod; 406 nl_ext_t *extcall; 407 int error; 408 409 extmod = npf_extmod_get(name, &extcall); 410 if (extmod == NULL) { 411 yyerror("unknown rule procedure '%s'", name); 412 } 413 414 for (size_t i = 0; i < npfvar_get_count(args); i++) { 415 const char *param, *value; 416 proc_param_t *p; 417 418 p = npfvar_get_data(args, NPFVAR_PROC_PARAM, i); 419 param = p->pp_param; 420 value = p->pp_value; 421 422 error = npf_extmod_param(extmod, extcall, param, value); 423 switch (error) { 424 case EINVAL: 425 yyerror("invalid parameter '%s'", param); 426 default: 427 break; 428 } 429 } 430 error = npf_rproc_extcall(rp, extcall); 431 if (error) { 432 yyerror(error == EEXIST ? 433 "duplicate procedure call" : "unexpected error"); 434 } 435 } 436 437 /* 438 * npfctl_build_rproc: create and insert a rule procedure. 439 */ 440 void 441 npfctl_build_rproc(const char *name, npfvar_t *procs) 442 { 443 nl_rproc_t *rp; 444 size_t i; 445 446 rp = npf_rproc_create(name); 447 if (rp == NULL) { 448 errx(EXIT_FAILURE, "%s failed", __func__); 449 } 450 npf_rproc_insert(npf_conf, rp); 451 452 for (i = 0; i < npfvar_get_count(procs); i++) { 453 proc_call_t *pc = npfvar_get_data(procs, NPFVAR_PROC, i); 454 npfctl_build_rpcall(rp, pc->pc_name, pc->pc_opts); 455 } 456 } 457 458 void 459 npfctl_build_maprset(const char *name, int attr, u_int if_idx) 460 { 461 const int attr_di = (NPF_RULE_IN | NPF_RULE_OUT); 462 nl_rule_t *rl; 463 464 /* If no direction is not specified, then both. */ 465 if ((attr & attr_di) == 0) { 466 attr |= attr_di; 467 } 468 /* Allow only "in/out" attributes. */ 469 attr = NPF_RULE_GROUP | NPF_RULE_GROUP | (attr & attr_di); 470 rl = npf_rule_create(name, attr, if_idx); 471 npf_nat_insert(npf_conf, rl, NPF_PRI_LAST); 472 } 473 474 /* 475 * npfctl_build_group: create a group, insert into the global ruleset, 476 * update the current group pointer and increase the nesting level. 477 */ 478 void 479 npfctl_build_group(const char *name, int attr, u_int if_idx, bool def) 480 { 481 const int attr_di = (NPF_RULE_IN | NPF_RULE_OUT); 482 nl_rule_t *rl; 483 484 if (def || (attr & attr_di) == 0) { 485 attr |= attr_di; 486 } 487 488 rl = npf_rule_create(name, attr | NPF_RULE_GROUP, if_idx); 489 npf_rule_setprio(rl, NPF_PRI_LAST); 490 if (def) { 491 if (defgroup) { 492 yyerror("multiple default groups are not valid"); 493 } 494 if (rule_nesting_level) { 495 yyerror("default group can only be at the top level"); 496 } 497 defgroup = rl; 498 } else { 499 nl_rule_t *cg = current_group[rule_nesting_level]; 500 npf_rule_insert(npf_conf, cg, rl); 501 } 502 503 /* Set the current group and increase the nesting level. */ 504 if (rule_nesting_level >= MAX_RULE_NESTING) { 505 yyerror("rule nesting limit reached"); 506 } 507 current_group[++rule_nesting_level] = rl; 508 } 509 510 void 511 npfctl_build_group_end(void) 512 { 513 assert(rule_nesting_level > 0); 514 current_group[rule_nesting_level--] = NULL; 515 } 516 517 /* 518 * npfctl_build_rule: create a rule, build n-code from filter options, 519 * if any, and insert into the ruleset of current group, or set the rule. 520 */ 521 void 522 npfctl_build_rule(uint32_t attr, u_int if_idx, sa_family_t family, 523 const opt_proto_t *op, const filt_opts_t *fopts, const char *rproc) 524 { 525 nl_rule_t *rl; 526 527 attr |= (npf_conf ? 0 : NPF_RULE_DYNAMIC); 528 529 rl = npf_rule_create(NULL, attr, if_idx); 530 npfctl_build_ncode(rl, family, op, fopts, false); 531 if (rproc) { 532 npf_rule_setproc(rl, rproc); 533 } 534 535 if (npf_conf) { 536 nl_rule_t *cg = current_group[rule_nesting_level]; 537 538 if (rproc && !npf_rproc_exists_p(npf_conf, rproc)) { 539 yyerror("rule procedure '%s' is not defined", rproc); 540 } 541 assert(cg != NULL); 542 npf_rule_setprio(rl, NPF_PRI_LAST); 543 npf_rule_insert(npf_conf, cg, rl); 544 } else { 545 /* We have parsed a single rule - set it. */ 546 the_rule = rl; 547 } 548 } 549 550 /* 551 * npfctl_build_nat: create a single NAT policy of a specified 552 * type with a given filter options. 553 */ 554 static void 555 npfctl_build_nat(int type, u_int if_idx, sa_family_t family, 556 const addr_port_t *ap, const filt_opts_t *fopts, bool binat) 557 { 558 const opt_proto_t op = { .op_proto = -1, .op_opts = NULL }; 559 fam_addr_mask_t *am; 560 in_port_t port; 561 nl_nat_t *nat; 562 563 if (!ap->ap_netaddr) { 564 yyerror("%s network segment is not specified", 565 type == NPF_NATIN ? "inbound" : "outbound"); 566 } 567 am = npfctl_get_singlefam(ap->ap_netaddr); 568 if (am->fam_family != family) { 569 yyerror("IPv6 NAT is not supported"); 570 } 571 572 switch (type) { 573 case NPF_NATOUT: 574 /* 575 * Outbound NAT (or source NAT) policy, usually used for the 576 * traditional NAPT. If it is a half for bi-directional NAT, 577 * then no port translation with mapping. 578 */ 579 nat = npf_nat_create(NPF_NATOUT, !binat ? 580 (NPF_NAT_PORTS | NPF_NAT_PORTMAP) : 0, 581 if_idx, &am->fam_addr, am->fam_family, 0); 582 break; 583 case NPF_NATIN: 584 /* 585 * Inbound NAT (or destination NAT). Unless bi-NAT, a port 586 * must be specified, since it has to be redirection. 587 */ 588 port = 0; 589 if (!binat) { 590 if (!ap->ap_portrange) { 591 yyerror("inbound port is not specified"); 592 } 593 port = npfctl_get_singleport(ap->ap_portrange); 594 } 595 nat = npf_nat_create(NPF_NATIN, !binat ? NPF_NAT_PORTS : 0, 596 if_idx, &am->fam_addr, am->fam_family, port); 597 break; 598 default: 599 assert(false); 600 } 601 602 npfctl_build_ncode(nat, family, &op, fopts, false); 603 npf_nat_insert(npf_conf, nat, NPF_PRI_LAST); 604 } 605 606 /* 607 * npfctl_build_natseg: validate and create NAT policies. 608 */ 609 void 610 npfctl_build_natseg(int sd, int type, u_int if_idx, const addr_port_t *ap1, 611 const addr_port_t *ap2, const filt_opts_t *fopts) 612 { 613 sa_family_t af = AF_INET; 614 filt_opts_t imfopts; 615 bool binat; 616 617 if (sd == NPFCTL_NAT_STATIC) { 618 yyerror("static NAT is not yet supported"); 619 } 620 assert(sd == NPFCTL_NAT_DYNAMIC); 621 assert(if_idx != 0); 622 623 /* 624 * Bi-directional NAT is a combination of inbound NAT and outbound 625 * NAT policies. Note that the translation address is local IP and 626 * the filter criteria is inverted accordingly. 627 */ 628 binat = (NPF_NATIN | NPF_NATOUT) == type; 629 630 /* 631 * If the filter criteria is not specified explicitly, apply implicit 632 * filtering according to the given network segments. 633 * 634 * Note: filled below, depending on the type. 635 */ 636 if (__predict_true(!fopts)) { 637 fopts = &imfopts; 638 } 639 640 if (type & NPF_NATIN) { 641 memset(&imfopts, 0, sizeof(filt_opts_t)); 642 memcpy(&imfopts.fo_to, ap2, sizeof(addr_port_t)); 643 npfctl_build_nat(NPF_NATIN, if_idx, af, ap1, fopts, binat); 644 } 645 if (type & NPF_NATOUT) { 646 memset(&imfopts, 0, sizeof(filt_opts_t)); 647 memcpy(&imfopts.fo_from, ap1, sizeof(addr_port_t)); 648 npfctl_build_nat(NPF_NATOUT, if_idx, af, ap2, fopts, binat); 649 } 650 } 651 652 /* 653 * npfctl_fill_table: fill NPF table with entries from a specified file. 654 */ 655 static void 656 npfctl_fill_table(nl_table_t *tl, u_int type, const char *fname) 657 { 658 char *buf = NULL; 659 int l = 0; 660 FILE *fp; 661 size_t n; 662 663 fp = fopen(fname, "r"); 664 if (fp == NULL) { 665 err(EXIT_FAILURE, "open '%s'", fname); 666 } 667 while (l++, getline(&buf, &n, fp) != -1) { 668 fam_addr_mask_t fam; 669 int alen; 670 671 if (*buf == '\n' || *buf == '#') { 672 continue; 673 } 674 675 if (!npfctl_parse_cidr(buf, &fam, &alen)) { 676 errx(EXIT_FAILURE, 677 "%s:%d: invalid table entry", fname, l); 678 } 679 if (type == NPF_TABLE_HASH && fam.fam_mask != NPF_NO_NETMASK) { 680 errx(EXIT_FAILURE, 681 "%s:%d: mask used with the hash table", fname, l); 682 } 683 684 /* Create and add a table entry. */ 685 npf_table_add_entry(tl, fam.fam_family, 686 &fam.fam_addr, fam.fam_mask); 687 } 688 if (buf != NULL) { 689 free(buf); 690 } 691 } 692 693 /* 694 * npfctl_build_table: create an NPF table, add to the configuration and, 695 * if required, fill with contents from a file. 696 */ 697 void 698 npfctl_build_table(const char *tid, u_int type, const char *fname) 699 { 700 nl_table_t *tl; 701 u_int id; 702 703 id = atoi(tid); 704 tl = npf_table_create(id, type); 705 assert(tl != NULL); 706 707 if (npf_table_insert(npf_conf, tl)) { 708 errx(EXIT_FAILURE, "table '%d' is already defined\n", id); 709 } 710 711 if (fname) { 712 npfctl_fill_table(tl, type, fname); 713 } 714 } 715 716 /* 717 * npfctl_build_alg: create an NPF application level gatewayl and add it 718 * to the configuration. 719 */ 720 void 721 npfctl_build_alg(const char *al_name) 722 { 723 if (_npf_alg_load(npf_conf, al_name) != 0) { 724 errx(EXIT_FAILURE, "ALG '%s' already loaded", al_name); 725 } 726 } 727