Skip to content
API Documentation
BastionGuard™
Firewall.cpp
Go to the documentation of this file.
1#include "bsc/fw/Firewall.hpp"
3
4#include <algorithm>
5#include <array>
6#include <cctype>
7#include <cstdlib>
8#include <filesystem>
9#include <fstream>
10#include <map>
11#include <nlohmann/json.hpp>
12#include <optional>
13#include <set>
14#include <sstream>
15#include <string>
16#include <vector>
17
18namespace bsc::fw {
19namespace fs = std::filesystem;
20using json = nlohmann::json;
21
22namespace {
23
24std::string trim(std::string s) {
25 auto not_space = [](unsigned char ch) { return !std::isspace(ch); };
26 s.erase(s.begin(), std::find_if(s.begin(), s.end(), not_space));
27 s.erase(std::find_if(s.rbegin(), s.rend(), not_space).base(), s.end());
28 return s;
29}
30
31std::string lower(std::string s) {
32 std::transform(s.begin(), s.end(), s.begin(),
33 [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
34 return s;
35}
36
37std::vector<std::string> split_csv_values(const std::string& raw) {
38 std::vector<std::string> out;
39 std::stringstream ss(raw);
40 std::string item;
41 while (std::getline(ss, item, ',')) {
42 item = trim(item);
43 if (!item.empty()) out.push_back(item);
44 }
45 if (out.empty()) {
46 const auto v = trim(raw);
47 if (!v.empty()) out.push_back(v);
48 }
49 return out;
50}
51
52std::optional<std::string> resolve_nft_binary() {
53 if (const char* env = std::getenv("NFT_BIN")) {
54 fs::path p(env);
55 if (env[0] && fs::exists(p)) return p.string();
56 }
57
58 if (const char* path_env = std::getenv("PATH")) {
59 std::stringstream ss(path_env);
60 std::string entry;
61 while (std::getline(ss, entry, ':')) {
62 if (entry.empty()) continue;
63 fs::path p = fs::path(entry) / "nft";
64 if (fs::exists(p)) return p.string();
65 }
66 }
67
68 static constexpr std::array<const char*, 4> candidates{
69 "/usr/sbin/nft", "/sbin/nft", "/usr/bin/nft", "/bin/nft"
70 };
71 for (const auto* c : candidates) {
72 if (fs::exists(c)) return std::string(c);
73 }
74 return std::nullopt;
75}
76
77std::string shell_quote(const std::string& in) {
78 std::string out = "'";
79 for (char ch : in) {
80 if (ch == '\'') {
81 out += "'\\''";
82 } else {
83 out.push_back(ch);
84 }
85 }
86 out += "'";
87 return out;
88}
89
90std::string normalize_chain_type(const std::string& type, const std::string& hook) {
91 const auto t = lower(type);
92 const auto h = lower(hook);
93 if (t == "natdest" || t == "natsource" || t == "nat") return "nat";
94 if (t == "route") return "route";
95 if (t == "mangle") return h == "output" ? "route" : "filter";
96 return "filter";
97}
98
99std::string default_priority_for(const std::string& type) {
100 const auto t = lower(type);
101 if (t == "natdest") return "dstnat";
102 if (t == "natsource") return "srcnat";
103 if (t == "mangle") return "mangle";
104 return "filter";
105}
106
107std::string normalize_chain_name(std::string name) {
108 name = lower(trim(std::move(name)));
109 if (name == "input" || name == "output" || name == "forward" || name == "prerouting" || name == "postrouting") {
110 return name;
111 }
112 return name;
113}
114
115std::string normalize_table_name(std::string name) {
116 name = lower(trim(std::move(name)));
117 return name.empty() ? std::string{"filter"} : name;
118}
119
120std::string render_value_set(const std::vector<std::string>& values) {
121 if (values.empty()) return {};
122 if (values.size() == 1) return values.front();
123
124 std::ostringstream oss;
125 oss << "{ ";
126 for (std::size_t i = 0; i < values.size(); ++i) {
127 if (i) oss << ", ";
128 oss << values[i];
129 }
130 oss << " }";
131 return oss.str();
132}
133
134std::string render_statement(const json& stmt) {
135 if (!stmt.is_object()) return {};
136
137 const std::string name = trim(stmt.value("Name", std::string{}));
138 if (name.empty()) return {};
139
140 std::map<std::string, std::vector<std::string>> grouped;
141 if (stmt.contains("Values") && stmt["Values"].is_array()) {
142 for (const auto& value : stmt["Values"]) {
143 if (!value.is_object()) continue;
144 const auto key = trim(value.value("Key", std::string{}));
145 const auto raw = trim(value.value("Value", std::string{}));
146 if (raw.empty()) continue;
147 for (const auto& item : split_csv_values(raw)) grouped[key].push_back(item);
148 }
149 }
150
151 std::ostringstream oss;
152 bool first = true;
153 const auto op = trim(stmt.value("Op", std::string{}));
154
155 for (const auto& kv : grouped) {
156 if (!first) oss << ' ';
157 first = false;
158 oss << name;
159 if (!kv.first.empty()) oss << ' ' << kv.first;
160 if (!op.empty()) oss << ' ' << op;
161 oss << ' ' << render_value_set(kv.second);
162 }
163 if (!grouped.empty()) return oss.str();
164
165 if (!op.empty()) return name + " " + op;
166 return name;
167}
168
169std::string render_rule(const json& rule) {
170 if (!rule.is_object()) return {};
171 if (rule.contains("Enabled") && rule["Enabled"].is_boolean() && !rule["Enabled"].get<bool>()) return {};
172
173 std::vector<std::string> parts;
174
175 if (rule.contains("Parameters") && rule["Parameters"].is_string()) {
176 const auto params = trim(rule["Parameters"].get<std::string>());
177 if (!params.empty()) parts.push_back(params);
178 }
179
180 if (rule.contains("Expressions") && rule["Expressions"].is_array()) {
181 for (const auto& expr : rule["Expressions"]) {
182 if (!expr.is_object()) continue;
183 if (expr.contains("Statement")) {
184 const auto stmt = render_statement(expr["Statement"]);
185 if (!stmt.empty()) parts.push_back(stmt);
186 }
187 }
188 }
189
190 const auto target = trim(lower(rule.value("Target", std::string{})));
191 if (!target.empty()) parts.push_back(target);
192
193 if (rule.contains("TargetParameters") && rule["TargetParameters"].is_string()) {
194 const auto params = trim(rule["TargetParameters"].get<std::string>());
195 if (!params.empty()) parts.push_back(params);
196 }
197
198 std::ostringstream oss;
199 for (std::size_t i = 0; i < parts.size(); ++i) {
200 if (i) oss << ' ';
201 oss << parts[i];
202 }
203 return trim(oss.str());
204}
205
206std::string convert_legacy_parameters(const std::string& params) {
207 std::istringstream iss(params);
208 std::vector<std::string> out;
209 std::string tok;
210 while (iss >> tok) {
211 if ((tok == "-p" || tok == "--protocol") && iss >> tok) out.push_back(lower(tok));
212 else if ((tok == "--dport" || tok == "--destination-port") && iss >> tok) out.push_back("tcp dport " + tok);
213 else if ((tok == "--sport" || tok == "--source-port") && iss >> tok) out.push_back("tcp sport " + tok);
214 else if ((tok == "-d" || tok == "--destination") && iss >> tok) out.push_back("ip daddr " + tok);
215 else if ((tok == "-s" || tok == "--source") && iss >> tok) out.push_back("ip saddr " + tok);
216 }
217 std::ostringstream oss;
218 for (std::size_t i = 0; i < out.size(); ++i) {
219 if (i) oss << ' ';
220 oss << out[i];
221 }
222 return trim(oss.str());
223}
224
226 std::string family{"inet"};
227 std::string table;
228 std::string chain;
229 std::string rule;
230};
231
232std::optional<RenderedRuleLine> render_legacy_rule(const json& legacy_rule) {
233 if (!legacy_rule.is_object()) return std::nullopt;
234 if (legacy_rule.contains("Enabled") && legacy_rule["Enabled"].is_boolean() && !legacy_rule["Enabled"].get<bool>()) return std::nullopt;
235
236 RenderedRuleLine line;
237 line.table = normalize_table_name(legacy_rule.value("Table", std::string{}));
238 line.chain = normalize_chain_name(legacy_rule.value("Chain", std::string{}));
239 std::vector<std::string> parts;
240
241 if (legacy_rule.contains("Parameters") && legacy_rule["Parameters"].is_string()) {
242 const auto converted = convert_legacy_parameters(legacy_rule["Parameters"].get<std::string>());
243 if (!converted.empty()) parts.push_back(converted);
244 }
245
246 if (legacy_rule.contains("Expressions") && legacy_rule["Expressions"].is_array()) {
247 for (const auto& expr : legacy_rule["Expressions"]) {
248 if (!expr.is_object() || !expr.contains("Statement")) continue;
249 const auto stmt = render_statement(expr["Statement"]);
250 if (!stmt.empty()) parts.push_back(stmt);
251 }
252 }
253
254 const auto target = trim(lower(legacy_rule.value("Target", std::string{})));
255 if (!target.empty()) parts.push_back(target);
256 if (legacy_rule.contains("TargetParameters") && legacy_rule["TargetParameters"].is_string()) {
257 const auto params = trim(legacy_rule["TargetParameters"].get<std::string>());
258 if (!params.empty()) parts.push_back(params);
259 }
260
261 std::ostringstream oss;
262 for (std::size_t i = 0; i < parts.size(); ++i) {
263 if (i) oss << ' ';
264 oss << parts[i];
265 }
266 line.rule = trim(oss.str());
267 if (line.table.empty() || line.chain.empty() || line.rule.empty()) return std::nullopt;
268 return line;
269}
270
271void ensure_table_and_chain(std::ostringstream& script,
272 std::set<std::string>& recreated_tables,
273 std::set<std::string>& created_chains,
274 const std::string& family,
275 const std::string& table,
276 const std::string& chain,
277 const std::string& type,
278 const std::string& hook,
279 const std::string& priority,
280 const std::string& policy) {
281 const auto table_key = family + ":" + table;
282 if (recreated_tables.find(table_key) == recreated_tables.end()) {
283 script << "delete table " << family << ' ' << table << "\n";
284 script << "add table " << family << ' ' << table << "\n";
285 recreated_tables.insert(table_key);
286 }
287
288 const auto chain_key = table_key + ":" + chain;
289 if (created_chains.find(chain_key) != created_chains.end()) return;
290
291 script << "add chain " << family << ' ' << table << ' ' << chain;
292 if (!hook.empty()) {
293 script << " { type " << type
294 << " hook " << hook
295 << " priority " << priority
296 << "; policy " << policy << "; }";
297 }
298 script << "\n";
299 created_chains.insert(chain_key);
300}
301
302bool render_json_config_to_nft(const json& j, const fs::path& script_path) {
303 if (j.contains("Enabled") && j["Enabled"].is_boolean() && !j["Enabled"].get<bool>()) {
304 bsc::logging::Logger::instance().info("firewall config caricata ma disabilitata: skip reload");
305 return true;
306 }
307
308 if (!j.contains("SystemRules") || !j["SystemRules"].is_array()) return false;
309
310 std::ostringstream script;
311 script << "#!/usr/sbin/nft -f\n";
312
313 std::set<std::string> recreated_tables;
314 std::set<std::string> created_chains;
315 std::size_t added_rules = 0; // contatore regole effettivamente emesse
316
317 for (const auto& group : j["SystemRules"]) {
318 if (!group.is_object()) continue;
319
320 if (group.contains("Rule") && group["Rule"].is_object()) {
321 if (auto legacy = render_legacy_rule(group["Rule"])) {
322 ensure_table_and_chain(script, recreated_tables, created_chains,
323 legacy->family, legacy->table, legacy->chain,
324 normalize_chain_type(legacy->table, legacy->chain),
325 legacy->chain,
326 default_priority_for(legacy->table),
327 "accept");
328 script << "add rule " << legacy->family << ' ' << legacy->table << ' ' << legacy->chain
329 << ' ' << legacy->rule << "\n";
330 ++added_rules;
331 }
332 }
333
334 if (!group.contains("Chains") || !group["Chains"].is_array()) continue;
335
336 for (const auto& chain : group["Chains"]) {
337 if (!chain.is_object()) continue;
338
339 const auto family = lower(trim(chain.value("Family", std::string{"inet"})));
340 const auto table = trim(chain.value("Table", std::string{}));
341 const auto name = trim(chain.value("Name", std::string{}));
342 const auto hook = lower(trim(chain.value("Hook", std::string{})));
343 if (table.empty() || name.empty()) continue;
344
345 // Conta quante Rules dentro questa chain sono enabled. Se zero,
346 // non creiamo neanche la chain: con `system-fw.json` legacy di
347 // OpenSnitch tutte le chain sono placeholder vuote che nft non
348 // sa gestire senza priority valida.
349 std::size_t chain_enabled_rules = 0;
350 if (chain.contains("Rules") && chain["Rules"].is_array()) {
351 for (const auto& rule : chain["Rules"]) {
352 if (rule.is_object() && rule.contains("Enabled") &&
353 rule["Enabled"].is_boolean() && !rule["Enabled"].get<bool>()) continue;
354 if (!render_rule(rule).empty()) ++chain_enabled_rules;
355 }
356 }
357 if (chain_enabled_rules == 0) continue;
358
359 const auto policy = lower(trim(chain.value("Policy", std::string{"accept"})));
360 const auto priority = trim(chain.value("Priority", std::string{}));
361 const auto type = normalize_chain_type(chain.value("Type", std::string{"filter"}), hook);
362 ensure_table_and_chain(script, recreated_tables, created_chains,
363 family, table, name, type, hook,
364 priority.empty() ? default_priority_for(chain.value("Type", std::string{"filter"})) : priority,
365 policy);
366
367 for (const auto& rule : chain["Rules"]) {
368 const auto rendered = render_rule(rule);
369 if (!rendered.empty()) {
370 script << "add rule " << family << ' ' << table << ' ' << name
371 << ' ' << rendered << "\n";
372 ++added_rules;
373 }
374 }
375 }
376 }
377
378 // Se non abbiamo emesso nessuna regola effettiva, non scriviamo neanche
379 // lo script: il chiamante interpretera' questo come "niente da fare"
380 // (ritorno true) e non generera' un warning inutile.
381 if (added_rules == 0) {
383 "system-fw.json presente ma nessuna regola abilitata: skip reload");
384 return true;
385 }
386
387 std::ofstream out(script_path);
388 out << script.str();
389 return static_cast<bool>(out);
390}
391
392} // namespace
393
394bool Firewall::reload(const std::string& config_path) const {
395 if (backend_ != "nftables") return false;
396 if (config_path.empty() || !fs::exists(config_path)) return false;
397
398 const auto nft_bin = resolve_nft_binary();
399 if (!nft_bin) {
401 "nft non trovato nel sistema: reload firewall non effettuato");
402 return false;
403 }
404
405 if (fs::path(config_path).extension() == ".nft") {
406 const auto cmd = *nft_bin + " -f " + shell_quote(config_path) + " >/dev/null 2>&1";
407 return std::system(cmd.c_str()) == 0;
408 }
409
410 try {
411 std::ifstream in(config_path);
412 json j;
413 in >> j;
414
415 static const std::vector<std::string> candidate_keys{
416 "NftFile", "NftablesFile", "RulesFile", "ScriptPath"
417 };
418
419 for (const auto& key : candidate_keys) {
420 if (j.contains(key) && j[key].is_string()) {
421 const auto nft_file = j[key].get<std::string>();
422 if (!nft_file.empty() && fs::exists(nft_file)) {
423 const auto cmd = *nft_bin + " -f " + shell_quote(nft_file) + " >/dev/null 2>&1";
424 return std::system(cmd.c_str()) == 0;
425 }
426 }
427 }
428
429 const auto rendered = fs::path(config_path).parent_path() / ".bsc-rendered-system-fw.nft";
430 // Rimuovi eventuale script residuo di un run precedente, cosi' dopo
431 // possiamo verificare univocamente "il render ha prodotto regole?"
432 std::error_code rm_ec;
433 fs::remove(rendered, rm_ec);
434
435 if (!render_json_config_to_nft(j, rendered)) {
437 "firewall JSON non supportato o incompleto: reload non effettuato");
438 return false;
439 }
440
441 // Se render_json_config_to_nft ha ritornato true ma non ha scritto
442 // lo script (config valida ma nessuna regola enabled), consideriamo
443 // l'operazione riuscita silenziosamente: niente da caricare in nft.
444 if (!fs::exists(rendered)) {
445 return true;
446 }
447
448 bsc::logging::Logger::instance().info("script nft renderizzato: " + rendered.string());
449
450 const auto cmd = *nft_bin + " -f " + shell_quote(rendered.string()) + " >/dev/null 2>&1";
451 const auto rc = std::system(cmd.c_str());
452 if (rc != 0) {
454 "reload firewall fallito eseguendo script nft renderizzato da " + config_path);
455 return false;
456 }
457
459 "firewall nftables ricaricato da " + config_path + " usando " + *nft_bin);
460 return true;
461 } catch (const std::exception& e) {
463 std::string("reload firewall fallito: ") + e.what());
464 } catch (...) {
466 "reload firewall fallito: errore sconosciuto");
467 }
468
469 return false;
470}
471
472bool Firewall::install_nfqueue_rule(std::uint16_t queue_num, bool queue_bypass) const {
473 if (backend_ != "nftables") return false;
474
475 const auto nft_bin = resolve_nft_binary();
476 if (!nft_bin) {
478 "nft non trovato: impossibile installare la regola NFQUEUE, "
479 "fallback su polling /proc");
480 return false;
481 }
482
483 // Catena dedicata: inet bsc_intercept output, priority mangle - 10
484 // (prima delle regole utente comuni). Usa un nome prefissato bsc_* per
485 // non collidere con regole esistenti dell'utente.
486 std::ostringstream script;
487 script << "add table inet bsc_intercept\n";
488 // Elimina eventuale chain precedente (idempotente).
489 script << "delete chain inet bsc_intercept output\n";
490 script << "add chain inet bsc_intercept output "
491 "{ type filter hook output priority mangle - 10; policy accept; }\n";
492
493 // ── Whitelist: traffico infrastrutturale che NON va mai chiesto ─────
494 // Chiedere conferma per ogni query DNS o broadcast DHCP rende il sistema
495 // inusabile (decine di popup al minuto). Questi protocolli passano sempre.
496 script << "add rule inet bsc_intercept output oif lo accept\n";
497 script << "add rule inet bsc_intercept output meta mark 0x42 accept\n";
498 // DNS (UDP e TCP porta 53)
499 script << "add rule inet bsc_intercept output udp dport 53 accept\n";
500 script << "add rule inet bsc_intercept output tcp dport 53 accept\n";
501 // DHCP client (UDP 67/68)
502 script << "add rule inet bsc_intercept output udp sport { 67, 68 } accept\n";
503 script << "add rule inet bsc_intercept output udp dport { 67, 68 } accept\n";
504 // mDNS (UDP 5353) — usato da avahi/Bonjour per service discovery locale
505 script << "add rule inet bsc_intercept output udp dport 5353 accept\n";
506 // NTP (UDP 123) — sincronizzazione orario
507 script << "add rule inet bsc_intercept output udp dport 123 accept\n";
508 // ICMP (ping, destination unreachable, ecc.) — diagnostica
509 script << "add rule inet bsc_intercept output meta l4proto icmp accept\n";
510 script << "add rule inet bsc_intercept output meta l4proto icmpv6 accept\n";
511 // IGMP (multicast management)
512 script << "add rule inet bsc_intercept output meta l4proto igmp accept\n";
513
514 // Regola principale: tutto il nuovo traffico (stato ct new) va in coda.
515 // 'bypass' permette al traffico di passare se il daemon non e' in ascolto.
516 script << "add rule inet bsc_intercept output ct state new queue num "
517 << queue_num;
518 if (queue_bypass) script << " bypass";
519 script << "\n";
520
521 // Scriviamo lo script in un file temporaneo invece di pipe, cosi' nft
522 // riporta errori leggibili nel log (se abilitato).
523 fs::path tmp_script = fs::temp_directory_path() / "bsc-nfqueue-install.nft";
524 {
525 std::ofstream out(tmp_script);
526 if (!out) {
528 "Impossibile creare lo script temporaneo nft in " + tmp_script.string());
529 return false;
530 }
531 out << script.str();
532 }
533
534 const auto cmd = *nft_bin + " -f " + shell_quote(tmp_script.string()) +
535 " 2>/dev/null";
536 // Il primo delete chain puo' fallire se la tabella non esiste ancora;
537 // nft in modalita' atomica fallisce tutto. Rifaccio un retry senza il
538 // delete se il primo tentativo fallisce (prima esecuzione).
539 int rc = std::system(cmd.c_str());
540 if (rc != 0) {
541 std::ostringstream s2;
542 s2 << "add table inet bsc_intercept\n";
543 s2 << "add chain inet bsc_intercept output "
544 "{ type filter hook output priority mangle - 10; policy accept; }\n";
545 s2 << "flush chain inet bsc_intercept output\n";
546 s2 << "add rule inet bsc_intercept output oif lo accept\n";
547 s2 << "add rule inet bsc_intercept output meta mark 0x42 accept\n";
548 s2 << "add rule inet bsc_intercept output udp dport 53 accept\n";
549 s2 << "add rule inet bsc_intercept output tcp dport 53 accept\n";
550 s2 << "add rule inet bsc_intercept output udp sport { 67, 68 } accept\n";
551 s2 << "add rule inet bsc_intercept output udp dport { 67, 68 } accept\n";
552 s2 << "add rule inet bsc_intercept output udp dport 5353 accept\n";
553 s2 << "add rule inet bsc_intercept output udp dport 123 accept\n";
554 s2 << "add rule inet bsc_intercept output meta l4proto icmp accept\n";
555 s2 << "add rule inet bsc_intercept output meta l4proto icmpv6 accept\n";
556 s2 << "add rule inet bsc_intercept output meta l4proto igmp accept\n";
557 s2 << "add rule inet bsc_intercept output ct state new queue num "
558 << queue_num;
559 if (queue_bypass) s2 << " bypass";
560 s2 << "\n";
561 std::ofstream out(tmp_script);
562 out << s2.str();
563 out.close();
564 rc = std::system(cmd.c_str());
565 }
566
567 std::error_code ec;
568 fs::remove(tmp_script, ec);
569
570 if (rc != 0) {
572 "installazione regola NFQUEUE fallita "
573 "(permessi insufficienti? manca CAP_NET_ADMIN?): fallback su /proc");
574 return false;
575 }
576
578 "regola NFQUEUE installata sulla coda " + std::to_string(queue_num) +
579 (queue_bypass ? " (bypass abilitato)" : ""));
580 return true;
581}
582
584 if (backend_ != "nftables") return;
585 const auto nft_bin = resolve_nft_binary();
586 if (!nft_bin) return;
587
588 // Rimuoviamo solo la nostra tabella, non tocchiamo altro.
589 const auto cmd = *nft_bin +
590 " delete table inet bsc_intercept >/dev/null 2>&1";
591 (void)std::system(cmd.c_str());
592}
593
594} // namespace bsc::fw
bool install_nfqueue_rule(std::uint16_t queue_num, bool queue_bypass=true) const
Definition Firewall.cpp:472
void remove_nfqueue_rule() const
Definition Firewall.cpp:583
bool reload(const std::string &config_path) const
Definition Firewall.cpp:394
std::string backend_
Definition Firewall.hpp:24
void warning(const std::string &msg)
Definition Logger.cpp:22
void info(const std::string &msg)
Definition Logger.cpp:21
static Logger & instance()
Definition Logger.cpp:11
std::string trim(std::string s)
Definition Firewall.cpp:24
std::string lower(std::string s)
Definition Firewall.cpp:31
std::string normalize_table_name(std::string name)
Definition Firewall.cpp:115
std::string render_rule(const json &rule)
Definition Firewall.cpp:169
bool render_json_config_to_nft(const json &j, const fs::path &script_path)
Definition Firewall.cpp:302
std::string render_value_set(const std::vector< std::string > &values)
Definition Firewall.cpp:120
std::string normalize_chain_type(const std::string &type, const std::string &hook)
Definition Firewall.cpp:90
void ensure_table_and_chain(std::ostringstream &script, std::set< std::string > &recreated_tables, std::set< std::string > &created_chains, const std::string &family, const std::string &table, const std::string &chain, const std::string &type, const std::string &hook, const std::string &priority, const std::string &policy)
Definition Firewall.cpp:271
std::optional< RenderedRuleLine > render_legacy_rule(const json &legacy_rule)
Definition Firewall.cpp:232
std::optional< std::string > resolve_nft_binary()
Definition Firewall.cpp:52
std::string default_priority_for(const std::string &type)
Definition Firewall.cpp:99
std::string shell_quote(const std::string &in)
Definition Firewall.cpp:77
std::vector< std::string > split_csv_values(const std::string &raw)
Definition Firewall.cpp:37
std::string normalize_chain_name(std::string name)
Definition Firewall.cpp:107
std::string convert_legacy_parameters(const std::string &params)
Definition Firewall.cpp:206
std::string render_statement(const json &stmt)
Definition Firewall.cpp:134
nlohmann::json json
Definition Firewall.cpp:20