--- /dev/null
+This directory may contain partial nftables files which are automatically
+included into the nftables ruleset generated by the fw4 program.
+
+Only accessible files (no broken symlinks, no files with insufficient
+permissions) with an `*.nft` file extension are considered.
+
+The include position of each file within the overall ruleset is derived
+from the file path:
+
+ - Files in ./table-pre/ and ./table-post/ are included before and after
+ the `table inet fw4 { ... }` declaration respectively
+
+ - Files in ./ruleset-pre/ and ./ruleset-post/ are included before the
+ first chain and after the last chain declaration within the fw4 table
+ respectively
+
+ - Files in ./chain-pre/${chain}/ and ./chain-post/${chain}/ are included
+ before the first and after the last rule within the mentioned chain of
+ the fw4 table respectively
+
+Automatic inclusion of these files can be disabled by setting the global
+`auto_includes` option within the defaults section of /etc/config/firewall.
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 ?? ''}/*.nft`))
+ if (fs.access(path))
+ this.parse_include({ type: 'nftables', position, chain, path });
+ }
+
+
if (use_statefile) {
let fd = fs.open(STATEFILE, "w");
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)
return;
}
+ if (!data['.name'])
+ this.warn(`Automatically including '${path}'`);
+
push(this.state.includes ||= [], { ...inc, path });
},
[!] Section @defaults[0] specifies unknown option 'unknown_defaults_option'
[!] Section @rule[9] (Test-Deprecated-Rule-Option) option '_name' is deprecated by fw4
[!] Section @rule[9] (Test-Deprecated-Rule-Option) specifies unknown option 'unknown_rule_option'
+[call] fs.glob pattern </usr/share/nftables.d/ruleset-pre//*.nft>
+[call] fs.glob pattern </usr/share/nftables.d/ruleset-post//*.nft>
+[call] fs.glob pattern </usr/share/nftables.d/table-pre//*.nft>
+[call] fs.glob pattern </usr/share/nftables.d/table-post//*.nft>
+[call] fs.lsdir path </usr/share/nftables.d/chain-pre>
+[call] fs.lsdir path </usr/share/nftables.d/chain-post>
[call] ctx.call object <network.device> method <status> args <null>
[call] fs.opendir path </sys/class/net/br-lan>
[call] fs.opendir path </sys/class/net/eth0>
[call] ctx.call object <network.interface> method <dump> args <null>
[call] ctx.call object <service> method <get_data> args <{ "type": "firewall" }>
[call] fs.open path </proc/version> mode <r>
+[call] fs.glob pattern </usr/share/nftables.d/ruleset-pre//*.nft>
+[call] fs.glob pattern </usr/share/nftables.d/ruleset-post//*.nft>
+[call] fs.glob pattern </usr/share/nftables.d/table-pre//*.nft>
+[call] fs.glob pattern </usr/share/nftables.d/table-post//*.nft>
+[call] fs.lsdir path </usr/share/nftables.d/chain-pre>
+[call] fs.lsdir path </usr/share/nftables.d/chain-post>
[call] fs.popen cmdline </usr/sbin/nft --terse --json list flowtables inet> mode <r>
-- End --
--- /dev/null
+Testing that include sections with `option enabled 0` are skipped.
+
+-- Testcase --
+{%
+ include("./root/usr/share/firewall4/main.uc", {
+ getenv: function(varname) {
+ switch (varname) {
+ case 'ACTION':
+ return 'print';
+ }
+ }
+ })
+%}
+-- End --
+
+-- File uci/helpers.json --
+{}
+-- End --
+
+-- File fs/open~_sys_class_net_eth0_flags.txt --
+0x1103
+-- End --
+
+-- File fs/open~_etc_testinclude1_nft.txt --
+# dummy
+-- End --
+
+-- File fs/open~_etc_testinclude2_nft.txt --
+# dummy
+-- End --
+
+-- File fs/open~_etc_testinclude3_nft.txt --
+# dummy
+-- End --
+
+-- File uci/firewall.json --
+{
+ "zone": [
+ {
+ "name": "test",
+ "device": [ "eth0" ],
+ "auto_helper": 0
+ }
+ ],
+ "include": [
+ {
+ ".description": "By default, this include should be processed due to implicit enabled 1",
+ "path": "/etc/testinclude1.nft",
+ "type": "nftables"
+ },
+
+ {
+ ".description": "This include should be processed due to explicit enabled 1",
+ "path": "/etc/testinclude2.nft",
+ "type": "nftables",
+ "enabled": "1"
+ },
+
+ {
+ ".description": "This include should be skipped due to explicit enabled 0",
+ "path": "/etc/testinclude3.nft",
+ "type": "nftables",
+ "enabled": "0"
+ }
+ ]
+}
+-- End --
+
+-- Expect stderr --
+[!] Section @include[2] is disabled, ignoring section
+-- End --
+
+-- Expect stdout --
+table inet fw4
+flush table inet fw4
+
+table inet fw4 {
+ #
+ # Defines
+ #
+
+ define test_devices = { "eth0" }
+ define test_subnets = { }
+
+
+ #
+ # User includes
+ #
+
+ include "/etc/nftables.d/*.nft"
+
+
+ #
+ # Filter rules
+ #
+
+ chain input {
+ type filter hook input priority filter; policy drop;
+
+ iifname "lo" accept comment "!fw4: Accept traffic from loopback"
+
+ ct state established,related accept comment "!fw4: Allow inbound established and related flows"
+ iifname "eth0" jump input_test comment "!fw4: Handle test IPv4/IPv6 input traffic"
+ }
+
+ chain forward {
+ type filter hook forward priority filter; policy drop;
+
+ ct state established,related accept comment "!fw4: Allow forwarded established and related flows"
+ iifname "eth0" jump forward_test comment "!fw4: Handle test IPv4/IPv6 forward traffic"
+ }
+
+ chain output {
+ type filter hook output priority filter; policy drop;
+
+ oifname "lo" accept comment "!fw4: Accept traffic towards loopback"
+
+ ct state established,related accept comment "!fw4: Allow outbound established and related flows"
+ oifname "eth0" jump output_test comment "!fw4: Handle test IPv4/IPv6 output traffic"
+ }
+
+ chain prerouting {
+ type filter hook prerouting priority filter; policy accept;
+ }
+
+ chain handle_reject {
+ meta l4proto tcp reject with tcp reset comment "!fw4: Reject TCP traffic"
+ reject with icmpx type port-unreachable comment "!fw4: Reject any other traffic"
+ }
+
+ chain input_test {
+ jump drop_from_test
+ }
+
+ chain output_test {
+ jump drop_to_test
+ }
+
+ chain forward_test {
+ jump drop_to_test
+ }
+
+ chain drop_from_test {
+ iifname "eth0" counter drop comment "!fw4: drop test IPv4/IPv6 traffic"
+ }
+
+ chain drop_to_test {
+ oifname "eth0" counter drop comment "!fw4: drop test IPv4/IPv6 traffic"
+ }
+
+
+ #
+ # NAT rules
+ #
+
+ chain dstnat {
+ type nat hook prerouting priority dstnat; policy accept;
+ }
+
+ chain srcnat {
+ type nat hook postrouting priority srcnat; policy accept;
+ }
+
+
+ #
+ # Raw rules (notrack)
+ #
+
+ chain raw_prerouting {
+ type filter hook prerouting priority raw; policy accept;
+ }
+
+ chain raw_output {
+ type filter hook output priority raw; policy accept;
+ }
+
+
+ #
+ # Mangle rules
+ #
+
+ chain mangle_prerouting {
+ type filter hook prerouting priority mangle; policy accept;
+ }
+
+ chain mangle_postrouting {
+ type filter hook postrouting priority mangle; policy accept;
+ }
+
+ chain mangle_input {
+ type filter hook input priority mangle; policy accept;
+ }
+
+ chain mangle_output {
+ type route hook output priority mangle; policy accept;
+ }
+
+ chain mangle_forward {
+ type filter hook forward priority mangle; policy accept;
+ }
+
+ include "/etc/testinclude1.nft"
+ include "/etc/testinclude2.nft"
+}
+-- End --
};
},
+ glob: (pattern) => {
+ let file = sprintf("fs/glob~%s.json", replace(pattern, /[^A-Za-z0-9_-]+/g, '_')),
+ mock = mocklib.read_json_file(file),
+ index = 0;
+
+ if (!mock || mock != mock) {
+ mocklib.I("No stat result fixture defined for fs.glob() call on %s.", pattern);
+ mocklib.I("Provide a mock result through the following JSON file:\n%s\n", file);
+
+ mock = [];
+ }
+
+ mocklib.trace_call("fs", "glob", { pattern });
+
+ return mock;
+ },
+
+ lsdir: (path) => {
+ let file = sprintf("fs/opendir~%s.json", replace(path, /[^A-Za-z0-9_-]+/g, '_')),
+ mock = mocklib.read_json_file(file),
+ index = 0;
+
+ if (!mock || mock != mock) {
+ mocklib.I("No stat result fixture defined for fs.lsdir() call on %s.", path);
+ mocklib.I("Provide a mock result through the following JSON file:\n%s\n", file);
+
+ mock = [];
+ }
+
+ mocklib.trace_call("fs", "lsdir", { path });
+
+ return mock;
+ },
+
error: () => "Unspecified error"
};
--- /dev/null
+DEVTYPE=bridge
+INTERFACE=switch0
+IFINDEX=12
--- /dev/null
+DEVTYPE=dsa
+OF_NAME=port
+OF_FULLNAME=/ethernet@1e100000/mdio-bus/switch@1f/ports/port@0
+OF_COMPATIBLE_N=0
+INTERFACE=eth0
+IFINDEX=3
--- /dev/null
+DEVTYPE=dsa
+OF_NAME=port
+OF_FULLNAME=/ethernet@1e100000/mdio-bus/switch@1f/ports/port@1
+OF_COMPATIBLE_N=0
+INTERFACE=eth1
+IFINDEX=4