fw4: consolidate helper code
authorJo-Philipp Wich <jo@mein.io>
Fri, 4 Feb 2022 22:44:25 +0000 (23:44 +0100)
committerJo-Philipp Wich <jo@mein.io>
Sat, 5 Feb 2022 23:33:04 +0000 (00:33 +0100)
 - Move various local helper functions out of main.uc into the fw4 class
 - Rework settype reading to use nft JSON output as terse mode now works
 - Simplify testing flowtable enable conditions
 - Adjust testcases to changed flowtable logic

Signed-off-by: Jo-Philipp Wich <jo@mein.io>
root/usr/share/firewall4/main.uc
root/usr/share/firewall4/templates/ruleset.uc
root/usr/share/ucode/fw4.uc
tests/01_configuration/01_ruleset
tests/mocks/fs/opendir~_sys_class_net_br-lan.json [new file with mode: 0644]
tests/mocks/fs/opendir~_sys_class_net_eth0.json [new file with mode: 0644]
tests/mocks/fs/opendir~_sys_class_net_eth1.json [new file with mode: 0644]
tests/mocks/fs/popen~_usr_sbin_nft_--terse_--json_list_flowtables_inet.txt [new file with mode: 0644]
tests/mocks/ubus/network.device~status.json [new file with mode: 0644]

