{% if (fw4.check_flowtable()): %}
delete flowtable inet fw4 ft
{% endif %}
+{% fw4.includes('ruleset-prepend') %}
table inet fw4 {
{% if (length(flowtable_devices) > 0): %}
#
include "/etc/nftables.d/*.nft"
+{% fw4.includes('table-prepend') %}
#
iifname "lo" accept comment "!fw4: Accept traffic from loopback"
+{% fw4.includes('chain-prepend', 'input') %}
ct state established,related accept comment "!fw4: Allow inbound established and related flows"
{% if (fw4.default_option("drop_invalid")): %}
ct state invalid drop comment "!fw4: Drop flows with invalid conntrack state"
{% if (fw4.input_policy() == "reject"): %}
jump handle_reject
{% endif %}
+{% fw4.includes('chain-append', 'input') %}
}
chain forward {
{% if (length(flowtable_devices) > 0): %}
meta l4proto { tcp, udp } flow offload @ft;
{% endif %}
+{% fw4.includes('chain-prepend', 'forward') %}
ct state established,related accept comment "!fw4: Allow forwarded established and related flows"
{% if (fw4.default_option("drop_invalid")): %}
ct state invalid drop comment "!fw4: Drop flows with invalid conntrack state"
{% for (let zone in fw4.zones()): for (let rule in zone.match_rules): %}
{%+ include("zone-jump.uc", { fw4, zone, rule, direction: "forward" }) %}
{% endfor; endfor %}
+{% fw4.includes('chain-append', 'forward') %}
{% if (fw4.forward_policy() == "reject"): %}
jump handle_reject
{% endif %}
oifname "lo" accept comment "!fw4: Accept traffic towards loopback"
+{% fw4.includes('chain-prepend', 'output') %}
ct state established,related accept comment "!fw4: Allow outbound established and related flows"
{% if (fw4.default_option("drop_invalid")): %}
ct state invalid drop comment "!fw4: Drop flows with invalid conntrack state"
{%+ include("zone-jump.uc", { fw4, zone, rule, direction: "output" }) %}
{% endfor %}
{% endfor %}
+{% fw4.includes('chain-append', 'output') %}
{% if (fw4.output_policy() == "reject"): %}
jump handle_reject
{% endif %}
{% endif %}
{% for (let zone in fw4.zones()): %}
chain input_{{ zone.name }} {
+{% fw4.includes('chain-prepend', `input_${zone.name}`) %}
{% for (let rule in fw4.rules(`input_${zone.name}`)): %}
{%+ include("rule.uc", { fw4, rule }) %}
{% endfor %}
{% if (zone.dflags.dnat): %}
ct status dnat accept comment "!fw4: Accept port redirections"
{% endif %}
+{% fw4.includes('chain-append', `input_${zone.name}`) %}
jump {{ zone.input }}_from_{{ zone.name }}
}
chain output_{{ zone.name }} {
+{% fw4.includes('chain-prepend', `output_${zone.name}`) %}
{% for (let rule in fw4.rules(`output_${zone.name}`)): %}
{%+ include("rule.uc", { fw4, rule }) %}
{% endfor %}
+{% fw4.includes('chain-append', `output_${zone.name}`) %}
jump {{ zone.output }}_to_{{ zone.name }}
}
chain forward_{{ zone.name }} {
+{% fw4.includes('chain-prepend', `forward_${zone.name}`) %}
{% for (let rule in fw4.rules(`forward_${zone.name}`)): %}
{%+ include("rule.uc", { fw4, rule }) %}
{% endfor %}
{% if (zone.dflags.dnat): %}
ct status dnat accept comment "!fw4: Accept port forwards"
{% endif %}
+{% fw4.includes('chain-append', `forward_${zone.name}`) %}
jump {{ zone.forward }}_to_{{ zone.name }}
}
chain dstnat {
type nat hook prerouting priority dstnat; policy accept;
+{% fw4.includes('chain-prepend', 'dstnat') %}
{% for (let zone in fw4.zones()): %}
{% if (zone.dflags.dnat): %}
{% for (let rule in zone.match_rules): %}
{% endfor %}
{% endif %}
{% endfor %}
+{% fw4.includes('chain-append', 'dstnat') %}
}
chain srcnat {
type nat hook postrouting priority srcnat; policy accept;
+{% fw4.includes('chain-prepend', 'srcnat') %}
{% for (let redirect in fw4.redirects("srcnat")): %}
{%+ include("redirect.uc", { fw4, redirect }) %}
{% endfor %}
{% endfor %}
{% endif %}
{% endfor %}
+{% fw4.includes('chain-append', 'srcnat') %}
}
{% for (let zone in fw4.zones()): %}
{% if (zone.dflags.dnat): %}
chain dstnat_{{ zone.name }} {
+{% fw4.includes('chain-prepend', `dstnat_${zone.name}`) %}
{% for (let redirect in fw4.redirects(`dstnat_${zone.name}`)): %}
{%+ include("redirect.uc", { fw4, redirect }) %}
{% endfor %}
+{% fw4.includes('chain-append', `dstnat_${zone.name}`) %}
}
{% endif %}
{% if (zone.dflags.snat): %}
chain srcnat_{{ zone.name }} {
+{% fw4.includes('chain-prepend', `srcnat_${zone.name}`) %}
{% for (let redirect in fw4.redirects(`srcnat_${zone.name}`)): %}
{%+ include("redirect.uc", { fw4, redirect }) %}
{% endfor %}
{% endfor %}
{% endfor %}
{% endif %}
+{% fw4.includes('chain-append', `srcnat_${zone.name}`) %}
}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
+{% fw4.includes('chain-append', 'raw_prerouting') %}
}
chain raw_output {
type filter hook output priority raw; policy accept;
+{% fw4.includes('chain-prepend', 'raw_output') %}
{% for (let zone in fw4.zones()): %}
{% if (zone.dflags["notrack"]): %}
{% for (let rule in zone.match_rules): %}
{% endfor %}
{% endif %}
{% endfor %}
+{% fw4.includes('chain-append', 'raw_output') %}
}
{% for (let zone in fw4.zones()): %}
chain mangle_prerouting {
type filter hook prerouting priority mangle; policy accept;
+{% fw4.includes('chain-prepend', 'mangle_prerouting') %}
{% for (let rule in fw4.rules("mangle_prerouting")): %}
{%+ include("rule.uc", { fw4, rule }) %}
{% endfor %}
+{% fw4.includes('chain-append', 'mangle_prerouting') %}
}
chain mangle_postrouting {
type filter hook postrouting priority mangle; policy accept;
+{% fw4.includes('chain-prepend', 'mangle_postrouting') %}
{% for (let rule in fw4.rules("mangle_postrouting")): %}
{%+ include("rule.uc", { fw4, rule }) %}
{% endfor %}
+{% fw4.includes('chain-append', 'mangle_postrouting') %}
}
chain mangle_input {
type filter hook input priority mangle; policy accept;
+{% fw4.includes('chain-prepend', 'mangle_input') %}
{% for (let rule in fw4.rules("mangle_input")): %}
{%+ include("rule.uc", { fw4, rule }) %}
{% endfor %}
+{% fw4.includes('chain-append', 'mangle_input') %}
}
chain mangle_output {
type route hook output priority mangle; policy accept;
+{% fw4.includes('chain-prepend', 'mangle_output') %}
{% for (let rule in fw4.rules("mangle_output")): %}
{%+ include("rule.uc", { fw4, rule }) %}
{% endfor %}
+{% fw4.includes('chain-append', 'mangle_output') %}
}
chain mangle_forward {
type filter hook forward priority mangle; policy accept;
+{% fw4.includes('chain-prepend', 'mangle_forward') %}
{% for (let rule in fw4.rules("mangle_forward")): %}
{%+ include("rule.uc", { fw4, rule }) %}
{% endfor %}
{% endfor %}
{% endif %}
{% endfor %}
+{% fw4.includes('chain-append', 'mangle_forward') %}
}
+{% fw4.includes('table-append') %}
}
+{% fw4.includes('ruleset-append') %}
this.cursor.foreach("firewall", "nat", n => self.parse_nat(n));
+ //
+ // Build list of includes
+ //
+
+ this.cursor.foreach("firewall", "include", i => self.parse_include(i));
+
+
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();
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");
}
},
+ 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.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;
+ }
+
+ push(this.state.includes ||= [], { ...inc, path });
+ },
+
parse_ipset: function(data) {
let ipset = this.parse_options(data, {
enabled: [ "bool", "1" ],