{%
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();
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))
}
}
-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) {
+{% 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 %}
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"
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() {
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;
flowtable ft {
hook ingress priority 0;
- devices = { "br-lan", "eth1" };
+ devices = { "eth0", "eth1" };
flags offload;
}
[!] 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 --
--- /dev/null
+[
+ "lower_eth0"
+]
--- /dev/null
+{"nftables": [{"metainfo": {"version": "1.0.1", "release_name": "Fearless Fosdick #3", "json_schema_version": 1}}]}
--- /dev/null
+{
+ "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
+ }
+ }
+}