for (let ifc in ifaces.interface) {
let net = {
up: ifc.up,
- device: ifc.l3_device,
+ device: ifc.l3_device ?? ifc.device,
physdev: ifc.device,
zone: ifc.data?.zone
};
...rulespec,
name: (rulespec.type != 'ipset') ? `ubus:${ifc.interface}[${ifc.proto}] ${rulespec.type || 'rule'} ${n}` : rulespec.name,
- device: rulespec.device || ifc.l3_device
+ device: rulespec.device ?? ifc.l3_device ?? ifc.device
});
n++;
this.cursor.foreach("firewall", "nat", n => self.parse_nat(n));
+ //
+ // Build list of includes
+ //
+
+ this.cursor.foreach("firewall", "include", i => self.parse_include(i));
+
+
+ //
+ // Discover automatic includes
+ //
+
+ if (this.default_option("auto_includes")) {
+ for (let position in [ 'ruleset-pre', 'ruleset-post', 'table-pre', 'table-post', 'chain-pre', 'chain-post' ])
+ for (let chain in (position in [ 'chain-pre', 'chain-post' ]) ? fs.lsdir(`/usr/share/nftables.d/${position}`) : [ null ])
+ for (let path in fs.glob(`/usr/share/nftables.d/${position}${chain ? `/${chain}` : ''}/*.nft`))
+ if (fs.access(path))
+ this.parse_include({ type: 'nftables', position, chain, path });
+ }
+
+
if (use_statefile) {
let fd = fs.open(STATEFILE, "w");
zones: this.state.zones,
ipsets: this.state.ipsets,
networks: this.state.networks,
- ubus_rules: this.state.ubus_rules
+ ubus_rules: this.state.ubus_rules,
+ includes: this.state.includes
});
fd.close();
if (!rv)
return null;
- let helper = filter(this.state.helpers, h => (h.name == rv.val))[0];
+ let helper = filter(this.state.helpers, h => (h.name == rv.val))?.[0];
return helper ? { ...rv, ...helper } : null;
},
return length(rv) ? rv : null;
},
+ parse_includetype: function(val) {
+ return this.parse_enum(val, [
+ "script",
+ "nftables"
+ ]);
+ },
+
+ parse_includeposition: function(val) {
+ return replace(this.parse_enum(val, [
+ "ruleset-prepend",
+ "ruleset-postpend",
+ "ruleset-append",
+
+ "table-prepend",
+ "table-postpend",
+ "table-append",
+
+ "chain-prepend",
+ "chain-postpend",
+ "chain-append"
+ ]), "postpend", "append");
+ },
+
parse_string: function(val) {
return "" + val;
},
return this.state.ipsets;
},
+ includes: function(position, chain) {
+ let stmts = [];
+ let pad = '';
+ let pre = '';
+
+ switch (position) {
+ case 'table-prepend':
+ case 'table-append':
+ pad = '\t';
+ pre = '\n';
+ break;
+
+ case 'chain-prepend':
+ case 'chain-append':
+ pad = '\t\t';
+ break;
+
+ default:
+ pre = '\n';
+ }
+
+ push(stmts, pre);
+
+ for (let inc in this.state.includes)
+ if (inc.type == 'nftables' && inc.position == position && (!chain || inc.chain == chain))
+ push(stmts, `${pad}include "${inc.path}"\n`);
+
+ print(length(stmts) > 1 ? join('', stmts) : '');
+ },
+
parse_setfile: function(set, cb) {
let fd = fs.open(set.loadfile, "r");
custom_chains: [ "bool", null, UNSUPPORTED ],
disable_ipv6: [ "bool", null, UNSUPPORTED ],
flow_offloading: [ "bool", "0" ],
- flow_offloading_hw: [ "bool", "0" ]
+ flow_offloading_hw: [ "bool", "0" ],
+
+ auto_includes: [ "bool", "1" ]
});
if (defs.synflood_protect === null)
};
let family = infer_family(zone.family, [
- zone.helper, "ct helper",
match_subnets, "subnet list"
]);
set_dscp: [ "dscp", null, NO_INVERT ],
counter: [ "bool", "1" ],
+ log: [ "string" ],
target: [ "target" ]
});
return;
}
+ switch (this.parse_bool(rule.log)) {
+ case true:
+ rule.log = `${rule.name}: `;
+ break;
+
+ case false:
+ delete rule.log;
+ }
+
let ipset;
if (rule.ipset) {
reflection_zone: [ "zone_ref", null, PARSE_LIST ],
counter: [ "bool", "1" ],
+ log: [ "string" ],
target: [ "target", "dnat" ]
});
redir.target = "dnat";
}
+ switch (this.parse_bool(redir.log)) {
+ case true:
+ redir.log = `${redir.name}: `;
+ break;
+
+ case false:
+ delete redir.log;
+ }
+
let ipset;
if (redir.ipset) {
redir.dest.zone.dflags[redir.target] = true;
}
-
let add_rule = (family, proto, saddrs, daddrs, raddrs, sport, dport, rport, ipset, redir) => {
let r = {
...redir,
mark: [ "mark" ],
+ counter: [ "bool", "1" ],
+ log: [ "string" ],
+
target: [ "target", "masquerade" ]
});
return;
}
+ switch (this.parse_bool(snat.log)) {
+ case true:
+ snat.log = `${snat.name}: `;
+ break;
+
+ case false:
+ delete snat.log;
+ }
+
let add_rule = (family, proto, saddrs, daddrs, raddrs, sport, dport, rport, snat) => {
let n = {
...snat,
}
},
+ parse_include: function(data) {
+ let inc = this.parse_options(data, {
+ enabled: [ "bool", "1" ],
+
+ path: [ "string", null, REQUIRED ],
+ type: [ "includetype", "script" ],
+
+ fw4_compatible: [ "bool", data.path != "/etc/firewall.user" ],
+
+ family: [ "family", null, UNSUPPORTED ],
+ reload: [ "bool", null, UNSUPPORTED ],
+
+ position: [ "includeposition" ],
+ chain: [ "string" ]
+ });
+
+ if (!inc.enabled) {
+ this.warn_section(data, "is disabled, ignoring section");
+ return;
+ }
+
+ if (inc.type == "script" && !inc.fw4_compatible) {
+ this.warn_section(data, "is not marked as compatible with fw4, ignoring section");
+ this.warn_section(data, "requires 'option fw4_compatible 1' to be considered compatible");
+ return;
+ }
+
+ for (let opt in [ "table", "chain", "position" ]) {
+ if (inc.type != "nftables" && inc[opt]) {
+ this.warn_section(data, `must not specify '${opt}' for non-nftables includes, ignoring section`);
+ return;
+ }
+ }
+
+ switch (inc.position ??= 'table-append') {
+ case 'ruleset-prepend':
+ case 'ruleset-append':
+ case 'table-prepend':
+ case 'table-append':
+ if (inc.chain)
+ this.warn_section(data, `specifies 'chain' which has no effect for position ${inc.position}`);
+
+ delete inc.chain;
+ break;
+
+ case 'chain-prepend':
+ case 'chain-append':
+ if (!inc.chain) {
+ this.warn_section(data, `must specify 'chain' for position ${inc.position}, ignoring section`);
+ return;
+ }
+
+ break;
+ }
+
+ let path = fs.readlink(inc.path) ?? inc.path;
+
+ if (!fs.access(path)) {
+ this.warn_section(data, `specifies unreachable path '${path}', ignoring section`);
+ return;
+ }
+
+ if (!data['.name'])
+ this.warn(`Automatically including '${path}'`);
+
+ push(this.state.includes ||= [], { ...inc, path });
+ },
+
parse_ipset: function(data) {
let ipset = this.parse_options(data, {
enabled: [ "bool", "1" ],
interval: interval
};
+ if (s.interval)
+ push(s.flags ??= [], 'interval');
+
+ if (s.timeout >= 0)
+ push(s.flags ??= [], 'timeout');
+
s.entries = filter(map(ipset.entry, (e) => {
let v = this.parse_ipsetentry(e, s);