1 const fs = require("fs"); 2 const uci = require("uci"); 3 const ubus = require("ubus"); 4 5 const STATEFILE = "/var/run/fw4.state"; 6 7 const PARSE_LIST = 0x01; 8 const FLATTEN_LIST = 0x02; 9 const NO_INVERT = 0x04; 10 const UNSUPPORTED = 0x08; 11 const REQUIRED = 0x10; 12 const DEPRECATED = 0x20; 13 14 const ipv4_icmptypes = { 15 "any": [ 0xFF, 0, 0xFF ], 16 "echo-reply": [ 0, 0, 0xFF ], 17 "pong": [ 0, 0, 0xFF ], /* Alias */ 18 19 "destination-unreachable": [ 3, 0, 0xFF ], 20 "network-unreachable": [ 3, 0, 0 ], 21 "host-unreachable": [ 3, 1, 1 ], 22 "protocol-unreachable": [ 3, 2, 2 ], 23 "port-unreachable": [ 3, 3, 3 ], 24 "fragmentation-needed": [ 3, 4, 4 ], 25 "source-route-failed": [ 3, 5, 5 ], 26 "network-unknown": [ 3, 6, 6 ], 27 "host-unknown": [ 3, 7, 7 ], 28 "network-prohibited": [ 3, 9, 9 ], 29 "host-prohibited": [ 3, 10, 10 ], 30 "TOS-network-unreachable": [ 3, 11, 11 ], 31 "TOS-host-unreachable": [ 3, 12, 12 ], 32 "communication-prohibited": [ 3, 13, 13 ], 33 "host-precedence-violation": [ 3, 14, 14 ], 34 "precedence-cutoff": [ 3, 15, 15 ], 35 36 "source-quench": [ 4, 0, 0xFF ], 37 38 "redirect": [ 5, 0, 0xFF ], 39 "network-redirect": [ 5, 0, 0 ], 40 "host-redirect": [ 5, 1, 1 ], 41 "TOS-network-redirect": [ 5, 2, 2 ], 42 "TOS-host-redirect": [ 5, 3, 3 ], 43 44 "echo-request": [ 8, 0, 0xFF ], 45 "ping": [ 8, 0, 0xFF ], /* Alias */ 46 47 "router-advertisement": [ 9, 0, 0xFF ], 48 49 "router-solicitation": [ 10, 0, 0xFF ], 50 51 "time-exceeded": [ 11, 0, 0xFF ], 52 "ttl-exceeded": [ 11, 0, 0xFF ], /* Alias */ 53 "ttl-zero-during-transit": [ 11, 0, 0 ], 54 "ttl-zero-during-reassembly": [ 11, 1, 1 ], 55 56 "parameter-problem": [ 12, 0, 0xFF ], 57 "ip-header-bad": [ 12, 0, 0 ], 58 "required-option-missing": [ 12, 1, 1 ], 59 60 "timestamp-request": [ 13, 0, 0xFF ], 61 62 "timestamp-reply": [ 14, 0, 0xFF ], 63 64 "address-mask-request": [ 17, 0, 0xFF ], 65 66 "address-mask-reply": [ 18, 0, 0xFF ] 67 }; 68 69 const ipv6_icmptypes = { 70 /* "name": [ type-code, code-min, code-max ] */ 71 "destination-unreachable": [ 1, 0, 0xFF ], 72 "no-route": [ 1, 0, 0 ], 73 "communication-prohibited": [ 1, 1, 1 ], 74 "address-unreachable": [ 1, 3, 3 ], 75 "port-unreachable": [ 1, 4, 4 ], 76 77 "packet-too-big": [ 2, 0, 0xFF ], 78 79 "time-exceeded": [ 3, 0, 0xFF ], 80 "ttl-exceeded": [ 3, 0, 0xFF ], /* Alias */ 81 "ttl-zero-during-transit": [ 3, 0, 0 ], 82 "ttl-zero-during-reassembly": [ 3, 1, 1 ], 83 84 "parameter-problem": [ 4, 0, 0xFF ], 85 "bad-header": [ 4, 0, 0 ], 86 "unknown-header-type": [ 4, 1, 1 ], 87 "unknown-option": [ 4, 2, 2 ], 88 89 "echo-request": [ 128, 0, 0xFF ], 90 "ping": [ 128, 0, 0xFF ], /* Alias */ 91 92 "echo-reply": [ 129, 0, 0xFF ], 93 "pong": [ 129, 0, 0xFF ], /* Alias */ 94 95 /* only code zero (0) */ 96 "multicast-listener-query": [ 130, 0, 0xFF ], 97 "multicast-listener-report": [ 131, 0, 0xFF ], 98 "multicast-listener-done": [ 132, 0, 0xFF ], 99 100 "router-solicitation": [ 133, 0, 0xFF ], 101 102 "router-advertisement": [ 134, 0, 0xFF ], 103 104 "neighbour-solicitation": [ 135, 0, 0xFF ], 105 "neighbor-solicitation": [ 135, 0, 0xFF ], /* Alias */ 106 107 "neighbour-advertisement": [ 136, 0, 0xFF ], 108 "neighbor-advertisement": [ 136, 0, 0xFF ], /* Alias */ 109 110 "redirect": [ 137, 0, 0xFF ], 111 112 /* codes 0, 1, 255 */ 113 "router-renumbering": [ 138, 0, 0xFF ], 114 115 /* codes 0, 1, 2 */ 116 "node-info-query": [ 139, 0, 0xFF ], 117 /* codes 0, 1, 2 */ 118 "node-info-response": [ 140, 0, 0xFF ], 119 120 "inverse-neighbour-discovery-solicitation-message": [ 141, 0, 0xFF ], 121 "inverse-neighbor-discovery-solicitation-message": [ 141, 0, 0xFF ], /* Alias */ 122 123 "inverse-neighbour-discovery-advertisement-message": [ 142, 0, 0xFF ], 124 "inverse-neighbor-discovery-advertisement-message": [ 142, 0, 0xFF ], /* Alias */ 125 126 "v2-multicast-listener-report": [ 143, 0, 0xFF ], 127 128 "home-agent-address-discovery-request-message": [ 144, 0, 0xFF ], 129 "home-agent-address-discovery-reply-message": [ 145, 0, 0xFF ], 130 131 "mobile-prefix-solicitation": [ 146, 0, 0xFF ], 132 "mobile-prefix-advertisement": [ 147, 0, 0xFF ], 133 134 /* SEND */ 135 "certification-path-solicitation-message": [ 148, 0, 0xFF ], 136 "certification-path-advertisement-message": [ 149, 0, 0xFF ], 137 138 "seamoby-protocol-message": [ 150, 0, 0xFF ], 139 140 /* IPv6 multicast: FF02:0:0:0:0:0:0:6A, no code fields */ 141 "multicast-router-advertisement": [ 151, 0, 0xFF ], 142 "multicast-router-solicitation": [ 152, 0, 0xFF ], 143 "multicast-router-termination": [ 153, 0, 0xFF ], 144 145 /* codes 0-5, 6-255 */ 146 "fmipv6-message": [ 154, 0, 0xFF ], 147 148 "rpl-control-message": [ 155, 0, 0xFF ], 149 150 "ilnpv6-locator-update-message": [ 156, 0, 0xFF ], 151 152 /* codes 0-4, 5-15 */ 153 "duplicate-address-request": [ 157, 0, 0xFF ], 154 /* codes 0-4, 5-15 */ 155 "duplicate-address-confirmation": [ 158, 0, 0xFF ], 156 157 /* Multicast Protocol for Low-Power */ 158 "mpl-control-message": [ 159, 0, 0xFF ], 159 160 /* codes 0, 1-255 */ 161 "extended-echo-request": [ 160, 0, 0xFF ], 162 "extended-ping": [ 160, 0, 0xFF ], /* Alias */ 163 164 /* codes 0-4, 5-255 */ 165 "extended-echo-reply": [ 161, 0, 0xFF ], 166 "extended-pong": [ 161, 0, 0xFF ] /* Alias */ 167 }; 168 169 const dscp_classes = { 170 "CS0": 0x00, 171 "CS1": 0x08, 172 "CS2": 0x10, 173 "CS3": 0x18, 174 "CS4": 0x20, 175 "CS5": 0x28, 176 "CS6": 0x30, 177 "CS7": 0x38, 178 "BE": 0x00, 179 "LE": 0x01, 180 "AF11": 0x0a, 181 "AF12": 0x0c, 182 "AF13": 0x0e, 183 "AF21": 0x12, 184 "AF22": 0x14, 185 "AF23": 0x16, 186 "AF31": 0x1a, 187 "AF32": 0x1c, 188 "AF33": 0x1e, 189 "AF41": 0x22, 190 "AF42": 0x24, 191 "AF43": 0x26, 192 "EF": 0x2e 193 }; 194 195 function to_mask(bits, v6) { 196 let m = [], n = false; 197 198 if (bits < 0) { 199 n = true; 200 bits = -bits; 201 } 202 203 if (bits > (v6 ? 128 : 32)) 204 return null; 205 206 for (let i = 0; i < (v6 ? 16 : 4); i++) { 207 let b = (bits < 8) ? bits : 8; 208 m[i] = (n ? ~(0xff << (8 - b)) : (0xff << (8 - b))) & 0xff; 209 bits -= b; 210 } 211 212 return arrtoip(m); 213 } 214 215 function to_bits(mask) { 216 let a = iptoarr(mask); 217 218 if (!a) 219 return null; 220 221 let bits = 0; 222 223 for (let i = 0, z = false; i < length(a); i++) { 224 z ||= !a[i]; 225 226 while (!z && (a[i] & 0x80)) { 227 a[i] = (a[i] << 1) & 0xff; 228 bits++; 229 } 230 231 if (a[i]) 232 return null; 233 } 234 235 return bits; 236 } 237 238 function apply_mask(addr, mask) { 239 let a = iptoarr(addr); 240 241 if (!a) 242 return null; 243 244 if (type(mask) == "int") { 245 for (let i = 0; i < length(a); i++) { 246 let b = (mask < 8) ? mask : 8; 247 a[i] &= (0xff << (8 - b)) & 0xff; 248 mask -= b; 249 } 250 } 251 else { 252 let m = iptoarr(mask); 253 254 if (!m || length(a) != length(m)) 255 return null; 256 257 for (let i = 0; i < length(a); i++) 258 a[i] &= m[i]; 259 } 260 261 return arrtoip(a); 262 } 263 264 function to_array(x) { 265 if (type(x) == "array") 266 return x; 267 268 if (x == null) 269 return []; 270 271 if (type(x) == "object") 272 return [ x ]; 273 274 x = trim("" + x); 275 276 return (x == "") ? [] : split(x, /[ \t]+/); 277 } 278 279 function filter_pos(x) { 280 let rv = filter(x, e => !e.invert); 281 return length(rv) ? rv : null; 282 } 283 284 function filter_neg(x) { 285 let rv = filter(x, e => e.invert); 286 return length(rv) ? rv : null; 287 } 288 289 function null_if_empty(x) { 290 return length(x) ? x : null; 291 } 292 293 function subnets_split_af(x) { 294 let rv = {}; 295 296 for (let ag in to_array(x)) { 297 for (let a in filter(ag.addrs, a => (a.family == 4))) 298 push(rv[0] ||= [], { ...a, invert: ag.invert }); 299 300 for (let a in filter(ag.addrs, a => (a.family == 6))) 301 push(rv[1] ||= [], { ...a, invert: ag.invert }); 302 } 303 304 if (rv[0] || rv[1]) 305 rv.family = (!rv[0] ^ !rv[1]) ? (rv[0] ? 4 : 6) : 0; 306 307 return rv; 308 } 309 310 function subnets_group_by_masking(x) { 311 let groups = [], plain = [], nc = [], invert_plain = [], invert_masked = []; 312 313 for (let a in to_array(x)) { 314 if (a.bits == -1 && !a.invert) 315 push(nc, a); 316 else if (!a.invert) 317 push(plain, a); 318 else if (a.bits == -1) 319 push(invert_masked, a); 320 else 321 push(invert_plain, a); 322 } 323 324 for (let a in nc) 325 push(groups, [ null, null_if_empty(invert_plain), [ a, ...invert_masked ] ]); 326 327 if (length(plain)) { 328 push(groups, [ 329 plain, 330 null_if_empty(invert_plain), 331 null_if_empty(invert_masked) 332 ]); 333 } 334 else if (!length(groups)) { 335 push(groups, [ 336 null, 337 null_if_empty(invert_plain), 338 null_if_empty(invert_masked) 339 ]); 340 } 341 342 return groups; 343 } 344 345 function ensure_tcpudp(x) { 346 if (length(filter(x, p => (p.name == "tcp" || p.name == "udp")))) 347 return true; 348 349 let rest = filter(x, p => !p.any), 350 any = filter(x, p => p.any); 351 352 if (length(any) && !length(rest)) { 353 splice(x, 0); 354 push(x, { name: "tcp" }, { name: "udp" }); 355 return true; 356 } 357 358 return false; 359 } 360 361 let is_family = (x, v) => (!x.family || x.family == v); 362 let family_is_ipv4 = (x) => (!x.family || x.family == 4); 363 let family_is_ipv6 = (x) => (!x.family || x.family == 6); 364 365 function infer_family(f, objects) { 366 let res = f; 367 let by = null; 368 369 for (let i = 0; i < length(objects); i += 2) { 370 let objs = to_array(objects[i]), 371 desc = objects[i + 1]; 372 373 for (let obj in objs) { 374 if (!obj || !obj.family || obj.family == res) 375 continue; 376 377 if (!res) { 378 res = obj.family; 379 by = obj.desc; 380 continue; 381 } 382 383 return by 384 ? `references IPv${obj.family} only ${desc} but is restricted to IPv${res} by ${by}` 385 : `is restricted to IPv${res} but referenced ${desc} is IPv${obj.family} only`; 386 } 387 } 388 389 return res; 390 } 391 392 function map_setmatch(set, match, proto) { 393 if (!set || (('inet_service' in set.types) && proto != 'tcp' && proto != 'udp')) 394 return null; 395 396 let fields = []; 397 398 for (let i, t in set.types) { 399 let dir = ((match.dir?.[i] || set.directions[i] || 'src') == 'src' ? 's' : 'd'); 400 401 switch (t) { 402 case 'ipv4_addr': 403 fields[i] = `ip ${dir}addr`; 404 break; 405 406 case 'ipv6_addr': 407 fields[i] = `ip6 ${dir}addr`; 408 break; 409 410 case 'ether_addr': 411 if (dir != 's') 412 return NaN; 413 414 fields[i] = 'ether saddr'; 415 break; 416 417 case 'inet_service': 418 fields[i] = `${proto} ${dir}port`; 419 break; 420 } 421 } 422 423 return fields; 424 } 425 426 function resolve_lower_devices(devstatus, devname) { 427 let dir = fs.opendir(`/sys/class/net/${devname}`); 428 let devs = []; 429 430 if (dir) { 431 switch (devstatus[devname]?.devtype) { 432 case 'vlan': 433 case 'bridge': 434 let e; 435 436 while ((e = dir.read()) != null) 437 if (index(e, "lower_") === 0) 438 push(devs, ...resolve_lower_devices(devstatus, substr(e, 6))); 439 440 break; 441 442 default: 443 push(devs, devname); 444 445 break; 446 } 447 448 dir.close(); 449 } 450 451 return devs; 452 } 453 454 function nft_json_command(...args) { 455 let cmd = [ "/usr/sbin/nft", "--terse", "--json", ...args ]; 456 let nft = fs.popen(join(" ", cmd), "r"); 457 let info; 458 459 if (nft) { 460 try { 461 info = filter(json(nft.read("all"))?.nftables, 462 item => (type(item) == "object" && !item.metainfo)); 463 } 464 catch (e) { 465 warn(`Unable to parse nftables JSON output: ${e}\n`); 466 } 467 468 nft.close(); 469 } 470 else { 471 warn(`Unable to popen() ${cmd}: ${fs.error()}\n`); 472 } 473 474 return info || []; 475 } 476 477 function nft_try_hw_offload(devices) { 478 let nft_test = ` 479 add table inet fw4-hw-offload-test; 480 add flowtable inet fw4-hw-offload-test ft { 481 hook ingress priority 0; 482 devices = { "${join('", "', devices)}" }; 483 flags offload; 484 } 485 `; 486 487 let rc = system(`/usr/sbin/nft -c '${replace(nft_test, "'", "'\\''")}' 2>/dev/null`); 488 489 return (rc == 0); 490 } 491 492 493 return { 494 read_kernel_version: function() { 495 let fd = fs.open("/proc/version", "r"), 496 v = 0; 497 498 if (fd) { 499 let m = match(fd.read("line"), /^Linux version ([0-9]+)\.([0-9]+)\.([0-9]+)/); 500 501 v = m ? (+m[1] << 24) | (+m[2] << 16) | (+m[3] << 8) : 0; 502 fd.close(); 503 } 504 505 return v; 506 }, 507 508 resolve_hw_offload_devices: function() { 509 if (!this.default_option("flow_offloading_hw")) 510 return null; 511 512 let devstatus = null; 513 let devices = null; 514 let bus = ubus.connect(); 515 516 if (bus) { 517 devstatus = bus.call("network.device", "status") || {}; 518 bus.disconnect(); 519 } 520 521 for (let zone in this.zones()) 522 for (let device in zone.related_physdevs) 523 push(devices ||= [], ...resolve_lower_devices(devstatus, device)); 524 525 if (!devices) 526 return null; 527 528 devices = sort(uniq(devices)); 529 530 if (!nft_try_hw_offload(devices)) { 531 this.warn('Hardware flow offloading unavailable, falling back to software offloading'); 532 this.state.defaults.flow_offloading_hw = false; 533 534 return null; 535 } 536 537 return devices; 538 }, 539 540 resolve_offload_devices: function() { 541 if (!this.default_option("flow_offloading")) 542 return []; 543 544 let devices = this.resolve_hw_offload_devices(); 545 546 if (!devices) { 547 devices = []; 548 549 for (let zone in this.zones()) 550 for (let device in zone.related_physdevs) 551 if (fs.access(`/sys/class/net/${device}`)) 552 push(devices, device); 553 554 devices = sort(uniq(devices)); 555 } 556 557 return devices; 558 }, 559 560 check_set_types: function() { 561 let sets = {}; 562 563 for (let item in nft_json_command("list", "sets", "inet")) 564 if (item.set?.table == "fw4") 565 sets[item.set.name] = (type(item.set.type) == "array") ? item.set.type : [ item.set.type ]; 566 567 return sets; 568 }, 569 570 check_flowtable: function() { 571 for (let item in nft_json_command("list", "flowtables", "inet")) 572 if (item.flowtable?.table == "fw4" && item.flowtable?.name == "ft") 573 return true; 574 575 return false; 576 }, 577 578 read_state: function() { 579 let fd = fs.open(STATEFILE, "r"); 580 let state = null; 581 582 if (fd) { 583 try { 584 state = json(fd.read("all")); 585 } 586 catch (e) { 587 warn(`Unable to parse '${STATEFILE}': ${e}\n`); 588 } 589 590 fd.close(); 591 } 592 593 return state; 594 }, 595 596 read_ubus: function() { 597 let self = this, 598 ifaces, services, 599 rules = [], networks = {}, 600 bus = ubus.connect(); 601 602 if (bus) { 603 ifaces = bus.call("network.interface", "dump"); 604 services = bus.call("service", "get_data", { "type": "firewall" }); 605 606 bus.disconnect(); 607 } 608 else { 609 warn(`Unable to connect to ubus: ${ubus.error()}\n`); 610 } 611 612 613 // 614 // Gather logical network information from ubus 615 // 616 617 if (type(ifaces?.interface) == "array") { 618 for (let ifc in ifaces.interface) { 619 let net = { 620 up: ifc.up, 621 device: ifc.l3_device ?? ifc.device, 622 physdev: ifc.device, 623 zone: ifc.data?.zone 624 }; 625 626 if (type(ifc["ipv4-address"]) == "array") { 627 for (let addr in ifc["ipv4-address"]) { 628 push(net.ipaddrs ||= [], { 629 family: 4, 630 addr: addr.address, 631 mask: to_mask(addr.mask, false), 632 bits: addr.mask 633 }); 634 } 635 } 636 637 if (type(ifc["ipv6-address"]) == "array") { 638 for (let addr in ifc["ipv6-address"]) { 639 push(net.ipaddrs ||= [], { 640 family: 6, 641 addr: addr.address, 642 mask: to_mask(addr.mask, true), 643 bits: addr.mask 644 }); 645 } 646 } 647 648 if (type(ifc["ipv6-prefix-assignment"]) == "array") { 649 for (let addr in ifc["ipv6-prefix-assignment"]) { 650 if (addr["local-address"]) { 651 push(net.ipaddrs ||= [], { 652 family: 6, 653 addr: addr["local-address"].address, 654 mask: to_mask(addr["local-address"].mask, true), 655 bits: addr["local-address"].mask 656 }); 657 } 658 } 659 } 660 661 if (type(ifc.data?.firewall) == "array") { 662 let n = 0; 663 664 for (let rulespec in ifc.data.firewall) { 665 push(rules, { 666 ...rulespec, 667 668 name: (rulespec.type != 'ipset') ? `ubus:${ifc.interface}[${ifc.proto}] ${rulespec.type || 'rule'} ${n}` : rulespec.name, 669 device: rulespec.device ?? ifc.l3_device ?? ifc.device 670 }); 671 672 n++; 673 } 674 } 675 676 networks[ifc.interface] = net; 677 } 678 } 679 680 681 // 682 // Gather firewall rule definitions from ubus services 683 // 684 685 if (type(services) == "object") { 686 for (let svcname, service in services) { 687 if (type(service?.firewall) == "array") { 688 let n = 0; 689 690 for (let rulespec in services[svcname].firewall) { 691 push(rules, { 692 ...rulespec, 693 694 name: (rulespec.type != 'ipset') ? `ubus:${svcname} ${rulespec.type || 'rule'} ${n}` : rulespec.name 695 }); 696 697 n++; 698 } 699 } 700 701 for (let svcinst, instance in service) { 702 if (type(instance?.firewall) == "array") { 703 let n = 0; 704 705 for (let rulespec in instance.firewall) { 706 push(rules, { 707 ...rulespec, 708 709 name: (rulespec.type != 'ipset') ? `ubus:${svcname}[${svcinst}] ${rulespec.type || 'rule'} ${n}` : rulespec.name 710 }); 711 712 n++; 713 } 714 } 715 } 716 } 717 } 718 719 return { 720 networks: networks, 721 ubus_rules: rules 722 }; 723 }, 724 725 load: function(use_statefile) { 726 let self = this; 727 728 this.state = use_statefile ? this.read_state() : null; 729 730 this.cursor = uci.cursor(); 731 this.cursor.load("firewall"); 732 this.cursor.load("/usr/share/firewall4/helpers"); 733 734 if (!this.state) 735 this.state = this.read_ubus(); 736 737 this.kernel = this.read_kernel_version(); 738 739 740 // 741 // Read helper mapping 742 // 743 744 this.cursor.foreach("helpers", "helper", h => self.parse_helper(h)); 745 746 747 // 748 // Read default policies 749 // 750 751 this.cursor.foreach("firewall", "defaults", d => self.parse_defaults(d)); 752 753 if (!this.state.defaults) 754 this.parse_defaults({}); 755 756 757 // 758 // Build list of ipsets 759 // 760 761 if (!this.state.ipsets) { 762 map(filter(this.state.ubus_rules, n => (n.type == "ipset")), s => self.parse_ipset(s)); 763 this.cursor.foreach("firewall", "ipset", s => self.parse_ipset(s)); 764 } 765 766 767 // 768 // Build list of logical zones 769 // 770 771 if (!this.state.zones) 772 this.cursor.foreach("firewall", "zone", z => self.parse_zone(z)); 773 774 775 // 776 // Build list of rules 777 // 778 779 map(filter(this.state.ubus_rules, r => (r.type == "rule")), r => self.parse_rule(r)); 780 this.cursor.foreach("firewall", "rule", r => self.parse_rule(r)); 781 782 783 // 784 // Build list of forwardings 785 // 786 787 this.cursor.foreach("firewall", "forwarding", f => self.parse_forwarding(f)); 788 789 790 // 791 // Build list of redirects 792 // 793 794 map(filter(this.state.ubus_rules, r => (r.type == "redirect")), r => self.parse_redirect(r)); 795 this.cursor.foreach("firewall", "redirect", r => self.parse_redirect(r)); 796 797 798 // 799 // Build list of snats 800 // 801 802 map(filter(this.state.ubus_rules, n => (n.type == "nat")), n => self.parse_nat(n)); 803 this.cursor.foreach("firewall", "nat", n => self.parse_nat(n)); 804 805 806 // 807 // Build list of includes 808 // 809 810 this.cursor.foreach("firewall", "include", i => self.parse_include(i)); 811 812 813 // 814 // Discover automatic includes 815 // 816 817 if (this.default_option("auto_includes")) { 818 for (let position in [ 'ruleset-pre', 'ruleset-post', 'table-pre', 'table-post', 'chain-pre', 'chain-post' ]) 819 for (let chain in (position in [ 'chain-pre', 'chain-post' ]) ? fs.lsdir(`/usr/share/nftables.d/${position}`) : [ null ]) 820 for (let path in fs.glob(`/usr/share/nftables.d/${position}${chain ? `/${chain}` : ''}/*.nft`)) 821 if (fs.access(path)) 822 this.parse_include({ type: 'nftables', position, chain, path }); 823 } 824 825 826 if (use_statefile) { 827 let fd = fs.open(STATEFILE, "w"); 828 829 if (fd) { 830 fd.write({ 831 zones: this.state.zones, 832 ipsets: this.state.ipsets, 833 networks: this.state.networks, 834 ubus_rules: this.state.ubus_rules, 835 includes: this.state.includes 836 }); 837 838 fd.close(); 839 } 840 else { 841 warn(`Unable to write '${STATEFILE}': ${fs.error()}\n`); 842 } 843 } 844 }, 845 846 warn: function(fmt, ...args) { 847 if (getenv("QUIET")) 848 return; 849 850 let msg = sprintf(fmt, ...args); 851 852 if (getenv("TTY")) 853 warn(`\033[33m${msg}\033[m\n`); 854 else 855 warn(`[!] ${msg}\n`); 856 }, 857 858 get: function(sid, opt) { 859 return this.cursor.get("firewall", sid, opt); 860 }, 861 862 get_all: function(sid) { 863 return this.cursor.get_all("firewall", sid); 864 }, 865 866 parse_options: function(s, spec) { 867 let rv = {}; 868 869 for (let key, val in spec) { 870 let datatype = `parse_${val[0]}`, 871 defval = val[1], 872 flags = val[2] || 0, 873 parsefn = (flags & PARSE_LIST) ? "parse_list" : "parse_opt"; 874 875 let res = this[parsefn](s, key, datatype, defval, flags); 876 877 if (res !== res) 878 return false; 879 880 if (type(res) == "object" && res.invert && (flags & NO_INVERT)) { 881 this.warn_section(s, `option '${key}' must not be negated`); 882 return false; 883 } 884 885 if (res != null) { 886 if (flags & DEPRECATED) 887 this.warn_section(s, `option '${key}' is deprecated by fw4`); 888 else if (flags & UNSUPPORTED) 889 this.warn_section(s, `option '${key}' is not supported by fw4`); 890 else 891 rv[key] = res; 892 } 893 } 894 895 for (let opt in s) { 896 if (index(opt, '.') != 0 && opt != 'type' && !exists(spec, opt)) { 897 this.warn_section(s, `specifies unknown option '${opt}'`); 898 } 899 } 900 901 return rv; 902 }, 903 904 parse_subnet: function(subnet) { 905 let parts = split(subnet, "/"); 906 let a, b, m, n; 907 908 switch (length(parts)) { 909 case 2: 910 a = iptoarr(parts[0]); 911 m = iptoarr(parts[1]); 912 913 if (!a) 914 return null; 915 916 if (m) { 917 if (length(a) != length(m)) 918 return null; 919 920 b = to_bits(parts[1]); 921 922 /* allow non-contiguous masks such as `::ffff:ffff:ffff:ffff` */ 923 if (b == null) { 924 b = -1; 925 926 for (let i, x in m) 927 a[i] &= x; 928 } 929 930 m = arrtoip(m); 931 } 932 else { 933 b = +parts[1]; 934 935 if (type(b) != "int") 936 return null; 937 938 m = to_mask(b, length(a) == 16); 939 b = max(-1, b); 940 } 941 942 return [{ 943 family: (length(a) == 16) ? 6 : 4, 944 addr: arrtoip(a), 945 mask: m, 946 bits: b 947 }]; 948 949 case 1: 950 parts = split(parts[0], "-"); 951 952 switch (length(parts)) { 953 case 2: 954 a = iptoarr(parts[0]); 955 b = iptoarr(parts[1]); 956 957 if (a && b && length(a) == length(b)) { 958 return [{ 959 family: (length(a) == 16) ? 6 : 4, 960 addr: arrtoip(a), 961 addr2: arrtoip(b), 962 range: true 963 }]; 964 } 965 966 break; 967 968 case 1: 969 a = iptoarr(parts[0]); 970 971 if (a) { 972 return [{ 973 family: (length(a) == 16) ? 6 : 4, 974 addr: arrtoip(a), 975 mask: to_mask(length(a) * 8, length(a) == 16), 976 bits: length(a) * 8 977 }]; 978 } 979 980 n = this.state.networks[parts[0]]; 981 982 if (n) 983 return [ ...(n.ipaddrs || []) ]; 984 } 985 } 986 987 return null; 988 }, 989 990 parse_enum: function(val, choices) { 991 if (type(val) == "string") { 992 val = lc(val); 993 994 for (let i = 0; i < length(choices); i++) 995 if (lc(substr(choices[i], 0, length(val))) == val) 996 return choices[i]; 997 } 998 999 return null; 1000 }, 1001 1002 section_id: function(sid) { 1003 let s = this.get_all(sid); 1004 1005 if (!s) 1006 return null; 1007 1008 if (s[".anonymous"]) { 1009 let c = 0; 1010 1011 this.cursor.foreach("firewall", s[".type"], function(ss) { 1012 if (ss[".name"] == s[".name"]) 1013 return false; 1014 1015 c++; 1016 }); 1017 1018 return `@${s['.type']}[${c}]`; 1019 } 1020 1021 return s[".name"]; 1022 }, 1023 1024 warn_section: function(s, msg) { 1025 if (s[".name"]) { 1026 if (s.name) 1027 this.warn("Section %s (%s) %s", this.section_id(s[".name"]), s.name, msg); 1028 else 1029 this.warn("Section %s %s", this.section_id(s[".name"]), msg); 1030 } 1031 else { 1032 if (s.name) 1033 this.warn("ubus %s (%s) %s", s.type || "rule", s.name, msg); 1034 else 1035 this.warn("ubus %s %s", s.type || "rule", msg); 1036 } 1037 }, 1038 1039 parse_policy: function(val) { 1040 return this.parse_enum(val, [ 1041 "accept", 1042 "reject", 1043 "drop" 1044 ]); 1045 }, 1046 1047 parse_bool: function(val) { 1048 if (val == "1" || val == "on" || val == "true" || val == "yes") 1049 return true; 1050 else if (val == "0" || val == "off" || val == "false" || val == "no") 1051 return false; 1052 else 1053 return null; 1054 }, 1055 1056 parse_family: function(val) { 1057 if (val == 'any' || val == 'all' || val == '*') 1058 return 0; 1059 else if (val == 'inet' || index(val, '4') > -1) 1060 return 4; 1061 else if (index(val, '6') > -1) 1062 return 6; 1063 1064 return null; 1065 }, 1066 1067 parse_zone_ref: function(val) { 1068 if (val == null) 1069 return null; 1070 1071 if (val == '*') 1072 return { any: true }; 1073 1074 for (let zone in this.state.zones) { 1075 if (zone.name == val) { 1076 return { 1077 any: false, 1078 zone: zone 1079 }; 1080 } 1081 } 1082 1083 return null; 1084 }, 1085 1086 parse_device: function(val) { 1087 let rv = this.parse_invert(val); 1088 1089 if (!rv) 1090 return null; 1091 1092 if (rv.val == '*') 1093 rv.any = true; 1094 else 1095 rv.device = rv.val; 1096 1097 return rv; 1098 }, 1099 1100 parse_direction: function(val) { 1101 if (val == 'in' || val == 'ingress') 1102 return false; 1103 else if (val == 'out' || val == 'egress') 1104 return true; 1105 1106 return null; 1107 }, 1108 1109 parse_setmatch: function(val) { 1110 let rv = this.parse_invert(val); 1111 1112 if (!rv) 1113 return null; 1114 1115 rv.val = trim(replace(rv.val, /^[^ \t]+/, function(m) { 1116 rv.name = m; 1117 return ''; 1118 })); 1119 1120 let dir = split(rv.val, /[ \t,]/); 1121 1122 for (let i = 0; i < 3 && i < length(dir); i++) { 1123 if (dir[i] == "dst" || dir[i] == "dest") 1124 (rv.dir ||= [])[i] = "dst"; 1125 else if (dir[i] == "src") 1126 (rv.dir ||= [])[i] = "src"; 1127 } 1128 1129 return length(rv.name) ? rv : null; 1130 }, 1131 1132 parse_cthelper: function(val) { 1133 let rv = this.parse_invert(val); 1134 1135 if (!rv) 1136 return null; 1137 1138 let helper = filter(this.state.helpers, h => (h.name == rv.val))?.[0]; 1139 1140 return helper ? { ...rv, ...helper } : null; 1141 }, 1142 1143 parse_protocol: function(val) { 1144 let p = this.parse_invert(val); 1145 1146 if (!p) 1147 return null; 1148 1149 p.val = lc(p.val); 1150 1151 switch (p.val) { 1152 case 'all': 1153 case 'any': 1154 case '*': 1155 p.any = true; 1156 break; 1157 1158 case '1': 1159 case 'icmp': 1160 p.name = 'icmp'; 1161 break; 1162 1163 case '58': 1164 case 'icmpv6': 1165 case 'ipv6-icmp': 1166 p.name = 'ipv6-icmp'; 1167 break; 1168 1169 case 'tcpudp': 1170 return [ 1171 { invert: p.invert, name: 'tcp' }, 1172 { invert: p.invert, name: 'udp' } 1173 ]; 1174 1175 case '6': 1176 p.name = 'tcp'; 1177 break; 1178 1179 case '17': 1180 p.name = 'udp'; 1181 break; 1182 1183 default: 1184 p.name = p.val; 1185 } 1186 1187 return (p.any || length(p.name)) ? p : null; 1188 }, 1189 1190 parse_mac: function(val) { 1191 let mac = this.parse_invert(val); 1192 let m = mac ? match(mac.val, /^([0-9a-f]{1,2})[:-]([0-9a-f]{1,2})[:-]([0-9a-f]{1,2})[:-]([0-9a-f]{1,2})[:-]([0-9a-f]{1,2})[:-]([0-9a-f]{1,2})$/i) : null; 1193 1194 if (!m) 1195 return null; 1196 1197 mac.mac = sprintf('%02x:%02x:%02x:%02x:%02x:%02x', 1198 hex(m[1]), hex(m[2]), hex(m[3]), 1199 hex(m[4]), hex(m[5]), hex(m[6])); 1200 1201 return mac; 1202 }, 1203 1204 parse_port: function(val) { 1205 let port = this.parse_invert(val); 1206 let m = port ? match(port.val, /^([0-9]{1,5})([-:]([0-9]{1,5}))?$/i) : null; 1207 1208 if (!m) 1209 return null; 1210 1211 if (m[3]) { 1212 let min_port = +m[1]; 1213 let max_port = +m[3]; 1214 1215 if (min_port > max_port || 1216 min_port < 0 || max_port < 0 || 1217 min_port > 65535 || max_port > 65535) 1218 return null; 1219 1220 port.min = min_port; 1221 port.max = max_port; 1222 } 1223 else { 1224 let pn = +m[1]; 1225 1226 if (pn != pn || pn < 0 || pn > 65535) 1227 return null; 1228 1229 port.min = pn; 1230 port.max = pn; 1231 } 1232 1233 return port; 1234 }, 1235 1236 parse_network: function(val) { 1237 let rv = this.parse_invert(val); 1238 1239 if (!rv) 1240 return null; 1241 1242 let nets = this.parse_subnet(rv.val); 1243 1244 if (nets === null) 1245 return null; 1246 1247 if (length(nets)) 1248 rv.addrs = [ ...nets ]; 1249 1250 return rv; 1251 }, 1252 1253 parse_icmptype: function(val) { 1254 let rv = {}; 1255 1256 if (exists(ipv4_icmptypes, val)) { 1257 rv.family = 4; 1258 1259 rv.type = ipv4_icmptypes[val][0]; 1260 rv.code_min = ipv4_icmptypes[val][1]; 1261 rv.code_max = ipv4_icmptypes[val][2]; 1262 } 1263 1264 if (exists(ipv6_icmptypes, val)) { 1265 rv.family = rv.family ? 0 : 6; 1266 1267 rv.type6 = ipv6_icmptypes[val][0]; 1268 rv.code6_min = ipv6_icmptypes[val][1]; 1269 rv.code6_max = ipv6_icmptypes[val][2]; 1270 } 1271 1272 if (!exists(rv, "family")) { 1273 let m = match(val, /^([0-9]+)(\/([0-9]+))?$/); 1274 1275 if (!m) 1276 return null; 1277 1278 if (m[3]) { 1279 rv.type = +m[1]; 1280 rv.code_min = +m[3]; 1281 rv.code_max = rv.code_min; 1282 } 1283 else { 1284 rv.type = +m[1]; 1285 rv.code_min = 0; 1286 rv.code_max = 0xFF; 1287 } 1288 1289 if (rv.type > 0xFF || rv.code_min > 0xFF || rv.code_max > 0xFF) 1290 return null; 1291 1292 rv.family = 0; 1293 1294 rv.type6 = rv.type; 1295 rv.code6_min = rv.code_min; 1296 rv.code6_max = rv.code_max; 1297 } 1298 1299 return rv; 1300 }, 1301 1302 parse_invert: function(val) { 1303 if (val == null) 1304 return null; 1305 1306 let rv = { invert: false }; 1307 1308 rv.val = trim(replace(val, /^[ \t]*!/, () => (rv.invert = true, ''))); 1309 1310 return length(rv.val) ? rv : null; 1311 }, 1312 1313 parse_limit: function(val) { 1314 let rv = this.parse_invert(val); 1315 let m = rv ? match(rv.val, /^([0-9]+)(\/([a-z]+))?$/) : null; 1316 1317 if (!m) 1318 return null; 1319 1320 let n = +m[1]; 1321 let u = m[3] ? this.parse_enum(m[3], [ "second", "minute", "hour", "day" ]) : "second"; 1322 1323 if (!u) 1324 return null; 1325 1326 rv.rate = n; 1327 rv.unit = u; 1328 1329 return rv; 1330 }, 1331 1332 parse_int: function(val) { 1333 let n = +val; 1334 1335 return (n == n) ? n : null; 1336 }, 1337 1338 parse_date: function(val) { 1339 let d = match(val, /^([0-9]{4})(-([0-9]{1,2})(-([0-9]{1,2})(T([0-9:]+))?)?)?$/); 1340 1341 if (d == null || d[1] < 1970 || d[1] > 2038 || d[3] > 12 || d[5] > 31) 1342 return null; 1343 1344 let t = this.parse_time(d[7] ?? "0"); 1345 1346 if (t == null) 1347 return null; 1348 1349 return { 1350 year: +d[1], 1351 month: +d[3] || 1, 1352 day: +d[5] || 1, 1353 ...t 1354 }; 1355 }, 1356 1357 parse_time: function(val) { 1358 let t = match(val, /^([0-9]{1,2})(:([0-9]{1,2})(:([0-9]{1,2}))?)?$/); 1359 1360 if (t == null || t[1] > 23 || t[3] > 59 || t[5] > 59) 1361 return null; 1362 1363 return { 1364 hour: +t[1], 1365 min: +t[3], 1366 sec: +t[5] 1367 }; 1368 }, 1369 1370 parse_weekdays: function(val) { 1371 let rv = this.parse_invert(val); 1372 1373 if (!rv) 1374 return null; 1375 1376 for (let day in to_array(rv.val)) { 1377 day = this.parse_enum(day, [ 1378 "Monday", 1379 "Tuesday", 1380 "Wednesday", 1381 "Thursday", 1382 "Friday", 1383 "Saturday", 1384 "Sunday" 1385 ]); 1386 1387 if (!day) 1388 return null; 1389 1390 (rv.days ||= {})[day] = true; 1391 } 1392 1393 rv.days = keys(rv.days); 1394 1395 return rv.days ? rv : null; 1396 }, 1397 1398 parse_monthdays: function(val) { 1399 let rv = this.parse_invert(val); 1400 1401 if (!rv) 1402 return null; 1403 1404 for (let day in to_array(rv.val)) { 1405 day = +day; 1406 1407 if (day < 1 || day > 31) 1408 return null; 1409 1410 (rv.days ||= [])[day] = true; 1411 } 1412 1413 return rv.days ? rv : null; 1414 }, 1415 1416 parse_mark: function(val) { 1417 let rv = this.parse_invert(val); 1418 let m = rv ? match(rv.val, /^(0?x?[0-9a-f]+)(\/(0?x?[0-9a-f]+))?$/i) : null; 1419 1420 if (!m) 1421 return null; 1422 1423 let n = +m[1]; 1424 1425 if (n != n || n > 0xFFFFFFFF) 1426 return null; 1427 1428 rv.mark = n; 1429 rv.mask = 0xFFFFFFFF; 1430 1431 if (m[3]) { 1432 n = +m[3]; 1433 1434 if (n != n || n > 0xFFFFFFFF) 1435 return null; 1436 1437 rv.mask = n; 1438 } 1439 1440 return rv; 1441 }, 1442 1443 parse_dscp: function(val) { 1444 let rv = this.parse_invert(val); 1445 1446 if (!rv) 1447 return null; 1448 1449 rv.val = uc(rv.val); 1450 1451 if (exists(dscp_classes, rv.val)) { 1452 rv.dscp = dscp_classes[rv.val]; 1453 } 1454 else { 1455 let n = +rv.val; 1456 1457 if (n != n || n < 0 || n > 0x3F) 1458 return null; 1459 1460 rv.dscp = n; 1461 } 1462 1463 return rv; 1464 }, 1465 1466 parse_target: function(val) { 1467 return this.parse_enum(val, [ 1468 "accept", 1469 "reject", 1470 "drop", 1471 "notrack", 1472 "helper", 1473 "mark", 1474 "dscp", 1475 "dnat", 1476 "snat", 1477 "masquerade", 1478 "accept", 1479 "reject", 1480 "drop" 1481 ]); 1482 }, 1483 1484 parse_reject_code: function(val) { 1485 return this.parse_enum(val, [ 1486 "tcp-reset", 1487 "port-unreachable", 1488 "admin-prohibited", 1489 "host-unreachable", 1490 "no-route" 1491 ]); 1492 }, 1493 1494 parse_reflection_source: function(val) { 1495 return this.parse_enum(val, [ 1496 "internal", 1497 "external" 1498 ]); 1499 }, 1500 1501 parse_ipsettype: function(val) { 1502 let m = match(val, /^(src|dst|dest)_(.+)$/); 1503 let t = this.parse_enum(m ? m[2] : val, [ 1504 "ip", 1505 "port", 1506 "mac", 1507 "net", 1508 "set" 1509 ]); 1510 1511 return t ? [ (!m || m[1] == 'src') ? 'src' : 'dst', t ] : null; 1512 }, 1513 1514 parse_ipsetentry: function(val, set) { 1515 let values = split(val, /[ \t]+/); 1516 1517 if (length(values) != length(set.types)) 1518 return null; 1519 1520 let rv = []; 1521 let ip, mac, port; 1522 1523 for (let i, t in set.types) { 1524 switch (t) { 1525 case 'ipv4_addr': 1526 ip = filter(this.parse_subnet(values[i]), a => (a.family == 4)); 1527 1528 switch (length(ip) ?? 0) { 1529 case 0: return null; 1530 case 1: break; 1531 default: this.warn("Set entry '%s' resolves to multiple addresses, using first one", values[i]); 1532 } 1533 1534 rv[i] = ("net" in set.fw4types) ? `${ip[0].addr}/${ip[0].bits}` : ip[0].addr; 1535 break; 1536 1537 case 'ipv6_addr': 1538 ip = filter(this.parse_subnet(values[i]), a => (a.family == 6)); 1539 1540 switch (length(ip) ?? 0) { 1541 case 0: return null; 1542 case 1: break; 1543 case 2: this.warn("Set entry '%s' resolves to multiple addresses, using first one", values[i]); 1544 } 1545 1546 rv[i] = ("net" in set.fw4types) ? `${ip[0].addr}/${ip[0].bits}` : ip[0].addr; 1547 1548 break; 1549 1550 case 'ether_addr': 1551 mac = this.parse_mac(values[i]); 1552 1553 if (!mac || mac.invert) 1554 return null; 1555 1556 rv[i] = mac.mac; 1557 break; 1558 1559 case 'inet_service': 1560 port = this.parse_port(values[i]); 1561 1562 if (!port || port.invert || port.min != port.max) 1563 return null; 1564 1565 rv[i] = port.min; 1566 break; 1567 1568 default: 1569 rv[i] = values[i]; 1570 } 1571 } 1572 1573 return length(rv) ? rv : null; 1574 }, 1575 1576 parse_includetype: function(val) { 1577 return this.parse_enum(val, [ 1578 "script", 1579 "nftables" 1580 ]); 1581 }, 1582 1583 parse_includeposition: function(val) { 1584 return replace(this.parse_enum(val, [ 1585 "ruleset-prepend", 1586 "ruleset-postpend", 1587 "ruleset-append", 1588 1589 "table-prepend", 1590 "table-postpend", 1591 "table-append", 1592 1593 "chain-prepend", 1594 "chain-postpend", 1595 "chain-append" 1596 ]), "postpend", "append"); 1597 }, 1598 1599 parse_identifier: function(val) { 1600 return match(val, /^[a-zA-Z_.][a-zA-Z0-9\/_.-]*$/)?.[0]; 1601 }, 1602 1603 parse_string: function(val) { 1604 return "" + val; 1605 }, 1606 1607 parse_opt: function(s, opt, fn, defval, flags) { 1608 let val = s[opt]; 1609 1610 if (val === null) { 1611 if (flags & REQUIRED) { 1612 this.warn_section(s, `option '${opt}' is mandatory but not set`); 1613 return NaN; 1614 } 1615 1616 val = defval; 1617 } 1618 1619 if (type(val) == "array") { 1620 this.warn_section(s, `option '${opt}' must not be a list`); 1621 return NaN; 1622 } 1623 else if (val == null) { 1624 return null; 1625 } 1626 1627 let res = this[fn](val); 1628 1629 if (res === null) { 1630 this.warn_section(s, `option '${opt}' specifies invalid value '${val}'`); 1631 return NaN; 1632 } 1633 1634 return res; 1635 }, 1636 1637 parse_list: function(s, opt, fn, defval, flags) { 1638 let val = s[opt]; 1639 let rv = []; 1640 1641 if (val == null) { 1642 if (flags & REQUIRED) { 1643 this.warn_section(s, `option '${opt}' is mandatory but not set`); 1644 return NaN; 1645 } 1646 1647 val = defval; 1648 } 1649 1650 for (val in to_array(val)) { 1651 let res = this[fn](val); 1652 1653 if (res === null) { 1654 this.warn_section(s, `option '${opt}' specifies invalid value '${val}'`); 1655 return NaN; 1656 } 1657 1658 if (flags & FLATTEN_LIST) 1659 push(rv, ...to_array(res)); 1660 else 1661 push(rv, res); 1662 } 1663 1664 return length(rv) ? rv : null; 1665 }, 1666 1667 quote: function(s, force) { 1668 if (force === true || !match(s, /^([0-9A-Fa-f:.\/-]+)( \. [0-9A-Fa-f:.\/-]+)*$/)) 1669 return `"${replace(s, '"', "'")}"`; 1670 1671 return s; 1672 }, 1673 1674 cidr: function(a) { 1675 if (a.range) 1676 return `${a.addr}-${a.addr2}`; 1677 1678 if ((a.family == 4 && a.bits == 32) || 1679 (a.family == 6 && a.bits == 128)) 1680 return a.addr; 1681 1682 if (a.bits >= 0) 1683 return `${apply_mask(a.addr, a.bits)}/${a.bits}`; 1684 1685 return `${a.addr}/${a.mask}`; 1686 }, 1687 1688 host: function(a, v6brackets) { 1689 return a.range 1690 ? `${a.addr}-${a.addr2}` 1691 : (a.family == 6 && v6brackets) 1692 ? `[${apply_mask(a.addr, a.bits)}]` : apply_mask(a.addr, a.bits); 1693 }, 1694 1695 port: function(p) { 1696 if (p.min == p.max) 1697 return `${p.min}`; 1698 1699 return `${p.min}-${p.max}`; 1700 }, 1701 1702 set: function(v, force) { 1703 let seen = {}; 1704 1705 v = filter(to_array(v), item => !seen[item]++); 1706 1707 if (force || length(v) != 1) 1708 return `{ ${join(', ', map(v, this.quote))} }`; 1709 1710 return this.quote(v[0]); 1711 }, 1712 1713 concat: function(v) { 1714 return join(' . ', to_array(v)); 1715 }, 1716 1717 ipproto: function(family) { 1718 switch (family) { 1719 case 4: 1720 return "ip"; 1721 1722 case 6: 1723 return "ip6"; 1724 } 1725 }, 1726 1727 nfproto: function(family, human_readable) { 1728 switch (family) { 1729 case 4: 1730 return human_readable ? "IPv4" : "ipv4"; 1731 1732 case 6: 1733 return human_readable ? "IPv6" : "ipv6"; 1734 1735 default: 1736 return human_readable ? "IPv4/IPv6" : null; 1737 } 1738 }, 1739 1740 l4proto: function(family, proto) { 1741 switch (proto.name) { 1742 case 'icmp': 1743 switch (family ?? 0) { 1744 case 0: 1745 return this.set(['icmp', 'ipv6-icmp']); 1746 1747 case 6: 1748 return 'ipv6-icmp'; 1749 } 1750 1751 default: 1752 return proto.name; 1753 } 1754 }, 1755 1756 datetime: function(stamp) { 1757 return sprintf('"%04d-%02d-%02d %02d:%02d:%02d"', 1758 stamp.year, stamp.month, stamp.day, 1759 stamp.hour, stamp.min, stamp.sec); 1760 }, 1761 1762 date: function(stamp) { 1763 return sprintf('"%04d-%02d-%02d"', stamp.year, stamp.month, stamp.day); 1764 }, 1765 1766 datestamp: function(stamp) { 1767 return exists(stamp, 'hour') ? this.datetime(stamp) : this.date(stamp); 1768 }, 1769 1770 time: function(stamp) { 1771 return sprintf('"%02d:%02d:%02d"', stamp.hour, stamp.min, stamp.sec); 1772 }, 1773 1774 hex: function(n) { 1775 return sprintf('0x%x', n); 1776 }, 1777 1778 is_loopback_dev: function(dev) { 1779 return !!(+fs.readfile(`/sys/class/net/${dev}/flags`) & 0x8); 1780 }, 1781 1782 is_loopback_addr: function(addr) { 1783 return (index(addr, "127.") == 0 || addr == "::1" || addr == "::1/128"); 1784 }, 1785 1786 filter_loopback_devs: function(devs, invert) { 1787 return null_if_empty(filter(devs, d => (this.is_loopback_dev(d) == invert))); 1788 }, 1789 1790 filter_loopback_addrs: function(addrs, invert) { 1791 return null_if_empty(filter(addrs, a => (this.is_loopback_addr(a) == invert))); 1792 }, 1793 1794 1795 input_policy: function(reject_as_drop) { 1796 return (!reject_as_drop || this.state.defaults.input != 'reject') ? this.state.defaults.input : 'drop'; 1797 }, 1798 1799 output_policy: function(reject_as_drop) { 1800 return (!reject_as_drop || this.state.defaults.output != 'reject') ? this.state.defaults.output : 'drop'; 1801 }, 1802 1803 forward_policy: function(reject_as_drop) { 1804 return (!reject_as_drop || this.state.defaults.forward != 'reject') ? this.state.defaults.forward : 'drop'; 1805 }, 1806 1807 default_option: function(flag) { 1808 return this.state.defaults[flag]; 1809 }, 1810 1811 helpers: function() { 1812 return this.state.helpers; 1813 }, 1814 1815 zones: function() { 1816 return this.state.zones; 1817 }, 1818 1819 rules: function(chain) { 1820 return filter(this.state.rules, r => (r.chain == chain)); 1821 }, 1822 1823 redirects: function(chain) { 1824 return filter(this.state.redirects, r => (r.chain == chain)); 1825 }, 1826 1827 ipsets: function() { 1828 return this.state.ipsets; 1829 }, 1830 1831 includes: function(position, chain) { 1832 let stmts = []; 1833 let pad = ''; 1834 let pre = ''; 1835 1836 switch (position) { 1837 case 'table-prepend': 1838 case 'table-append': 1839 pad = '\t'; 1840 pre = '\n'; 1841 break; 1842 1843 case 'chain-prepend': 1844 case 'chain-append': 1845 pad = '\t\t'; 1846 break; 1847 1848 default: 1849 pre = '\n'; 1850 } 1851 1852 push(stmts, pre); 1853 1854 for (let inc in this.state.includes) 1855 if (inc.type == 'nftables' && inc.position == position && (!chain || inc.chain == chain)) 1856 push(stmts, `${pad}include "${inc.path}"\n`); 1857 1858 print(length(stmts) > 1 ? join('', stmts) : ''); 1859 }, 1860 1861 parse_setfile: function(set, cb) { 1862 let fd = fs.open(set.loadfile, "r"); 1863 1864 if (!fd) { 1865 warn(`Unable to load file '${set.loadfile}' for set '${set.name}': ${fs.error()}\n`); 1866 return; 1867 } 1868 1869 let count = 0; 1870 1871 for (let line = fd.read("line"); length(line); line = fd.read("line")) { 1872 line = trim(line); 1873 1874 if (length(line) == 0 || ord(line) == 35) 1875 continue; 1876 1877 let v = this.parse_ipsetentry(line, set); 1878 1879 if (!v) { 1880 this.warn(`Skipping invalid entry '${line}' in file '${set.loadfile}' for set '${set.name}'`); 1881 continue; 1882 } 1883 1884 cb(v); 1885 1886 count++; 1887 } 1888 1889 fd.close(); 1890 1891 return count; 1892 }, 1893 1894 print_setentries: function(set) { 1895 let first = true; 1896 let printer = (entry) => { 1897 if (first) { 1898 print("\t\telements = {\n"); 1899 first = false; 1900 } 1901 1902 print("\t\t\t", join(" . ", entry), ",\n"); 1903 }; 1904 1905 map(set.entries, printer); 1906 1907 if (set.loadfile) 1908 this.parse_setfile(set, printer); 1909 1910 if (!first) 1911 print("\t\t}\n"); 1912 }, 1913 1914 parse_helper: function(data) { 1915 let helper = this.parse_options(data, { 1916 name: [ "string", null, REQUIRED ], 1917 description: [ "string" ], 1918 module: [ "string" ], 1919 family: [ "family" ], 1920 proto: [ "protocol", null, PARSE_LIST | FLATTEN_LIST | NO_INVERT ], 1921 port: [ "port", null, NO_INVERT ] 1922 }); 1923 1924 if (helper === false) { 1925 this.warn("Helper definition '%s' skipped due to invalid options", data.name || data['.name']); 1926 return; 1927 } 1928 else if (helper.proto.any) { 1929 this.warn("Helper definition '%s' must not specify wildcard protocol", data.name || data['.name']); 1930 return; 1931 } 1932 else if (length(helper.proto) > 1) { 1933 this.warn("Helper definition '%s' must not specify multiple protocols", data.name || data['.name']); 1934 return; 1935 } 1936 1937 helper.available = (fs.stat(`/sys/module/${helper.module}`)?.type == "directory"); 1938 1939 push(this.state.helpers ||= [], helper); 1940 }, 1941 1942 parse_defaults: function(data) { 1943 if (this.state.defaults) { 1944 this.warn_section(data, ": ignoring duplicate defaults section"); 1945 return; 1946 } 1947 1948 let defs = this.parse_options(data, { 1949 input: [ "policy", "drop" ], 1950 output: [ "policy", "drop" ], 1951 forward: [ "policy", "drop" ], 1952 1953 drop_invalid: [ "bool" ], 1954 tcp_reject_code: [ "reject_code", "tcp-reset" ], 1955 any_reject_code: [ "reject_code", "port-unreachable" ], 1956 1957 syn_flood: [ "bool" ], 1958 synflood_protect: [ "bool" ], 1959 synflood_rate: [ "limit", "25/second" ], 1960 synflood_burst: [ "int", "50" ], 1961 1962 tcp_syncookies: [ "bool", "1" ], 1963 tcp_ecn: [ "int" ], 1964 tcp_window_scaling: [ "bool", "1" ], 1965 1966 accept_redirects: [ "bool" ], 1967 accept_source_route: [ "bool" ], 1968 1969 auto_helper: [ "bool", "1" ], 1970 custom_chains: [ "bool", null, UNSUPPORTED ], 1971 disable_ipv6: [ "bool", null, UNSUPPORTED ], 1972 flow_offloading: [ "bool", "0" ], 1973 flow_offloading_hw: [ "bool", "0" ], 1974 1975 auto_includes: [ "bool", "1" ] 1976 }); 1977 1978 if (defs.synflood_protect === null) 1979 defs.synflood_protect = defs.syn_flood; 1980 1981 delete defs.syn_flood; 1982 1983 this.state.defaults = defs; 1984 }, 1985 1986 parse_zone: function(data) { 1987 let zone = this.parse_options(data, { 1988 enabled: [ "bool", "1" ], 1989 1990 name: [ "identifier", null, REQUIRED ], 1991 family: [ "family" ], 1992 1993 network: [ "device", null, PARSE_LIST ], 1994 device: [ "device", null, PARSE_LIST ], 1995 subnet: [ "network", null, PARSE_LIST ], 1996 1997 input: [ "policy", this.state.defaults ? this.state.defaults.input : "drop" ], 1998 output: [ "policy", this.state.defaults ? this.state.defaults.output : "drop" ], 1999 forward: [ "policy", this.state.defaults ? this.state.defaults.forward : "drop" ], 2000 2001 masq: [ "bool" ], 2002 masq_allow_invalid: [ "bool" ], 2003 masq_src: [ "network", null, PARSE_LIST ], 2004 masq_dest: [ "network", null, PARSE_LIST ], 2005 2006 masq6: [ "bool" ], 2007 2008 extra: [ "string", null, UNSUPPORTED ], 2009 extra_src: [ "string", null, UNSUPPORTED ], 2010 extra_dest: [ "string", null, UNSUPPORTED ], 2011 2012 mtu_fix: [ "bool" ], 2013 custom_chains: [ "bool", null, UNSUPPORTED ], 2014 2015 log: [ "int" ], 2016 log_limit: [ "limit" ], 2017 2018 auto_helper: [ "bool", "1" ], 2019 helper: [ "cthelper", null, PARSE_LIST ], 2020 2021 counter: [ "bool", "1" ] 2022 }); 2023 2024 if (zone === false) { 2025 this.warn_section(data, "skipped due to invalid options"); 2026 return; 2027 } 2028 else if (!zone.enabled) { 2029 this.warn_section(data, "is disabled, ignoring section"); 2030 return; 2031 } 2032 2033 for (let helper in zone.helper) { 2034 if (!helper.available) { 2035 this.warn_section(data, `uses unavailable ct helper '${zone.helper.name}'`); 2036 } 2037 } 2038 2039 if (zone.mtu_fix && this.kernel < 0x040a0000) { 2040 this.warn_section(data, "option 'mtu_fix' requires kernel 4.10 or later"); 2041 return; 2042 } 2043 2044 if (this.state.defaults?.auto_helper === false) 2045 zone.auto_helper = false; 2046 2047 let match_devices = []; 2048 let related_physdevs = []; 2049 let related_subnets = []; 2050 let related_ubus_networks = []; 2051 let match_subnets, masq_src_subnets, masq_dest_subnets; 2052 2053 for (let name, net in this.state.networks) { 2054 if (net.zone === zone.name) 2055 push(related_ubus_networks, { invert: false, device: name }); 2056 } 2057 2058 zone.network = [ ...to_array(zone.network), ...related_ubus_networks ]; 2059 2060 for (let e in zone.network) { 2061 if (exists(this.state.networks, e.device)) { 2062 let net = this.state.networks[e.device]; 2063 2064 if (net.device) { 2065 push(match_devices, { 2066 invert: e.invert, 2067 device: net.device 2068 }); 2069 } 2070 2071 if (net.physdev && !e.invert) 2072 push(related_physdevs, net.physdev); 2073 2074 push(related_subnets, ...(net.ipaddrs || [])); 2075 } 2076 } 2077 2078 push(match_devices, ...to_array(zone.device)); 2079 2080 match_subnets = subnets_split_af(zone.subnet); 2081 masq_src_subnets = subnets_split_af(zone.masq_src); 2082 masq_dest_subnets = subnets_split_af(zone.masq_dest); 2083 2084 push(related_subnets, ...(match_subnets[0] || []), ...(match_subnets[1] || [])); 2085 2086 let match_rules = []; 2087 2088 let add_rule = (family, devices, subnets, zone) => { 2089 let r = {}; 2090 2091 r.family = family; 2092 2093 r.devices_pos = null_if_empty(devices[0]); 2094 r.devices_neg = null_if_empty(devices[1]); 2095 r.devices_neg_wildcard = null_if_empty(devices[2]); 2096 2097 r.subnets_pos = map(subnets[0], this.cidr); 2098 r.subnets_neg = map(subnets[1], this.cidr); 2099 r.subnets_masked = subnets[2]; 2100 2101 push(match_rules, r); 2102 }; 2103 2104 let family = infer_family(zone.family, [ 2105 match_subnets, "subnet list" 2106 ]); 2107 2108 if (type(family) == "string") { 2109 this.warn_section(data, `${family}, skipping`); 2110 return; 2111 } 2112 2113 // group non-inverted device matches into wildcard and non-wildcard ones 2114 let devices = [], plain_devices = [], plain_invert_devices = [], wildcard_invert_devices = []; 2115 2116 for (let device in match_devices) { 2117 let m = match(device.device, /^([^+]*)(\+)?$/); 2118 2119 if (!m) { 2120 this.warn_section(data, `skipping invalid wildcard pattern '${device.device}'`); 2121 continue; 2122 } 2123 2124 // filter `+` (match any device) since nftables does not support 2125 // wildcard only matches 2126 if (!device.invert && m[0] == '+') 2127 continue; 2128 2129 // replace inverted `+` (match no device) with invalid pattern 2130 if (device.invert && m[0] == '+') { 2131 device.device = '/never/'; 2132 device.invert = false; 2133 } 2134 2135 // replace "name+" matches with "name*" 2136 else if (m[2] == '+') 2137 device.device = m[1] + '*'; 2138 2139 device.wildcard = !!m[2]; 2140 2141 if (!device.invert && device.wildcard) 2142 push(devices, [ [ device.device ], plain_invert_devices, wildcard_invert_devices ]); 2143 else if (!device.invert) 2144 push(plain_devices, device.device); 2145 else if (device.wildcard) 2146 push(wildcard_invert_devices, device.device); 2147 else 2148 push(plain_invert_devices, device.device); 2149 } 2150 2151 if (length(plain_devices)) 2152 push(devices, [ 2153 plain_devices, 2154 plain_invert_devices, 2155 wildcard_invert_devices 2156 ]); 2157 else if (!length(devices)) 2158 push(devices, [ 2159 null, 2160 plain_invert_devices, 2161 wildcard_invert_devices 2162 ]); 2163 2164 // emit zone jump rules for each device group 2165 if (length(match_devices) || length(match_subnets[0]) || length(match_subnets[1])) { 2166 for (let devgroup in devices) { 2167 // check if there's no AF specific bits, in this case we can do AF agnostic matching 2168 if (!family && !length(match_subnets[0]) && !length(match_subnets[1])) { 2169 add_rule(0, devgroup, [], zone); 2170 } 2171 2172 // we need to emit one or two AF specific rules 2173 else { 2174 if (!family || family == 4) 2175 for (let subnets in subnets_group_by_masking(match_subnets[0])) 2176 add_rule(4, devgroup, subnets, zone); 2177 2178 if (!family || family == 6) 2179 for (let subnets in subnets_group_by_masking(match_subnets[1])) 2180 add_rule(6, devgroup, subnets, zone); 2181 } 2182 } 2183 } 2184 2185 zone.family = family; 2186 2187 zone.match_rules = match_rules; 2188 2189 zone.masq4_src_subnets = subnets_group_by_masking(masq_src_subnets[0]); 2190 zone.masq4_dest_subnets = subnets_group_by_masking(masq_dest_subnets[0]); 2191 2192 zone.masq6_src_subnets = subnets_group_by_masking(masq_src_subnets[1]); 2193 zone.masq6_dest_subnets = subnets_group_by_masking(masq_dest_subnets[1]); 2194 2195 zone.sflags = {}; 2196 zone.sflags[zone.input] = true; 2197 2198 zone.dflags = {}; 2199 zone.dflags[zone.output] = true; 2200 zone.dflags[zone.forward] = true; 2201 2202 zone.match_devices = map(filter(match_devices, d => !d.invert), d => d.device); 2203 zone.match_subnets = map(filter(related_subnets, s => !s.invert && s.bits != -1), this.cidr); 2204 2205 zone.related_subnets = related_subnets; 2206 zone.related_physdevs = related_physdevs; 2207 2208 if (zone.masq || zone.masq6) 2209 zone.dflags.snat = true; 2210 2211 if ((zone.auto_helper && !(zone.masq || zone.masq6)) || length(zone.helper)) { 2212 zone.dflags.helper = true; 2213 2214 for (let helper in (length(zone.helper) ? zone.helper : this.state.helpers)) { 2215 if (!helper.available) 2216 continue; 2217 2218 for (let proto in helper.proto) { 2219 push(this.state.rules ||= [], { 2220 chain: `helper_${zone.name}`, 2221 family: helper.family, 2222 name: helper.description || helper.name, 2223 proto: proto, 2224 src: zone, 2225 dports_pos: [ this.port(helper.port) ], 2226 target: "helper", 2227 set_helper: helper 2228 }); 2229 } 2230 } 2231 } 2232 2233 push(this.state.zones ||= [], zone); 2234 }, 2235 2236 parse_forwarding: function(data) { 2237 let fwd = this.parse_options(data, { 2238 enabled: [ "bool", "1" ], 2239 2240 name: [ "string" ], 2241 family: [ "family" ], 2242 2243 src: [ "zone_ref", null, REQUIRED ], 2244 dest: [ "zone_ref", null, REQUIRED ] 2245 }); 2246 2247 if (fwd === false) { 2248 this.warn_section(data, "skipped due to invalid options"); 2249 return; 2250 } 2251 else if (!fwd.enabled) { 2252 this.warn_section(data, "is disabled, ignoring section"); 2253 return; 2254 } 2255 2256 let add_rule = (family, fwd) => { 2257 let f = { 2258 ...fwd, 2259 2260 family: family, 2261 proto: { any: true } 2262 }; 2263 2264 f.name ||= `Accept ${fwd.src.any ? "any" : fwd.src.zone.name} to ${fwd.dest.any ? "any" : fwd.dest.zone.name} ${family ? `${this.nfproto(family, true)} ` : ''}forwarding`; 2265 f.chain = fwd.src.any ? "forward" : `forward_${fwd.src.zone.name}`; 2266 2267 if (fwd.dest.any) 2268 f.target = "accept"; 2269 else 2270 f.jump_chain = `accept_to_${fwd.dest.zone.name}`; 2271 2272 push(this.state.rules ||= [], f); 2273 }; 2274 2275 2276 /* inherit family restrictions from related zones */ 2277 let family = infer_family(fwd.family, [ 2278 fwd.src?.zone, "source zone", 2279 fwd.dest?.zone, "destination zone" 2280 ]); 2281 2282 if (type(family) == "string") { 2283 this.warn_section(data, `${family}, skipping`); 2284 return; 2285 } 2286 2287 add_rule(family, fwd); 2288 2289 if (fwd.dest.zone) 2290 fwd.dest.zone.dflags.accept = true; 2291 }, 2292 2293 parse_rule: function(data) { 2294 let rule = this.parse_options(data, { 2295 enabled: [ "bool", "1" ], 2296 2297 name: [ "string", this.section_id(data[".name"]) ], 2298 _name: [ "string", null, DEPRECATED ], 2299 family: [ "family" ], 2300 2301 src: [ "zone_ref" ], 2302 dest: [ "zone_ref" ], 2303 2304 device: [ "device", null, NO_INVERT ], 2305 direction: [ "direction" ], 2306 2307 ipset: [ "setmatch" ], 2308 helper: [ "cthelper" ], 2309 set_helper: [ "cthelper", null, NO_INVERT ], 2310 2311 proto: [ "protocol", "tcpudp", PARSE_LIST | FLATTEN_LIST ], 2312 2313 src_ip: [ "network", null, PARSE_LIST ], 2314 src_mac: [ "mac", null, PARSE_LIST ], 2315 src_port: [ "port", null, PARSE_LIST ], 2316 2317 dest_ip: [ "network", null, PARSE_LIST ], 2318 dest_port: [ "port", null, PARSE_LIST ], 2319 2320 icmp_type: [ "icmptype", null, PARSE_LIST ], 2321 extra: [ "string", null, UNSUPPORTED ], 2322 2323 limit: [ "limit" ], 2324 limit_burst: [ "int" ], 2325 2326 utc_time: [ "bool" ], 2327 start_date: [ "date" ], 2328 stop_date: [ "date" ], 2329 start_time: [ "time" ], 2330 stop_time: [ "time" ], 2331 weekdays: [ "weekdays" ], 2332 monthdays: [ "monthdays", null, UNSUPPORTED ], 2333 2334 mark: [ "mark" ], 2335 set_mark: [ "mark", null, NO_INVERT ], 2336 set_xmark: [ "mark", null, NO_INVERT ], 2337 2338 dscp: [ "dscp" ], 2339 set_dscp: [ "dscp", null, NO_INVERT ], 2340 2341 counter: [ "bool", "1" ], 2342 log: [ "string" ], 2343 log_limit: [ "limit" ], 2344 2345 target: [ "target" ] 2346 }); 2347 2348 if (rule === false) { 2349 this.warn_section(data, "skipped due to invalid options"); 2350 return; 2351 } 2352 else if (!rule.enabled) { 2353 this.warn_section(data, "is disabled, ignoring section"); 2354 return; 2355 } 2356 2357 if (rule.target in ["helper", "notrack"] && (!rule.src || !rule.src.zone)) { 2358 this.warn_section(data, `must specify a source zone for target '${rule.target}'`); 2359 return; 2360 } 2361 else if (rule.target == "dscp" && !rule.set_dscp) { 2362 this.warn_section(data, "must specify option 'set_dscp' for target 'dscp'"); 2363 return; 2364 } 2365 else if (rule.target == "mark" && !rule.set_mark && !rule.set_xmark) { 2366 this.warn_section(data, "must specify option 'set_mark' or 'set_xmark' for target 'mark'"); 2367 return; 2368 } 2369 else if (rule.target == "helper" && !rule.set_helper) { 2370 this.warn_section(data, "must specify option 'set_helper' for target 'helper'"); 2371 return; 2372 } 2373 else if (rule.device?.any) { 2374 this.warn_section(data, "must not specify '*' as device"); 2375 return; 2376 } 2377 2378 switch (this.parse_bool(rule.log)) { 2379 case true: 2380 rule.log = `${rule.name}: `; 2381 break; 2382 2383 case false: 2384 delete rule.log; 2385 } 2386 2387 let ipset; 2388 2389 if (rule.ipset) { 2390 ipset = filter(this.state.ipsets, s => (s.name == rule.ipset.name))?.[0]; 2391 2392 if (!ipset) { 2393 this.warn_section(data, `references unknown set '${rule.ipset.name}'`); 2394 return; 2395 } 2396 2397 if (('inet_service' in ipset.types) && !ensure_tcpudp(rule.proto)) { 2398 this.warn_section(data, "references named set with port match but no UDP/TCP protocol, ignoring section"); 2399 return; 2400 } 2401 } 2402 2403 let need_src_action_chain = (rule) => (rule.src?.zone?.log && rule.target != "accept"); 2404 2405 let add_rule = (family, proto, saddrs, daddrs, sports, dports, icmptypes, icmpcodes, ipset, rule) => { 2406 let r = { 2407 ...rule, 2408 2409 family: family, 2410 proto: proto, 2411 has_addrs: !!(saddrs[0] || saddrs[1] || saddrs[2] || daddrs[0] || daddrs[1] || daddrs[2]), 2412 has_ports: !!(length(sports) || length(dports)), 2413 saddrs_pos: map(saddrs[0], this.cidr), 2414 saddrs_neg: map(saddrs[1], this.cidr), 2415 saddrs_masked: saddrs[2], 2416 daddrs_pos: map(daddrs[0], this.cidr), 2417 daddrs_neg: map(daddrs[1], this.cidr), 2418 daddrs_masked: daddrs[2], 2419 sports_pos: map(filter_pos(sports), this.port), 2420 sports_neg: map(filter_neg(sports), this.port), 2421 dports_pos: map(filter_pos(dports), this.port), 2422 dports_neg: map(filter_neg(dports), this.port), 2423 smacs_pos: map(filter_pos(rule.src_mac), m => m.mac), 2424 smacs_neg: map(filter_neg(rule.src_mac), m => m.mac), 2425 icmp_types: map(icmptypes, i => (family == 4 ? i.type : i.type6)), 2426 icmp_codes: map(icmpcodes, ic => `${(family == 4) ? ic.type : ic.type6} . ${(family == 4) ? ic.code_min : ic.code6_min}`) 2427 }; 2428 2429 if (!length(r.icmp_types)) 2430 delete r.icmp_types; 2431 2432 if (!length(r.icmp_codes)) 2433 delete r.icmp_codes; 2434 2435 if (r.set_mark) { 2436 r.set_xmark = { 2437 invert: r.set_mark.invert, 2438 mark: r.set_mark.mark, 2439 mask: r.set_mark.mark | r.set_mark.mask 2440 }; 2441 2442 delete r.set_mark; 2443 } 2444 2445 let set_types = map_setmatch(ipset, rule.ipset, proto.name); 2446 2447 if (set_types !== set_types) { 2448 this.warn_section(data, "destination MAC address matching not supported"); 2449 return; 2450 } else if (set_types) { 2451 r.ipset = { ...r.ipset, fields: set_types }; 2452 } 2453 2454 if (r.target == "notrack") { 2455 r.chain = `notrack_${r.src.zone.name}`; 2456 r.src.zone.dflags.notrack = true; 2457 } 2458 else if (r.target == "helper") { 2459 r.chain = `helper_${r.src.zone.name}`; 2460 r.src.zone.dflags.helper = true; 2461 } 2462 else if (r.target == "mark" || r.target == "dscp") { 2463 if ((r.src?.any && r.dest?.any) || (r.src?.zone && r.dest?.zone)) 2464 r.chain = "mangle_forward"; 2465 else if (r.src?.any && r.dest?.zone) 2466 r.chain = "mangle_postrouting"; 2467 else if (r.src?.zone && r.dest?.any) 2468 r.chain = "mangle_prerouting"; 2469 else if (r.src && !r.dest) 2470 r.chain = "mangle_input"; 2471 else 2472 r.chain = "mangle_output"; 2473 2474 if (r.src?.zone) { 2475 r.src.zone.dflags[r.target] = true; 2476 r.iifnames = null_if_empty(r.src.zone.match_devices); 2477 } 2478 2479 if (r.dest?.zone) { 2480 r.dest.zone.dflags[r.target] = true; 2481 r.oifnames = null_if_empty(r.dest.zone.match_devices); 2482 } 2483 } 2484 else { 2485 r.chain = "output"; 2486 2487 if (r.src) { 2488 if (!r.src.any) 2489 r.chain = `${r.dest ? "forward" : "input"}_${r.src.zone.name}`; 2490 else 2491 r.chain = r.dest ? "forward" : "input"; 2492 } 2493 2494 if (r.dest && !r.src) { 2495 if (!r.dest.any) 2496 r.chain = sprintf("output_%s", r.dest.zone.name); 2497 else 2498 r.chain = "output"; 2499 } 2500 2501 if (r.target && r.dest && !r.dest.any) { 2502 r.jump_chain = `${r.target}_to_${r.dest.zone.name}`; 2503 r.dest.zone.dflags[r.target] = true; 2504 } 2505 else if (r.target && need_src_action_chain(r)) { 2506 r.jump_chain = `${r.target}_from_${r.src.zone.name}`; 2507 r.src.zone.sflags[r.target] = true; 2508 } 2509 else if (r.target == "reject") 2510 r.jump_chain = "handle_reject"; 2511 } 2512 2513 if (r.device) 2514 r[r.direction ? "oifnames" : "iifnames"] = [ r.device.device ]; 2515 2516 push(this.state.rules ||= [], r); 2517 }; 2518 2519 for (let proto in rule.proto) { 2520 let sip, dip, sports, dports, itypes4, itypes6; 2521 let family = rule.family; 2522 2523 switch (proto.name) { 2524 case "icmp": 2525 itypes4 = filter(rule.icmp_type || [], family_is_ipv4); 2526 itypes6 = filter(rule.icmp_type || [], family_is_ipv6); 2527 break; 2528 2529 case "ipv6-icmp": 2530 family = 6; 2531 itypes6 = filter(rule.icmp_type || [], family_is_ipv6); 2532 break; 2533 2534 case "tcp": 2535 case "udp": 2536 sports = rule.src_port; 2537 dports = rule.dest_port; 2538 break; 2539 } 2540 2541 sip = subnets_split_af(rule.src_ip); 2542 dip = subnets_split_af(rule.dest_ip); 2543 2544 family = infer_family(family, [ 2545 ipset, "set match", 2546 sip, "source IP", 2547 dip, "destination IP", 2548 rule.src?.zone, "source zone", 2549 rule.dest?.zone, "destination zone", 2550 rule.helper, "helper match", 2551 rule.set_helper, "helper to set" 2552 ]); 2553 2554 if (type(family) == "string") { 2555 this.warn_section(data, `${family}, skipping`); 2556 continue; 2557 } 2558 2559 let has_ipv4_specifics = (length(sip[0]) || length(dip[0]) || length(itypes4) || rule.dscp !== null); 2560 let has_ipv6_specifics = (length(sip[1]) || length(dip[1]) || length(itypes6) || rule.dscp !== null); 2561 2562 /* if no family was configured, infer target family from IP addresses */ 2563 if (family === null) { 2564 if (has_ipv4_specifics && !has_ipv6_specifics) 2565 family = 4; 2566 else if (has_ipv6_specifics && !has_ipv4_specifics) 2567 family = 6; 2568 else 2569 family = 0; 2570 } 2571 2572 /* check if there's no AF specific bits, in this case we can do an AF agnostic rule */ 2573 if (!family && rule.target != "dscp" && !has_ipv4_specifics && !has_ipv6_specifics) { 2574 add_rule(0, proto, [], [], sports, dports, null, null, null, rule); 2575 } 2576 2577 /* we need to emit one or two AF specific rules */ 2578 else { 2579 if (family == 0 || family == 4) { 2580 let icmp_types = filter(itypes4, i => (i.code_min == 0 && i.code_max == 0xFF)); 2581 let icmp_codes = filter(itypes4, i => (i.code_min != 0 || i.code_max != 0xFF)); 2582 2583 for (let saddrs in subnets_group_by_masking(sip[0])) { 2584 for (let daddrs in subnets_group_by_masking(dip[0])) { 2585 if (length(icmp_types) || (!length(icmp_types) && !length(icmp_codes))) 2586 add_rule(4, proto, saddrs, daddrs, sports, dports, icmp_types, null, ipset, rule); 2587 2588 if (length(icmp_codes)) 2589 add_rule(4, proto, saddrs, daddrs, sports, dports, null, icmp_codes, ipset, rule); 2590 } 2591 } 2592 } 2593 2594 if (family == 0 || family == 6) { 2595 let icmp_types = filter(itypes6, i => (i.code_min == 0 && i.code_max == 0xFF)); 2596 let icmp_codes = filter(itypes6, i => (i.code_min != 0 || i.code_max != 0xFF)); 2597 2598 for (let saddrs in subnets_group_by_masking(sip[1])) { 2599 for (let daddrs in subnets_group_by_masking(dip[1])) { 2600 if (length(icmp_types) || (!length(icmp_types) && !length(icmp_codes))) 2601 add_rule(6, proto, saddrs, daddrs, sports, dports, icmp_types, null, ipset, rule); 2602 2603 if (length(icmp_codes)) 2604 add_rule(6, proto, saddrs, daddrs, sports, dports, null, icmp_codes, ipset, rule); 2605 } 2606 } 2607 } 2608 } 2609 } 2610 }, 2611 2612 parse_redirect: function(data) { 2613 let redir = this.parse_options(data, { 2614 enabled: [ "bool", "1" ], 2615 2616 name: [ "string", this.section_id(data[".name"]) ], 2617 _name: [ "string", null, DEPRECATED ], 2618 family: [ "family" ], 2619 2620 src: [ "zone_ref" ], 2621 dest: [ "zone_ref" ], 2622 2623 ipset: [ "setmatch" ], 2624 helper: [ "cthelper", null, NO_INVERT ], 2625 2626 proto: [ "protocol", "tcpudp", PARSE_LIST | FLATTEN_LIST ], 2627 2628 src_ip: [ "network" ], 2629 src_mac: [ "mac", null, PARSE_LIST ], 2630 src_port: [ "port" ], 2631 2632 src_dip: [ "network" ], 2633 src_dport: [ "port" ], 2634 2635 dest_ip: [ "network" ], 2636 dest_port: [ "port" ], 2637 2638 extra: [ "string", null, UNSUPPORTED ], 2639 2640 limit: [ "limit" ], 2641 limit_burst: [ "int" ], 2642 2643 utc_time: [ "bool" ], 2644 start_date: [ "date" ], 2645 stop_date: [ "date" ], 2646 start_time: [ "time" ], 2647 stop_time: [ "time" ], 2648 weekdays: [ "weekdays" ], 2649 monthdays: [ "monthdays", null, UNSUPPORTED ], 2650 2651 mark: [ "mark" ], 2652 2653 reflection: [ "bool", "1" ], 2654 reflection_src: [ "reflection_source", "internal" ], 2655 2656 reflection_zone: [ "zone_ref", null, PARSE_LIST ], 2657 2658 counter: [ "bool", "1" ], 2659 log: [ "string" ], 2660 log_limit: [ "limit" ], 2661 2662 target: [ "target", "dnat" ] 2663 }); 2664 2665 if (redir === false) { 2666 this.warn_section(data, "skipped due to invalid options"); 2667 return; 2668 } 2669 else if (!redir.enabled) { 2670 this.warn_section(data, "is disabled, ignoring section"); 2671 return; 2672 } 2673 2674 if (!(redir.target in ["dnat", "snat"])) { 2675 this.warn_section(data, "has invalid target specified, defaulting to dnat"); 2676 redir.target = "dnat"; 2677 } 2678 2679 switch (this.parse_bool(redir.log)) { 2680 case true: 2681 redir.log = `${redir.name}: `; 2682 break; 2683 2684 case false: 2685 delete redir.log; 2686 } 2687 2688 let ipset; 2689 2690 if (redir.ipset) { 2691 ipset = filter(this.state.ipsets, s => (s.name == redir.ipset.name))?.[0]; 2692 2693 if (!ipset) { 2694 this.warn_section(data, `references unknown set '${redir.ipset.name}'`); 2695 return; 2696 } 2697 2698 if (('inet_service' in ipset.types) && !ensure_tcpudp(redir.proto)) { 2699 this.warn_section(data, "references named set with port match but no UDP/TCP protocol, ignoring section"); 2700 return; 2701 } 2702 } 2703 2704 let resolve_dest = (redir) => { 2705 for (let zone in this.state.zones) { 2706 for (let zone_addr in zone.related_subnets) { 2707 for (let dest_addr in redir.dest_ip.addrs) { 2708 if (dest_addr.family != zone_addr.family) 2709 continue; 2710 2711 let a = apply_mask(dest_addr.addr, zone_addr.mask); 2712 let b = apply_mask(zone_addr.addr, zone_addr.mask); 2713 2714 if (a != b) 2715 continue; 2716 2717 redir.dest = { 2718 any: false, 2719 zone: zone 2720 }; 2721 2722 return true; 2723 } 2724 } 2725 } 2726 2727 return false; 2728 }; 2729 2730 if (redir.target == "dnat") { 2731 if (!redir.src) 2732 return this.warn_section(data, "has no source specified"); 2733 else if (redir.src.any) 2734 return this.warn_section(data, "must not have source '*' for dnat target"); 2735 else if (redir.dest_ip && redir.dest_ip.invert) 2736 return this.warn_section(data, "must not specify a negated 'dest_ip' value"); 2737 else if (redir.dest_ip && length(filter(redir.dest_ip.addrs, a => a.bits == -1))) 2738 return this.warn_section(data, "must not use non-contiguous masks in 'dest_ip'"); 2739 2740 if (!redir.dest && redir.dest_ip && resolve_dest(redir)) 2741 this.warn_section(data, `does not specify a destination, assuming '${redir.dest.zone.name}'`); 2742 2743 if (!redir.dest_port) 2744 redir.dest_port = redir.src_dport; 2745 2746 if (redir.reflection && redir.dest?.zone && redir.src.zone.masq) { 2747 redir.dest.zone.dflags.accept = true; 2748 redir.dest.zone.dflags.dnat = true; 2749 redir.dest.zone.dflags.snat = true; 2750 } 2751 2752 if (redir.helper) 2753 redir.src.zone.dflags.helper = true; 2754 2755 redir.src.zone.dflags[redir.target] = true; 2756 } 2757 else { 2758 if (!redir.dest) 2759 return this.warn_section(data, "has no destination specified"); 2760 else if (redir.dest.any) 2761 return this.warn_section(data, "must not have destination '*' for snat target"); 2762 else if (!redir.src_dip) 2763 return this.warn_section(data, "has no 'src_dip' option specified"); 2764 else if (redir.src_dip.invert) 2765 return this.warn_section(data, "must not specify a negated 'src_dip' value"); 2766 else if (length(filter(redir.src_dip.addrs, a => a.bits == -1))) 2767 return this.warn_section(data, "must not use non-contiguous masks in 'src_dip'"); 2768 else if (redir.src_mac) 2769 return this.warn_section(data, "must not use 'src_mac' option for snat target"); 2770 else if (redir.helper) 2771 return this.warn_section(data, "must not use 'helper' option for snat target"); 2772 2773 redir.dest.zone.dflags[redir.target] = true; 2774 } 2775 2776 let add_rule = (family, proto, saddrs, daddrs, raddrs, sport, dport, rport, ipset, redir) => { 2777 let r = { 2778 ...redir, 2779 2780 family: family, 2781 proto: proto, 2782 has_addrs: !!(saddrs[0] || saddrs[1] || saddrs[2] || daddrs[0] || daddrs[1] || daddrs[2]), 2783 has_ports: !!(sport || dport || rport), 2784 saddrs_pos: map(saddrs[0], this.cidr), 2785 saddrs_neg: map(saddrs[1], this.cidr), 2786 saddrs_masked: saddrs[2], 2787 daddrs_pos: map(daddrs[0], this.cidr), 2788 daddrs_neg: map(daddrs[1], this.cidr), 2789 daddrs_masked: daddrs[2], 2790 sports_pos: map(filter_pos(to_array(sport)), this.port), 2791 sports_neg: map(filter_neg(to_array(sport)), this.port), 2792 dports_pos: map(filter_pos(to_array(dport)), this.port), 2793 dports_neg: map(filter_neg(to_array(dport)), this.port), 2794 smacs_pos: map(filter_pos(redir.src_mac), m => m.mac), 2795 smacs_neg: map(filter_neg(redir.src_mac), m => m.mac), 2796 2797 raddr: raddrs ? raddrs[0] : null, 2798 rport: rport 2799 }; 2800 2801 let set_types = map_setmatch(ipset, redir.ipset, proto.name); 2802 2803 if (set_types !== set_types) { 2804 this.warn_section(data, "destination MAC address matching not supported"); 2805 return; 2806 } else if (set_types) { 2807 r.ipset = { ...r.ipset, fields: set_types }; 2808 } 2809 2810 switch (r.target) { 2811 case "dnat": 2812 r.chain = `dstnat_${r.src.zone.name}`; 2813 r.src.zone.dflags.dnat = true; 2814 2815 if (!r.raddr) 2816 r.target = "redirect"; 2817 2818 break; 2819 2820 case "snat": 2821 r.chain = `srcnat_${r.dest.zone.name}`; 2822 r.dest.zone.dflags.snat = true; 2823 break; 2824 } 2825 2826 push(this.state.redirects ||= [], r); 2827 }; 2828 2829 let to_hostaddr = (a) => { 2830 let bits = (a.family == 4) ? 32 : 128; 2831 2832 return { 2833 family: a.family, 2834 addr: apply_mask(a.addr, bits), 2835 bits: bits 2836 }; 2837 }; 2838 2839 for (let proto in redir.proto) { 2840 let sip, dip, rip, iip, eip, refip, sport, dport, rport; 2841 let family = redir.family; 2842 2843 if (proto.name == "ipv6-icmp") 2844 family = 6; 2845 2846 switch (redir.target) { 2847 case "dnat": 2848 sip = subnets_split_af(redir.src_ip); 2849 dip = subnets_split_af(redir.src_dip); 2850 rip = subnets_split_af(redir.dest_ip); 2851 2852 switch (proto.name) { 2853 case "tcp": 2854 case "udp": 2855 sport = redir.src_port; 2856 dport = redir.src_dport; 2857 rport = redir.dest_port; 2858 break; 2859 } 2860 2861 break; 2862 2863 case "snat": 2864 sip = subnets_split_af(redir.src_ip); 2865 dip = subnets_split_af(redir.dest_ip); 2866 rip = subnets_split_af(redir.src_dip); 2867 2868 switch (proto.name) { 2869 case "tcp": 2870 case "udp": 2871 sport = redir.src_port; 2872 dport = redir.dest_port; 2873 rport = redir.src_dport; 2874 break; 2875 } 2876 2877 break; 2878 } 2879 2880 family = infer_family(family, [ 2881 ipset, "set match", 2882 sip, "source IP", 2883 dip, "destination IP", 2884 rip, "rewrite IP", 2885 redir.src?.zone, "source zone", 2886 redir.dest?.zone, "destination zone", 2887 redir.helper, "helper match" 2888 ]); 2889 2890 if (type(family) == "string") { 2891 this.warn_section(data, `${family}, skipping`); 2892 continue; 2893 } 2894 2895 /* build reflection rules */ 2896 if (redir.target == "dnat" && redir.reflection && 2897 (length(rip[0]) || length(rip[1])) && redir.src?.zone && redir.dest?.zone) { 2898 let refredir = { 2899 name: `${redir.name} (reflection)`, 2900 2901 helper: redir.helper, 2902 2903 // XXX: this likely makes no sense for reflection rules 2904 //src_mac: redir.src_mac, 2905 2906 limit: redir.limit, 2907 limit_burst: redir.limit_burst, 2908 2909 start_date: redir.start_date, 2910 stop_date: redir.stop_date, 2911 start_time: redir.start_time, 2912 stop_time: redir.stop_time, 2913 weekdays: redir.weekdays, 2914 2915 mark: redir.mark 2916 }; 2917 2918 let eaddrs = length(dip) ? dip : subnets_split_af({ addrs: map(redir.src.zone.related_subnets, to_hostaddr) }); 2919 let rzones = length(redir.reflection_zone) ? redir.reflection_zone : [ redir.dest ]; 2920 2921 for (let rzone in rzones) { 2922 if (!is_family(rzone, family)) { 2923 this.warn_section(data, 2924 `is restricted to IPv${family} but referenced reflection zone is IPv${rzone.family} only, skipping`); 2925 continue; 2926 } 2927 2928 let iaddrs = subnets_split_af({ addrs: rzone.zone.related_subnets }); 2929 let refaddrs = (redir.reflection_src == "internal") ? iaddrs : eaddrs; 2930 2931 for (let i = 0; i <= 1; i++) { 2932 if (redir.src.zone[i ? "masq6" : "masq"] && length(rip[i])) { 2933 let snat_addr = refaddrs[i]?.[0]; 2934 2935 /* For internal reflection sources try to find a suitable candiate IP 2936 * among the reflection zone subnets which is within the same subnet 2937 * as the original DNAT destination. If we can't find any matching 2938 * one then simply take the first candidate. */ 2939 if (redir.reflection_src == "internal") { 2940 for (let zone_addr in rzone.zone.related_subnets) { 2941 if (zone_addr.family != rip[i][0].family) 2942 continue; 2943 2944 let r = apply_mask(rip[i][0].addr, zone_addr.mask); 2945 let a = apply_mask(zone_addr.addr, zone_addr.mask); 2946 2947 if (r != a) 2948 continue; 2949 2950 snat_addr = zone_addr; 2951 break; 2952 } 2953 } 2954 2955 if (!snat_addr) { 2956 this.warn_section(data, `${redir.reflection_src || "external"} rewrite IP cannot be determined, disabling reflection`); 2957 } 2958 else if (!length(iaddrs[i])) { 2959 this.warn_section(data, "internal address range cannot be determined, disabling reflection"); 2960 } 2961 else if (!length(eaddrs[i])) { 2962 this.warn_section(data, "external address range cannot be determined, disabling reflection"); 2963 } 2964 else { 2965 refredir.src = rzone; 2966 refredir.dest = null; 2967 refredir.target = "dnat"; 2968 2969 for (let saddrs in subnets_group_by_masking(iaddrs[i])) 2970 for (let daddrs in subnets_group_by_masking(eaddrs[i])) 2971 add_rule(i ? 6 : 4, proto, saddrs, daddrs, rip[i], sport, dport, rport, null, refredir); 2972 2973 refredir.src = null; 2974 refredir.dest = rzone; 2975 refredir.target = "snat"; 2976 2977 for (let daddrs in subnets_group_by_masking(rip[i])) 2978 for (let saddrs in subnets_group_by_masking(iaddrs[i])) 2979 add_rule(i ? 6 : 4, proto, saddrs, daddrs, [ to_hostaddr(snat_addr) ], null, rport, null, null, refredir); 2980 } 2981 } 2982 } 2983 } 2984 } 2985 2986 if (length(rip[0]) > 1 || length(rip[1]) > 1) 2987 this.warn_section(data, "specifies multiple rewrite addresses, using only first one"); 2988 2989 let has_ip4_addr = length(sip[0]) || length(dip[0]) || length(rip[0]), 2990 has_ip6_addr = length(sip[1]) || length(dip[1]) || length(rip[1]), 2991 has_any_addr = has_ip4_addr || has_ip6_addr; 2992 2993 /* check if there's no AF specific bits, in this case we can do an AF agnostic rule */ 2994 if (!family && !has_any_addr) { 2995 /* for backwards compatibility, treat unspecified family as IPv4 unless user explicitly requested any (0) */ 2996 if (family == null) 2997 family = 4; 2998 2999 add_rule(family, proto, [], [], null, sport, dport, rport, null, redir); 3000 } 3001 3002 /* we need to emit one or two AF specific rules */ 3003 else { 3004 if ((!family || family == 4) && (!has_any_addr || has_ip4_addr)) { 3005 for (let saddrs in subnets_group_by_masking(sip[0])) 3006 for (let daddrs in subnets_group_by_masking(dip[0])) 3007 add_rule(4, proto, saddrs, daddrs, rip[0], sport, dport, rport, ipset, redir); 3008 } 3009 3010 if ((!family || family == 6) && (!has_any_addr || has_ip6_addr)) { 3011 for (let saddrs in subnets_group_by_masking(sip[1])) 3012 for (let daddrs in subnets_group_by_masking(dip[1])) 3013 add_rule(6, proto, saddrs, daddrs, rip[1], sport, dport, rport, ipset, redir); 3014 } 3015 } 3016 } 3017 }, 3018 3019 parse_nat: function(data) { 3020 let snat = this.parse_options(data, { 3021 enabled: [ "bool", "1" ], 3022 3023 name: [ "string", this.section_id(data[".name"]) ], 3024 family: [ "family" ], 3025 3026 src: [ "zone_ref" ], 3027 device: [ "string" ], 3028 3029 ipset: [ "setmatch", null, UNSUPPORTED ], 3030 3031 proto: [ "protocol", "all", PARSE_LIST | FLATTEN_LIST ], 3032 3033 src_ip: [ "network" ], 3034 src_port: [ "port" ], 3035 3036 snat_ip: [ "network", null, NO_INVERT ], 3037 snat_port: [ "port", null, NO_INVERT ], 3038 3039 dest_ip: [ "network" ], 3040 dest_port: [ "port" ], 3041 3042 extra: [ "string", null, UNSUPPORTED ], 3043 3044 limit: [ "limit" ], 3045 limit_burst: [ "int" ], 3046 3047 connlimit_ports: [ "bool" ], 3048 3049 utc_time: [ "bool" ], 3050 start_date: [ "date" ], 3051 stop_date: [ "date" ], 3052 start_time: [ "time" ], 3053 stop_time: [ "time" ], 3054 weekdays: [ "weekdays" ], 3055 monthdays: [ "monthdays", null, UNSUPPORTED ], 3056 3057 mark: [ "mark" ], 3058 3059 counter: [ "bool", "1" ], 3060 log: [ "string" ], 3061 3062 target: [ "target", "masquerade" ] 3063 }); 3064 3065 if (snat === false) { 3066 this.warn_section(data, "skipped due to invalid options"); 3067 return; 3068 } 3069 else if (!snat.enabled) { 3070 this.warn_section(data, "is disabled, ignoring section"); 3071 return; 3072 } 3073 3074 if (!(snat.target in ["accept", "snat", "masquerade"])) { 3075 this.warn_section(data, "has invalid target specified, defaulting to masquerade"); 3076 snat.target = "masquerade"; 3077 } 3078 3079 if (snat.target == "snat" && !snat.snat_ip && !snat.snat_port) { 3080 this.warn_section(data, "needs either 'snat_ip' or 'snat_port' for target snat, ignoring section"); 3081 return; 3082 } 3083 else if (snat.target != "snat" && snat.snat_ip) { 3084 this.warn_section(data, "must not use 'snat_ip' for non-snat target, ignoring section"); 3085 return; 3086 } 3087 else if (snat.target != "snat" && snat.snat_port) { 3088 this.warn_section(data, "must not use 'snat_port' for non-snat target, ignoring section"); 3089 return; 3090 } 3091 3092 if ((snat.snat_port || snat.src_port || snat.dest_port) && !ensure_tcpudp(snat.proto)) { 3093 this.warn_section(data, "specifies ports but no UDP/TCP protocol, ignoring section"); 3094 return; 3095 } 3096 3097 if (snat.snat_ip && length(filter(snat.snat_ip.addrs, a => a.bits == -1 || a.invert))) { 3098 this.warn_section(data, "must not use inversion or non-contiguous masks in 'snat_ip', ignoring section"); 3099 return; 3100 } 3101 3102 switch (this.parse_bool(snat.log)) { 3103 case true: 3104 snat.log = `${snat.name}: `; 3105 break; 3106 3107 case false: 3108 delete snat.log; 3109 } 3110 3111 let add_rule = (family, proto, saddrs, daddrs, raddrs, sport, dport, rport, snat) => { 3112 let n = { 3113 ...snat, 3114 3115 family: family, 3116 proto: proto, 3117 has_addrs: !!(saddrs[0] || saddrs[1] || saddrs[2] || daddrs[0] || daddrs[1] || daddrs[2]), 3118 has_ports: !!(sport || dport), 3119 saddrs_pos: map(saddrs[0], this.cidr), 3120 saddrs_neg: map(saddrs[1], this.cidr), 3121 saddrs_masked: saddrs[2], 3122 daddrs_pos: map(daddrs[0], this.cidr), 3123 daddrs_neg: map(daddrs[1], this.cidr), 3124 daddrs_masked: daddrs[2], 3125 sports_pos: map(filter_pos(to_array(sport)), this.port), 3126 sports_neg: map(filter_neg(to_array(sport)), this.port), 3127 dports_pos: map(filter_pos(to_array(dport)), this.port), 3128 dports_neg: map(filter_neg(to_array(dport)), this.port), 3129 3130 raddr: raddrs ? raddrs[0] : null, 3131 rport: rport, 3132 3133 chain: snat.src?.zone ? `srcnat_${snat.src.zone.name}` : "srcnat" 3134 }; 3135 3136 push(this.state.redirects ||= [], n); 3137 }; 3138 3139 for (let proto in snat.proto) { 3140 let sip, dip, rip, sport, dport, rport; 3141 let family = snat.family; 3142 3143 sip = subnets_split_af(snat.src_ip); 3144 dip = subnets_split_af(snat.dest_ip); 3145 rip = subnets_split_af(snat.snat_ip); 3146 3147 switch (proto.name) { 3148 case "tcp": 3149 case "udp": 3150 sport = snat.src_port; 3151 dport = snat.dest_port; 3152 rport = snat.snat_port; 3153 break; 3154 } 3155 3156 if (length(rip[0]) > 1 || length(rip[1]) > 1) 3157 this.warn_section(data, "specifies multiple rewrite addresses, using only first one"); 3158 3159 family = infer_family(family, [ 3160 sip, "source IP", 3161 dip, "destination IP", 3162 rip, "rewrite IP", 3163 snat.src?.zone, "source zone" 3164 ]); 3165 3166 if (type(family) == "string") { 3167 this.warn_section(data, `${family}, skipping`); 3168 continue; 3169 } 3170 3171 if (snat.src?.zone) 3172 snat.src.zone.dflags.snat = true; 3173 3174 /* if no family was configured, infer target family from IP addresses */ 3175 if (family === null) { 3176 if ((length(sip[0]) || length(dip[0]) || length(rip[0])) && !length(sip[1]) && !length(dip[1]) && !length(rip[1])) 3177 family = 4; 3178 else if ((length(sip[1]) || length(dip[1]) || length(rip[1])) && !length(sip[0]) && !length(dip[0]) && !length(rip[0])) 3179 family = 6; 3180 else 3181 family = 4; /* default to IPv4 only for backwards compatibility, unless an explict family any was configured */ 3182 } 3183 3184 /* check if there's no AF specific bits, in this case we can do an AF agnostic rule */ 3185 if (!family && !length(sip[0]) && !length(sip[1]) && !length(dip[0]) && !length(dip[1]) && !length(rip[0]) && !length(rip[1])) { 3186 add_rule(0, proto, [], [], null, sport, dport, rport, snat); 3187 } 3188 3189 /* we need to emit one or two AF specific rules */ 3190 else { 3191 if (family == 0 || family == 4) 3192 for (let saddr in subnets_group_by_masking(sip[0])) 3193 for (let daddr in subnets_group_by_masking(dip[0])) 3194 add_rule(4, proto, saddr, daddr, rip[0], sport, dport, rport, snat); 3195 3196 if (family == 0 || family == 6) 3197 for (let saddr in subnets_group_by_masking(sip[1])) 3198 for (let daddr in subnets_group_by_masking(dip[1])) 3199 add_rule(6, proto, saddr, daddr, rip[1], sport, dport, rport, snat); 3200 } 3201 } 3202 }, 3203 3204 parse_include: function(data) { 3205 let inc = this.parse_options(data, { 3206 enabled: [ "bool", "1" ], 3207 3208 path: [ "string", null, REQUIRED ], 3209 type: [ "includetype", "script" ], 3210 3211 fw4_compatible: [ "bool", data.path != "/etc/firewall.user" ], 3212 3213 family: [ "family", null, UNSUPPORTED ], 3214 reload: [ "bool", null, UNSUPPORTED ], 3215 3216 position: [ "includeposition" ], 3217 chain: [ "identifier" ] 3218 }); 3219 3220 if (!inc.enabled) { 3221 this.warn_section(data, "is disabled, ignoring section"); 3222 return; 3223 } 3224 3225 if (inc.type == "script" && !inc.fw4_compatible) { 3226 this.warn_section(data, "is not marked as compatible with fw4, ignoring section"); 3227 this.warn_section(data, "requires 'option fw4_compatible 1' to be considered compatible"); 3228 return; 3229 } 3230 3231 for (let opt in [ "table", "chain", "position" ]) { 3232 if (inc.type != "nftables" && inc[opt]) { 3233 this.warn_section(data, `must not specify '${opt}' for non-nftables includes, ignoring section`); 3234 return; 3235 } 3236 } 3237 3238 switch (inc.position ??= 'table-append') { 3239 case 'ruleset-prepend': 3240 case 'ruleset-append': 3241 case 'table-prepend': 3242 case 'table-append': 3243 if (inc.chain) 3244 this.warn_section(data, `specifies 'chain' which has no effect for position ${inc.position}`); 3245 3246 delete inc.chain; 3247 break; 3248 3249 case 'chain-prepend': 3250 case 'chain-append': 3251 if (!inc.chain) { 3252 this.warn_section(data, `must specify 'chain' for position ${inc.position}, ignoring section`); 3253 return; 3254 } 3255 3256 break; 3257 } 3258 3259 let path = fs.readlink(inc.path) ?? inc.path; 3260 3261 if (!fs.access(path)) { 3262 this.warn_section(data, `specifies unreachable path '${path}', ignoring section`); 3263 return; 3264 } 3265 3266 if (!data['.name']) 3267 this.warn(`Automatically including '${path}'`); 3268 3269 push(this.state.includes ||= [], { ...inc, path }); 3270 }, 3271 3272 parse_ipset: function(data) { 3273 let ipset = this.parse_options(data, { 3274 enabled: [ "bool", "1" ], 3275 reload_set: [ "bool" ], 3276 counters: [ "bool" ], 3277 comment: [ "string" ], 3278 3279 name: [ "identifier", null, REQUIRED ], 3280 family: [ "family", "4" ], 3281 3282 storage: [ "string", null, UNSUPPORTED ], 3283 match: [ "ipsettype", null, PARSE_LIST ], 3284 3285 iprange: [ "string", null, UNSUPPORTED ], 3286 portrange: [ "string", null, UNSUPPORTED ], 3287 3288 netmask: [ "int", null, UNSUPPORTED ], 3289 maxelem: [ "int" ], 3290 hashsize: [ "int", null, UNSUPPORTED ], 3291 timeout: [ "int", "-1" ], 3292 3293 external: [ "string", null, UNSUPPORTED ], 3294 3295 entry: [ "string", null, PARSE_LIST ], 3296 loadfile: [ "string" ] 3297 }); 3298 3299 if (ipset === false) { 3300 this.warn_section(data, "skipped due to invalid options"); 3301 return; 3302 } 3303 else if (!ipset.enabled) { 3304 this.warn_section(data, "is disabled, ignoring section"); 3305 return; 3306 } 3307 3308 if (ipset.family == 0) { 3309 this.warn_section(data, "must not specify family 'any'"); 3310 return; 3311 } 3312 else if (!length(ipset.match)) { 3313 this.warn_section(data, "has no datatypes assigned"); 3314 return; 3315 } 3316 3317 let dirs = map(ipset.match, m => m[0]), 3318 types = map(ipset.match, m => m[1]), 3319 interval = false; 3320 3321 if ("set" in types) { 3322 this.warn_section(data, "match type 'set' is not supported"); 3323 return; 3324 } 3325 3326 if ("net" in types) { 3327 if (this.kernel < 0x05060000) { 3328 this.warn_section(data, "match type 'net' requires kernel 5.6 or later"); 3329 return; 3330 } 3331 3332 interval = true; 3333 } 3334 3335 let s = { 3336 ...ipset, 3337 3338 fw4types: types, 3339 3340 types: map(types, (t) => { 3341 switch (t) { 3342 case 'ip': 3343 case 'net': 3344 return (ipset.family == 4) ? 'ipv4_addr' : 'ipv6_addr'; 3345 3346 case 'mac': 3347 return 'ether_addr'; 3348 3349 case 'port': 3350 return 'inet_service'; 3351 } 3352 }), 3353 3354 directions: dirs, 3355 interval: interval 3356 }; 3357 3358 if (s.interval) 3359 push(s.flags ??= [], 'interval'); 3360 3361 if (s.timeout >= 0) 3362 push(s.flags ??= [], 'timeout'); 3363 3364 s.entries = filter(map(ipset.entry, (e) => { 3365 let v = this.parse_ipsetentry(e, s); 3366 3367 if (!v) 3368 this.warn_section(data, `ignoring invalid ipset entry '${e}'`); 3369 3370 return v; 3371 }), (e) => (e != null)); 3372 3373 push(this.state.ipsets ||= [], s); 3374 } 3375 };
This page was automatically generated by LXR 0.3.1. • OpenWrt