fw4: gracefully handle unsupported hardware offloading
[project/firewall4.git] / root / usr / share / ucode / fw4.uc
index 842dde232de6eac15185142de6250acf932e07ea..9d2a0b49c18c7899b7beb9409764eabf0232aea9 100644 (file)
@@ -359,6 +359,65 @@ 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 || [];
+}
+
+function nft_try_hw_offload(devices) {
+       let nft_test =
+               'add table inet fw4-hw-offload-test; ' +
+               'add flowtable inet fw4-hw-offload-test ft { ' +
+                       'hook ingress priority 0; ' +
+                       'devices = { "' + join('", "', devices) + '" }; ' +
+                       'flags offload; ' +
+               '}';
+
+       let rc = system(sprintf("/usr/sbin/nft -c '%s' 2>/dev/null", replace(nft_test, "'", "'\\''")));
+
+       return (rc == 0);
+}
+
 
 return {
        read_kernel_version: function() {
@@ -375,6 +434,61 @@ 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 this.zones())
+                               for (let device in zone.match_devices)
+                                       push(devices, ...resolve_lower_devices(devstatus, device));
+
+                       devices = uniq(devices);
+
+                       if (nft_try_hw_offload(devices))
+                               return devices;
+
+                       this.warn('Hardware flow offloading unavailable, falling back to software offloading');
+                       this.state.defaults.flow_offloading_hw = false;
+               }
+
+               devices = [];
+
+               for (let zone in this.zones())
+                       for (let device in zone.match_devices)
+                               push(devices, ...resolve_lower_devices(null, 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;
@@ -575,18 +689,18 @@ return {
 
 
                //
-               // Build list of forwardings
+               // Build list of rules
                //
 
-               this.cursor.foreach("firewall", "forwarding", f => self.parse_forwarding(f));
+               map(filter(this.state.ubus_rules, r => (r.type == "rule")), r => self.parse_rule(r));
+               this.cursor.foreach("firewall", "rule", r => self.parse_rule(r));
 
 
                //
-               // Build list of rules
+               // Build list of forwardings
                //
 
-               map(filter(this.state.ubus_rules, r => (r.type == "rule")), r => self.parse_rule(r));
-               this.cursor.foreach("firewall", "rule", r => self.parse_rule(r));
+               this.cursor.foreach("firewall", "forwarding", f => self.parse_forwarding(f));
 
 
                //
@@ -1993,9 +2107,9 @@ return {
                        let f1 = fwd.src.zone ? fwd.src.zone.family : 0;
                        let f2 = fwd.dest.zone ? fwd.dest.zone.family : 0;
 
-                       if (f1 != 0 && f2 != 0 && f1 != f2) {
+                       if (f1 && f2 && f1 != f2) {
                                this.warn_section(data,
-                                       sprintf("references src %s restricted to %s and dest restricted to %s, ignoring forwarding",
+                                       sprintf("references src %s restricted to %s and dest %s restricted to %s, ignoring forwarding",
                                                fwd.src.zone.name, this.nfproto(f1, true),
                                                fwd.dest.zone.name, this.nfproto(f2, true)));
 
@@ -2572,10 +2686,7 @@ return {
                                }
 
                                /* build reflection rules */
-                               if (redir.reflection && (length(rip[0]) || length(rip[1])) &&
-                                   redir.src && redir.src.zone && redir.src.zone[family == 4 ? "masq" : "masq6"] &&
-                                   redir.dest && redir.dest.zone) {
-
+                               if (redir.reflection && (length(rip[0]) || length(rip[1])) && redir.src?.zone && redir.dest?.zone) {
                                        let refredir = {
                                                name: redir.name + " (reflection)",
 
@@ -2611,7 +2722,7 @@ return {
                                                let refaddrs = (redir.reflection_src == "internal") ? iaddrs : eaddrs;
 
                                                for (let i = 0; i <= 1; i++) {
-                                                       if (length(rip[i])) {
+                                                       if (redir.src.zone[i ? "masq6" : "masq"] && length(rip[i])) {
                                                                let snat_addr = refaddrs[i]?.[0];
 
                                                                /* For internal reflection sources try to find a suitable candiate IP