firewall/nftables: Add support for include files

Change-Id: Ibeebf4e764cab175bac6ca21485526569c261123
diff --git a/src/firewall/Firewall.cpp b/src/firewall/Firewall.cpp
index ca11f47..a9352bd 100644
--- a/src/firewall/Firewall.cpp
+++ b/src/firewall/Firewall.cpp
@@ -23,7 +23,7 @@
 const auto action = "/ietf-access-control-list:acls/acl/aces/ace/actions/forwarding";
 }
 
-std::string generateNftConfig(velia::Log logger, const libyang::DataNode& tree)
+std::string generateNftConfig(velia::Log logger, const libyang::DataNode& tree, const std::vector<std::filesystem::path>& nftIncludes)
 {
     using namespace std::string_view_literals;
     std::ostringstream ss;
@@ -99,11 +99,15 @@
         }
     }
 
+    for (const auto& path : nftIncludes) {
+        ss << "include \"" << path.c_str() << "\"\n";
+    }
+
     return ss.str();
 }
 }
 
-velia::firewall::SysrepoFirewall::SysrepoFirewall(sysrepo::Session srSess, NftConfigConsumer consumer)
+velia::firewall::SysrepoFirewall::SysrepoFirewall(sysrepo::Session srSess, NftConfigConsumer consumer, const std::vector<std::filesystem::path>& nftIncludeFiles)
     : m_sub()
     , m_log(spdlog::get("firewall"))
 {
@@ -111,11 +115,11 @@
     utils::ensureModuleImplemented(srSess, "ietf-access-control-list", "2019-03-04");
     utils::ensureModuleImplemented(srSess, "czechlight-firewall", "2021-01-25");
 
-    sysrepo::ModuleChangeCb cb = [logger = m_log, consumer = std::move(consumer)] (sysrepo::Session session, auto, auto, auto, auto, auto) {
+    sysrepo::ModuleChangeCb cb = [nftIncludeFiles, logger = m_log, consumer = std::move(consumer)] (sysrepo::Session session, auto, auto, auto, auto, auto) {
         logger->debug("Applying new data from sysrepo");
         auto data = session.getData("/" + ietf_acl_module + ":*");
 
-        auto config = generateNftConfig(logger, *data);
+        auto config = generateNftConfig(logger, *data, nftIncludeFiles);
         logger->trace("running the consumer...");
         consumer(config);
         logger->trace("consumer done.");
diff --git a/src/firewall/Firewall.h b/src/firewall/Firewall.h
index d15589e..d52e656 100644
--- a/src/firewall/Firewall.h
+++ b/src/firewall/Firewall.h
@@ -12,7 +12,7 @@
 class SysrepoFirewall {
 public:
     using NftConfigConsumer = std::function<void(const std::string& config)>;
-    SysrepoFirewall(sysrepo::Session srSess, NftConfigConsumer consumer);
+    SysrepoFirewall(sysrepo::Session srSess, NftConfigConsumer consumer, const std::vector<std::filesystem::path>& nftIncludeFiles = {});
 
 private:
     std::optional<sysrepo::Subscription> m_sub;
diff --git a/src/main-firewall.cpp b/src/main-firewall.cpp
index 5772f2f..f96f542 100644
--- a/src/main-firewall.cpp
+++ b/src/main-firewall.cpp
@@ -22,6 +22,7 @@
   veliad-firewall
     [--sysrepo-log-level=<Level>]
     [--firewall-log-level=<Level>]
+    [--nftables-include-file=<Path>]...
   veliad-firewall (-h | --help)
   veliad-firewall --version
 
@@ -32,6 +33,7 @@
   --sysrepo-log-level=<N>           Log level for the sysrepo library [default: 2]
                                     (0 -> critical, 1 -> error, 2 -> warning, 3 -> info,
                                     4 -> debug, 5 -> trace)
+  --nftables-include-file=<Path>    Files to include in the nftables config file.
 )";
 
 int main(int argc, char* argv[])
@@ -53,6 +55,11 @@
         spdlog::get("firewall")->set_level(parseLogLevel("Firewall logging", args["--firewall-log-level"]));
         spdlog::get("sysrepo")->set_level(parseLogLevel("Sysrepo library", args["--sysrepo-log-level"]));
 
+        std::vector<std::filesystem::path> nftIncludeFiles;
+        for (const auto& path : args["--nftables-include-file"].asStringList()) {
+            nftIncludeFiles.emplace_back(path);
+        }
+
         auto srConn = sysrepo::Connection{};
         auto srSess = srConn.sessionStart();
         velia::firewall::SysrepoFirewall firewall(srSess, [] (const auto& config) {
@@ -60,7 +67,7 @@
             velia::utils::execAndWait(spdlog::get("firewall"), NFT_EXECUTABLE, {"-f", "-"}, config);
 
             spdlog::get("firewall")->debug("nft config applied.");
-        });
+        }, nftIncludeFiles);
 
         waitUntilSignaled();
 
diff --git a/tests/sysrepo-firewall.cpp b/tests/sysrepo-firewall.cpp
index 2873056..2be2e00 100644
--- a/tests/sysrepo-firewall.cpp
+++ b/tests/sysrepo-firewall.cpp
@@ -38,6 +38,12 @@
     srSess.applyChanges(TIMEOUT);
     MockNft nft;
 
+    SECTION("include files")
+    {
+        REQUIRE_CALL(nft, consumeConfig(NFTABLES_OUTPUT_START + "include \"/some/file\"\n"));
+        velia::firewall::SysrepoFirewall fwWithIncludes(srSess, [&nft] (const std::string& config) {nft.consumeConfig(config);}, {"/some/file"});
+    }
+
     REQUIRE_CALL(nft, consumeConfig(NFTABLES_OUTPUT_START));
     velia::firewall::SysrepoFirewall fw(srSess, [&nft] (const std::string& config) {nft.consumeConfig(config);});
     std::string inputData;