1 /* $NetBSD: npf_data.c,v 1.6 2011/01/18 20:33:45 rmind Exp $ */ 2 3 /*- 4 * Copyright (c) 2009-2011 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 /* 30 * NPF proplib(9) dictionary producer. 31 * 32 * XXX: Needs some clean-up. 33 */ 34 35 #include <sys/cdefs.h> 36 __RCSID("$NetBSD: npf_data.c,v 1.6 2011/01/18 20:33:45 rmind Exp $"); 37 38 #include <sys/types.h> 39 #include <sys/socket.h> 40 #include <sys/ioctl.h> 41 #include <net/if.h> 42 #include <netinet/tcp.h> 43 44 #include <arpa/inet.h> 45 #include <prop/proplib.h> 46 47 #include <stdlib.h> 48 #include <string.h> 49 #include <unistd.h> 50 #include <ctype.h> 51 #include <err.h> 52 #include <ifaddrs.h> 53 #include <netdb.h> 54 #include <assert.h> 55 56 #include "npfctl.h" 57 58 static struct ifaddrs * ifs_list = NULL; 59 60 static prop_dictionary_t npf_dict, settings_dict; 61 static prop_array_t nat_arr, tables_arr, rproc_arr, rules_arr; 62 63 static pri_t gr_prio_counter = 1; 64 static pri_t rl_prio_counter = 1; 65 static pri_t nat_prio_counter = 1; 66 static u_int rproc_id_counter = 1; 67 68 void 69 npfctl_init_data(void) 70 { 71 72 if (getifaddrs(&ifs_list) == -1) 73 err(EXIT_FAILURE, "getifaddrs"); 74 75 npf_dict = prop_dictionary_create(); 76 77 nat_arr = prop_array_create(); 78 prop_dictionary_set(npf_dict, "translation", nat_arr); 79 80 settings_dict = prop_dictionary_create(); 81 prop_dictionary_set(npf_dict, "settings", settings_dict); 82 83 tables_arr = prop_array_create(); 84 prop_dictionary_set(npf_dict, "tables", tables_arr); 85 86 rproc_arr = prop_array_create(); 87 prop_dictionary_set(npf_dict, "rprocs", rproc_arr); 88 89 rules_arr = prop_array_create(); 90 prop_dictionary_set(npf_dict, "rules", rules_arr); 91 } 92 93 int 94 npfctl_ioctl_send(int fd) 95 { 96 int ret = 0, errval; 97 98 #ifdef _NPF_TESTING 99 prop_dictionary_externalize_to_file(npf_dict, "./npf.plist"); 100 #else 101 errval = prop_dictionary_send_ioctl(npf_dict, fd, IOC_NPF_RELOAD); 102 if (errval) { 103 errx(EXIT_FAILURE, "npfctl_ioctl_send: %s\n", strerror(errval)); 104 } 105 #endif 106 prop_object_release(npf_dict); 107 return ret; 108 } 109 110 int 111 npfctl_ioctl_flushse(int fd) 112 { 113 prop_dictionary_t sesdict; 114 prop_array_t selist; 115 int errval; 116 117 sesdict = prop_dictionary_create(); 118 selist = prop_array_create(); 119 prop_dictionary_set(sesdict, "session-list", selist); 120 errval = prop_dictionary_send_ioctl(sesdict, fd, IOC_NPF_SESSIONS_LOAD); 121 if (errval) { 122 errx(EXIT_FAILURE, "npfctl_ioctl_flushse: %s\n", 123 strerror(errval)); 124 } 125 prop_object_release(sesdict); 126 return errval; 127 } 128 129 int 130 npfctl_ioctl_sendse(int fd) 131 { 132 prop_dictionary_t sesdict; 133 int error; 134 135 sesdict = prop_dictionary_internalize_from_file(NPF_SESSDB_PATH); 136 if (sesdict == NULL) { 137 errx(EXIT_FAILURE, "npfctl: no sessions saved " 138 "('%s' does not exist)", NPF_SESSDB_PATH); 139 } 140 error = prop_dictionary_send_ioctl(sesdict, fd, IOC_NPF_SESSIONS_LOAD); 141 prop_object_release(sesdict); 142 if (error) { 143 err(EXIT_FAILURE, "npfctl_ioctl_sendse"); 144 } 145 return 0; 146 } 147 148 int 149 npfctl_ioctl_recvse(int fd) 150 { 151 prop_dictionary_t sesdict; 152 int error; 153 154 error = prop_dictionary_recv_ioctl(fd, IOC_NPF_SESSIONS_SAVE, &sesdict); 155 if (error) { 156 err(EXIT_FAILURE, "prop_array_recv_ioctl"); 157 } 158 if (!prop_dictionary_externalize_to_file(sesdict, NPF_SESSDB_PATH)) { 159 errx(EXIT_FAILURE, "could not save to '%s'", NPF_SESSDB_PATH); 160 } 161 prop_object_release(sesdict); 162 return 0; 163 } 164 165 /* 166 * Helper routines: 167 * 168 * npfctl_getif() - get interface addresses and index number from name. 169 * npfctl_parse_v4mask() - parse address/mask integers from CIDR block. 170 * npfctl_parse_port() - parse port number (which may be a service name). 171 * npfctl_parse_tcpfl() - parse TCP flags. 172 */ 173 174 struct ifaddrs * 175 npfctl_getif(char *ifname, unsigned int *if_idx, bool reqaddr) 176 { 177 struct ifaddrs *ifent; 178 struct sockaddr_in *sin; 179 180 for (ifent = ifs_list; ifent != NULL; ifent = ifent->ifa_next) { 181 sin = (struct sockaddr_in *)ifent->ifa_addr; 182 if (sin->sin_family != AF_INET && reqaddr) 183 continue; 184 if (strcmp(ifent->ifa_name, ifname) == 0) 185 break; 186 } 187 if (ifent) { 188 *if_idx = if_nametoindex(ifname); 189 } 190 return ifent; 191 } 192 193 bool 194 npfctl_parse_v4mask(char *ostr, in_addr_t *addr, in_addr_t *mask) 195 { 196 char *str = xstrdup(ostr); 197 char *p = strchr(str, '/'); 198 u_int bits; 199 bool ret; 200 201 /* In network byte order. */ 202 if (p) { 203 *p++ = '\0'; 204 bits = (u_int)atoi(p); 205 *mask = bits ? htonl(0xffffffff << (32 - bits)) : 0; 206 } else { 207 *mask = 0xffffffff; 208 } 209 ret = inet_aton(str, (struct in_addr *)addr) != 0; 210 free(str); 211 return ret; 212 } 213 214 static bool 215 npfctl_parse_port(char *ostr, bool *range, in_port_t *fport, in_port_t *tport) 216 { 217 char *str = xstrdup(ostr), *sep; 218 219 *range = false; 220 if ((sep = strchr(str, ':')) != NULL) { 221 /* Port range (only numeric). */ 222 *range = true; 223 *sep = '\0'; 224 225 } else if (isalpha((unsigned char)*str)) { 226 struct servent *se; 227 228 se = getservbyname(str, NULL); 229 if (se == NULL) { 230 free(str); 231 return false; 232 } 233 *fport = se->s_port; 234 } else { 235 *fport = htons(atoi(str)); 236 } 237 *tport = sep ? htons(atoi(sep + 1)) : *fport; 238 free(str); 239 return true; 240 } 241 242 static void 243 npfctl_parse_cidr(char *str, in_addr_t *addr, in_addr_t *mask) 244 { 245 246 if (strcmp(str, "any") == 0) { 247 *addr = 0x0; 248 *mask = 0x0; 249 250 } else if (isalpha((unsigned char)*str)) { 251 struct ifaddrs *ifa; 252 struct sockaddr_in *sin; 253 u_int idx; 254 255 if ((ifa = npfctl_getif(str, &idx, true)) == NULL) { 256 errx(EXIT_FAILURE, "invalid interface '%s'", str); 257 } 258 /* Interface address. */ 259 sin = (struct sockaddr_in *)ifa->ifa_addr; 260 *addr = sin->sin_addr.s_addr; 261 *mask = 0xffffffff; 262 263 } else if (!npfctl_parse_v4mask(str, addr, mask)) { 264 errx(EXIT_FAILURE, "invalid CIDR '%s'\n", str); 265 } 266 } 267 268 static bool 269 npfctl_parse_tcpfl(char *s, uint8_t *tfl, uint8_t *tfl_mask) 270 { 271 uint8_t tcpfl = 0; 272 bool mask = false; 273 274 while (*s) { 275 switch (*s) { 276 case 'F': tcpfl |= TH_FIN; break; 277 case 'S': tcpfl |= TH_SYN; break; 278 case 'R': tcpfl |= TH_RST; break; 279 case 'P': tcpfl |= TH_PUSH; break; 280 case 'A': tcpfl |= TH_ACK; break; 281 case 'U': tcpfl |= TH_URG; break; 282 case 'E': tcpfl |= TH_ECE; break; 283 case 'W': tcpfl |= TH_CWR; break; 284 case '/': 285 *s = '\0'; 286 *tfl = tcpfl; 287 tcpfl = 0; 288 mask = true; 289 break; 290 default: 291 return false; 292 } 293 s++; 294 } 295 if (!mask) { 296 *tfl = tcpfl; 297 } 298 *tfl_mask = tcpfl; 299 return true; 300 } 301 302 /* 303 * NPF table creation and construction routines. 304 */ 305 306 prop_dictionary_t 307 npfctl_lookup_table(char *tidstr) 308 { 309 prop_dictionary_t tl; 310 prop_object_iterator_t it; 311 prop_object_t obj; 312 u_int tid; 313 314 tid = atoi(tidstr); 315 it = prop_array_iterator(tables_arr); 316 while ((tl = prop_object_iterator_next(it)) != NULL) { 317 obj = prop_dictionary_get(tl, "id"); 318 if (tid == prop_number_integer_value(obj)) 319 break; 320 } 321 return tl; 322 } 323 324 prop_dictionary_t 325 npfctl_construct_table(int id, int type) 326 { 327 prop_dictionary_t tl; 328 329 tl = prop_dictionary_create(); 330 /* TODO: 1. check ID range 2. check if not a duplicate */ 331 prop_dictionary_set(tl, "id", prop_number_create_integer(id)); 332 prop_dictionary_set(tl, "type", prop_number_create_integer(type)); 333 prop_dictionary_set(tl, "entries", prop_array_create()); 334 prop_array_add(tables_arr, tl); 335 return tl; 336 } 337 338 void 339 npfctl_fill_table(prop_dictionary_t tl, char *fname) 340 { 341 prop_dictionary_t entdict; 342 prop_array_t tblents; 343 char *buf; 344 FILE *fp; 345 size_t n; 346 int l; 347 348 tblents = prop_dictionary_get(tl, "entries"); 349 assert(tblents != NULL); 350 351 fp = fopen(fname, "r"); 352 if (fp == NULL) { 353 err(EXIT_FAILURE, "open '%s'", fname); 354 } 355 l = 1; 356 buf = NULL; 357 while (getline(&buf, &n, fp) != -1) { 358 in_addr_t addr, mask; 359 360 if (*buf == '\n' || *buf == '#') 361 continue; 362 363 /* IPv4 CIDR: a.b.c.d/mask */ 364 if (!npfctl_parse_v4mask(buf, &addr, &mask)) 365 errx(EXIT_FAILURE, "invalid table entry at line %d", l); 366 367 /* Create and add table entry. */ 368 entdict = prop_dictionary_create(); 369 prop_dictionary_set(entdict, "addr", 370 prop_number_create_integer(addr)); 371 prop_dictionary_set(entdict, "mask", 372 prop_number_create_integer(mask)); 373 prop_array_add(tblents, entdict); 374 l++; 375 } 376 if (buf != NULL) { 377 free(buf); 378 } 379 } 380 381 /* 382 * npfctl_mk_rule: create a rule (or group) dictionary. 383 * 384 * Note: group is a rule containing subrules. It has no n-code, however. 385 */ 386 prop_dictionary_t 387 npfctl_mk_rule(bool group, prop_dictionary_t parent) 388 { 389 prop_dictionary_t rl; 390 prop_array_t subrl, rlset; 391 pri_t pri; 392 393 rl = prop_dictionary_create(); 394 if (group) { 395 subrl = prop_array_create(); 396 prop_dictionary_set(rl, "subrules", subrl); 397 /* Give new priority, reset rule priority counter. */ 398 pri = gr_prio_counter++; 399 rl_prio_counter = 1; 400 } else { 401 pri = rl_prio_counter++; 402 } 403 prop_dictionary_set(rl, "priority", prop_number_create_integer(pri)); 404 405 if (parent) { 406 rlset = prop_dictionary_get(parent, "subrules"); 407 assert(rlset != NULL); 408 } else { 409 rlset = rules_arr; 410 } 411 prop_array_add(rlset, rl); 412 return rl; 413 } 414 415 void 416 npfctl_rule_setattr(prop_dictionary_t rl, int attr, u_int iface) 417 { 418 prop_number_t attrnum, ifnum; 419 420 attrnum = prop_number_create_integer(attr); 421 prop_dictionary_set(rl, "attributes", attrnum); 422 if (iface) { 423 ifnum = prop_number_create_integer(iface); 424 prop_dictionary_set(rl, "interface", ifnum); 425 } 426 } 427 428 /* 429 * Main rule generation routines. 430 */ 431 432 static void 433 npfctl_rulenc_v4cidr(void **nc, int nblocks[], var_t *dat, bool sd) 434 { 435 element_t *el = dat->v_elements; 436 int foff; 437 438 /* If table, generate a single table matching block. */ 439 if (dat->v_type == VAR_TABLE) { 440 u_int tid = atoi(el->e_data); 441 442 nblocks[0]--; 443 foff = npfctl_failure_offset(nblocks); 444 npfctl_gennc_tbl(nc, foff, tid, sd); 445 return; 446 } 447 448 /* Generate v4 CIDR matching blocks. */ 449 for (el = dat->v_elements; el != NULL; el = el->e_next) { 450 in_addr_t addr, mask; 451 452 npfctl_parse_cidr(el->e_data, &addr, &mask); 453 454 nblocks[1]--; 455 foff = npfctl_failure_offset(nblocks); 456 npfctl_gennc_v4cidr(nc, foff, addr, mask, sd); 457 } 458 } 459 460 static void 461 npfctl_rulenc_ports(void **nc, int nblocks[], var_t *dat, bool tcpudp, 462 bool both, bool sd) 463 { 464 element_t *el = dat->v_elements; 465 int foff; 466 467 assert(dat->v_type != VAR_TABLE); 468 469 /* Generate TCP/UDP port matching blocks. */ 470 for (el = dat->v_elements; el != NULL; el = el->e_next) { 471 in_port_t fport, tport; 472 bool range; 473 474 if (!npfctl_parse_port(el->e_data, &range, &fport, &tport)) { 475 errx(EXIT_FAILURE, "invalid service '%s'", el->e_data); 476 } 477 nblocks[0]--; 478 foff = both ? 0 : npfctl_failure_offset(nblocks); 479 npfctl_gennc_ports(nc, foff, fport, tport, tcpudp, sd); 480 } 481 } 482 483 static void 484 npfctl_rulenc_block(void **nc, int nblocks[], var_t *cidr, var_t *ports, 485 bool both, bool tcpudp, bool sd) 486 { 487 488 npfctl_rulenc_v4cidr(nc, nblocks, cidr, sd); 489 if (ports == NULL) { 490 return; 491 } 492 npfctl_rulenc_ports(nc, nblocks, ports, tcpudp, both, sd); 493 if (!both) { 494 return; 495 } 496 npfctl_rulenc_ports(nc, nblocks, ports, !tcpudp, false, sd); 497 } 498 499 void 500 npfctl_rule_protodata(prop_dictionary_t rl, char *proto, char *tcp_flags, 501 int icmp_type, int icmp_code, 502 var_t *from, var_t *fports, var_t *to, var_t *tports) 503 { 504 prop_data_t ncdata; 505 bool icmp, tcpudp, both; 506 int foff, nblocks[3] = { 0, 0, 0 }; 507 void *ncptr, *nc; 508 size_t sz; 509 510 /* 511 * Default: both TCP and UDP. 512 */ 513 icmp = false; 514 tcpudp = true; 515 if (proto == NULL) { 516 both = true; 517 goto skip_proto; 518 } 519 both = false; 520 521 if (strcmp(proto, "icmp") == 0) { 522 /* ICMP case. */ 523 fports = NULL; 524 tports = NULL; 525 icmp = true; 526 527 } else if (strcmp(proto, "tcp") == 0) { 528 /* Just TCP. */ 529 tcpudp = true; 530 531 } else if (strcmp(proto, "udp") == 0) { 532 /* Just UDP. */ 533 tcpudp = false; 534 535 } else { 536 /* Default. */ 537 } 538 skip_proto: 539 if (icmp || icmp_type != -1) { 540 assert(tcp_flags == NULL); 541 icmp = true; 542 nblocks[2] += 1; 543 } 544 if (tcpudp && tcp_flags) { 545 assert(icmp_type == -1 && icmp_code == -1); 546 nblocks[2] += 1; 547 } 548 549 /* Calculate how blocks to determince n-code. */ 550 if (from && from->v_count) { 551 if (from->v_type == VAR_TABLE) 552 nblocks[0] += 1; 553 else 554 nblocks[1] += from->v_count; 555 if (fports && fports->v_count) 556 nblocks[0] += fports->v_count * (both ? 2 : 1); 557 } 558 if (to && to->v_count) { 559 if (to->v_type == VAR_TABLE) 560 nblocks[0] += 1; 561 else 562 nblocks[1] += to->v_count; 563 if (tports && tports->v_count) 564 nblocks[0] += tports->v_count * (both ? 2 : 1); 565 } 566 567 /* Any n-code to generate? */ 568 if (!icmp && (nblocks[0] + nblocks[1] + nblocks[2]) == 0) { 569 /* Done, if none. */ 570 return; 571 } 572 573 /* Allocate memory for the n-code. */ 574 sz = npfctl_calc_ncsize(nblocks); 575 ncptr = malloc(sz); 576 if (ncptr == NULL) { 577 perror("malloc"); 578 exit(EXIT_FAILURE); 579 } 580 nc = ncptr; 581 582 /* 583 * Generate v4 CIDR matching blocks and TCP/UDP port matching. 584 */ 585 if (from) { 586 npfctl_rulenc_block(&nc, nblocks, from, fports, 587 both, tcpudp, true); 588 } 589 if (to) { 590 npfctl_rulenc_block(&nc, nblocks, to, tports, 591 both, tcpudp, false); 592 } 593 594 if (icmp) { 595 /* 596 * ICMP case. 597 */ 598 nblocks[2]--; 599 foff = npfctl_failure_offset(nblocks); 600 npfctl_gennc_icmp(&nc, foff, icmp_type, icmp_code); 601 602 } else if (tcpudp && tcp_flags) { 603 /* 604 * TCP case, flags. 605 */ 606 uint8_t tfl = 0, tfl_mask; 607 608 nblocks[2]--; 609 foff = npfctl_failure_offset(nblocks); 610 if (!npfctl_parse_tcpfl(tcp_flags, &tfl, &tfl_mask)) { 611 errx(EXIT_FAILURE, "invalid TCP flags '%s'", tcp_flags); 612 } 613 npfctl_gennc_tcpfl(&nc, foff, tfl, tfl_mask); 614 } 615 npfctl_gennc_complete(&nc); 616 617 if ((uintptr_t)nc - (uintptr_t)ncptr != sz) { 618 errx(EXIT_FAILURE, "n-code size got wrong (%tu != %zu)", 619 (uintptr_t)nc - (uintptr_t)ncptr, sz); 620 } 621 622 #ifdef DEBUG 623 uint32_t *op = ncptr; 624 size_t n = sz; 625 do { 626 DPRINTF(("\t> |0x%02x|\n", (u_int)*op)); 627 op++; 628 n -= sizeof(*op); 629 } while (n); 630 #endif 631 632 /* Create a final memory block of data, ready to send. */ 633 ncdata = prop_data_create_data(ncptr, sz); 634 if (ncdata == NULL) { 635 perror("prop_data_create_data"); 636 exit(EXIT_FAILURE); 637 } 638 prop_dictionary_set(rl, "ncode", ncdata); 639 free(ncptr); 640 } 641 642 /* 643 * Rule procedure construction routines. 644 */ 645 646 prop_dictionary_t 647 npfctl_mk_rproc(void) 648 { 649 prop_dictionary_t rp; 650 651 rp = prop_dictionary_create(); 652 prop_dictionary_set(rp, "id", 653 prop_number_create_unsigned_integer(rproc_id_counter++)); 654 prop_array_add(rproc_arr, rp); 655 return rp; 656 } 657 658 bool 659 npfctl_find_rproc(prop_dictionary_t rl, char *name) 660 { 661 prop_dictionary_t rp; 662 prop_object_iterator_t it; 663 prop_object_t obj; 664 665 it = prop_array_iterator(rproc_arr); 666 while ((rp = prop_object_iterator_next(it)) != NULL) { 667 obj = prop_dictionary_get(rp, "name"); 668 if (strcmp(prop_string_cstring(obj), name) == 0) 669 break; 670 } 671 if (rp == NULL) { 672 return false; 673 } 674 prop_dictionary_set(rl, "rproc-id", prop_dictionary_get(rp, "id")); 675 return true; 676 } 677 678 /* 679 * NAT policy construction routines. 680 */ 681 682 prop_dictionary_t 683 npfctl_mk_nat(void) 684 { 685 prop_dictionary_t rl; 686 pri_t pri; 687 688 /* NAT policy is rule with extra info. */ 689 rl = prop_dictionary_create(); 690 pri = nat_prio_counter++; 691 prop_dictionary_set(rl, "priority", prop_number_create_integer(pri)); 692 prop_array_add(nat_arr, rl); 693 return rl; 694 } 695 696 void 697 npfctl_nat_setup(prop_dictionary_t rl, int type, int flags, 698 u_int iface, char *taddr, char *rport) 699 { 700 int attr = NPF_RULE_PASS | NPF_RULE_FINAL; 701 in_addr_t addr, _dummy; 702 prop_data_t addrdat; 703 704 /* Translation type and flags. */ 705 prop_dictionary_set(rl, "type", 706 prop_number_create_integer(type)); 707 prop_dictionary_set(rl, "flags", 708 prop_number_create_integer(flags)); 709 710 /* Interface and attributes. */ 711 attr |= (type == NPF_NATOUT) ? NPF_RULE_OUT : NPF_RULE_IN; 712 npfctl_rule_setattr(rl, attr, iface); 713 714 /* Translation IP. */ 715 npfctl_parse_cidr(taddr, &addr, &_dummy); 716 addrdat = prop_data_create_data(&addr, sizeof(in_addr_t)); 717 if (addrdat == NULL) { 718 err(EXIT_FAILURE, "prop_data_create_data"); 719 } 720 prop_dictionary_set(rl, "translation-ip", addrdat); 721 722 /* Translation port (for redirect case). */ 723 if (rport) { 724 in_port_t port; 725 bool range; 726 727 if (!npfctl_parse_port(rport, &range, &port, &port)) { 728 errx(EXIT_FAILURE, "invalid service '%s'", rport); 729 } 730 if (range) { 731 errx(EXIT_FAILURE, "range is not supported for 'rdr'"); 732 } 733 prop_dictionary_set(rl, "translation-port", 734 prop_number_create_integer(port)); 735 } 736 } 737