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[4]) : 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 source = `ubus:${ifc.interface}[${ifc.proto}]`; 663 let n = 0; 664 665 for (let rulespec in ifc.data.firewall) { 666 let device_scoped = (rulespec.type == 'rule' || rulespec.type == 'nat'); 667 let rule = { ...rulespec }; 668 669 if (rulespec.type != 'ipset' && rulespec.type != 'zone') 670 rule.name = `${source} ${rulespec.type || 'rule'} ${n}`; 671 672 // an empty device opts a rule or nat out of the interface pin 673 if (device_scoped) { 674 if (rule.device == "") { 675 if (!rule.src && !(rulespec.type == 'rule' && rule.dest)) { 676 this.warn_section(rule, "must reference a zone when opting out of device binding, ignoring section"); 677 n++; 678 continue; 679 } 680 681 delete rule.device; 682 } 683 else if (rule.device == null) 684 rule.device = ifc.l3_device ?? ifc.device; 685 } 686 687 if (rulespec.type == 'zone') 688 rule[".source"] = source; 689 690 push(rules, rule); 691 692 n++; 693 } 694 } 695 696 networks[ifc.interface] = net; 697 } 698 } 699 700 701 // 702 // Gather firewall rule definitions from ubus services 703 // 704 705 if (type(services) == "object") { 706 for (let svcname, service in services) { 707 if (type(service?.firewall) == "array") { 708 let source = `ubus:${svcname}`; 709 let n = 0; 710 711 for (let rulespec in services[svcname].firewall) { 712 let rule = { ...rulespec }; 713 714 if (rulespec.type == 'zone') 715 rule[".source"] = source; 716 else if (rulespec.type != 'ipset') 717 rule.name = `${source} ${rulespec.type || 'rule'} ${n}`; 718 719 push(rules, rule); 720 721 n++; 722 } 723 } 724 725 for (let svcinst, instance in service) { 726 if (type(instance?.firewall) == "array") { 727 let source = `ubus:${svcname}[${svcinst}]`; 728 let n = 0; 729 730 for (let rulespec in instance.firewall) { 731 let rule = { ...rulespec }; 732 733 if (rulespec.type == 'zone') 734 rule[".source"] = source; 735 else if (rulespec.type != 'ipset') 736 rule.name = `${source} ${rulespec.type || 'rule'} ${n}`; 737 738 push(rules, rule); 739 740 n++; 741 } 742 } 743 } 744 } 745 } 746 747 return { 748 networks: networks, 749 ubus_rules: rules 750 }; 751 }, 752 753 load: function(use_statefile) { 754 let self = this; 755 756 this.state = use_statefile ? this.read_state() : null; 757 758 this.cursor = uci.cursor(); 759 this.cursor.load("firewall"); 760 this.cursor.load("/usr/share/firewall4/helpers"); 761 762 if (!this.state) 763 this.state = this.read_ubus(); 764 765 this.kernel = this.read_kernel_version(); 766 767 768 // 769 // Read helper mapping 770 // 771 772 this.cursor.foreach("helpers", "helper", h => self.parse_helper(h)); 773 774 775 // 776 // Read default policies 777 // 778 779 this.cursor.foreach("firewall", "defaults", d => self.parse_defaults(d)); 780 781 if (!this.state.defaults) 782 this.parse_defaults({}); 783 784 785 // 786 // Warn about ubus rule specs of unknown type 787 // 788 789 let known_types = [ "ipset", "zone", "rule", "forwarding", "redirect", "nat" ]; 790 791 for (let r in this.state.ubus_rules) 792 if (r.type && !(r.type in known_types)) 793 this.warn_section(r, `specifies unknown type '${r.type}', ignoring section`); 794 795 796 // 797 // Build list of ipsets 798 // 799 800 if (!this.state.ipsets) { 801 map(filter(this.state.ubus_rules, n => (n.type == "ipset")), s => self.parse_ipset(s)); 802 this.cursor.foreach("firewall", "ipset", s => self.parse_ipset(s)); 803 } 804 805 806 // 807 // Build list of logical zones 808 // 809 810 if (!this.state.zones) { 811 map(filter(this.state.ubus_rules, z => (z.type == "zone")), z => self.parse_zone(z)); 812 this.cursor.foreach("firewall", "zone", z => self.parse_zone(z)); 813 } 814 815 816 // 817 // Build list of rules 818 // 819 820 map(filter(this.state.ubus_rules, r => (r.type == "rule")), r => self.parse_rule(r)); 821 this.cursor.foreach("firewall", "rule", r => self.parse_rule(r)); 822 823 824 // 825 // Build list of forwardings 826 // 827 828 map(filter(this.state.ubus_rules, f => (f.type == "forwarding")), f => self.parse_forwarding(f)); 829 this.cursor.foreach("firewall", "forwarding", f => self.parse_forwarding(f)); 830 831 832 // 833 // Build list of redirects 834 // 835 836 map(filter(this.state.ubus_rules, r => (r.type == "redirect")), r => self.parse_redirect(r)); 837 this.cursor.foreach("firewall", "redirect", r => self.parse_redirect(r)); 838 839 840 // 841 // Build list of snats 842 // 843 844 map(filter(this.state.ubus_rules, n => (n.type == "nat")), n => self.parse_nat(n)); 845 this.cursor.foreach("firewall", "nat", n => self.parse_nat(n)); 846 847 848 // 849 // Build list of includes 850 // 851 852 this.cursor.foreach("firewall", "include", i => self.parse_include(i)); 853 854 855 // 856 // Discover automatic includes 857 // 858 859 if (this.default_option("auto_includes")) { 860 for (let position in [ 'ruleset-pre', 'ruleset-post', 'table-pre', 'table-post', 'chain-pre', 'chain-post' ]) 861 for (let chain in (position in [ 'chain-pre', 'chain-post' ]) ? fs.lsdir(`/usr/share/nftables.d/${position}`) : [ null ]) 862 for (let path in fs.glob(`/usr/share/nftables.d/${position}${chain ? `/${chain}` : ''}/*.nft`)) 863 if (fs.access(path)) 864 this.parse_include({ type: 'nftables', position, chain, path }); 865 } 866 867 868 if (use_statefile) { 869 let fd = fs.open(STATEFILE, "w"); 870 871 if (fd) { 872 fd.write({ 873 zones: this.state.zones, 874 ipsets: this.state.ipsets, 875 networks: this.state.networks, 876 ubus_rules: this.state.ubus_rules, 877 includes: this.state.includes 878 }); 879 880 fd.close(); 881 } 882 else { 883 warn(`Unable to write '${STATEFILE}': ${fs.error()}\n`); 884 } 885 } 886 }, 887 888 warn: function(fmt, ...args) { 889 if (getenv("QUIET")) 890 return; 891 892 let msg = sprintf(fmt, ...args); 893 894 if (getenv("TTY")) 895 warn(`\033[33m${msg}\033[m\n`); 896 else 897 warn(`[!] ${msg}\n`); 898 }, 899 900 get: function(sid, opt) { 901 return this.cursor.get("firewall", sid, opt); 902 }, 903 904 get_all: function(sid) { 905 return this.cursor.get_all("firewall", sid); 906 }, 907 908 parse_options: function(s, spec) { 909 let rv = {}; 910 911 for (let key, val in spec) { 912 let datatype = `parse_${val[0]}`, 913 defval = val[1], 914 flags = val[2] || 0, 915 parsefn = (flags & PARSE_LIST) ? "parse_list" : "parse_opt"; 916 917 let res = this[parsefn](s, key, datatype, defval, flags); 918 919 if (res !== res) 920 return false; 921 922 if (type(res) == "object" && res.invert && (flags & NO_INVERT)) { 923 this.warn_section(s, `option '${key}' must not be negated`); 924 return false; 925 } 926 927 if (res != null) { 928 if (flags & DEPRECATED) 929 this.warn_section(s, `option '${key}' is deprecated by fw4`); 930 else if (flags & UNSUPPORTED) 931 this.warn_section(s, `option '${key}' is not supported by fw4`); 932 else 933 rv[key] = res; 934 } 935 } 936 937 for (let opt in s) { 938 if (index(opt, '.') != 0 && opt != 'type' && !exists(spec, opt)) { 939 this.warn_section(s, `specifies unknown option '${opt}'`); 940 } 941 } 942 943 return rv; 944 }, 945 946 parse_subnet: function(subnet) { 947 let parts = split(subnet, "/"); 948 let a, b, m, n; 949 950 switch (length(parts)) { 951 case 2: 952 a = iptoarr(parts[0]); 953 m = iptoarr(parts[1]); 954 955 if (!a) 956 return null; 957 958 if (m) { 959 if (length(a) != length(m)) 960 return null; 961 962 b = to_bits(parts[1]); 963 964 /* allow non-contiguous masks such as `::ffff:ffff:ffff:ffff` */ 965 if (b == null) { 966 b = -1; 967 968 for (let i, x in m) 969 a[i] &= x; 970 } 971 972 m = arrtoip(m); 973 } 974 else { 975 b = +parts[1]; 976 977 if (type(b) != "int") 978 return null; 979 980 m = to_mask(b, length(a) == 16); 981 b = max(-1, b); 982 } 983 984 return [{ 985 family: (length(a) == 16) ? 6 : 4, 986 addr: arrtoip(a), 987 mask: m, 988 bits: b 989 }]; 990 991 case 1: 992 parts = split(parts[0], "-"); 993 994 switch (length(parts)) { 995 case 2: 996 a = iptoarr(parts[0]); 997 b = iptoarr(parts[1]); 998 999 if (a && b && length(a) == length(b)) { 1000 return [{ 1001 family: (length(a) == 16) ? 6 : 4, 1002 addr: arrtoip(a), 1003 addr2: arrtoip(b), 1004 range: true 1005 }]; 1006 } 1007 1008 break; 1009 1010 case 1: 1011 a = iptoarr(parts[0]); 1012 1013 if (a) { 1014 return [{ 1015 family: (length(a) == 16) ? 6 : 4, 1016 addr: arrtoip(a), 1017 mask: to_mask(length(a) * 8, length(a) == 16), 1018 bits: length(a) * 8 1019 }]; 1020 } 1021 1022 n = this.state.networks[parts[0]]; 1023 1024 if (n) 1025 return [ ...(n.ipaddrs || []) ]; 1026 } 1027 } 1028 1029 return null; 1030 }, 1031 1032 parse_enum: function(val, choices) { 1033 if (type(val) == "string") { 1034 val = lc(val); 1035 1036 for (let i = 0; i < length(choices); i++) 1037 if (lc(substr(choices[i], 0, length(val))) == val) 1038 return choices[i]; 1039 } 1040 1041 return null; 1042 }, 1043 1044 section_id: function(sid) { 1045 let s = this.get_all(sid); 1046 1047 if (!s) 1048 return null; 1049 1050 if (s[".anonymous"]) { 1051 let c = 0; 1052 1053 this.cursor.foreach("firewall", s[".type"], function(ss) { 1054 if (ss[".name"] == s[".name"]) 1055 return false; 1056 1057 c++; 1058 }); 1059 1060 return `@${s['.type']}[${c}]`; 1061 } 1062 1063 return s[".name"]; 1064 }, 1065 1066 warn_section: function(s, msg) { 1067 if (s[".name"]) { 1068 if (s.name) 1069 this.warn("Section %s (%s) %s", this.section_id(s[".name"]), s.name, msg); 1070 else 1071 this.warn("Section %s %s", this.section_id(s[".name"]), msg); 1072 } 1073 else { 1074 let src = s[".source"] ?? "ubus"; 1075 1076 if (s.name) 1077 this.warn("%s %s (%s) %s", src, s.type || "rule", s.name, msg); 1078 else 1079 this.warn("%s %s %s", src, s.type || "rule", msg); 1080 } 1081 }, 1082 1083 parse_policy: function(val) { 1084 return this.parse_enum(val, [ 1085 "accept", 1086 "reject", 1087 "drop" 1088 ]); 1089 }, 1090 1091 parse_bool: function(val) { 1092 if (val == "1" || val == "on" || val == "true" || val == "yes") 1093 return true; 1094 else if (val == "0" || val == "off" || val == "false" || val == "no") 1095 return false; 1096 else 1097 return null; 1098 }, 1099 1100 parse_family: function(val) { 1101 if (val == 'any' || val == 'all' || val == '*') 1102 return 0; 1103 else if (val == 'inet' || index(val, '4') > -1) 1104 return 4; 1105 else if (index(val, '6') > -1) 1106 return 6; 1107 1108 return null; 1109 }, 1110 1111 parse_zone_ref: function(val) { 1112 if (val == null) 1113 return null; 1114 1115 if (val == '*') 1116 return { any: true }; 1117 1118 for (let zone in this.state.zones) { 1119 if (zone.name == val) { 1120 return { 1121 any: false, 1122 zone: zone 1123 }; 1124 } 1125 } 1126 1127 return null; 1128 }, 1129 1130 parse_device: function(val) { 1131 let rv = this.parse_invert(val); 1132 1133 if (!rv) 1134 return null; 1135 1136 if (rv.val == '*') 1137 rv.any = true; 1138 else 1139 rv.device = rv.val; 1140 1141 return rv; 1142 }, 1143 1144 parse_direction: function(val) { 1145 if (val == 'in' || val == 'ingress') 1146 return false; 1147 else if (val == 'out' || val == 'egress') 1148 return true; 1149 1150 return null; 1151 }, 1152 1153 parse_setmatch: function(val) { 1154 let rv = this.parse_invert(val); 1155 1156 if (!rv) 1157 return null; 1158 1159 rv.val = trim(replace(rv.val, /^[^ \t]+/, function(m) { 1160 rv.name = m; 1161 return ''; 1162 })); 1163 1164 let dir = split(rv.val, /[ \t,]/); 1165 1166 for (let i = 0; i < 3 && i < length(dir); i++) { 1167 if (dir[i] == "dst" || dir[i] == "dest") 1168 (rv.dir ||= [])[i] = "dst"; 1169 else if (dir[i] == "src") 1170 (rv.dir ||= [])[i] = "src"; 1171 } 1172 1173 return length(rv.name) ? rv : null; 1174 }, 1175 1176 parse_cthelper: function(val) { 1177 let rv = this.parse_invert(val); 1178 1179 if (!rv) 1180 return null; 1181 1182 let helper = filter(this.state.helpers, h => (h.name == rv.val))?.[0]; 1183 1184 return helper ? { ...rv, ...helper } : null; 1185 }, 1186 1187 parse_protocol: function(val) { 1188 let p = this.parse_invert(val); 1189 1190 if (!p) 1191 return null; 1192 1193 p.val = lc(p.val); 1194 1195 switch (p.val) { 1196 case 'all': 1197 case 'any': 1198 case '*': 1199 p.any = true; 1200 break; 1201 1202 case '1': 1203 case 'icmp': 1204 p.name = 'icmp'; 1205 break; 1206 1207 case '58': 1208 case 'icmpv6': 1209 case 'ipv6-icmp': 1210 p.name = 'ipv6-icmp'; 1211 break; 1212 1213 case 'tcpudp': 1214 return [ 1215 { invert: p.invert, name: 'tcp' }, 1216 { invert: p.invert, name: 'udp' } 1217 ]; 1218 1219 case '6': 1220 p.name = 'tcp'; 1221 break; 1222 1223 case '17': 1224 p.name = 'udp'; 1225 break; 1226 1227 default: 1228 p.name = p.val; 1229 } 1230 1231 return (p.any || length(p.name)) ? p : null; 1232 }, 1233 1234 parse_mac: function(val) { 1235 let mac = this.parse_invert(val); 1236 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; 1237 1238 if (!m) 1239 return null; 1240 1241 mac.mac = sprintf('%02x:%02x:%02x:%02x:%02x:%02x', 1242 hex(m[1]), hex(m[2]), hex(m[3]), 1243 hex(m[4]), hex(m[5]), hex(m[6])); 1244 1245 return mac; 1246 }, 1247 1248 parse_port: function(val) { 1249 let port = this.parse_invert(val); 1250 let m = port ? match(port.val, /^([0-9]{1,5})([-:]([0-9]{1,5}))?$/i) : null; 1251 1252 if (!m) 1253 return null; 1254 1255 if (m[3]) { 1256 let min_port = +m[1]; 1257 let max_port = +m[3]; 1258 1259 if (min_port > max_port || 1260 min_port < 0 || max_port < 0 || 1261 min_port > 65535 || max_port > 65535) 1262 return null; 1263 1264 port.min = min_port; 1265 port.max = max_port; 1266 } 1267 else { 1268 let pn = +m[1]; 1269 1270 if (pn != pn || pn < 0 || pn > 65535) 1271 return null; 1272 1273 port.min = pn; 1274 port.max = pn; 1275 } 1276 1277 return port; 1278 }, 1279 1280 parse_network: function(val) { 1281 let rv = this.parse_invert(val); 1282 1283 if (!rv) 1284 return null; 1285 1286 let nets = this.parse_subnet(rv.val); 1287 1288 if (nets === null) 1289 return null; 1290 1291 if (length(nets)) 1292 rv.addrs = [ ...nets ]; 1293 1294 return rv; 1295 }, 1296 1297 parse_icmptype: function(val) { 1298 let rv = {}; 1299 1300 if (exists(ipv4_icmptypes, val)) { 1301 rv.family = 4; 1302 1303 rv.type = ipv4_icmptypes[val][0]; 1304 rv.code_min = ipv4_icmptypes[val][1]; 1305 rv.code_max = ipv4_icmptypes[val][2]; 1306 } 1307 1308 if (exists(ipv6_icmptypes, val)) { 1309 rv.family = rv.family ? 0 : 6; 1310 1311 rv.type6 = ipv6_icmptypes[val][0]; 1312 rv.code6_min = ipv6_icmptypes[val][1]; 1313 rv.code6_max = ipv6_icmptypes[val][2]; 1314 } 1315 1316 if (!exists(rv, "family")) { 1317 let m = match(val, /^([0-9]+)(\/([0-9]+))?$/); 1318 1319 if (!m) 1320 return null; 1321 1322 if (m[3]) { 1323 rv.type = +m[1]; 1324 rv.code_min = +m[3]; 1325 rv.code_max = rv.code_min; 1326 } 1327 else { 1328 rv.type = +m[1]; 1329 rv.code_min = 0; 1330 rv.code_max = 0xFF; 1331 } 1332 1333 if (rv.type > 0xFF || rv.code_min > 0xFF || rv.code_max > 0xFF) 1334 return null; 1335 1336 rv.family = 0; 1337 1338 rv.type6 = rv.type; 1339 rv.code6_min = rv.code_min; 1340 rv.code6_max = rv.code_max; 1341 } 1342 1343 return rv; 1344 }, 1345 1346 parse_invert: function(val) { 1347 if (val == null) 1348 return null; 1349 1350 let rv = { invert: false }; 1351 1352 rv.val = trim(replace(val, /^[ \t]*!/, () => (rv.invert = true, ''))); 1353 1354 return length(rv.val) ? rv : null; 1355 }, 1356 1357 parse_limit: function(val) { 1358 let rv = this.parse_invert(val); 1359 let m = rv ? match(rv.val, /^([0-9]+)(\/([a-z]+))?$/) : null; 1360 1361 if (!m) 1362 return null; 1363 1364 let n = +m[1]; 1365 let u = m[3] ? this.parse_enum(m[3], [ "second", "minute", "hour", "day" ]) : "second"; 1366 1367 if (!u) 1368 return null; 1369 1370 rv.rate = n; 1371 rv.unit = u; 1372 1373 return rv; 1374 }, 1375 1376 parse_int: function(val) { 1377 let n = +val; 1378 1379 return (n == n) ? n : null; 1380 }, 1381 1382 parse_date: function(val) { 1383 let d = match(val, /^([0-9]{4})(-([0-9]{1,2})(-([0-9]{1,2})(T([0-9:]+))?)?)?$/); 1384 1385 if (d == null || d[1] < 1970 || d[1] > 2038 || d[3] > 12 || d[5] > 31) 1386 return null; 1387 1388 let t = this.parse_time(d[7] ?? "0"); 1389 1390 if (t == null) 1391 return null; 1392 1393 return { 1394 year: +d[1], 1395 month: +d[3] || 1, 1396 day: +d[5] || 1, 1397 ...t 1398 }; 1399 }, 1400 1401 parse_time: function(val) { 1402 let t = match(val, /^([0-9]{1,2})(:([0-9]{1,2})(:([0-9]{1,2}))?)?$/); 1403 1404 if (t == null || t[1] > 23 || t[3] > 59 || t[5] > 59) 1405 return null; 1406 1407 return { 1408 hour: +t[1], 1409 min: +t[3], 1410 sec: +t[5] 1411 }; 1412 }, 1413 1414 parse_weekdays: function(val) { 1415 let rv = this.parse_invert(val); 1416 1417 if (!rv) 1418 return null; 1419 1420 for (let day in to_array(rv.val)) { 1421 day = this.parse_enum(day, [ 1422 "Monday", 1423 "Tuesday", 1424 "Wednesday", 1425 "Thursday", 1426 "Friday", 1427 "Saturday", 1428 "Sunday" 1429 ]); 1430 1431 if (!day) 1432 return null; 1433 1434 (rv.days ||= {})[day] = true; 1435 } 1436 1437 rv.days = keys(rv.days); 1438 1439 return rv.days ? rv : null; 1440 }, 1441 1442 parse_monthdays: function(val) { 1443 let rv = this.parse_invert(val); 1444 1445 if (!rv) 1446 return null; 1447 1448 for (let day in to_array(rv.val)) { 1449 day = +day; 1450 1451 if (day < 1 || day > 31) 1452 return null; 1453 1454 (rv.days ||= [])[day] = true; 1455 } 1456 1457 return rv.days ? rv : null; 1458 }, 1459 1460 parse_mark: function(val) { 1461 let rv = this.parse_invert(val); 1462 let m = rv ? match(rv.val, /^(0?x?[0-9a-f]+)(\/(0?x?[0-9a-f]+))?$/i) : null; 1463 1464 if (!m) 1465 return null; 1466 1467 let n = +m[1]; 1468 1469 if (n != n || n > 0xFFFFFFFF) 1470 return null; 1471 1472 rv.mark = n; 1473 rv.mask = 0xFFFFFFFF; 1474 1475 if (m[3]) { 1476 n = +m[3]; 1477 1478 if (n != n || n > 0xFFFFFFFF) 1479 return null; 1480 1481 rv.mask = n; 1482 } 1483 1484 return rv; 1485 }, 1486 1487 parse_dscp: function(val) { 1488 let rv = this.parse_invert(val); 1489 1490 if (!rv) 1491 return null; 1492 1493 rv.val = uc(rv.val); 1494 1495 if (exists(dscp_classes, rv.val)) { 1496 rv.dscp = dscp_classes[rv.val]; 1497 } 1498 else { 1499 let n = +rv.val; 1500 1501 if (n != n || n < 0 || n > 0x3F) 1502 return null; 1503 1504 rv.dscp = n; 1505 } 1506 1507 return rv; 1508 }, 1509 1510 parse_target: function(val) { 1511 return this.parse_enum(val, [ 1512 "accept", 1513 "reject", 1514 "drop", 1515 "notrack", 1516 "helper", 1517 "mark", 1518 "dscp", 1519 "dnat", 1520 "snat", 1521 "masquerade", 1522 "accept", 1523 "reject", 1524 "drop" 1525 ]); 1526 }, 1527 1528 parse_reject_code: function(val) { 1529 return this.parse_enum(val, [ 1530 "tcp-reset", 1531 "port-unreachable", 1532 "admin-prohibited", 1533 "host-unreachable", 1534 "no-route" 1535 ]); 1536 }, 1537 1538 parse_reflection_source: function(val) { 1539 return this.parse_enum(val, [ 1540 "internal", 1541 "external" 1542 ]); 1543 }, 1544 1545 parse_ipsettype: function(val) { 1546 let m = match(val, /^(src|dst|dest)_(.+)$/); 1547 let t = this.parse_enum(m ? m[2] : val, [ 1548 "ip", 1549 "port", 1550 "mac", 1551 "net", 1552 "set" 1553 ]); 1554 1555 return t ? [ (!m || m[1] == 'src') ? 'src' : 'dst', t ] : null; 1556 }, 1557 1558 parse_ipsetentry: function(val, set) { 1559 let values = split(val, /[ \t]+/); 1560 1561 if (length(values) != length(set.types)) 1562 return null; 1563 1564 let rv = []; 1565 let ip, mac, port; 1566 1567 for (let i, t in set.types) { 1568 switch (t) { 1569 case 'ipv4_addr': 1570 ip = filter(this.parse_subnet(values[i]), a => (a.family == 4)); 1571 1572 switch (length(ip) ?? 0) { 1573 case 0: return null; 1574 case 1: break; 1575 default: this.warn("Set entry '%s' resolves to multiple addresses, using first one", values[i]); 1576 } 1577 1578 rv[i] = ("net" in set.fw4types) ? `${ip[0].addr}/${ip[0].bits}` : ip[0].addr; 1579 break; 1580 1581 case 'ipv6_addr': 1582 ip = filter(this.parse_subnet(values[i]), a => (a.family == 6)); 1583 1584 switch (length(ip) ?? 0) { 1585 case 0: return null; 1586 case 1: break; 1587 case 2: this.warn("Set entry '%s' resolves to multiple addresses, using first one", values[i]); 1588 } 1589 1590 rv[i] = ("net" in set.fw4types) ? `${ip[0].addr}/${ip[0].bits}` : ip[0].addr; 1591 1592 break; 1593 1594 case 'ether_addr': 1595 mac = this.parse_mac(values[i]); 1596 1597 if (!mac || mac.invert) 1598 return null; 1599 1600 rv[i] = mac.mac; 1601 break; 1602 1603 case 'inet_service': 1604 port = this.parse_port(values[i]); 1605 1606 if (!port || port.invert || port.min != port.max) 1607 return null; 1608 1609 rv[i] = port.min; 1610 break; 1611 1612 default: 1613 rv[i] = values[i]; 1614 } 1615 } 1616 1617 return length(rv) ? rv : null; 1618 }, 1619 1620 parse_includetype: function(val) { 1621 return this.parse_enum(val, [ 1622 "script", 1623 "nftables" 1624 ]); 1625 }, 1626 1627 parse_includeposition: function(val) { 1628 return replace(this.parse_enum(val, [ 1629 "ruleset-prepend", 1630 "ruleset-postpend", 1631 "ruleset-append", 1632 1633 "table-prepend", 1634 "table-postpend", 1635 "table-append", 1636 1637 "chain-prepend", 1638 "chain-postpend", 1639 "chain-append" 1640 ]), "postpend", "append"); 1641 }, 1642 1643 parse_identifier: function(val) { 1644 return match(val, /^[a-zA-Z_.][a-zA-Z0-9\/_.-]*$/)?.[0]; 1645 }, 1646 1647 parse_string: function(val) { 1648 return "" + val; 1649 }, 1650 1651 parse_opt: function(s, opt, fn, defval, flags) { 1652 let val = s[opt]; 1653 1654 if (val === null) { 1655 if (flags & REQUIRED) { 1656 this.warn_section(s, `option '${opt}' is mandatory but not set`); 1657 return NaN; 1658 } 1659 1660 val = defval; 1661 } 1662 1663 if (type(val) == "array") { 1664 this.warn_section(s, `option '${opt}' must not be a list`); 1665 return NaN; 1666 } 1667 else if (val == null) { 1668 return null; 1669 } 1670 1671 let res = this[fn](val); 1672 1673 if (res === null) { 1674 this.warn_section(s, `option '${opt}' specifies invalid value '${val}'`); 1675 return NaN; 1676 } 1677 1678 return res; 1679 }, 1680 1681 parse_list: function(s, opt, fn, defval, flags) { 1682 let val = s[opt]; 1683 let rv = []; 1684 1685 if (val == null) { 1686 if (flags & REQUIRED) { 1687 this.warn_section(s, `option '${opt}' is mandatory but not set`); 1688 return NaN; 1689 } 1690 1691 val = defval; 1692 } 1693 1694 for (val in to_array(val)) { 1695 let res = this[fn](val); 1696 1697 if (res === null) { 1698 this.warn_section(s, `option '${opt}' specifies invalid value '${val}'`); 1699 return NaN; 1700 } 1701 1702 if (flags & FLATTEN_LIST) 1703 push(rv, ...to_array(res)); 1704 else 1705 push(rv, res); 1706 } 1707 1708 return length(rv) ? rv : null; 1709 }, 1710 1711 quote: function(s, force) { 1712 if (force === true || !match(s, /^([0-9A-Fa-f:.\/-]+)( \. [0-9A-Fa-f:.\/-]+)*$/)) 1713 return `"${replace(s, '"', "'")}"`; 1714 1715 return s; 1716 }, 1717 1718 cidr: function(a) { 1719 if (a.range) 1720 return `${a.addr}-${a.addr2}`; 1721 1722 if ((a.family == 4 && a.bits == 32) || 1723 (a.family == 6 && a.bits == 128)) 1724 return a.addr; 1725 1726 if (a.bits >= 0) 1727 return `${apply_mask(a.addr, a.bits)}/${a.bits}`; 1728 1729 return `${a.addr}/${a.mask}`; 1730 }, 1731 1732 host: function(a, v6brackets) { 1733 return a.range 1734 ? `${a.addr}-${a.addr2}` 1735 : (a.family == 6 && v6brackets) 1736 ? `[${apply_mask(a.addr, a.bits)}]` : apply_mask(a.addr, a.bits); 1737 }, 1738 1739 port: function(p) { 1740 if (p.min == p.max) 1741 return `${p.min}`; 1742 1743 return `${p.min}-${p.max}`; 1744 }, 1745 1746 set: function(v, force) { 1747 let seen = {}; 1748 1749 v = filter(to_array(v), item => !seen[item]++); 1750 1751 if (force || length(v) != 1) 1752 return `{ ${join(', ', map(v, this.quote))} }`; 1753 1754 return this.quote(v[0]); 1755 }, 1756 1757 concat: function(v) { 1758 return join(' . ', to_array(v)); 1759 }, 1760 1761 ipproto: function(family) { 1762 switch (family) { 1763 case 4: 1764 return "ip"; 1765 1766 case 6: 1767 return "ip6"; 1768 } 1769 }, 1770 1771 nfproto: function(family, human_readable) { 1772 switch (family) { 1773 case 4: 1774 return human_readable ? "IPv4" : "ipv4"; 1775 1776 case 6: 1777 return human_readable ? "IPv6" : "ipv6"; 1778 1779 default: 1780 return human_readable ? "IPv4/IPv6" : null; 1781 } 1782 }, 1783 1784 l4proto: function(family, proto) { 1785 switch (proto.name) { 1786 case 'icmp': 1787 switch (family ?? 0) { 1788 case 0: 1789 return this.set(['icmp', 'ipv6-icmp']); 1790 1791 case 6: 1792 return 'ipv6-icmp'; 1793 } 1794 1795 default: 1796 return proto.name; 1797 } 1798 }, 1799 1800 datetime: function(stamp) { 1801 return sprintf('"%04d-%02d-%02d %02d:%02d:%02d"', 1802 stamp.year, stamp.month, stamp.day, 1803 stamp.hour, stamp.min, stamp.sec); 1804 }, 1805 1806 date: function(stamp) { 1807 return sprintf('"%04d-%02d-%02d"', stamp.year, stamp.month, stamp.day); 1808 }, 1809 1810 datestamp: function(stamp) { 1811 return exists(stamp, 'hour') ? this.datetime(stamp) : this.date(stamp); 1812 }, 1813 1814 time: function(stamp) { 1815 return sprintf('"%02d:%02d:%02d"', stamp.hour, stamp.min, stamp.sec); 1816 }, 1817 1818 hex: function(n) { 1819 return sprintf('0x%x', n); 1820 }, 1821 1822 is_loopback_dev: function(dev) { 1823 return !!(+fs.readfile(`/sys/class/net/${dev}/flags`) & 0x8); 1824 }, 1825 1826 is_loopback_addr: function(addr) { 1827 return (index(addr, "127.") == 0 || addr == "::1" || addr == "::1/128"); 1828 }, 1829 1830 filter_loopback_devs: function(devs, invert) { 1831 return null_if_empty(filter(devs, d => (this.is_loopback_dev(d) == invert))); 1832 }, 1833 1834 filter_loopback_addrs: function(addrs, invert) { 1835 return null_if_empty(filter(addrs, a => (this.is_loopback_addr(a) == invert))); 1836 }, 1837 1838 1839 input_policy: function(reject_as_drop) { 1840 return (!reject_as_drop || this.state.defaults.input != 'reject') ? this.state.defaults.input : 'drop'; 1841 }, 1842 1843 output_policy: function(reject_as_drop) { 1844 return (!reject_as_drop || this.state.defaults.output != 'reject') ? this.state.defaults.output : 'drop'; 1845 }, 1846 1847 forward_policy: function(reject_as_drop) { 1848 return (!reject_as_drop || this.state.defaults.forward != 'reject') ? this.state.defaults.forward : 'drop'; 1849 }, 1850 1851 default_option: function(flag) { 1852 return this.state.defaults[flag]; 1853 }, 1854 1855 helpers: function() { 1856 return this.state.helpers; 1857 }, 1858 1859 zones: function() { 1860 return this.state.zones; 1861 }, 1862 1863 rules: function(chain) { 1864 return filter(this.state.rules, r => (r.chain == chain)); 1865 }, 1866 1867 redirects: function(chain) { 1868 return filter(this.state.redirects, r => (r.chain == chain)); 1869 }, 1870 1871 ipsets: function() { 1872 return this.state.ipsets; 1873 }, 1874 1875 includes: function(position, chain) { 1876 let stmts = []; 1877 let pad = ''; 1878 let pre = ''; 1879 1880 switch (position) { 1881 case 'table-prepend': 1882 case 'table-append': 1883 pad = '\t'; 1884 pre = '\n'; 1885 break; 1886 1887 case 'chain-prepend': 1888 case 'chain-append': 1889 pad = '\t\t'; 1890 break; 1891 1892 default: 1893 pre = '\n'; 1894 } 1895 1896 push(stmts, pre); 1897 1898 for (let inc in this.state.includes) 1899 if (inc.type == 'nftables' && inc.position == position && (!chain || inc.chain == chain)) 1900 push(stmts, `${pad}include "${inc.path}"\n`); 1901 1902 print(length(stmts) > 1 ? join('', stmts) : ''); 1903 }, 1904 1905 parse_setfile: function(set, cb) { 1906 let fd = fs.open(set.loadfile, "r"); 1907 1908 if (!fd) { 1909 warn(`Unable to load file '${set.loadfile}' for set '${set.name}': ${fs.error()}\n`); 1910 return; 1911 } 1912 1913 let count = 0; 1914 1915 for (let line = fd.read("line"); length(line); line = fd.read("line")) { 1916 line = trim(line); 1917 1918 if (length(line) == 0 || ord(line) == 35) 1919 continue; 1920 1921 let v = this.parse_ipsetentry(line, set); 1922 1923 if (!v) { 1924 this.warn(`Skipping invalid entry '${line}' in file '${set.loadfile}' for set '${set.name}'`); 1925 continue; 1926 } 1927 1928 cb(v); 1929 1930 count++; 1931 } 1932 1933 fd.close(); 1934 1935 return count; 1936 }, 1937 1938 print_setentries: function(set) { 1939 let first = true; 1940 let printer = (entry) => { 1941 if (first) { 1942 print("\t\telements = {\n"); 1943 first = false; 1944 } 1945 1946 print("\t\t\t", join(" . ", entry), ",\n"); 1947 }; 1948 1949 map(set.entries, printer); 1950 1951 if (set.loadfile) 1952 this.parse_setfile(set, printer); 1953 1954 if (!first) 1955 print("\t\t}\n"); 1956 }, 1957 1958 parse_helper: function(data) { 1959 let helper = this.parse_options(data, { 1960 name: [ "string", null, REQUIRED ], 1961 description: [ "string" ], 1962 module: [ "string" ], 1963 family: [ "family" ], 1964 proto: [ "protocol", null, PARSE_LIST | FLATTEN_LIST | NO_INVERT ], 1965 port: [ "port", null, NO_INVERT ] 1966 }); 1967 1968 if (helper === false) { 1969 this.warn("Helper definition '%s' skipped due to invalid options", data.name || data['.name']); 1970 return; 1971 } 1972 else if (helper.proto.any) { 1973 this.warn("Helper definition '%s' must not specify wildcard protocol", data.name || data['.name']); 1974 return; 1975 } 1976 else if (length(helper.proto) > 1) { 1977 this.warn("Helper definition '%s' must not specify multiple protocols", data.name || data['.name']); 1978 return; 1979 } 1980 1981 helper.available = (fs.stat(`/sys/module/${helper.module}`)?.type == "directory"); 1982 1983 push(this.state.helpers ||= [], helper); 1984 }, 1985 1986 parse_defaults: function(data) { 1987 if (this.state.defaults) { 1988 this.warn_section(data, ": ignoring duplicate defaults section"); 1989 return; 1990 } 1991 1992 let defs = this.parse_options(data, { 1993 input: [ "policy", "drop" ], 1994 output: [ "policy", "drop" ], 1995 forward: [ "policy", "drop" ], 1996 1997 drop_invalid: [ "bool" ], 1998 tcp_reject_code: [ "reject_code", "tcp-reset" ], 1999 any_reject_code: [ "reject_code", "port-unreachable" ], 2000 2001 syn_flood: [ "bool" ], 2002 synflood_protect: [ "bool" ], 2003 synflood_rate: [ "limit", "25/second" ], 2004 synflood_burst: [ "int", "50" ], 2005 2006 tcp_syncookies: [ "bool", "1" ], 2007 tcp_ecn: [ "int" ], 2008 tcp_window_scaling: [ "bool", "1" ], 2009 2010 accept_redirects: [ "bool" ], 2011 accept_source_route: [ "bool" ], 2012 2013 auto_helper: [ "bool", "1" ], 2014 custom_chains: [ "bool", null, UNSUPPORTED ], 2015 disable_ipv6: [ "bool", null, UNSUPPORTED ], 2016 flow_offloading: [ "bool", "0" ], 2017 flow_offloading_hw: [ "bool", "0" ], 2018 2019 auto_includes: [ "bool", "1" ] 2020 }); 2021 2022 if (defs.synflood_protect === null) 2023 defs.synflood_protect = defs.syn_flood; 2024 2025 delete defs.syn_flood; 2026 2027 this.state.defaults = defs; 2028 }, 2029 2030 parse_zone: function(data) { 2031 let policy_def = (!data[".name"] && data.type == "zone") 2032 ? "drop" 2033 : (this.state.defaults ? null : "drop"); 2034 2035 let zone = this.parse_options(data, { 2036 enabled: [ "bool", "1" ], 2037 2038 name: [ "identifier", null, REQUIRED ], 2039 family: [ "family" ], 2040 2041 network: [ "device", null, PARSE_LIST ], 2042 device: [ "device", null, PARSE_LIST ], 2043 subnet: [ "network", null, PARSE_LIST ], 2044 2045 input: [ "policy", policy_def ?? this.state.defaults.input ], 2046 output: [ "policy", policy_def ?? this.state.defaults.output ], 2047 forward: [ "policy", policy_def ?? this.state.defaults.forward ], 2048 2049 masq: [ "bool" ], 2050 masq_allow_invalid: [ "bool" ], 2051 masq_src: [ "network", null, PARSE_LIST ], 2052 masq_dest: [ "network", null, PARSE_LIST ], 2053 2054 masq6: [ "bool" ], 2055 2056 extra: [ "string", null, UNSUPPORTED ], 2057 extra_src: [ "string", null, UNSUPPORTED ], 2058 extra_dest: [ "string", null, UNSUPPORTED ], 2059 2060 mtu_fix: [ "bool" ], 2061 custom_chains: [ "bool", null, UNSUPPORTED ], 2062 2063 log: [ "int" ], 2064 log_limit: [ "limit" ], 2065 2066 auto_helper: [ "bool", "1" ], 2067 helper: [ "cthelper", null, PARSE_LIST ], 2068 2069 counter: [ "bool", "1" ] 2070 }); 2071 2072 if (zone === false) { 2073 this.warn_section(data, "skipped due to invalid options"); 2074 return; 2075 } 2076 else if (!zone.enabled) { 2077 this.warn_section(data, "is disabled, ignoring section"); 2078 return; 2079 } 2080 2081 if (filter(this.state.zones, z => z.name == zone.name)?.[0]) { 2082 this.warn_section(data, "has a duplicate name, ignoring section"); 2083 return; 2084 } 2085 2086 if (!data[".name"]) { 2087 let uci_names = []; 2088 2089 this.cursor.foreach("firewall", "zone", s => push(uci_names, s.name)); 2090 2091 if (index(uci_names, zone.name) >= 0) { 2092 this.warn_section(data, "duplicates a uci zone, ignoring section"); 2093 return; 2094 } 2095 } 2096 2097 for (let helper in zone.helper) { 2098 if (!helper.available) { 2099 this.warn_section(data, `uses unavailable ct helper '${zone.helper.name}'`); 2100 } 2101 } 2102 2103 if (zone.mtu_fix && this.kernel < 0x040a0000) { 2104 this.warn_section(data, "option 'mtu_fix' requires kernel 4.10 or later"); 2105 return; 2106 } 2107 2108 if (this.state.defaults?.auto_helper === false) 2109 zone.auto_helper = false; 2110 2111 let match_devices = []; 2112 let related_physdevs = []; 2113 let related_subnets = []; 2114 let related_ubus_networks = []; 2115 let match_subnets, masq_src_subnets, masq_dest_subnets; 2116 2117 for (let name, net in this.state.networks) { 2118 if (net.zone === zone.name) 2119 push(related_ubus_networks, { invert: false, device: name }); 2120 } 2121 2122 zone.network = [ ...to_array(zone.network), ...related_ubus_networks ]; 2123 2124 for (let e in zone.network) { 2125 if (exists(this.state.networks, e.device)) { 2126 let net = this.state.networks[e.device]; 2127 2128 if (net.device) { 2129 push(match_devices, { 2130 invert: e.invert, 2131 device: net.device 2132 }); 2133 } 2134 2135 if (net.physdev && !e.invert) 2136 push(related_physdevs, net.physdev); 2137 2138 push(related_subnets, ...(net.ipaddrs || [])); 2139 } 2140 } 2141 2142 push(match_devices, ...to_array(zone.device)); 2143 2144 match_subnets = subnets_split_af(zone.subnet); 2145 masq_src_subnets = subnets_split_af(zone.masq_src); 2146 masq_dest_subnets = subnets_split_af(zone.masq_dest); 2147 2148 push(related_subnets, ...(match_subnets[0] || []), ...(match_subnets[1] || [])); 2149 2150 let match_rules = []; 2151 2152 let add_rule = (family, devices, subnets, zone) => { 2153 let r = {}; 2154 2155 r.family = family; 2156 2157 r.devices_pos = null_if_empty(devices[0]); 2158 r.devices_neg = null_if_empty(devices[1]); 2159 r.devices_neg_wildcard = null_if_empty(devices[2]); 2160 2161 r.subnets_pos = map(subnets[0], this.cidr); 2162 r.subnets_neg = map(subnets[1], this.cidr); 2163 r.subnets_masked = subnets[2]; 2164 2165 push(match_rules, r); 2166 }; 2167 2168 let family = infer_family(zone.family, [ 2169 match_subnets, "subnet list" 2170 ]); 2171 2172 if (type(family) == "string") { 2173 this.warn_section(data, `${family}, skipping`); 2174 return; 2175 } 2176 2177 // group non-inverted device matches into wildcard and non-wildcard ones 2178 let devices = [], plain_devices = [], plain_invert_devices = [], wildcard_invert_devices = []; 2179 2180 for (let device in match_devices) { 2181 let m = match(device.device, /^([^+]*)(\+)?$/); 2182 2183 if (!m) { 2184 this.warn_section(data, `skipping invalid wildcard pattern '${device.device}'`); 2185 continue; 2186 } 2187 2188 // filter `+` (match any device) since nftables does not support 2189 // wildcard only matches 2190 if (!device.invert && m[0] == '+') 2191 continue; 2192 2193 // replace inverted `+` (match no device) with invalid pattern 2194 if (device.invert && m[0] == '+') { 2195 device.device = '/never/'; 2196 device.invert = false; 2197 } 2198 2199 // replace "name+" matches with "name*" 2200 else if (m[2] == '+') 2201 device.device = m[1] + '*'; 2202 2203 device.wildcard = !!m[2]; 2204 2205 if (!device.invert && device.wildcard) 2206 push(devices, [ [ device.device ], plain_invert_devices, wildcard_invert_devices ]); 2207 else if (!device.invert) 2208 push(plain_devices, device.device); 2209 else if (device.wildcard) 2210 push(wildcard_invert_devices, device.device); 2211 else 2212 push(plain_invert_devices, device.device); 2213 } 2214 2215 if (length(plain_devices)) 2216 push(devices, [ 2217 plain_devices, 2218 plain_invert_devices, 2219 wildcard_invert_devices 2220 ]); 2221 else if (!length(devices)) 2222 push(devices, [ 2223 null, 2224 plain_invert_devices, 2225 wildcard_invert_devices 2226 ]); 2227 2228 // emit zone jump rules for each device group 2229 if (length(match_devices) || length(match_subnets[0]) || length(match_subnets[1])) { 2230 for (let devgroup in devices) { 2231 // check if there's no AF specific bits, in this case we can do AF agnostic matching 2232 if (!family && !length(match_subnets[0]) && !length(match_subnets[1])) { 2233 add_rule(0, devgroup, [], zone); 2234 } 2235 2236 // we need to emit one or two AF specific rules 2237 else { 2238 if (!family || family == 4) 2239 for (let subnets in subnets_group_by_masking(match_subnets[0])) 2240 add_rule(4, devgroup, subnets, zone); 2241 2242 if (!family || family == 6) 2243 for (let subnets in subnets_group_by_masking(match_subnets[1])) 2244 add_rule(6, devgroup, subnets, zone); 2245 } 2246 } 2247 } 2248 2249 zone.family = family; 2250 2251 zone.match_rules = match_rules; 2252 2253 zone.masq4_src_subnets = subnets_group_by_masking(masq_src_subnets[0]); 2254 zone.masq4_dest_subnets = subnets_group_by_masking(masq_dest_subnets[0]); 2255 2256 zone.masq6_src_subnets = subnets_group_by_masking(masq_src_subnets[1]); 2257 zone.masq6_dest_subnets = subnets_group_by_masking(masq_dest_subnets[1]); 2258 2259 zone.sflags = {}; 2260 zone.sflags[zone.input] = true; 2261 2262 zone.dflags = {}; 2263 zone.dflags[zone.output] = true; 2264 zone.dflags[zone.forward] = true; 2265 2266 zone.match_devices = map(filter(match_devices, d => !d.invert), d => d.device); 2267 zone.match_subnets = map(filter(related_subnets, s => !s.invert && s.bits != -1), this.cidr); 2268 2269 zone.related_subnets = related_subnets; 2270 zone.related_physdevs = related_physdevs; 2271 2272 if (zone.masq || zone.masq6) 2273 zone.dflags.snat = true; 2274 2275 if ((zone.auto_helper && !(zone.masq || zone.masq6)) || length(zone.helper)) { 2276 zone.dflags.helper = true; 2277 2278 for (let helper in (length(zone.helper) ? zone.helper : this.state.helpers)) { 2279 if (!helper.available) 2280 continue; 2281 2282 for (let proto in helper.proto) { 2283 push(this.state.rules ||= [], { 2284 chain: `helper_${zone.name}`, 2285 family: helper.family, 2286 name: helper.description || helper.name, 2287 proto: proto, 2288 src: zone, 2289 dports_pos: [ this.port(helper.port) ], 2290 target: "helper", 2291 set_helper: helper 2292 }); 2293 } 2294 } 2295 } 2296 2297 push(this.state.zones ||= [], zone); 2298 }, 2299 2300 parse_forwarding: function(data) { 2301 let fwd = this.parse_options(data, { 2302 enabled: [ "bool", "1" ], 2303 2304 name: [ "string" ], 2305 family: [ "family" ], 2306 2307 src: [ "zone_ref", null, REQUIRED ], 2308 dest: [ "zone_ref", null, REQUIRED ] 2309 }); 2310 2311 if (fwd === false) { 2312 this.warn_section(data, "skipped due to invalid options"); 2313 return; 2314 } 2315 else if (!fwd.enabled) { 2316 this.warn_section(data, "is disabled, ignoring section"); 2317 return; 2318 } 2319 2320 let add_rule = (family, fwd) => { 2321 let f = { 2322 ...fwd, 2323 2324 family: family, 2325 proto: { any: true } 2326 }; 2327 2328 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`; 2329 f.chain = fwd.src.any ? "forward" : `forward_${fwd.src.zone.name}`; 2330 2331 if (fwd.dest.any) 2332 f.target = "accept"; 2333 else 2334 f.jump_chain = `accept_to_${fwd.dest.zone.name}`; 2335 2336 push(this.state.rules ||= [], f); 2337 }; 2338 2339 2340 /* inherit family restrictions from related zones */ 2341 let family = infer_family(fwd.family, [ 2342 fwd.src?.zone, "source zone", 2343 fwd.dest?.zone, "destination zone" 2344 ]); 2345 2346 if (type(family) == "string") { 2347 this.warn_section(data, `${family}, skipping`); 2348 return; 2349 } 2350 2351 add_rule(family, fwd); 2352 2353 if (fwd.dest.zone) 2354 fwd.dest.zone.dflags.accept = true; 2355 }, 2356 2357 parse_rule: function(data) { 2358 let rule = this.parse_options(data, { 2359 enabled: [ "bool", "1" ], 2360 2361 name: [ "string", this.section_id(data[".name"]) ], 2362 _name: [ "string", null, DEPRECATED ], 2363 family: [ "family" ], 2364 2365 src: [ "zone_ref" ], 2366 dest: [ "zone_ref" ], 2367 2368 device: [ "device", null, NO_INVERT ], 2369 direction: [ "direction" ], 2370 2371 ipset: [ "setmatch" ], 2372 helper: [ "cthelper" ], 2373 set_helper: [ "cthelper", null, NO_INVERT ], 2374 2375 proto: [ "protocol", "tcpudp", PARSE_LIST | FLATTEN_LIST ], 2376 2377 src_ip: [ "network", null, PARSE_LIST ], 2378 src_mac: [ "mac", null, PARSE_LIST ], 2379 src_port: [ "port", null, PARSE_LIST ], 2380 2381 dest_ip: [ "network", null, PARSE_LIST ], 2382 dest_port: [ "port", null, PARSE_LIST ], 2383 2384 icmp_type: [ "icmptype", null, PARSE_LIST ], 2385 extra: [ "string", null, UNSUPPORTED ], 2386 2387 limit: [ "limit" ], 2388 limit_burst: [ "int" ], 2389 2390 utc_time: [ "bool" ], 2391 start_date: [ "date" ], 2392 stop_date: [ "date" ], 2393 start_time: [ "time" ], 2394 stop_time: [ "time" ], 2395 weekdays: [ "weekdays" ], 2396 monthdays: [ "monthdays", null, UNSUPPORTED ], 2397 2398 mark: [ "mark" ], 2399 set_mark: [ "mark", null, NO_INVERT ], 2400 set_xmark: [ "mark", null, NO_INVERT ], 2401 2402 dscp: [ "dscp" ], 2403 set_dscp: [ "dscp", null, NO_INVERT ], 2404 2405 counter: [ "bool", "1" ], 2406 log: [ "string" ], 2407 log_limit: [ "limit" ], 2408 2409 target: [ "target" ] 2410 }); 2411 2412 if (rule === false) { 2413 this.warn_section(data, "skipped due to invalid options"); 2414 return; 2415 } 2416 else if (!rule.enabled) { 2417 this.warn_section(data, "is disabled, ignoring section"); 2418 return; 2419 } 2420 2421 if (rule.target in ["helper", "notrack"] && (!rule.src || !rule.src.zone)) { 2422 this.warn_section(data, `must specify a source zone for target '${rule.target}'`); 2423 return; 2424 } 2425 else if (rule.target == "dscp" && !rule.set_dscp) { 2426 this.warn_section(data, "must specify option 'set_dscp' for target 'dscp'"); 2427 return; 2428 } 2429 else if (rule.target == "mark" && !rule.set_mark && !rule.set_xmark) { 2430 this.warn_section(data, "must specify option 'set_mark' or 'set_xmark' for target 'mark'"); 2431 return; 2432 } 2433 else if (rule.target == "helper" && !rule.set_helper) { 2434 this.warn_section(data, "must specify option 'set_helper' for target 'helper'"); 2435 return; 2436 } 2437 else if (rule.device?.any) { 2438 this.warn_section(data, "must not specify '*' as device"); 2439 return; 2440 } 2441 2442 switch (this.parse_bool(rule.log)) { 2443 case true: 2444 rule.log = `${rule.name}: `; 2445 break; 2446 2447 case false: 2448 delete rule.log; 2449 } 2450 2451 let ipset; 2452 2453 if (rule.ipset) { 2454 ipset = filter(this.state.ipsets, s => (s.name == rule.ipset.name))?.[0]; 2455 2456 if (!ipset) { 2457 this.warn_section(data, `references unknown set '${rule.ipset.name}'`); 2458 return; 2459 } 2460 2461 if (('inet_service' in ipset.types) && !ensure_tcpudp(rule.proto)) { 2462 this.warn_section(data, "references named set with port match but no UDP/TCP protocol, ignoring section"); 2463 return; 2464 } 2465 } 2466 2467 let need_src_action_chain = (rule) => (rule.src?.zone?.log && rule.target != "accept"); 2468 2469 let add_rule = (family, proto, saddrs, daddrs, sports, dports, icmptypes, icmpcodes, ipset, rule) => { 2470 let r = { 2471 ...rule, 2472 2473 family: family, 2474 proto: proto, 2475 has_addrs: !!(saddrs[0] || saddrs[1] || saddrs[2] || daddrs[0] || daddrs[1] || daddrs[2]), 2476 has_ports: !!(length(sports) || length(dports)), 2477 saddrs_pos: map(saddrs[0], this.cidr), 2478 saddrs_neg: map(saddrs[1], this.cidr), 2479 saddrs_masked: saddrs[2], 2480 daddrs_pos: map(daddrs[0], this.cidr), 2481 daddrs_neg: map(daddrs[1], this.cidr), 2482 daddrs_masked: daddrs[2], 2483 sports_pos: map(filter_pos(sports), this.port), 2484 sports_neg: map(filter_neg(sports), this.port), 2485 dports_pos: map(filter_pos(dports), this.port), 2486 dports_neg: map(filter_neg(dports), this.port), 2487 smacs_pos: map(filter_pos(rule.src_mac), m => m.mac), 2488 smacs_neg: map(filter_neg(rule.src_mac), m => m.mac), 2489 icmp_types: map(icmptypes, i => (family == 4 ? i.type : i.type6)), 2490 icmp_codes: map(icmpcodes, ic => `${(family == 4) ? ic.type : ic.type6} . ${(family == 4) ? ic.code_min : ic.code6_min}`) 2491 }; 2492 2493 if (!length(r.icmp_types)) 2494 delete r.icmp_types; 2495 2496 if (!length(r.icmp_codes)) 2497 delete r.icmp_codes; 2498 2499 if (r.set_mark) { 2500 r.set_xmark = { 2501 invert: r.set_mark.invert, 2502 mark: r.set_mark.mark, 2503 mask: r.set_mark.mark | r.set_mark.mask 2504 }; 2505 2506 delete r.set_mark; 2507 } 2508 2509 let set_types = map_setmatch(ipset, rule.ipset, proto.name); 2510 2511 if (set_types !== set_types) { 2512 this.warn_section(data, "destination MAC address matching not supported"); 2513 return; 2514 } else if (set_types) { 2515 r.ipset = { ...r.ipset, fields: set_types }; 2516 } 2517 2518 if (r.target == "notrack") { 2519 r.chain = `notrack_${r.src.zone.name}`; 2520 r.src.zone.dflags.notrack = true; 2521 } 2522 else if (r.target == "helper") { 2523 r.chain = `helper_${r.src.zone.name}`; 2524 r.src.zone.dflags.helper = true; 2525 } 2526 else if (r.target == "mark" || r.target == "dscp") { 2527 if ((r.src?.any && r.dest?.any) || (r.src?.zone && r.dest?.zone)) 2528 r.chain = "mangle_forward"; 2529 else if (r.src?.any && r.dest?.zone) 2530 r.chain = "mangle_postrouting"; 2531 else if (r.src?.zone && r.dest?.any) 2532 r.chain = "mangle_prerouting"; 2533 else if (r.src && !r.dest) 2534 r.chain = "mangle_input"; 2535 else 2536 r.chain = "mangle_output"; 2537 2538 if (r.src?.zone) { 2539 r.src.zone.dflags[r.target] = true; 2540 r.iifnames = null_if_empty(r.src.zone.match_devices); 2541 } 2542 2543 if (r.dest?.zone) { 2544 r.dest.zone.dflags[r.target] = true; 2545 r.oifnames = null_if_empty(r.dest.zone.match_devices); 2546 } 2547 } 2548 else { 2549 r.chain = "output"; 2550 2551 if (r.src) { 2552 if (!r.src.any) 2553 r.chain = `${r.dest ? "forward" : "input"}_${r.src.zone.name}`; 2554 else 2555 r.chain = r.dest ? "forward" : "input"; 2556 } 2557 2558 if (r.dest && !r.src) { 2559 if (!r.dest.any) 2560 r.chain = sprintf("output_%s", r.dest.zone.name); 2561 else 2562 r.chain = "output"; 2563 } 2564 2565 if (r.target && r.dest && !r.dest.any) { 2566 r.jump_chain = `${r.target}_to_${r.dest.zone.name}`; 2567 r.dest.zone.dflags[r.target] = true; 2568 } 2569 else if (r.target && need_src_action_chain(r)) { 2570 r.jump_chain = `${r.target}_from_${r.src.zone.name}`; 2571 r.src.zone.sflags[r.target] = true; 2572 } 2573 else if (r.target == "reject") 2574 r.jump_chain = "handle_reject"; 2575 } 2576 2577 if (r.device) 2578 r[r.direction ? "oifnames" : "iifnames"] = [ r.device.device ]; 2579 2580 push(this.state.rules ||= [], r); 2581 }; 2582 2583 for (let proto in rule.proto) { 2584 let sip, dip, sports, dports, itypes4, itypes6; 2585 let family = rule.family; 2586 2587 switch (proto.name) { 2588 case "icmp": 2589 itypes4 = filter(rule.icmp_type || [], family_is_ipv4); 2590 itypes6 = filter(rule.icmp_type || [], family_is_ipv6); 2591 break; 2592 2593 case "ipv6-icmp": 2594 family = 6; 2595 itypes6 = filter(rule.icmp_type || [], family_is_ipv6); 2596 break; 2597 2598 case "tcp": 2599 case "udp": 2600 sports = rule.src_port; 2601 dports = rule.dest_port; 2602 break; 2603 } 2604 2605 sip = subnets_split_af(rule.src_ip); 2606 dip = subnets_split_af(rule.dest_ip); 2607 2608 family = infer_family(family, [ 2609 ipset, "set match", 2610 sip, "source IP", 2611 dip, "destination IP", 2612 rule.src?.zone, "source zone", 2613 rule.dest?.zone, "destination zone", 2614 rule.helper, "helper match", 2615 rule.set_helper, "helper to set" 2616 ]); 2617 2618 if (type(family) == "string") { 2619 this.warn_section(data, `${family}, skipping`); 2620 continue; 2621 } 2622 2623 let has_ipv4_specifics = (length(sip[0]) || length(dip[0]) || length(itypes4) || rule.dscp !== null); 2624 let has_ipv6_specifics = (length(sip[1]) || length(dip[1]) || length(itypes6) || rule.dscp !== null); 2625 2626 /* if no family was configured, infer target family from IP addresses */ 2627 if (family === null) { 2628 if (has_ipv4_specifics && !has_ipv6_specifics) 2629 family = 4; 2630 else if (has_ipv6_specifics && !has_ipv4_specifics) 2631 family = 6; 2632 else 2633 family = 0; 2634 } 2635 2636 /* check if there's no AF specific bits, in this case we can do an AF agnostic rule */ 2637 if (!family && rule.target != "dscp" && !has_ipv4_specifics && !has_ipv6_specifics) { 2638 add_rule(0, proto, [], [], sports, dports, null, null, ipset, rule); 2639 } 2640 2641 /* we need to emit one or two AF specific rules */ 2642 else { 2643 if (family == 0 || family == 4) { 2644 let icmp_types = filter(itypes4, i => (i.code_min == 0 && i.code_max == 0xFF)); 2645 let icmp_codes = filter(itypes4, i => (i.code_min != 0 || i.code_max != 0xFF)); 2646 2647 for (let saddrs in subnets_group_by_masking(sip[0])) { 2648 for (let daddrs in subnets_group_by_masking(dip[0])) { 2649 if (length(icmp_types) || (!length(icmp_types) && !length(icmp_codes))) 2650 add_rule(4, proto, saddrs, daddrs, sports, dports, icmp_types, null, ipset, rule); 2651 2652 if (length(icmp_codes)) 2653 add_rule(4, proto, saddrs, daddrs, sports, dports, null, icmp_codes, ipset, rule); 2654 } 2655 } 2656 } 2657 2658 if (family == 0 || family == 6) { 2659 let icmp_types = filter(itypes6, i => (i.code_min == 0 && i.code_max == 0xFF)); 2660 let icmp_codes = filter(itypes6, i => (i.code_min != 0 || i.code_max != 0xFF)); 2661 2662 for (let saddrs in subnets_group_by_masking(sip[1])) { 2663 for (let daddrs in subnets_group_by_masking(dip[1])) { 2664 if (length(icmp_types) || (!length(icmp_types) && !length(icmp_codes))) 2665 add_rule(6, proto, saddrs, daddrs, sports, dports, icmp_types, null, ipset, rule); 2666 2667 if (length(icmp_codes)) 2668 add_rule(6, proto, saddrs, daddrs, sports, dports, null, icmp_codes, ipset, rule); 2669 } 2670 } 2671 } 2672 } 2673 } 2674 }, 2675 2676 parse_redirect: function(data) { 2677 let redir = this.parse_options(data, { 2678 enabled: [ "bool", "1" ], 2679 2680 name: [ "string", this.section_id(data[".name"]) ], 2681 _name: [ "string", null, DEPRECATED ], 2682 family: [ "family" ], 2683 2684 src: [ "zone_ref" ], 2685 dest: [ "zone_ref" ], 2686 2687 ipset: [ "setmatch" ], 2688 helper: [ "cthelper", null, NO_INVERT ], 2689 2690 proto: [ "protocol", "tcpudp", PARSE_LIST | FLATTEN_LIST ], 2691 2692 src_ip: [ "network" ], 2693 src_mac: [ "mac", null, PARSE_LIST ], 2694 src_port: [ "port" ], 2695 2696 src_dip: [ "network" ], 2697 src_dport: [ "port" ], 2698 2699 dest_ip: [ "network" ], 2700 dest_port: [ "port" ], 2701 2702 extra: [ "string", null, UNSUPPORTED ], 2703 2704 limit: [ "limit" ], 2705 limit_burst: [ "int" ], 2706 2707 utc_time: [ "bool" ], 2708 start_date: [ "date" ], 2709 stop_date: [ "date" ], 2710 start_time: [ "time" ], 2711 stop_time: [ "time" ], 2712 weekdays: [ "weekdays" ], 2713 monthdays: [ "monthdays", null, UNSUPPORTED ], 2714 2715 mark: [ "mark" ], 2716 2717 reflection: [ "bool", "1" ], 2718 reflection_src: [ "reflection_source", "internal" ], 2719 2720 reflection_zone: [ "zone_ref", null, PARSE_LIST ], 2721 2722 counter: [ "bool", "1" ], 2723 log: [ "string" ], 2724 log_limit: [ "limit" ], 2725 2726 target: [ "target", "dnat" ] 2727 }); 2728 2729 if (redir === false) { 2730 this.warn_section(data, "skipped due to invalid options"); 2731 return; 2732 } 2733 else if (!redir.enabled) { 2734 this.warn_section(data, "is disabled, ignoring section"); 2735 return; 2736 } 2737 2738 if (!(redir.target in ["dnat", "snat"])) { 2739 this.warn_section(data, "has invalid target specified, defaulting to dnat"); 2740 redir.target = "dnat"; 2741 } 2742 2743 switch (this.parse_bool(redir.log)) { 2744 case true: 2745 redir.log = `${redir.name}: `; 2746 break; 2747 2748 case false: 2749 delete redir.log; 2750 } 2751 2752 let ipset; 2753 2754 if (redir.ipset) { 2755 ipset = filter(this.state.ipsets, s => (s.name == redir.ipset.name))?.[0]; 2756 2757 if (!ipset) { 2758 this.warn_section(data, `references unknown set '${redir.ipset.name}'`); 2759 return; 2760 } 2761 2762 if (('inet_service' in ipset.types) && !ensure_tcpudp(redir.proto)) { 2763 this.warn_section(data, "references named set with port match but no UDP/TCP protocol, ignoring section"); 2764 return; 2765 } 2766 } 2767 2768 let resolve_dest = (redir) => { 2769 for (let zone in this.state.zones) { 2770 for (let zone_addr in zone.related_subnets) { 2771 for (let dest_addr in redir.dest_ip.addrs) { 2772 if (dest_addr.family != zone_addr.family) 2773 continue; 2774 2775 let a = apply_mask(dest_addr.addr, zone_addr.mask); 2776 let b = apply_mask(zone_addr.addr, zone_addr.mask); 2777 2778 if (a != b) 2779 continue; 2780 2781 redir.dest = { 2782 any: false, 2783 zone: zone 2784 }; 2785 2786 return true; 2787 } 2788 } 2789 } 2790 2791 return false; 2792 }; 2793 2794 if (redir.target == "dnat") { 2795 if (!redir.src) 2796 return this.warn_section(data, "has no source specified"); 2797 else if (redir.src.any) 2798 return this.warn_section(data, "must not have source '*' for dnat target"); 2799 else if (redir.dest_ip && redir.dest_ip.invert) 2800 return this.warn_section(data, "must not specify a negated 'dest_ip' value"); 2801 else if (redir.dest_ip && length(filter(redir.dest_ip.addrs, a => a.bits == -1))) 2802 return this.warn_section(data, "must not use non-contiguous masks in 'dest_ip'"); 2803 2804 if (!redir.dest && redir.dest_ip && resolve_dest(redir)) 2805 this.warn_section(data, `does not specify a destination, assuming '${redir.dest.zone.name}'`); 2806 2807 if (!redir.dest_port) 2808 redir.dest_port = redir.src_dport; 2809 2810 if (redir.reflection && redir.dest?.zone && redir.src.zone.masq) { 2811 redir.dest.zone.dflags.accept = true; 2812 redir.dest.zone.dflags.dnat = true; 2813 redir.dest.zone.dflags.snat = true; 2814 } 2815 2816 if (redir.helper) 2817 redir.src.zone.dflags.helper = true; 2818 2819 redir.src.zone.dflags[redir.target] = true; 2820 } 2821 else { 2822 if (!redir.dest) 2823 return this.warn_section(data, "has no destination specified"); 2824 else if (redir.dest.any) 2825 return this.warn_section(data, "must not have destination '*' for snat target"); 2826 else if (!redir.src_dip) 2827 return this.warn_section(data, "has no 'src_dip' option specified"); 2828 else if (redir.src_dip.invert) 2829 return this.warn_section(data, "must not specify a negated 'src_dip' value"); 2830 else if (length(filter(redir.src_dip.addrs, a => a.bits == -1))) 2831 return this.warn_section(data, "must not use non-contiguous masks in 'src_dip'"); 2832 else if (redir.src_mac) 2833 return this.warn_section(data, "must not use 'src_mac' option for snat target"); 2834 else if (redir.helper) 2835 return this.warn_section(data, "must not use 'helper' option for snat target"); 2836 2837 redir.dest.zone.dflags[redir.target] = true; 2838 } 2839 2840 let add_rule = (family, proto, saddrs, daddrs, raddrs, sport, dport, rport, ipset, redir) => { 2841 let r = { 2842 ...redir, 2843 2844 family: family, 2845 proto: proto, 2846 has_addrs: !!(saddrs[0] || saddrs[1] || saddrs[2] || daddrs[0] || daddrs[1] || daddrs[2]), 2847 has_ports: !!(sport || dport || rport), 2848 saddrs_pos: map(saddrs[0], this.cidr), 2849 saddrs_neg: map(saddrs[1], this.cidr), 2850 saddrs_masked: saddrs[2], 2851 daddrs_pos: map(daddrs[0], this.cidr), 2852 daddrs_neg: map(daddrs[1], this.cidr), 2853 daddrs_masked: daddrs[2], 2854 sports_pos: map(filter_pos(to_array(sport)), this.port), 2855 sports_neg: map(filter_neg(to_array(sport)), this.port), 2856 dports_pos: map(filter_pos(to_array(dport)), this.port), 2857 dports_neg: map(filter_neg(to_array(dport)), this.port), 2858 smacs_pos: map(filter_pos(redir.src_mac), m => m.mac), 2859 smacs_neg: map(filter_neg(redir.src_mac), m => m.mac), 2860 2861 raddr: raddrs ? raddrs[0] : null, 2862 rport: rport 2863 }; 2864 2865 let set_types = map_setmatch(ipset, redir.ipset, proto.name); 2866 2867 if (set_types !== set_types) { 2868 this.warn_section(data, "destination MAC address matching not supported"); 2869 return; 2870 } else if (set_types) { 2871 r.ipset = { ...r.ipset, fields: set_types }; 2872 } 2873 2874 switch (r.target) { 2875 case "dnat": 2876 r.chain = `dstnat_${r.src.zone.name}`; 2877 r.src.zone.dflags.dnat = true; 2878 2879 if (!r.raddr) 2880 r.target = "redirect"; 2881 2882 break; 2883 2884 case "snat": 2885 r.chain = `srcnat_${r.dest.zone.name}`; 2886 r.dest.zone.dflags.snat = true; 2887 break; 2888 } 2889 2890 push(this.state.redirects ||= [], r); 2891 }; 2892 2893 let to_hostaddr = (a) => { 2894 let bits = (a.family == 4) ? 32 : 128; 2895 2896 return { 2897 family: a.family, 2898 addr: apply_mask(a.addr, bits), 2899 bits: bits 2900 }; 2901 }; 2902 2903 for (let proto in redir.proto) { 2904 let sip, dip, rip, iip, eip, refip, sport, dport, rport; 2905 let family = redir.family; 2906 2907 if (proto.name == "ipv6-icmp") 2908 family = 6; 2909 2910 switch (redir.target) { 2911 case "dnat": 2912 sip = subnets_split_af(redir.src_ip); 2913 dip = subnets_split_af(redir.src_dip); 2914 rip = subnets_split_af(redir.dest_ip); 2915 2916 switch (proto.name) { 2917 case "tcp": 2918 case "udp": 2919 sport = redir.src_port; 2920 dport = redir.src_dport; 2921 rport = redir.dest_port; 2922 break; 2923 } 2924 2925 break; 2926 2927 case "snat": 2928 sip = subnets_split_af(redir.src_ip); 2929 dip = subnets_split_af(redir.dest_ip); 2930 rip = subnets_split_af(redir.src_dip); 2931 2932 switch (proto.name) { 2933 case "tcp": 2934 case "udp": 2935 sport = redir.src_port; 2936 dport = redir.dest_port; 2937 rport = redir.src_dport; 2938 break; 2939 } 2940 2941 break; 2942 } 2943 2944 family = infer_family(family, [ 2945 ipset, "set match", 2946 sip, "source IP", 2947 dip, "destination IP", 2948 rip, "rewrite IP", 2949 redir.src?.zone, "source zone", 2950 redir.dest?.zone, "destination zone", 2951 redir.helper, "helper match" 2952 ]); 2953 2954 if (type(family) == "string") { 2955 this.warn_section(data, `${family}, skipping`); 2956 continue; 2957 } 2958 2959 /* build reflection rules */ 2960 if (redir.target == "dnat" && redir.reflection && 2961 (length(rip[0]) || length(rip[1])) && redir.src?.zone && redir.dest?.zone) { 2962 let refredir = { 2963 name: `${redir.name} (reflection)`, 2964 2965 helper: redir.helper, 2966 2967 // XXX: this likely makes no sense for reflection rules 2968 //src_mac: redir.src_mac, 2969 2970 limit: redir.limit, 2971 limit_burst: redir.limit_burst, 2972 2973 start_date: redir.start_date, 2974 stop_date: redir.stop_date, 2975 start_time: redir.start_time, 2976 stop_time: redir.stop_time, 2977 weekdays: redir.weekdays, 2978 2979 mark: redir.mark 2980 }; 2981 2982 let eaddrs = length(dip) ? dip : subnets_split_af({ addrs: map(redir.src.zone.related_subnets, to_hostaddr) }); 2983 let rzones = length(redir.reflection_zone) ? redir.reflection_zone : [ redir.dest ]; 2984 2985 for (let rzone in rzones) { 2986 if (!is_family(rzone, family)) { 2987 this.warn_section(data, 2988 `is restricted to IPv${family} but referenced reflection zone is IPv${rzone.family} only, skipping`); 2989 continue; 2990 } 2991 2992 let iaddrs = subnets_split_af({ addrs: rzone.zone.related_subnets }); 2993 let refaddrs = (redir.reflection_src == "internal") ? iaddrs : eaddrs; 2994 2995 for (let i = 0; i <= 1; i++) { 2996 if (redir.src.zone[i ? "masq6" : "masq"] && length(rip[i])) { 2997 let snat_addr = refaddrs[i]?.[0]; 2998 2999 /* For internal reflection sources try to find a suitable candiate IP 3000 * among the reflection zone subnets which is within the same subnet 3001 * as the original DNAT destination. If we can't find any matching 3002 * one then simply take the first candidate. */ 3003 if (redir.reflection_src == "internal") { 3004 for (let zone_addr in rzone.zone.related_subnets) { 3005 if (zone_addr.family != rip[i][0].family) 3006 continue; 3007 3008 let r = apply_mask(rip[i][0].addr, zone_addr.mask); 3009 let a = apply_mask(zone_addr.addr, zone_addr.mask); 3010 3011 if (r != a) 3012 continue; 3013 3014 snat_addr = zone_addr; 3015 break; 3016 } 3017 } 3018 3019 if (!snat_addr) { 3020 this.warn_section(data, `${redir.reflection_src || "external"} rewrite IP cannot be determined, disabling reflection`); 3021 } 3022 else if (!length(iaddrs[i])) { 3023 this.warn_section(data, "internal address range cannot be determined, disabling reflection"); 3024 } 3025 else if (!length(eaddrs[i])) { 3026 this.warn_section(data, "external address range cannot be determined, disabling reflection"); 3027 } 3028 else { 3029 refredir.src = rzone; 3030 refredir.dest = null; 3031 refredir.target = "dnat"; 3032 3033 for (let saddrs in subnets_group_by_masking(iaddrs[i])) 3034 for (let daddrs in subnets_group_by_masking(eaddrs[i])) 3035 add_rule(i ? 6 : 4, proto, saddrs, daddrs, rip[i], sport, dport, rport, null, refredir); 3036 3037 refredir.src = null; 3038 refredir.dest = rzone; 3039 refredir.target = "snat"; 3040 3041 for (let daddrs in subnets_group_by_masking(rip[i])) 3042 for (let saddrs in subnets_group_by_masking(iaddrs[i])) 3043 add_rule(i ? 6 : 4, proto, saddrs, daddrs, [ to_hostaddr(snat_addr) ], null, rport, null, null, refredir); 3044 } 3045 } 3046 } 3047 } 3048 } 3049 3050 if (length(rip[0]) > 1 || length(rip[1]) > 1) 3051 this.warn_section(data, "specifies multiple rewrite addresses, using only first one"); 3052 3053 let has_ip4_addr = length(sip[0]) || length(dip[0]) || length(rip[0]), 3054 has_ip6_addr = length(sip[1]) || length(dip[1]) || length(rip[1]), 3055 has_any_addr = has_ip4_addr || has_ip6_addr; 3056 3057 /* check if there's no AF specific bits, in this case we can do an AF agnostic rule */ 3058 if (!family && !has_any_addr) { 3059 /* for backwards compatibility, treat unspecified family as IPv4 unless user explicitly requested any (0) */ 3060 if (family == null) 3061 family = 4; 3062 3063 add_rule(family, proto, [], [], null, sport, dport, rport, ipset, redir); 3064 } 3065 3066 /* we need to emit one or two AF specific rules */ 3067 else { 3068 if ((!family || family == 4) && (!has_any_addr || has_ip4_addr)) { 3069 for (let saddrs in subnets_group_by_masking(sip[0])) 3070 for (let daddrs in subnets_group_by_masking(dip[0])) 3071 add_rule(4, proto, saddrs, daddrs, rip[0], sport, dport, rport, ipset, redir); 3072 } 3073 3074 if ((!family || family == 6) && (!has_any_addr || has_ip6_addr)) { 3075 for (let saddrs in subnets_group_by_masking(sip[1])) 3076 for (let daddrs in subnets_group_by_masking(dip[1])) 3077 add_rule(6, proto, saddrs, daddrs, rip[1], sport, dport, rport, ipset, redir); 3078 } 3079 } 3080 } 3081 }, 3082 3083 parse_nat: function(data) { 3084 let snat = this.parse_options(data, { 3085 enabled: [ "bool", "1" ], 3086 3087 name: [ "string", this.section_id(data[".name"]) ], 3088 family: [ "family" ], 3089 3090 src: [ "zone_ref" ], 3091 device: [ "string" ], 3092 3093 ipset: [ "setmatch", null, UNSUPPORTED ], 3094 3095 proto: [ "protocol", "all", PARSE_LIST | FLATTEN_LIST ], 3096 3097 src_ip: [ "network" ], 3098 src_port: [ "port" ], 3099 3100 snat_ip: [ "network", null, NO_INVERT ], 3101 snat_port: [ "port", null, NO_INVERT ], 3102 3103 dest_ip: [ "network" ], 3104 dest_port: [ "port" ], 3105 3106 extra: [ "string", null, UNSUPPORTED ], 3107 3108 limit: [ "limit" ], 3109 limit_burst: [ "int" ], 3110 3111 connlimit_ports: [ "bool" ], 3112 3113 utc_time: [ "bool" ], 3114 start_date: [ "date" ], 3115 stop_date: [ "date" ], 3116 start_time: [ "time" ], 3117 stop_time: [ "time" ], 3118 weekdays: [ "weekdays" ], 3119 monthdays: [ "monthdays", null, UNSUPPORTED ], 3120 3121 mark: [ "mark" ], 3122 3123 counter: [ "bool", "1" ], 3124 log: [ "string" ], 3125 3126 target: [ "target", "masquerade" ] 3127 }); 3128 3129 if (snat === false) { 3130 this.warn_section(data, "skipped due to invalid options"); 3131 return; 3132 } 3133 else if (!snat.enabled) { 3134 this.warn_section(data, "is disabled, ignoring section"); 3135 return; 3136 } 3137 3138 if (!(snat.target in ["accept", "snat", "masquerade"])) { 3139 this.warn_section(data, "has invalid target specified, defaulting to masquerade"); 3140 snat.target = "masquerade"; 3141 } 3142 3143 if (snat.target == "snat" && !snat.snat_ip && !snat.snat_port) { 3144 this.warn_section(data, "needs either 'snat_ip' or 'snat_port' for target snat, ignoring section"); 3145 return; 3146 } 3147 else if (snat.target != "snat" && snat.snat_ip) { 3148 this.warn_section(data, "must not use 'snat_ip' for non-snat target, ignoring section"); 3149 return; 3150 } 3151 else if (snat.target != "snat" && snat.snat_port) { 3152 this.warn_section(data, "must not use 'snat_port' for non-snat target, ignoring section"); 3153 return; 3154 } 3155 3156 if ((snat.src_port || snat.dest_port) && !ensure_tcpudp(snat.proto)) { 3157 this.warn_section(data, "specifies ports but no UDP/TCP protocol, ignoring section"); 3158 return; 3159 } 3160 3161 if (snat.snat_port && !ensure_tcpudp(snat.proto) && 3162 !length(filter(snat.proto, p => (p.name in [ "icmp", "ipv6-icmp" ])))) { 3163 this.warn_section(data, "specifies ports but no UDP/TCP/ICMP protocol, ignoring section"); 3164 return; 3165 } 3166 3167 if (snat.snat_ip && length(filter(snat.snat_ip.addrs, a => a.bits == -1 || a.invert))) { 3168 this.warn_section(data, "must not use inversion or non-contiguous masks in 'snat_ip', ignoring section"); 3169 return; 3170 } 3171 3172 switch (this.parse_bool(snat.log)) { 3173 case true: 3174 snat.log = `${snat.name}: `; 3175 break; 3176 3177 case false: 3178 delete snat.log; 3179 } 3180 3181 let add_rule = (family, proto, saddrs, daddrs, raddrs, sport, dport, rport, snat) => { 3182 let n = { 3183 ...snat, 3184 3185 family: family, 3186 proto: proto, 3187 has_addrs: !!(saddrs[0] || saddrs[1] || saddrs[2] || daddrs[0] || daddrs[1] || daddrs[2]), 3188 has_ports: !!(sport || dport), 3189 saddrs_pos: map(saddrs[0], this.cidr), 3190 saddrs_neg: map(saddrs[1], this.cidr), 3191 saddrs_masked: saddrs[2], 3192 daddrs_pos: map(daddrs[0], this.cidr), 3193 daddrs_neg: map(daddrs[1], this.cidr), 3194 daddrs_masked: daddrs[2], 3195 sports_pos: map(filter_pos(to_array(sport)), this.port), 3196 sports_neg: map(filter_neg(to_array(sport)), this.port), 3197 dports_pos: map(filter_pos(to_array(dport)), this.port), 3198 dports_neg: map(filter_neg(to_array(dport)), this.port), 3199 3200 raddr: raddrs ? raddrs[0] : null, 3201 rport: rport, 3202 3203 chain: snat.src?.zone ? `srcnat_${snat.src.zone.name}` : "srcnat" 3204 }; 3205 3206 push(this.state.redirects ||= [], n); 3207 }; 3208 3209 for (let proto in snat.proto) { 3210 let sip, dip, rip, sport, dport, rport; 3211 let family = snat.family; 3212 3213 sip = subnets_split_af(snat.src_ip); 3214 dip = subnets_split_af(snat.dest_ip); 3215 rip = subnets_split_af(snat.snat_ip); 3216 3217 switch (proto.name) { 3218 case "tcp": 3219 case "udp": 3220 sport = snat.src_port; 3221 dport = snat.dest_port; 3222 rport = snat.snat_port; 3223 break; 3224 3225 case "icmp": 3226 rport = snat.snat_port; 3227 break; 3228 3229 case "ipv6-icmp": 3230 family = 6; 3231 rport = snat.snat_port; 3232 break; 3233 } 3234 3235 if (length(rip[0]) > 1 || length(rip[1]) > 1) 3236 this.warn_section(data, "specifies multiple rewrite addresses, using only first one"); 3237 3238 family = infer_family(family, [ 3239 sip, "source IP", 3240 dip, "destination IP", 3241 rip, "rewrite IP", 3242 snat.src?.zone, "source zone" 3243 ]); 3244 3245 if (type(family) == "string") { 3246 this.warn_section(data, `${family}, skipping`); 3247 continue; 3248 } 3249 3250 if (snat.src?.zone) 3251 snat.src.zone.dflags.snat = true; 3252 3253 /* if no family was configured, infer target family from IP addresses */ 3254 if (family === null) { 3255 if ((length(sip[0]) || length(dip[0]) || length(rip[0])) && !length(sip[1]) && !length(dip[1]) && !length(rip[1])) 3256 family = 4; 3257 else if ((length(sip[1]) || length(dip[1]) || length(rip[1])) && !length(sip[0]) && !length(dip[0]) && !length(rip[0])) 3258 family = 6; 3259 else 3260 family = 4; /* default to IPv4 only for backwards compatibility, unless an explict family any was configured */ 3261 } 3262 3263 /* check if there's no AF specific bits, in this case we can do an AF agnostic rule */ 3264 if (!family && !length(sip[0]) && !length(sip[1]) && !length(dip[0]) && !length(dip[1]) && !length(rip[0]) && !length(rip[1])) { 3265 add_rule(0, proto, [], [], null, sport, dport, rport, snat); 3266 } 3267 3268 /* we need to emit one or two AF specific rules */ 3269 else { 3270 if (family == 0 || family == 4) 3271 for (let saddr in subnets_group_by_masking(sip[0])) 3272 for (let daddr in subnets_group_by_masking(dip[0])) 3273 add_rule(4, proto, saddr, daddr, rip[0], sport, dport, rport, snat); 3274 3275 if (family == 0 || family == 6) 3276 for (let saddr in subnets_group_by_masking(sip[1])) 3277 for (let daddr in subnets_group_by_masking(dip[1])) 3278 add_rule(6, proto, saddr, daddr, rip[1], sport, dport, rport, snat); 3279 } 3280 } 3281 }, 3282 3283 parse_include: function(data) { 3284 let inc = this.parse_options(data, { 3285 enabled: [ "bool", "1" ], 3286 3287 path: [ "string", null, REQUIRED ], 3288 type: [ "includetype", "script" ], 3289 3290 fw4_compatible: [ "bool", data.path != "/etc/firewall.user" ], 3291 3292 family: [ "family", null, UNSUPPORTED ], 3293 reload: [ "bool", null, UNSUPPORTED ], 3294 3295 position: [ "includeposition" ], 3296 chain: [ "identifier" ] 3297 }); 3298 3299 if (!inc.enabled) { 3300 this.warn_section(data, "is disabled, ignoring section"); 3301 return; 3302 } 3303 3304 if (inc.type == "script" && !inc.fw4_compatible) { 3305 this.warn_section(data, "is not marked as compatible with fw4, ignoring section"); 3306 this.warn_section(data, "requires 'option fw4_compatible 1' to be considered compatible"); 3307 return; 3308 } 3309 3310 for (let opt in [ "table", "chain", "position" ]) { 3311 if (inc.type != "nftables" && inc[opt]) { 3312 this.warn_section(data, `must not specify '${opt}' for non-nftables includes, ignoring section`); 3313 return; 3314 } 3315 } 3316 3317 switch (inc.position ??= 'table-append') { 3318 case 'ruleset-prepend': 3319 case 'ruleset-append': 3320 case 'table-prepend': 3321 case 'table-append': 3322 if (inc.chain) 3323 this.warn_section(data, `specifies 'chain' which has no effect for position ${inc.position}`); 3324 3325 delete inc.chain; 3326 break; 3327 3328 case 'chain-prepend': 3329 case 'chain-append': 3330 if (!inc.chain) { 3331 this.warn_section(data, `must specify 'chain' for position ${inc.position}, ignoring section`); 3332 return; 3333 } 3334 3335 break; 3336 } 3337 3338 let path = fs.readlink(inc.path) ?? inc.path; 3339 3340 if (!fs.access(path)) { 3341 this.warn_section(data, `specifies unreachable path '${path}', ignoring section`); 3342 return; 3343 } 3344 3345 if (!data['.name']) 3346 this.warn(`Automatically including '${path}'`); 3347 3348 push(this.state.includes ||= [], { ...inc, path }); 3349 }, 3350 3351 parse_ipset: function(data) { 3352 let ipset = this.parse_options(data, { 3353 enabled: [ "bool", "1" ], 3354 reload_set: [ "bool" ], 3355 counters: [ "bool" ], 3356 comment: [ "string" ], 3357 3358 name: [ "identifier", null, REQUIRED ], 3359 family: [ "family", "4" ], 3360 3361 storage: [ "string", null, UNSUPPORTED ], 3362 match: [ "ipsettype", null, PARSE_LIST ], 3363 3364 iprange: [ "string", null, UNSUPPORTED ], 3365 portrange: [ "string", null, UNSUPPORTED ], 3366 3367 netmask: [ "int", null, UNSUPPORTED ], 3368 maxelem: [ "int" ], 3369 hashsize: [ "int", null, UNSUPPORTED ], 3370 timeout: [ "int", "-1" ], 3371 3372 external: [ "string", null, UNSUPPORTED ], 3373 3374 entry: [ "string", null, PARSE_LIST ], 3375 loadfile: [ "string" ] 3376 }); 3377 3378 if (ipset === false) { 3379 this.warn_section(data, "skipped due to invalid options"); 3380 return; 3381 } 3382 else if (!ipset.enabled) { 3383 this.warn_section(data, "is disabled, ignoring section"); 3384 return; 3385 } 3386 3387 if (!length(ipset.match)) { 3388 this.warn_section(data, "has no datatypes assigned"); 3389 return; 3390 } 3391 3392 let dirs = map(ipset.match, m => m[0]), 3393 types = map(ipset.match, m => m[1]), 3394 interval = false; 3395 3396 if (("ip" in types || "net" in types) && ipset.family == 0) { 3397 this.warn_section(data, "must not specify family 'any' when matching type 'ip' or 'net'"); 3398 return; 3399 } 3400 3401 if ("set" in types) { 3402 this.warn_section(data, "match type 'set' is not supported"); 3403 return; 3404 } 3405 3406 if ("net" in types) { 3407 if (this.kernel < 0x05060000) { 3408 this.warn_section(data, "match type 'net' requires kernel 5.6 or later"); 3409 return; 3410 } 3411 3412 interval = true; 3413 } 3414 3415 let s = { 3416 ...ipset, 3417 3418 fw4types: types, 3419 3420 types: map(types, (t) => { 3421 switch (t) { 3422 case 'ip': 3423 case 'net': 3424 return (ipset.family == 4) ? 'ipv4_addr' : 'ipv6_addr'; 3425 3426 case 'mac': 3427 return 'ether_addr'; 3428 3429 case 'port': 3430 return 'inet_service'; 3431 } 3432 }), 3433 3434 directions: dirs, 3435 interval: interval 3436 }; 3437 3438 if (s.interval) 3439 push(s.flags ??= [], 'interval'); 3440 3441 if (s.timeout >= 0) 3442 push(s.flags ??= [], 'timeout'); 3443 3444 s.entries = filter(map(ipset.entry, (e) => { 3445 let v = this.parse_ipsetentry(e, s); 3446 3447 if (!v) 3448 this.warn_section(data, `ignoring invalid ipset entry '${e}'`); 3449 3450 return v; 3451 }), (e) => (e != null)); 3452 3453 push(this.state.ipsets ||= [], s); 3454 } 3455 };
This page was automatically generated by LXR 0.3.1. • OpenWrt