index 839346b9b4d75bf89d30ea6805286ecca519b1f6..692d9aa0347682a1645ffb8566bb35b072323b7a 100644 (file)
@@ -1,50 +1,6 @@
 {%
 
 let fw4 = require("fw4");
-let ubus = require("ubus");
-let fs = require("fs");
-
-/* Find existing sets.
- *
- * Unfortunately, terse mode (-t) is incompatible with JSON output so
- * we either need to parse a potentially huge JSON just to get the set
- * header data or scrape the ordinary nft output to obtain the same
- * information. Opt for the latter to avoid parsing potentially huge
- * JSON documents.
- */
-function find_existing_sets() {
-       let fd = fs.popen("nft -t list sets inet", "r");
-
-       if (!fd) {
-               warn(sprintf("Unable to execute 'nft' for listing existing sets: %s\n",
-                            fs.error()));
-               return {};
-       }
-
-       let line, table, set;
-       let sets = {};
-
-       while ((line = fd.read("line")) !== "") {
-               let m;
-
-               if ((m = match(line, /^table inet (.+) \{\n$/)) != null) {
-                       table = m[1];
-               }
-               else if ((m = match(line, /^\tset (.+) \{\n$/)) != null) {
-                       set = m[1];
-               }
-               else if ((m = match(line, /^\t\ttype (.+)\n$/)) != null) {
-                       if (table == "fw4" && set)
-                               sets[set] = split(m[1], " . ");
-
-                       set = null;
-               }
-       }
-
-       fd.close();
-
-       return sets;
-}
 
 function read_state() {
        let state = fw4.read_state();
@@ -59,7 +15,7 @@ function read_state() {
 
 function reload_sets() {
        let state = read_state(),
-           sets = find_existing_sets();
+           sets = fw4.check_set_types();
 
        for (let set in state.ipsets) {
                if (!set.loadfile || !length(set.entries))
@@ -96,81 +52,10 @@ function reload_sets() {
        }
 }
 
-function resolve_lower_devices(devstatus, devname) {
-       let dir = fs.opendir("/sys/class/net/" + devname);
-       let devs = [];
-
-       if (dir) {
-               if (!devstatus || devstatus[devname]?.["hw-tc-offload"]) {
-                       push(devs, devname);
-               }
-               else {
-                       let e;
-
-                       while ((e = dir.read()) != null)
-                               if (index(e, "lower_") === 0)
-                                       push(devs, ...resolve_lower_devices(devstatus, substr(e, 6)));
-               }
-
-               dir.close();
-       }
-
-       return devs;
-}
-
-function resolve_offload_devices() {
-       if (!fw4.default_option("flow_offloading"))
-               return [];
-
-       let devstatus = null;
-       let devices = [];
-
-       if (fw4.default_option("flow_offloading_hw")) {
-               let bus = require("ubus").connect();
-
-               if (bus) {
-                       devstatus = bus.call("network.device", "status") || {};
-                       bus.disconnect();
-               }
-       }
-
-       for (let zone in fw4.zones())
-               for (let device in zone.match_devices)
-                       push(devices, ...resolve_lower_devices(devstatus, device));
-
-       return uniq(devices);
-}
-
-function check_flowtable() {
-       let nft = fs.popen("nft --terse --json list flowtables inet");
-       let info;
-
-       if (nft) {
-               try {
-                       info = json(nft.read("all"));
-               }
-               catch (e) {
-                       info = {};
-               }
-
-               nft.close();
-       }
-
-       for (let item in info?.nftables)
-               if (item?.flowtable?.table == "fw4" && item?.flowtable?.name == "ft")
-                       return true;
-
-       return false;
-}
-
 function render_ruleset(use_statefile) {
        fw4.load(use_statefile);
 
-       include("templates/ruleset.uc", {
-               fw4, type, exists, length, include,
-               devices: resolve_offload_devices(),
-               flowtable: check_flowtable()
-       });
+       include("templates/ruleset.uc", { fw4, type, exists, length, include });
 }
 
 function lookup_network(net) {
index 8020bed69fb1b9c50b82289974544563f4866b59..b4023157bf2dc8578d3edfcc46dcc4fe19343795 100644 (file)
@@ -1,18 +1,20 @@
+{% let flowtable_devices = fw4.resolve_offload_devices(); -%}
+
 table inet fw4
 flush table inet fw4
-{% if (flowtable): %}
+{% if (fw4.check_flowtable()): %}
 delete flowtable inet fw4 ft
 {% endif %}
 
 table inet fw4 {
-{% if (fw4.default_option("flow_offloading") && length(devices) > 0): %}
+{% if (length(flowtable_devices) > 0): %}
        #
        # Flowtable
        #
 
        flowtable ft {
                hook ingress priority 0;
-               devices = {{ fw4.set(devices, true) }};
+               devices = {{ fw4.set(flowtable_devices, true) }};
 {% if (fw4.default_option("flow_offloading_hw")): %}
                flags offload;
 {% endif %}
@@ -90,7 +92,7 @@ table inet fw4 {
        chain forward {
                type filter hook forward priority filter; policy {{ fw4.forward_policy(true) }};
 
-{% if (fw4.default_option("flow_offloading") && length(devices) > 0): %}
+{% if (length(flowtable_devices) > 0): %}
                meta l4proto { tcp, udp } flow offload @ft;
 {% endif %}
                ct state established,related accept comment "!fw4: Allow forwarded established and related flows"
index fb8720859763633edf7ca4ba48d361f5eea672ad..932637e39f11b034b386c7cffa3d30340d5554d6 100644 (file)
@@ -359,6 +359,51 @@ function map_setmatch(set, match, proto) {
        return fields;
 }
 
+function resolve_lower_devices(devstatus, devname) {
+       let dir = fs.opendir("/sys/class/net/" + devname);
+       let devs = [];
+
+       if (dir) {
+               if (!devstatus || devstatus[devname]?.["hw-tc-offload"]) {
+                       push(devs, devname);
+               }
+               else {
+                       let e;
+
+                       while ((e = dir.read()) != null)
+                               if (index(e, "lower_") === 0)
+                                       push(devs, ...resolve_lower_devices(devstatus, substr(e, 6)));
+               }
+
+               dir.close();
+       }
+
+       return devs;
+}
+
+function nft_json_command(...args) {
+       let cmd = [ "/usr/sbin/nft", "--terse", "--json", ...args ];
+       let nft = fs.popen(join(" ", cmd), "r");
+       let info;
+
+       if (nft) {
+               try {
+                       info = filter(json(nft.read("all"))?.nftables,
+                               item => (type(item) == "object" && !item.metainfo));
+               }
+               catch (e) {
+                       warn(sprintf("Unable to parse nftables JSON output: %s\n", e));
+               }
+
+               nft.close();
+       }
+       else {
+               warn(sprintf("Unable to popen() %s: %s\n", cmd, fs.error()));
+       }
+
+       return info || [];
+}
+
 
 return {
        read_kernel_version: function() {
@@ -375,6 +420,47 @@ return {
                return v;
        },
 
+       resolve_offload_devices: function() {
+               if (!this.default_option("flow_offloading"))
+                       return [];
+
+               let devstatus = null;
+               let devices = [];
+
+               if (this.default_option("flow_offloading_hw")) {
+                       let bus = ubus.connect();
+
+                       if (bus) {
+                               devstatus = bus.call("network.device", "status") || {};
+                               bus.disconnect();
+                       }
+               }
+
+               for (let zone in fw4.zones())
+                       for (let device in zone.match_devices)
+                               push(devices, ...resolve_lower_devices(devstatus, device));
+
+               return uniq(devices);
+       },
+
+       check_set_types: function() {
+               let sets = {};
+
+               for (let item in nft_json_command("list", "sets", "inet"))
+                       if (item.set?.table == "fw4")
+                               sets[item.set.name] = (type(item.set.type) == "array") ? item.set.type : [ item.set.type ];
+
+               return sets;
+       },
+
+       check_flowtable: function() {
+               for (let item in nft_json_command("list", "flowtables", "inet"))
+                       if (item.flowtable?.table == "fw4" && item.flowtable?.name == "ft")
+                               return true;
+
+               return false;
+       },
+
        read_state: function() {
                let fd = fs.open(STATEFILE, "r");
                let state = null;
index 7f4fcfd01964ea22413d9d8683526e21310d281b..879bdbf66596c05df78ce99debfcfcf912d203b7 100644 (file)
@@ -26,7 +26,7 @@ table inet fw4 {
 
        flowtable ft {
                hook ingress priority 0;
-               devices = { "br-lan", "eth1" };
+               devices = { "eth0", "eth1" };
                flags offload;
        }
 
@@ -296,6 +296,12 @@ table inet fw4 {
 [!] 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] 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] fs.opendir path </sys/class/net/eth1>
+[call] fs.opendir path </sys/class/net/eth1>
+[call] fs.popen cmdline </usr/sbin/nft --terse --json list flowtables inet> mode <r>
 [call] fs.open path </sys/class/net/br-lan/flags> mode <r>
 [call] fs.open path </sys/class/net/br-lan/flags> mode <r>
 -- End --
diff --git a/tests/mocks/fs/opendir~_sys_class_net_br-lan.json b/tests/mocks/fs/opendir~_sys_class_net_br-lan.json
new file mode 100644 (file)
index 0000000..5b163ad
--- /dev/null
@@ -0,0 +1,3 @@
+[
+       "lower_eth0"
+]
diff --git a/tests/mocks/fs/opendir~_sys_class_net_eth0.json b/tests/mocks/fs/opendir~_sys_class_net_eth0.json
new file mode 100644 (file)
index 0000000..fe51488
--- /dev/null
@@ -0,0 +1 @@
+[]
diff --git a/tests/mocks/fs/opendir~_sys_class_net_eth1.json b/tests/mocks/fs/opendir~_sys_class_net_eth1.json
new file mode 100644 (file)
index 0000000..fe51488
--- /dev/null
@@ -0,0 +1 @@
+[]
diff --git a/tests/mocks/fs/popen~_usr_sbin_nft_--terse_--json_list_flowtables_inet.txt b/tests/mocks/fs/popen~_usr_sbin_nft_--terse_--json_list_flowtables_inet.txt
new file mode 100644 (file)
index 0000000..72d2dfe
--- /dev/null
@@ -0,0 +1 @@
+{"nftables": [{"metainfo": {"version": "1.0.1", "release_name": "Fearless Fosdick #3", "json_schema_version": 1}}]}
diff --git a/tests/mocks/ubus/network.device~status.json b/tests/mocks/ubus/network.device~status.json
new file mode 100644 (file)
index 0000000..5d402f0
--- /dev/null
@@ -0,0 +1,302 @@
+{
+       "eth0": {
+               "external": false,
+               "present": true,
+               "type": "Network device",
+               "up": true,
+               "carrier": true,
+               "auth_status": false,
+               "link-advertising": [
+                       "10baseT-H",
+                       "10baseT-F",
+                       "100baseT-H",
+                       "100baseT-F",
+                       "1000baseT-F"
+               ],
+               "link-partner-advertising": [
+                       "10baseT-H",
+                       "10baseT-F",
+                       "100baseT-H",
+                       "100baseT-F",
+                       "1000baseT-H",
+                       "1000baseT-F"
+               ],
+               "link-supported": [
+                       "10baseT-H",
+                       "10baseT-F",
+                       "100baseT-H",
+                       "100baseT-F",
+                       "1000baseT-F"
+               ],
+               "speed": "1000F",
+               "autoneg": true,
+               "hw-tc-offload": true,
+               "devtype": "dsa",
+               "mtu": 1500,
+               "mtu6": 1500,
+               "macaddr": "74:ac:b9:a1:84:46",
+               "txqueuelen": 1000,
+               "ipv6": false,
+               "ip6segmentrouting": false,
+               "promisc": false,
+               "rpfilter": 0,
+               "acceptlocal": false,
+               "igmpversion": 0,
+               "mldversion": 0,
+               "neigh4reachabletime": 30000,
+               "neigh6reachabletime": 30000,
+               "neigh4gcstaletime": 60,
+               "neigh6gcstaletime": 60,
+               "neigh4locktime": 100,
+               "dadtransmits": 1,
+               "multicast": true,
+               "sendredirects": true,
+               "drop_v4_unicast_in_l2_multicast": false,
+               "drop_v6_unicast_in_l2_multicast": false,
+               "drop_gratuitous_arp": false,
+               "drop_unsolicited_na": false,
+               "arp_accept": false,
+               "statistics": {
+                       "collisions": 0,
+                       "rx_frame_errors": 0,
+                       "tx_compressed": 0,
+                       "multicast": 0,
+                       "rx_length_errors": 0,
+                       "tx_dropped": 0,
+                       "rx_bytes": 53206036390,
+                       "rx_missed_errors": 0,
+                       "tx_errors": 0,
+                       "rx_compressed": 0,
+                       "rx_over_errors": 0,
+                       "tx_fifo_errors": 0,
+                       "rx_crc_errors": 0,
+                       "rx_packets": 115554141,
+                       "tx_heartbeat_errors": 0,
+                       "rx_dropped": 0,
+                       "tx_aborted_errors": 0,
+                       "tx_packets": 44534107,
+                       "rx_errors": 0,
+                       "tx_bytes": 41926154781,
+                       "tx_window_errors": 0,
+                       "rx_fifo_errors": 0,
+                       "tx_carrier_errors": 0
+               }
+       },
+       "eth1": {
+               "external": false,
+               "present": true,
+               "type": "Network device",
+               "up": true,
+               "carrier": true,
+               "auth_status": false,
+               "link-advertising": [
+                       "10baseT-H",
+                       "10baseT-F",
+                       "100baseT-H",
+                       "100baseT-F",
+                       "1000baseT-F"
+               ],
+               "link-partner-advertising": [
+                       "10baseT-H",
+                       "10baseT-F",
+                       "100baseT-H",
+                       "100baseT-F",
+                       "1000baseT-F"
+               ],
+               "link-supported": [
+                       "10baseT-H",
+                       "10baseT-F",
+                       "100baseT-H",
+                       "100baseT-F",
+                       "1000baseT-F"
+               ],
+               "speed": "1000F",
+               "autoneg": true,
+               "hw-tc-offload": true,
+               "devtype": "dsa",
+               "mtu": 1500,
+               "mtu6": 1500,
+               "macaddr": "74:ac:b9:a1:84:47",
+               "txqueuelen": 1000,
+               "ipv6": false,
+               "ip6segmentrouting": false,
+               "promisc": false,
+               "rpfilter": 0,
+               "acceptlocal": false,
+               "igmpversion": 0,
+               "mldversion": 0,
+               "neigh4reachabletime": 30000,
+               "neigh6reachabletime": 30000,
+               "neigh4gcstaletime": 60,
+               "neigh6gcstaletime": 60,
+               "neigh4locktime": 100,
+               "dadtransmits": 1,
+               "multicast": true,
+               "sendredirects": true,
+               "drop_v4_unicast_in_l2_multicast": false,
+               "drop_v6_unicast_in_l2_multicast": false,
+               "drop_gratuitous_arp": false,
+               "drop_unsolicited_na": false,
+               "arp_accept": false,
+               "statistics": {
+                       "collisions": 0,
+                       "rx_frame_errors": 0,
+                       "tx_compressed": 0,
+                       "multicast": 0,
+                       "rx_length_errors": 0,
+                       "tx_dropped": 0,
+                       "rx_bytes": 470925492,
+                       "rx_missed_errors": 0,
+                       "tx_errors": 0,
+                       "rx_compressed": 0,
+                       "rx_over_errors": 0,
+                       "tx_fifo_errors": 0,
+                       "rx_crc_errors": 0,
+                       "rx_packets": 4591620,
+                       "tx_heartbeat_errors": 0,
+                       "rx_dropped": 0,
+                       "tx_aborted_errors": 0,
+                       "tx_packets": 4210181,
+                       "rx_errors": 0,
+                       "tx_bytes": 7724980929,
+                       "tx_window_errors": 0,
+                       "rx_fifo_errors": 0,
+                       "tx_carrier_errors": 0
+               }
+       },
+       "lo": {
+               "external": false,
+               "present": true,
+               "type": "Network device",
+               "up": true,
+               "carrier": true,
+               "auth_status": false,
+               "hw-tc-offload": false,
+               "devtype": "loopback",
+               "mtu": 65536,
+               "mtu6": 65536,
+               "macaddr": "00:00:00:00:00:00",
+               "txqueuelen": 1000,
+               "ipv6": true,
+               "ip6segmentrouting": false,
+               "promisc": false,
+               "rpfilter": 0,
+               "acceptlocal": false,
+               "igmpversion": 0,
+               "mldversion": 0,
+               "neigh4reachabletime": 30000,
+               "neigh6reachabletime": 30000,
+               "neigh4gcstaletime": 60,
+               "neigh6gcstaletime": 60,
+               "neigh4locktime": 100,
+               "dadtransmits": 1,
+               "multicast": false,
+               "sendredirects": true,
+               "drop_v4_unicast_in_l2_multicast": false,
+               "drop_v6_unicast_in_l2_multicast": false,
+               "drop_gratuitous_arp": false,
+               "drop_unsolicited_na": false,
+               "arp_accept": false,
+               "statistics": {
+                       "collisions": 0,
+                       "rx_frame_errors": 0,
+                       "tx_compressed": 0,
+                       "multicast": 0,
+                       "rx_length_errors": 0,
+                       "tx_dropped": 0,
+                       "rx_bytes": 2993476,
+                       "rx_missed_errors": 0,
+                       "tx_errors": 0,
+                       "rx_compressed": 0,
+                       "rx_over_errors": 0,
+                       "tx_fifo_errors": 0,
+                       "rx_crc_errors": 0,
+                       "rx_packets": 34056,
+                       "tx_heartbeat_errors": 0,
+                       "rx_dropped": 0,
+                       "tx_aborted_errors": 0,
+                       "tx_packets": 34056,
+                       "rx_errors": 0,
+                       "tx_bytes": 2993476,
+                       "tx_window_errors": 0,
+                       "rx_fifo_errors": 0,
+                       "tx_carrier_errors": 0
+               }
+       },
+       "br-lan": {
+               "external": false,
+               "present": true,
+               "type": "bridge",
+               "up": true,
+               "carrier": true,
+               "auth_status": false,
+               "link-advertising": [
+
+               ],
+               "link-partner-advertising": [
+
+               ],
+               "link-supported": [
+
+               ],
+               "speed": "1000F",
+               "autoneg": false,
+               "hw-tc-offload": false,
+               "devtype": "bridge",
+               "bridge-members": [
+                       "eth0"
+               ],
+               "bridge-vlans": [
+
+               ],
+               "mtu": 1500,
+               "mtu6": 1500,
+               "macaddr": "74:ac:b9:a1:84:46",
+               "txqueuelen": 1000,
+               "ipv6": true,
+               "ip6segmentrouting": false,
+               "promisc": false,
+               "rpfilter": 0,
+               "acceptlocal": false,
+               "igmpversion": 0,
+               "mldversion": 0,
+               "neigh4reachabletime": 30000,
+               "neigh6reachabletime": 30000,
+               "neigh4gcstaletime": 60,
+               "neigh6gcstaletime": 60,
+               "neigh4locktime": 100,
+               "dadtransmits": 1,
+               "multicast": true,
+               "sendredirects": true,
+               "drop_v4_unicast_in_l2_multicast": false,
+               "drop_v6_unicast_in_l2_multicast": false,
+               "drop_gratuitous_arp": false,
+               "drop_unsolicited_na": false,
+               "arp_accept": false,
+               "statistics": {
+                       "collisions": 0,
+                       "rx_frame_errors": 0,
+                       "tx_compressed": 0,
+                       "multicast": 27625,
+                       "rx_length_errors": 0,
+                       "tx_dropped": 0,
+                       "rx_bytes": 9505679939,
+                       "rx_missed_errors": 0,
+                       "tx_errors": 0,
+                       "rx_compressed": 0,
+                       "rx_over_errors": 0,
+                       "tx_fifo_errors": 0,
+                       "rx_crc_errors": 0,
+                       "rx_packets": 13282955,
+                       "tx_heartbeat_errors": 0,
+                       "rx_dropped": 0,
+                       "tx_aborted_errors": 0,
+                       "tx_packets": 12647462,
+                       "rx_errors": 0,
+                       "tx_bytes": 12983448170,
+                       "tx_window_errors": 0,
+                       "rx_fifo_errors": 0,
+                       "tx_carrier_errors": 0
+               }
+       }
+}