fw4: improve flowtable handling
[project/firewall4.git] / root / usr / share / firewall4 / main.uc
1 {%
2
3 let fw4 = require("fw4");
4 let ubus = require("ubus");
5 let fs = require("fs");
6
7 /* Find existing sets.
8 *
9 * Unfortunately, terse mode (-t) is incompatible with JSON output so
10 * we either need to parse a potentially huge JSON just to get the set
11 * header data or scrape the ordinary nft output to obtain the same
12 * information. Opt for the latter to avoid parsing potentially huge
13 * JSON documents.
14 */
15 function find_existing_sets() {
16 let fd = fs.popen("nft -t list sets inet", "r");
17
18 if (!fd) {
19 warn(sprintf("Unable to execute 'nft' for listing existing sets: %s\n",
20 fs.error()));
21 return {};
22 }
23
24 let line, table, set;
25 let sets = {};
26
27 while ((line = fd.read("line")) !== "") {
28 let m;
29
30 if ((m = match(line, /^table inet (.+) \{\n$/)) != null) {
31 table = m[1];
32 }
33 else if ((m = match(line, /^\tset (.+) \{\n$/)) != null) {
34 set = m[1];
35 }
36 else if ((m = match(line, /^\t\ttype (.+)\n$/)) != null) {
37 if (table == "fw4" && set)
38 sets[set] = split(m[1], " . ");
39
40 set = null;
41 }
42 }
43
44 fd.close();
45
46 return sets;
47 }
48
49 function read_state() {
50 let state = fw4.read_state();
51
52 if (!state) {
53 warn("Unable to read firewall state - do you need to start the firewall?\n");
54 exit(1);
55 }
56
57 return state;
58 }
59
60 function reload_sets() {
61 let state = read_state(),
62 sets = find_existing_sets();
63
64 for (let set in state.ipsets) {
65 if (!set.loadfile || !length(set.entries))
66 continue;
67
68 if (!exists(sets, set.name)) {
69 warn(sprintf("Named set '%s' does not exist - do you need to restart the firewall?\n",
70 set.name));
71 continue;
72 }
73 else if (fw4.concat(sets[set.name]) != fw4.concat(set.types)) {
74 warn(sprintf("Named set '%s' has a different type - want '%s' but is '%s' - do you need to restart the firewall?\n",
75 set.name, fw4.concat(set.types), fw4.concat(sets[set.name])));
76 continue;
77 }
78
79 let first = true;
80 let printer = (entry) => {
81 if (first) {
82 print("add element inet fw4 ", set.name, " {\n");
83 first = false;
84 }
85
86 print("\t", join(" . ", entry), ",\n");
87 };
88
89 print("flush set inet fw4 ", set.name, "\n");
90
91 map(set.entries, printer);
92 fw4.parse_setfile(set, printer);
93
94 if (!first)
95 print("}\n\n");
96 }
97 }
98
99 function resolve_lower_devices(devstatus, devname) {
100 let dir = fs.opendir("/sys/class/net/" + devname);
101 let devs = [];
102
103 if (dir) {
104 if (!devstatus || devstatus[devname]?.["hw-tc-offload"]) {
105 push(devs, devname);
106 }
107 else {
108 let e;
109
110 while ((e = dir.read()) != null)
111 if (index(e, "lower_") === 0)
112 push(devs, ...resolve_lower_devices(devstatus, substr(e, 6)));
113 }
114
115 dir.close();
116 }
117
118 return devs;
119 }
120
121 function resolve_offload_devices() {
122 if (!fw4.default_option("flow_offloading"))
123 return [];
124
125 let devstatus = null;
126 let devices = [];
127
128 if (fw4.default_option("flow_offloading_hw")) {
129 let bus = require("ubus").connect();
130
131 if (bus) {
132 devstatus = bus.call("network.device", "status") || {};
133 bus.disconnect();
134 }
135 }
136
137 for (let zone in fw4.zones())
138 for (let device in zone.match_devices)
139 push(devices, ...resolve_lower_devices(devstatus, device));
140
141 return uniq(devices);
142 }
143
144 function check_flowtable() {
145 let nft = fs.popen("nft --terse --json list flowtables inet");
146 let info;
147
148 if (nft) {
149 try {
150 info = json(nft.read("all"));
151 }
152 catch (e) {
153 info = {};
154 }
155
156 nft.close();
157 }
158
159 for (let item in info?.nftables)
160 if (item?.flowtable?.table == "fw4" && item?.flowtable?.name == "ft")
161 return true;
162
163 return false;
164 }
165
166 function render_ruleset(use_statefile) {
167 fw4.load(use_statefile);
168
169 include("templates/ruleset.uc", {
170 fw4, type, exists, length, include,
171 devices: resolve_offload_devices(),
172 flowtable: check_flowtable()
173 });
174 }
175
176 function lookup_network(net) {
177 let state = read_state();
178
179 for (let zone in state.zones) {
180 for (let network in (zone.network || [])) {
181 if (network.device == net) {
182 print(zone.name, "\n");
183 exit(0);
184 }
185 }
186 }
187
188 exit(1);
189 }
190
191 function lookup_device(dev) {
192 let state = read_state();
193
194 for (let zone in state.zones) {
195 for (let rule in (zone.match_rules || [])) {
196 if (dev in rule.devices_pos) {
197 print(zone.name, "\n");
198 exit(0);
199 }
200 }
201 }
202
203 exit(1);
204 }
205
206 function lookup_zone(name, dev) {
207 let state = read_state();
208
209 for (let zone in state.zones) {
210 if (zone.name == name) {
211 let devices = [];
212 map(zone.match_rules, (r) => push(devices, ...(r.devices_pos || [])));
213
214 if (dev) {
215 if (dev in devices) {
216 print(dev, "\n");
217 exit(0);
218 }
219
220 exit(1);
221 }
222
223 if (length(devices))
224 print(join("\n", devices), "\n");
225
226 exit(0);
227 }
228 }
229
230 exit(1);
231 }
232
233
234 switch (getenv("ACTION")) {
235 case "start":
236 return render_ruleset(true);
237
238 case "print":
239 return render_ruleset(false);
240
241 case "reload-sets":
242 return reload_sets();
243
244 case "network":
245 return lookup_network(getenv("OBJECT"));
246
247 case "device":
248 return lookup_device(getenv("OBJECT"));
249
250 case "zone":
251 return lookup_zone(getenv("OBJECT"), getenv("DEVICE"));
252 }