11#include <nlohmann/json.hpp>
19namespace fs = std::filesystem;
20using json = nlohmann::json;
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());
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)); });
38 std::vector<std::string> out;
39 std::stringstream ss(raw);
41 while (std::getline(ss, item,
',')) {
43 if (!item.empty()) out.push_back(item);
46 const auto v =
trim(raw);
47 if (!v.empty()) out.push_back(v);
53 if (
const char* env = std::getenv(
"NFT_BIN")) {
55 if (env[0] && fs::exists(p))
return p.string();
58 if (
const char* path_env = std::getenv(
"PATH")) {
59 std::stringstream ss(path_env);
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();
68 static constexpr std::array<const char*, 4> candidates{
69 "/usr/sbin/nft",
"/sbin/nft",
"/usr/bin/nft",
"/bin/nft"
71 for (
const auto* c : candidates) {
72 if (fs::exists(c))
return std::string(c);
78 std::string out =
"'";
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";
100 const auto t =
lower(type);
101 if (t ==
"natdest")
return "dstnat";
102 if (t ==
"natsource")
return "srcnat";
103 if (t ==
"mangle")
return "mangle";
109 if (name ==
"input" || name ==
"output" || name ==
"forward" || name ==
"prerouting" || name ==
"postrouting") {
117 return name.empty() ? std::string{
"filter"} : name;
121 if (values.empty())
return {};
122 if (values.size() == 1)
return values.front();
124 std::ostringstream oss;
126 for (std::size_t i = 0; i < values.size(); ++i) {
135 if (!stmt.is_object())
return {};
137 const std::string name =
trim(stmt.value(
"Name", std::string{}));
138 if (name.empty())
return {};
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);
151 std::ostringstream oss;
153 const auto op =
trim(stmt.value(
"Op", std::string{}));
155 for (
const auto& kv : grouped) {
156 if (!first) oss <<
' ';
159 if (!kv.first.empty()) oss <<
' ' << kv.first;
160 if (!op.empty()) oss <<
' ' << op;
163 if (!grouped.empty())
return oss.str();
165 if (!op.empty())
return name +
" " + op;
170 if (!
rule.is_object())
return {};
171 if (
rule.contains(
"Enabled") &&
rule[
"Enabled"].is_boolean() && !
rule[
"Enabled"].get<
bool>())
return {};
173 std::vector<std::string> parts;
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);
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")) {
185 if (!stmt.empty()) parts.push_back(stmt);
190 const auto target =
trim(
lower(
rule.value(
"Target", std::string{})));
191 if (!target.empty()) parts.push_back(target);
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);
198 std::ostringstream oss;
199 for (std::size_t i = 0; i < parts.size(); ++i) {
203 return trim(oss.str());
207 std::istringstream iss(params);
208 std::vector<std::string> out;
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);
217 std::ostringstream oss;
218 for (std::size_t i = 0; i < out.size(); ++i) {
222 return trim(oss.str());
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;
239 std::vector<std::string> parts;
241 if (legacy_rule.contains(
"Parameters") && legacy_rule[
"Parameters"].is_string()) {
243 if (!converted.empty()) parts.push_back(converted);
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;
250 if (!stmt.empty()) parts.push_back(stmt);
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);
261 std::ostringstream oss;
262 for (std::size_t i = 0; i < parts.size(); ++i) {
266 line.rule =
trim(oss.str());
267 if (line.table.empty() || line.chain.empty() || line.rule.empty())
return std::nullopt;
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);
288 const auto chain_key = table_key +
":" + chain;
289 if (created_chains.find(chain_key) != created_chains.end())
return;
291 script <<
"add chain " << family <<
' ' << table <<
' ' << chain;
293 script <<
" { type " << type
295 <<
" priority " << priority
296 <<
"; policy " << policy <<
"; }";
299 created_chains.insert(chain_key);
303 if (j.contains(
"Enabled") && j[
"Enabled"].is_boolean() && !j[
"Enabled"].get<
bool>()) {
308 if (!j.contains(
"SystemRules") || !j[
"SystemRules"].is_array())
return false;
310 std::ostringstream script;
311 script <<
"#!/usr/sbin/nft -f\n";
313 std::set<std::string> recreated_tables;
314 std::set<std::string> created_chains;
315 std::size_t added_rules = 0;
317 for (
const auto& group : j[
"SystemRules"]) {
318 if (!group.is_object())
continue;
320 if (group.contains(
"Rule") && group[
"Rule"].is_object()) {
323 legacy->family, legacy->table, legacy->chain,
328 script <<
"add rule " << legacy->family <<
' ' << legacy->table <<
' ' << legacy->chain
329 <<
' ' << legacy->rule <<
"\n";
334 if (!group.contains(
"Chains") || !group[
"Chains"].is_array())
continue;
336 for (
const auto& chain : group[
"Chains"]) {
337 if (!chain.is_object())
continue;
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;
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;
357 if (chain_enabled_rules == 0)
continue;
359 const auto policy =
lower(
trim(chain.value(
"Policy", std::string{
"accept"})));
360 const auto priority =
trim(chain.value(
"Priority", std::string{}));
363 family, table, name, type, hook,
367 for (
const auto&
rule : chain[
"Rules"]) {
369 if (!rendered.empty()) {
370 script <<
"add rule " << family <<
' ' << table <<
' ' << name
371 <<
' ' << rendered <<
"\n";
381 if (added_rules == 0) {
383 "system-fw.json presente ma nessuna regola abilitata: skip reload");
387 std::ofstream out(script_path);
389 return static_cast<bool>(out);
395 if (
backend_ !=
"nftables")
return false;
396 if (config_path.empty() || !fs::exists(config_path))
return false;
398 const auto nft_bin = resolve_nft_binary();
401 "nft non trovato nel sistema: reload firewall non effettuato");
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;
411 std::ifstream in(config_path);
415 static const std::vector<std::string> candidate_keys{
416 "NftFile",
"NftablesFile",
"RulesFile",
"ScriptPath"
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;
429 const auto rendered = fs::path(config_path).parent_path() /
".bsc-rendered-system-fw.nft";
432 std::error_code rm_ec;
433 fs::remove(rendered, rm_ec);
435 if (!render_json_config_to_nft(j, rendered)) {
437 "firewall JSON non supportato o incompleto: reload non effettuato");
444 if (!fs::exists(rendered)) {
450 const auto cmd = *nft_bin +
" -f " + shell_quote(rendered.string()) +
" >/dev/null 2>&1";
451 const auto rc = std::system(cmd.c_str());
454 "reload firewall fallito eseguendo script nft renderizzato da " + config_path);
459 "firewall nftables ricaricato da " + config_path +
" usando " + *nft_bin);
461 }
catch (
const std::exception& e) {
463 std::string(
"reload firewall fallito: ") + e.what());
466 "reload firewall fallito: errore sconosciuto");
473 if (
backend_ !=
"nftables")
return false;
475 const auto nft_bin = resolve_nft_binary();
478 "nft non trovato: impossibile installare la regola NFQUEUE, "
479 "fallback su polling /proc");
486 std::ostringstream script;
487 script <<
"add table inet bsc_intercept\n";
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";
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";
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";
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";
505 script <<
"add rule inet bsc_intercept output udp dport 5353 accept\n";
507 script <<
"add rule inet bsc_intercept output udp dport 123 accept\n";
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";
512 script <<
"add rule inet bsc_intercept output meta l4proto igmp accept\n";
516 script <<
"add rule inet bsc_intercept output ct state new queue num "
518 if (queue_bypass) script <<
" bypass";
523 fs::path tmp_script = fs::temp_directory_path() /
"bsc-nfqueue-install.nft";
525 std::ofstream out(tmp_script);
528 "Impossibile creare lo script temporaneo nft in " + tmp_script.string());
534 const auto cmd = *nft_bin +
" -f " + shell_quote(tmp_script.string()) +
539 int rc = std::system(cmd.c_str());
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 "
559 if (queue_bypass) s2 <<
" bypass";
561 std::ofstream out(tmp_script);
564 rc = std::system(cmd.c_str());
568 fs::remove(tmp_script, ec);
572 "installazione regola NFQUEUE fallita "
573 "(permessi insufficienti? manca CAP_NET_ADMIN?): fallback su /proc");
578 "regola NFQUEUE installata sulla coda " + std::to_string(queue_num) +
579 (queue_bypass ?
" (bypass abilitato)" :
""));
585 const auto nft_bin = resolve_nft_binary();
586 if (!nft_bin)
return;
589 const auto cmd = *nft_bin +
590 " delete table inet bsc_intercept >/dev/null 2>&1";
591 (void)std::system(cmd.c_str());
bool install_nfqueue_rule(std::uint16_t queue_num, bool queue_bypass=true) const
void remove_nfqueue_rule() const
bool reload(const std::string &config_path) const
void warning(const std::string &msg)
void info(const std::string &msg)
static Logger & instance()
std::string trim(std::string s)
std::string lower(std::string s)
std::string normalize_table_name(std::string name)
std::string render_rule(const json &rule)
bool render_json_config_to_nft(const json &j, const fs::path &script_path)
std::string render_value_set(const std::vector< std::string > &values)
std::string normalize_chain_type(const std::string &type, const std::string &hook)
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)
std::optional< RenderedRuleLine > render_legacy_rule(const json &legacy_rule)
std::optional< std::string > resolve_nft_binary()
std::string default_priority_for(const std::string &type)
std::string shell_quote(const std::string &in)
std::vector< std::string > split_csv_values(const std::string &raw)
std::string normalize_chain_name(std::string name)
std::string convert_legacy_parameters(const std::string ¶ms)
std::string render_statement(const json &stmt)