Initial source code for nether 0.0.1 (source code only) 86/44086/1
authorRomanKubiak <r.kubiak@samsung.com>
Thu, 16 Jul 2015 14:54:22 +0000 (16:54 +0200)
committerRomanKubiak <r.kubiak@samsung.com>
Thu, 16 Jul 2015 15:02:59 +0000 (17:02 +0200)
Change-Id: I16970c3dedd9071c970523a478fbf35e009d13ef

14 files changed:
include/nether_CynaraBackend.h [new file with mode: 0644]
include/nether_DummyBackend.h [new file with mode: 0644]
include/nether_FileBackend.h [new file with mode: 0644]
include/nether_Manager.h [new file with mode: 0644]
include/nether_Netlink.h [new file with mode: 0644]
include/nether_PolicyBackend.h [new file with mode: 0644]
include/nether_Types.h [new file with mode: 0644]
include/nether_Utils.h [new file with mode: 0644]
src/nether_CynaraBackend.cpp [new file with mode: 0644]
src/nether_FileBackend.cpp [new file with mode: 0644]
src/nether_Main.cpp [new file with mode: 0644]
src/nether_Manager.cpp [new file with mode: 0644]
src/nether_Netlink.cpp [new file with mode: 0644]
src/nether_NetworkUtils.cpp [new file with mode: 0644]

diff --git a/include/nether_CynaraBackend.h b/include/nether_CynaraBackend.h
new file mode 100644 (file)
index 0000000..1242797
--- /dev/null
@@ -0,0 +1,50 @@
+#ifndef NETHER_CYNARA_BACKEND_H
+#define NETHER_CYNARA_BACKEND_H
+
+// #ifdef HAVE_CYNARA
+
+#include <cynara-client-async.h>
+#include "nether_PolicyBackend.h"
+#include <vector>
+
+#define NETHER_CYNARA_INTERNET_PRIVILEGE "http://tizen.org/privilege/internet"
+
+static const std::string cynaraErrorCodeToString(int cynaraErrorCode)
+{
+    char errorString[512];
+    int ret;
+
+    if ((ret = cynara_strerror(cynaraErrorCode, errorString, 512)) == CYNARA_API_SUCCESS)
+        return (std::string(errorString, strlen(errorString)));
+    else
+        return ("Failed to get error string representation, code="+ret);
+}
+
+class NetherManager;
+
+class NetherCynaraBackend : public NetherPolicyBackend
+{
+    public:
+        NetherCynaraBackend(const NetherConfig &netherConfig);
+        ~NetherCynaraBackend();
+        const bool initialize();
+        const bool isValid();
+        const bool enqueueVerdict (const NetherPacket &packet);
+        const bool processEvents();
+        const int getDescriptor();
+        const NetherDescriptorStatus getDescriptorStatus();
+        void setCynaraDescriptor(const int _currentCynaraDescriptor, const NetherDescriptorStatus _currentCynaraDescriptorStatus);
+        void setCynaraVerdict(cynara_check_id checkId, int cynaraResult);
+        static void statusCallback(int oldFd, int newFd, cynara_async_status status, void *data);
+        static void checkCallback(cynara_check_id check_id, cynara_async_call_cause cause, int response, void *data);
+
+    private:
+        cynara_async *cynaraContext;
+        NetherDescriptorStatus currentCynaraDescriptorStatus;
+        int currentCynaraDescriptor;
+        std::vector<u_int32_t> responseQueue;
+        int cynaraLastResult;
+};
+
+// #endif
+#endif
diff --git a/include/nether_DummyBackend.h b/include/nether_DummyBackend.h
new file mode 100644 (file)
index 0000000..438e0f7
--- /dev/null
@@ -0,0 +1,35 @@
+#ifndef NETHER_DUMMY_BACKEND_H
+#define NETHER_DUMMY_BACKEND_H
+
+
+#include "nether_PolicyBackend.h"
+
+class NetherDummyBackend : public NetherPolicyBackend
+{
+    public:
+        NetherDummyBackend(const NetherConfig &netherConfig)
+            : NetherPolicyBackend(netherConfig) {}
+        ~NetherDummyBackend() {}
+
+        const bool isValid()
+        {
+            return (true);
+        }
+
+        const bool initialize()
+        {
+            return (true);
+        }
+
+        const bool enqueueVerdict(const NetherPacket &packet)
+        {
+            return (castVerdict (packet, netherConfig.defaultVerdict));
+        }
+
+        const bool processEvents()
+        {
+            return (true);
+        }
+};
+
+#endif
diff --git a/include/nether_FileBackend.h b/include/nether_FileBackend.h
new file mode 100644 (file)
index 0000000..4060153
--- /dev/null
@@ -0,0 +1,63 @@
+#ifndef NETHER_FILE_BACKEND_H
+#define NETHER_FILE_BACKEND_H
+
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <vector>
+#include <tuple>
+
+#include "nether_PolicyBackend.h"
+
+#define NETHER_POLICY_CREDS_DELIM   ":"
+
+class NetherManager;
+
+enum PolicyFileTokens
+{
+    uidT,
+    gidT,
+    secctxT,
+    verdictT
+};
+
+struct PolicyEntry
+{
+    uid_t uid;
+    gid_t gid;
+    std::string securityContext;
+    NetherVerdict verdict;
+};
+
+static const std::string dumpPolicyEntry(const PolicyEntry &entry)
+{
+    std::stringstream stream;
+    stream << "UID=";
+    if (entry.uid == NETHER_INVALID_UID) stream << "*"; else stream << entry.uid;
+    stream << " GID=";
+    if (entry.gid == NETHER_INVALID_GID) stream << "*"; else stream << entry.gid;
+    stream << " SECCTX=";
+    if (entry.securityContext.empty()) stream << "*"; else stream << entry.securityContext;
+    stream << " VERDICT=";
+    stream << verdictToString(entry.verdict);
+
+    return (stream.str());
+}
+
+class NetherFileBackend : public NetherPolicyBackend
+{
+    public:
+        NetherFileBackend(const NetherConfig &netherConfig);
+        ~NetherFileBackend();
+        const bool isValid();
+        const bool initialize();
+        const bool reload();
+        const bool enqueueVerdict(const NetherPacket &packet);
+        const bool parsePolicyFile(std::ifstream &policyFile);
+        const bool processEvents() { return (true); }
+        std::vector<std::string> split(const std::string  &str, const std::string  &delim);
+    private:
+        std::vector<PolicyEntry> policy;
+};
+
+#endif
diff --git a/include/nether_Manager.h b/include/nether_Manager.h
new file mode 100644 (file)
index 0000000..8e58d56
--- /dev/null
@@ -0,0 +1,30 @@
+#ifndef NETHER_MANAGER_H
+#define NETHER_MANAGER_H
+
+#include "nether_Types.h"
+#include "nether_DummyBackend.h"
+#include "nether_Netlink.h"
+
+
+class NetherManager : public NetherVerdictListener, public NetherProcessedPacketListener
+{
+    public:
+        NetherManager(const NetherConfig &_netherConfig);
+        ~NetherManager();
+        const bool initialize();
+        const bool process();
+        NetherConfig &getConfig();
+        static NetherPolicyBackend *getPolicyBackend(const NetherConfig &netherConfig, const bool primary = true);
+        bool verdictCast (const u_int32_t packetId, const NetherVerdict verdict);
+        void packetReceived (const NetherPacket &packet);
+
+    private:
+        NetherPolicyBackend *netherPrimaryPolicyBackend, *netherBackupPolicyBackend;
+        NetherDummyBackend *netherFallbackPolicyBackend;
+        NetherNetlink *netherNetlink;
+        NetherConfig netherConfig;
+        int netlinkDescriptor, backendDescriptor, signalDescriptor;
+        sigset_t signalMask;
+};
+
+#endif
diff --git a/include/nether_Netlink.h b/include/nether_Netlink.h
new file mode 100644 (file)
index 0000000..8b934a7
--- /dev/null
@@ -0,0 +1,33 @@
+#ifndef NETHER_NETLINK_H\r
+#define NETHER_NETLINK_H\r
+\r
+#include "nether_Types.h"\r
+#include "nether_Utils.h"
+\r
+class NetherManager;\r
+\r
+class NetherNetlink : public NetherPacketProcessor\r
+{\r
+    public:\r
+        NetherNetlink(NetherConfig &netherConfig);\r
+        ~NetherNetlink();\r
+        const bool initialize();
+        const bool reload();\r
+        static int callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *data);
+        const bool processPacket (char *packetBuffer, const int packetReadSize);
+        void setVerdict(const u_int32_t packetId, const NetherVerdict verdict);
+        int getDescriptor();\r
+        const bool isValid();
+\r
+    protected:
+        NetherPacket *processedPacket;
+\r
+    private:\r
+        struct nfq_q_handle *queueHandle;\r
+        struct nfq_handle *nfqHandle;
+        struct nlif_handle *nlif;\r
+        int fd;\r
+        uint32_t queue;\r
+};\r
+\r
+#endif  // NETLINK_H_INCLUDED\r
diff --git a/include/nether_PolicyBackend.h b/include/nether_PolicyBackend.h
new file mode 100644 (file)
index 0000000..55879bf
--- /dev/null
@@ -0,0 +1,24 @@
+#ifndef NETHER_POLICY_BACKEND_H
+#define NETHER_POLICY_BACKEND_H
+
+#include "nether_Types.h"
+#include "nether_Utils.h"
+
+class NetherPolicyBackend : public NetherVerdictCaster
+{
+    public:
+        NetherPolicyBackend(const NetherConfig &_netherConfig) : netherConfig(_netherConfig) {}
+        virtual ~NetherPolicyBackend() {}
+        virtual const bool enqueueVerdict (const NetherPacket &packet) = 0;
+        virtual const bool initialize() = 0;
+        virtual const bool reload() { return (true); };
+        virtual const bool isValid() = 0;
+        virtual const int getDescriptor() { return (-1); }
+        virtual const NetherDescriptorStatus getDescriptorStatus() { return (unknownStatus); }
+        virtual const bool processEvents() = 0;
+
+    protected:
+        NetherConfig netherConfig;
+};
+
+#endif
diff --git a/include/nether_Types.h b/include/nether_Types.h
new file mode 100644 (file)
index 0000000..624abe8
--- /dev/null
@@ -0,0 +1,192 @@
+#ifndef NETHER_TYPES_H
+#define NETHER_TYPES_H
+
+#include <iostream>
+#include <errno.h>
+#include <iostream>
+#include <sstream>
+#include <unistd.h>
+#include <memory>
+
+#include <sys/types.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <strings.h>
+#include <getopt.h>
+#include <assert.h>
+#include <netinet/in.h>
+#include <sys/signalfd.h>
+#include <linux/types.h>
+#include <linux/netfilter.h>
+#include <libnetfilter_queue/libnetfilter_queue.h>
+#include "logger/logger.hpp"
+#include "logger/backend-file.hpp"
+#include "logger/backend-stderr.hpp"
+#include "logger/backend-syslog.hpp"
+
+#ifdef HAVE_CYNARA
+ #define NETHER_PRIMARY_BACKEND          cynaraBackend
+ #define NETHER_BACKUP_BACKEND           fileBackend
+#else
+ #define NETHER_PRIMARY_BACKEND          fileBackend
+ #define NETHER_BACKUP_BACKEND           dummyBackend
+#endif
+
+#define NETHER_DEFAULT_VERDICT          allowAndLog
+#define NETHER_PACKET_BUFFER_SIZE       4096
+#define NETHER_INVALID_UID              (uid_t) -1
+#define NETHER_INVALID_GID              (gid_t) -1
+#define NETHER_NETWORK_ADDR_LEN         16 /* enough to hold ipv4 and ipv6 */
+#define NETHER_NETWORK_IPV4_ADDR_LEN    4
+#define NETHER_NETWORK_IPV6_ADDR_LEN    16
+#define NETHER_MAX_USER_LEN             32
+#define NETLINK_DROP_MARK               3
+#define NETLINK_ALLOWLOG_MARK           4
+#define NETHER_LOG_BACKEND              stderrBackend
+
+enum NetherPolicyBackendType
+{
+    cynaraBackend,
+    fileBackend,
+    dummyBackend
+};
+
+enum NetherLogBackendType
+{
+    stderrBackend,
+    syslogBackend,
+    journalBackend,
+    logfileBackend,
+    nullBackend
+};
+
+enum NetherVerdict
+{
+    allow,
+    allowAndLog,
+    deny,
+    noVerdictYet
+};
+
+enum NetherDescriptorStatus
+{
+    readOnly,
+    writeOnly,
+    readWrite,
+    unknownStatus
+};
+
+enum NetherTransportType
+{
+    TCP,
+    UDP,
+    ICMP,
+    IGMP,
+    unknownTransportType
+};
+
+enum NetherProtocolType
+{
+    IPv4,
+    IPv6,
+    unknownProtocolType
+};
+
+
+struct NetherPacket
+{
+    u_int32_t id;
+    std::string securityContext;
+    uid_t uid;
+    gid_t gid;
+    pid_t pid;
+    NetherTransportType transportType;
+    NetherProtocolType protocolType;
+    char localAddress[NETHER_NETWORK_ADDR_LEN];
+    int localPort;
+    char remoteAddress[NETHER_NETWORK_ADDR_LEN];
+    int remotePort;
+};
+
+struct NetherConfig
+{
+    NetherVerdict defaultVerdict                = NETHER_DEFAULT_VERDICT;
+    NetherPolicyBackendType primaryBackendType  = NETHER_PRIMARY_BACKEND;
+    NetherPolicyBackendType backupBackendType   = NETHER_BACKUP_BACKEND;
+    NetherLogBackendType logBackend             = NETHER_LOG_BACKEND;
+    int primaryBackendRetries                   = 3;
+    int backupBackendRetries                    = 3;
+    int debugMode                               = 0;
+    int nodaemonMode                            = 0;
+    int queueNumber                             = 0;
+    std::string backupBackendArgs;
+    std::string primaryBackendArgs;
+    std::string logBackendArgs;
+    uint8_t markDeny                            = NETLINK_DROP_MARK;
+    uint8_t markAllowAndLog                     = NETLINK_ALLOWLOG_MARK;
+};
+
+class NetherVerdictListener
+{
+    public:
+        virtual bool verdictCast (const u_int32_t packetId, const NetherVerdict verdict) = 0;
+};
+
+class NetherVerdictCaster
+{
+    public:
+        NetherVerdictCaster() : verdictListener(nullptr) {}
+        virtual ~NetherVerdictCaster() {}
+
+        void setListener(NetherVerdictListener *listenerToSet)
+        {
+            verdictListener = listenerToSet;
+        }
+
+        bool castVerdict (const NetherPacket &packet, const NetherVerdict verdict)
+        {
+            if (verdictListener)
+                return (verdictListener->verdictCast(packet.id, verdict));
+            return (false);
+        }
+
+        bool castVerdict (const u_int32_t packetId, const NetherVerdict verdict)
+        {
+            if (verdictListener)
+                return (verdictListener->verdictCast(packetId, verdict));
+            return (false);
+        }
+
+    protected:
+        NetherVerdictListener *verdictListener;
+};
+
+class NetherProcessedPacketListener
+{
+    public:
+        virtual void packetReceived (const NetherPacket &packet) = 0;
+};
+
+class NetherPacketProcessor
+{
+    public:
+        NetherPacketProcessor(NetherConfig &_netherConfig) : netherConfig(_netherConfig), packetListener(nullptr) {}
+        virtual ~NetherPacketProcessor() {}
+        virtual const bool reload() { return (true); }
+        void setListener(NetherProcessedPacketListener *listenerToSet)
+        {
+            packetListener = listenerToSet;
+        }
+
+        void processNetherPacket (NetherPacket packetInfoToWrite)
+        {
+            if (packetListener) packetListener->packetReceived(packetInfoToWrite);
+        }
+
+        virtual void setVerdict(const NetherPacket &packet, const NetherVerdict verdict) {}
+    protected:
+        NetherProcessedPacketListener *packetListener;
+        NetherConfig netherConfig;
+};
+#endif
diff --git a/include/nether_Utils.h b/include/nether_Utils.h
new file mode 100644 (file)
index 0000000..5d654e9
--- /dev/null
@@ -0,0 +1,164 @@
+#ifndef NETHER_UTILS_H
+#define NETHER_UTILS_H
+#include "nether_Types.h"
+void decodePacket(NetherPacket &packet, unsigned char *payload);
+void decodeIPv4Packet(NetherPacket &packet, unsigned char *payload);
+void decodeIPv6Packet(NetherPacket &packet, unsigned char *payload);
+void decodeTcp(NetherPacket &packet, unsigned char *payload);
+void decodeUdp(NetherPacket &packet, unsigned char *payload);
+const std::string ipAddressToString(const char *src, enum NetherProtocolType type);
+
+template <typename Type>
+inline void deleteAndZero (Type& pointer)                           { delete pointer; pointer = nullptr; }
+
+static const NetherVerdict stringToVerdict (char *verdictAsString)
+{
+    if (verdictAsString)
+    {
+        if (strncasecmp (verdictAsString, "allow_log", 9) == 0)
+            return (allowAndLog);
+        if (strncasecmp (verdictAsString, "allow", 6) == 0)
+            return (allow);
+        if (strncasecmp (verdictAsString, "deny", 4) == 0)
+            return (deny);
+    }
+    return (allowAndLog);
+}
+
+static const NetherPolicyBackendType stringToBackendType (char *backendAsString)
+{
+    if (strcasecmp (backendAsString, "cynara") == 0)
+      return (cynaraBackend);
+    if (strcasecmp (backendAsString, "file") == 0)
+      return (fileBackend);
+    if (strcasecmp (backendAsString, "dummy") == 0)
+      return (dummyBackend);
+
+    return (dummyBackend);
+}
+
+static const NetherLogBackendType stringToLogBackendType(char *backendAsString)
+{
+    if (strcasecmp (backendAsString, "stderr") == 0)
+      return (stderrBackend);
+    if (strcasecmp (backendAsString, "syslog") == 0)
+      return (syslogBackend);
+    if (strcasecmp (backendAsString, "journal") == 0)
+      return (journalBackend);
+    if (strcasecmp (backendAsString, "file") == 0)
+      return (logfileBackend);
+    if (strcasecmp (backendAsString, "null") == 0)
+      return (nullBackend);
+
+    return (nullBackend);
+}
+
+static const std::string logBackendTypeToString(const NetherLogBackendType backendType)
+{
+    switch (backendType)
+    {
+        case stderrBackend:
+            return ("stderr");
+        case syslogBackend:
+            return ("syslog");
+        case journalBackend:
+            return ("journal");
+    case logfileBackend:
+            return ("file");
+        case nullBackend:
+            return ("null");
+    }
+    return ("null");
+}
+
+static const std::string backendTypeToString (const NetherPolicyBackendType backendType)
+{
+    switch (backendType)
+    {
+        case cynaraBackend:
+            return ("cynara");
+        case fileBackend:
+            return ("file");
+        case dummyBackend:
+        default:
+            return ("dummy");
+    }
+}
+
+static const std::string verdictToString (const NetherVerdict verdict)
+{
+    switch (verdict)
+    {
+        case allow:
+            return ("ALLOW");
+        case allowAndLog:
+            return ("ALLOW_LOG");
+        case deny:
+            return ("DENY");
+    }
+}
+
+static const std::string transportToString(const NetherTransportType transportType)
+{
+    switch (transportType)
+    {
+        case TCP:
+            return ("TCP");
+        case UDP:
+            return ("UDP");
+        case ICMP:
+            return ("ICMP");
+        case IGMP:
+            return ("IGMP");
+        case unknownTransportType:
+        default:
+            return ("UNKNOWN");
+    }
+}
+
+static const std::string protocolToString(const NetherProtocolType protocolType)
+{
+    switch (protocolType)
+    {
+        case IPv4:
+            return ("IPv4");
+        case IPv6:
+            return ("IPv6");
+        default:
+            return ("UNKNOWN");
+    }
+}
+
+static const std::string packetToString (const NetherPacket &packet)
+{
+    std::stringstream stream;
+    stream << "ID=";
+    stream << packet.id;
+    stream << " SECCTX=";
+    stream << packet.securityContext;
+    stream << " UID=";
+    stream << packet.uid;
+    stream << " PROTO=";
+    stream << protocolToString(packet.protocolType);
+    stream << " TRANSPORT=";
+    stream << transportToString(packet.transportType);
+    stream << " SADDR=";
+    stream << ipAddressToString(&packet.localAddress[0], packet.protocolType);
+    stream << ":";
+    stream << packet.localPort;
+    stream << " DADDR=";
+    stream << ipAddressToString(&packet.remoteAddress[0], packet.protocolType);
+    stream << ":";
+    stream << packet.remotePort;
+    return (stream.str());
+}
+
+template<typename ... Args>
+std::string stringFormat( const char* format, Args ... args )
+{
+    size_t size = snprintf( nullptr, 0, format, args ... ) + 1; // Extra space for '\0'
+    std::unique_ptr<char[]> buf( new char[ size ] );
+    snprintf( buf.get(), size, format, args ... );
+    return std::string( buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside
+}
+#endif
diff --git a/src/nether_CynaraBackend.cpp b/src/nether_CynaraBackend.cpp
new file mode 100644 (file)
index 0000000..10b426e
--- /dev/null
@@ -0,0 +1,156 @@
+#include "nether_CynaraBackend.h"
+
+// #ifdef HAVE_CYNARA
+
+NetherCynaraBackend::NetherCynaraBackend(const NetherConfig &netherConfig)
+    :   NetherPolicyBackend(netherConfig), currentCynaraDescriptor(0),
+        cynaraLastResult(CYNARA_API_UNKNOWN_ERROR)
+{
+    responseQueue.reserve(1024);
+}
+
+NetherCynaraBackend::~NetherCynaraBackend()
+{
+}
+
+const bool NetherCynaraBackend::initialize()
+{
+    cynaraLastResult  = cynara_async_initialize(&cynaraContext, NULL, &statusCallback, this);
+    if (cynaraLastResult != CYNARA_API_SUCCESS)
+    {
+        LOGE("Failed to initialize cynara client " << cynaraErrorCodeToString(cynaraLastResult));
+        return (false);
+    }
+
+    return (true);
+}
+
+void NetherCynaraBackend::statusCallback(int oldFd, int newFd, cynara_async_status status, void *data)
+{
+    LOGD("oldFd=" << oldFd << "newFd=" << newFd);
+
+    NetherCynaraBackend *backend = static_cast<NetherCynaraBackend *>(data);
+
+    if (status == CYNARA_STATUS_FOR_READ)
+        backend->setCynaraDescriptor(newFd, readOnly);
+
+    if (status == CYNARA_STATUS_FOR_RW)
+        backend->setCynaraDescriptor(newFd, readWrite);
+}
+
+void NetherCynaraBackend::checkCallback(cynara_check_id check_id,
+                                        cynara_async_call_cause cause,
+                                        int response,
+                                        void *data)
+{
+    NetherCynaraBackend *backend = static_cast<NetherCynaraBackend *>(data);
+
+    if (cause == CYNARA_CALL_CAUSE_ANSWER)
+        backend->setCynaraVerdict (check_id, response);
+    else
+        LOGI("unknown reason for call cause="<< cause <<" response="<< response);
+}
+
+const bool NetherCynaraBackend::enqueueVerdict (const NetherPacket &packet)
+{
+    char user[NETHER_MAX_USER_LEN];
+    cynara_check_id checkId;
+
+    snprintf (user, sizeof(user), "%du", packet.uid);
+
+    cynaraLastResult = cynara_async_check_cache(cynaraContext, packet.securityContext.c_str(), "", user, NETHER_CYNARA_INTERNET_PRIVILEGE);
+
+    switch (cynaraLastResult)
+    {
+        case CYNARA_API_ACCESS_ALLOWED:
+            LOGD(cynaraErrorCodeToString(cynaraLastResult).c_str());
+            return (castVerdict(packet, allow));
+
+        case CYNARA_API_ACCESS_DENIED:
+            LOGD(cynaraErrorCodeToString(cynaraLastResult).c_str());
+            return (castVerdict(packet, deny));
+
+        case CYNARA_API_CACHE_MISS:
+            cynaraLastResult = cynara_async_create_request(cynaraContext,
+                                                packet.securityContext.c_str(),
+                                                "",
+                                                user,
+                                                NETHER_CYNARA_INTERNET_PRIVILEGE,
+                                                &checkId,
+                                                &checkCallback,
+                                                this);
+            if (cynaraLastResult == CYNARA_API_SUCCESS)
+            {
+                responseQueue.insert (responseQueue.begin() + checkId, packet.id);
+
+                return (true);
+            }
+            else if (cynaraLastResult == CYNARA_API_SERVICE_NOT_AVAILABLE)
+            {
+                LOGW("Cynara offline, fall back to another backend");
+                return (false);
+            }
+            else
+            {
+                LOGW("Error on cynara request create after CYNARA_API_CACHE_MISS " << cynaraErrorCodeToString(cynaraLastResult));
+                return (false);
+            }
+
+        default:
+            LOGW("Error on cynara request create unhandled result from cynara_async_check_cache "<<cynaraErrorCodeToString(cynaraLastResult));
+            return (false);
+    }
+
+    return (true);
+}
+
+void NetherCynaraBackend::setCynaraVerdict(cynara_check_id checkId, int cynaraResult)
+{
+    u_int32_t packetId = 0;
+    if ((packetId = responseQueue.at(checkId)) >= 0)
+    {
+        responseQueue.erase(responseQueue.begin() + checkId);
+
+        if (cynaraResult == CYNARA_API_ACCESS_ALLOWED)
+            castVerdict (packetId, allow);
+        else
+            castVerdict (packetId, deny);
+
+        return;
+    }
+
+    LOGW("checkId=" << checkId << " has no assosiated packetId");
+}
+
+const bool NetherCynaraBackend::isValid()
+{
+    return ((cynaraLastResult ==  CYNARA_API_SUCCESS ? true : false) && cynaraContext);
+}
+
+const int NetherCynaraBackend::getDescriptor()
+{
+    return (currentCynaraDescriptor);
+}
+
+const NetherDescriptorStatus NetherCynaraBackend::getDescriptorStatus()
+{
+    return (currentCynaraDescriptorStatus);
+}
+
+void NetherCynaraBackend::setCynaraDescriptor(const int _currentCynaraDescriptor, const NetherDescriptorStatus _currentCynaraDescriptorStatus)
+{
+    currentCynaraDescriptorStatus   = _currentCynaraDescriptorStatus;
+    currentCynaraDescriptor         = _currentCynaraDescriptor;
+}
+
+const bool NetherCynaraBackend::processEvents()
+{
+    int ret = cynara_async_process(cynaraContext);
+
+    if (ret == CYNARA_API_SUCCESS)
+        return (true);
+
+    LOGW("cynara_async_process failed " << cynaraErrorCodeToString(ret));
+    return (false);
+}
+//#endif
diff --git a/src/nether_FileBackend.cpp b/src/nether_FileBackend.cpp
new file mode 100644 (file)
index 0000000..b73281e
--- /dev/null
@@ -0,0 +1,101 @@
+#include "nether_FileBackend.h"
+
+NetherFileBackend::NetherFileBackend (const NetherConfig &netherConfig)
+    : NetherPolicyBackend(netherConfig)
+{
+}
+
+NetherFileBackend::~NetherFileBackend()
+{
+}
+
+const bool NetherFileBackend::isValid()
+{
+    return (true);
+}
+
+const bool NetherFileBackend::initialize()
+{
+    std::ifstream policyFile;
+    policyFile.open (netherConfig.backupBackendArgs, std::ifstream::in);
+
+    if (!policyFile)
+    {
+        LOGE("Can't open policy file at: " << netherConfig.backupBackendArgs);
+        return (false);
+    }
+
+    return (parsePolicyFile(policyFile));
+}
+
+const bool NetherFileBackend::reload()
+{
+    return (initialize());
+}
+
+const bool NetherFileBackend::enqueueVerdict(const NetherPacket &packet)
+{
+    for (auto &policyIterator : policy)
+    {
+        if (
+                ( (policyIterator.uid == packet.uid) || policyIterator.uid == NETHER_INVALID_UID ) &&
+                ( (policyIterator.gid == packet.gid) || policyIterator.gid == NETHER_INVALID_GID ) &&
+                ( (policyIterator.securityContext == packet.securityContext) || policyIterator.securityContext.empty() )
+            )
+        {
+            LOGD("policy match " << dumpPolicyEntry(policyIterator));
+            return (castVerdict(packet, policyIterator.verdict));
+        }
+    }
+
+    return (false);
+}
+
+const bool NetherFileBackend::parsePolicyFile(std::ifstream &policyFile)
+{
+    std::string line;
+    std::vector<std::string> tokens;
+    policy.clear();
+
+    while (!policyFile.eof())
+    {
+        getline(policyFile, line);
+        if (line[0] == '#' || line.empty() || !line.find(NETHER_POLICY_CREDS_DELIM, 0))
+            continue;
+
+        tokens = split (line, NETHER_POLICY_CREDS_DELIM);
+
+        if (tokens.size() > 0)
+        {
+            PolicyEntry entry { tokens[uidT].empty() ? NETHER_INVALID_UID : (uid_t)strtol(tokens[uidT].c_str(), NULL, 10),   /* uid */
+                                tokens[gidT].empty() ? NETHER_INVALID_GID : (gid_t)strtol(tokens[gidT].c_str(), NULL, 10),   /* gid */
+                                tokens[secctxT],                                                /* security context */
+                                stringToVerdict((char *)tokens[verdictT].c_str())               /* verdict */
+                            };
+
+            LOGD("\t"<<dumpPolicyEntry(entry).c_str());
+            policy.push_back(entry);
+        }
+    }
+
+    return (true);
+}
+
+std::vector<std::string> NetherFileBackend::split(const std::string &str, const std::string &delim)
+{
+    std::vector<std::string> tokens;
+    size_t  start = 0, end = 0;
+
+    while (end != std::string::npos)
+    {
+        end = str.find(delim, start);
+
+        // If at end, use length=maxLength.  Else use length=end-start.
+        tokens.push_back(str.substr(start, (end == std::string::npos) ? std::string::npos : end - start));
+
+        // If at end, use start=maxSize.  Else use start=end+delimiter.
+        start = ((end > (std::string::npos - delim.size())) ?  std::string::npos  :  end + delim.size());
+    }
+
+    return (tokens);
+}
diff --git a/src/nether_Main.cpp b/src/nether_Main.cpp
new file mode 100644 (file)
index 0000000..1ab5e9b
--- /dev/null
@@ -0,0 +1,167 @@
+#include "nether_Types.h"
+#include "nether_Utils.h"
+#include "nether_Manager.h"
+
+using namespace std;
+void showHelp(char *arg);
+
+int main(int argc, char *argv[])
+{
+    int optionIndex, c;
+    struct NetherConfig netherConfig;
+
+    static struct option longOptions[] =
+    {
+          {"nodaemon",                 no_argument,            &netherConfig.nodaemonMode,     1},
+          {"log",                   required_argument,  0,              'l'},
+          {"log-args",              required_argument,  0,              'L'},
+          {"default-verdict",      required_argument,  0,                              'V'},
+          {"primary-backend",              required_argument,  0,                              'p'},
+          {"primary-backend-args",  required_argument,  0,              'P'},
+          {"backup-backend",       required_argument,  0,                              'b'},
+          {"backup-backend-args",   required_argument,         0,                              'B'},
+          {"queue-num",                        required_argument,      0,                              'q'},
+          {"mark-deny",             required_argument,  0,              'm'},
+          {"mark-allow-log",        required_argument,  0,              'M'},
+          {"help",                         no_argument,                0,                              'h'},
+          {0, 0, 0, 0}
+    };
+
+    while (1)
+    {
+        c = getopt_long (argc, argv, ":nl:L:V:p:P:b:B:q:m:M:h", longOptions, &optionIndex);
+
+        if (c == -1)
+            break;
+
+        switch (c)
+        {
+            case 0:
+              break;
+
+            case 'n':
+                netherConfig.nodaemonMode           = 1;
+                break;
+
+            case 'l':
+                netherConfig.logBackend             = stringToLogBackendType(optarg);
+                break;
+
+            case 'L':
+                netherConfig.logBackendArgs         = optarg;
+                break;
+
+            case 'V':
+                netherConfig.defaultVerdict         = stringToVerdict (optarg);
+                break;
+
+            case 'p':
+                netherConfig.primaryBackendType     = stringToBackendType (optarg);
+                break;
+
+            case 'P':
+                netherConfig.primaryBackendArgs     = optarg;
+                break;
+
+            case 'b':
+                netherConfig.backupBackendType      = stringToBackendType (optarg);
+                break;
+
+            case 'B':
+                netherConfig.backupBackendArgs      = optarg;
+                break;
+
+            case 'q':
+                if (atoi(optarg) < 0 || atoi(optarg) >= 65535)
+                {
+                    cerr << "Queue number is invalid (must be >= 0 and < 65535): " << atoi(optarg);
+                    exit (1);
+                }
+                netherConfig.queueNumber            = atoi(optarg);
+                break;
+
+            case 'm':
+                if (atoi(optarg) <= 0 || atoi(optarg) >= 255)
+                {
+                    cerr << "Packet mark for DENY is invalid (must be > 0 and < 255): " << atoi(optarg);
+                    exit (1);
+                }
+                netherConfig.markDeny               = atoi(optarg);
+                break;
+
+            case 'M':
+                if (atoi(optarg) <= 0 || atoi(optarg) >= 255)
+                {
+                    cerr << "Packet mark for ALLOW_LOG is invalid (must be > 0 and < 255): " << atoi(optarg);
+                    exit (1);
+                }
+                netherConfig.markAllowAndLog        = atoi(optarg);
+                break;
+
+            case 'h':
+              showHelp (argv[0]);
+              exit (1);
+        }
+    }
+    switch (netherConfig.logBackend)
+    {
+        case stderrBackend:
+            logger::Logger::setLogBackend (new logger::StderrBackend(false));
+            break;
+        case syslogBackend:
+            logger::Logger::setLogBackend (new logger::SyslogBackend());
+            break;
+        case logfileBackend:
+            logger::Logger::setLogBackend (new logger::FileBackend(netherConfig.logBackendArgs));
+            break;
+        default:
+            logger::Logger::setLogBackend (new logger::StderrBackend(false));
+            break;
+    }
+
+    LOGD("NETHER OPTIONS:"
+#if defined(_DEBUG)
+        << " debug"
+#endif
+        << " nodaemon="              << netherConfig.nodaemonMode
+        << " queue="                 << netherConfig.queueNumber);
+    LOGD("primary-backend="       << backendTypeToString (netherConfig.primaryBackendType)
+        << " primary-backend-args="  << netherConfig.primaryBackendArgs);
+    LOGD("backup-backend="        << backendTypeToString (netherConfig.backupBackendType)
+        << " backup-backend-args="   << netherConfig.backupBackendArgs);
+    LOGD("default-verdict="       << verdictToString(netherConfig.defaultVerdict)
+        << " mark-deny="             << (int)netherConfig.markDeny
+        << " mark-allow-log="        << (int)netherConfig.markAllowAndLog);
+    LOGD("log-backend="           << logBackendTypeToString(netherConfig.logBackend)
+        << " log-backend-args="      << netherConfig.logBackendArgs);
+
+    NetherManager manager (netherConfig);
+
+    if (!manager.initialize())
+    {
+        LOGE("NetherManager failed to initialize, exiting");
+        return (1);
+    }
+
+    manager.process();
+
+    return (0);
+}
+
+void showHelp(char *arg)
+{
+    cout<< "Usage:\t"<< arg << " [OPTIONS]\n\n";
+    cout<< "  -n,--nodaemon\t\t\t\tDon't run as daemon in the background\n";
+    cout<< "  -d,--debug\t\t\t\tRun in debug mode (implies --nodaemon)\n";
+    cout<< "  -l,--log=<backend>\t\t\tSet logging backend STDERR,SYSLOG,JOURNAL (default:"<< logBackendTypeToString(NETHER_LOG_BACKEND) << ")\n";
+    cout<< "  -L,--log-args=<arguments>\t\tSet logging backend arguments\n";
+    cout<< "  -V,--verdict=<verdict>\t\tWhat verdict to cast when policy backend is not available\n\t\t\t\t\tACCEPT,ALLOW_LOG,DENY (default:"<<verdictToString(NETHER_DEFAULT_VERDICT)<<")\n";
+    cout<< "  -p,--primary-backend=<module>\t\tPrimary policy backend\n\t\t\t\t\tCYNARA,FILE,NONE (defualt:"<< backendTypeToString(NETHER_PRIMARY_BACKEND)<<")\n";
+    cout<< "  -P,--primary-backend-args=<arguments>\tPrimary policy backend arguments\n";
+    cout<< "  -b,--backup-backend=<module>\t\tBackup policy backend\n\t\t\t\t\tCYNARA,FILE,NONE (defualt:"<< backendTypeToString(NETHER_BACKUP_BACKEND)<< ")\n";
+    cout<< "  -B,--backup-backend-args=<arguments>\tBackup policy backend arguments\n";
+    cout<< "  -q,--queue-num=<queue number>\t\tNFQUEUE queue number to use for receiving packets\n";
+    cout<< "  -m,--mark-deny=<mark>\t\t\tPacket mark to use for DENY verdicts (default:"<< NETLINK_DROP_MARK << ")\n";
+    cout<< "  -M,--mark-allow-log=<mark>\t\tPacket mark to use for ALLOW_LOG verdicts (default:" << NETLINK_ALLOWLOG_MARK << ")\n";
+    cout<< "  -h,--help\t\t\t\tshow help information\n";
+}
diff --git a/src/nether_Manager.cpp b/src/nether_Manager.cpp
new file mode 100644 (file)
index 0000000..0ec0ae6
--- /dev/null
@@ -0,0 +1,253 @@
+#include "nether_Manager.h"
+#include "nether_CynaraBackend.h"
+#include "nether_FileBackend.h"
+#include "nether_DummyBackend.h"
+
+NetherManager::NetherManager(const NetherConfig &_netherConfig)
+    :   netherConfig(_netherConfig),
+        netherPrimaryPolicyBackend(nullptr),
+        netherBackupPolicyBackend(nullptr),
+        netherFallbackPolicyBackend(nullptr)
+{
+    netherNetlink               = new NetherNetlink(netherConfig);
+    netherNetlink->setListener (this);
+
+    netherPrimaryPolicyBackend = getPolicyBackend (netherConfig);
+    netherPrimaryPolicyBackend->setListener (this);
+
+    netherBackupPolicyBackend   = getPolicyBackend (netherConfig, false);
+    netherBackupPolicyBackend->setListener (this);
+
+    netherFallbackPolicyBackend = new NetherDummyBackend(netherConfig);
+}
+
+NetherManager::~NetherManager()
+{
+    deleteAndZero (netherPrimaryPolicyBackend);
+    deleteAndZero (netherBackupPolicyBackend);
+    deleteAndZero (netherFallbackPolicyBackend);
+    deleteAndZero (netherNetlink);
+    close (signalDescriptor);
+}
+
+const bool NetherManager::initialize()
+{
+    sigemptyset(&signalMask);
+    sigaddset(&signalMask, SIGHUP);
+
+    if (sigprocmask(SIG_BLOCK, &signalMask, NULL) == -1)
+    {
+        LOGE("Failed to block signals sigprocmask()");
+        return (false);
+    }
+
+    signalDescriptor = signalfd(-1, &signalMask, 0);
+    if (signalDescriptor == -1)
+    {
+        LOGE("Failed acquire signalfd descriptor");
+        return (false);
+    }
+
+    if (!netherNetlink->initialize())
+    {
+        LOGE("Failed to initialize netlink subsystem, exiting");
+        return (false);
+    }
+
+    if (!netherPrimaryPolicyBackend->initialize())
+    {
+        LOGE("Failed to initialize primary policy backend, exiting");
+        return (false);
+    }
+
+    if (!netherBackupPolicyBackend->initialize())
+    {
+        LOGE("Failed to initialize backup backend, exiting");
+        return (false);
+    }
+
+    if ((netlinkDescriptor = netherNetlink->getDescriptor()) == -1)
+    {
+        LOGE("Netlink subsystem did not return a valid descriptor, exiting");
+        return (false);
+    }
+
+    if ((backendDescriptor = netherPrimaryPolicyBackend->getDescriptor()) == -1)
+    {
+        LOGI("Policy backend does not provide descriptor for select()");
+    }
+    return (true);
+}
+
+const bool NetherManager::process()
+{
+    NetherPacket receivedPacket;
+    int packetReadSize;
+    ssize_t signalRead;
+    struct signalfd_siginfo signalfdSignalInfo;\r
+    fd_set watchedReadDescriptorsSet, watchedWriteDescriptorsSet;\r
+    struct timeval timeoutSpecification;\r
+    char packetBuffer[NETHER_PACKET_BUFFER_SIZE] __attribute__ ((aligned));\r
+\r
+    while (1)\r
+    {\r
+           FD_ZERO (&watchedReadDescriptorsSet);
+           FD_ZERO (&watchedWriteDescriptorsSet);
+
+        /* Always listen for signals */
+        FD_SET (signalDescriptor, &watchedReadDescriptorsSet);
+
+           if ((netlinkDescriptor = netherNetlink->getDescriptor()) >= 0)
+        {\r
+            FD_SET(netlinkDescriptor, &watchedReadDescriptorsSet);
+        }
+
+        if ((backendDescriptor = netherPrimaryPolicyBackend->getDescriptor()) >= 0)
+        {
+            if (netherPrimaryPolicyBackend->getDescriptorStatus() == readOnly)
+            {
+                FD_SET(backendDescriptor, &watchedReadDescriptorsSet);
+            }
+            else if (netherPrimaryPolicyBackend->getDescriptorStatus() == readWrite)
+            {
+                FD_SET(backendDescriptor, &watchedReadDescriptorsSet);
+                FD_SET(backendDescriptor, &watchedWriteDescriptorsSet);
+            }
+        }
+\r
+           timeoutSpecification.tv_sec     = 240;\r
+        timeoutSpecification.tv_usec    = 0;
+\r
+        if (select (FD_SETSIZE, &watchedReadDescriptorsSet, &watchedWriteDescriptorsSet, NULL, &timeoutSpecification) < 0)\r
+        {\r
+            LOGE("select error " << strerror(errno));\r
+            return (false);\r
+        }\r
+
+        if (FD_ISSET(signalDescriptor, &watchedReadDescriptorsSet))
+        {
+            LOGD("received signal");
+            signalRead = read (signalDescriptor, &signalfdSignalInfo, sizeof(struct signalfd_siginfo));
+
+            if (signalRead != sizeof(struct signalfd_siginfo))
+            {
+                LOGW("Received incomplete signal information, ignore");
+                continue;
+            }
+
+            if (signalfdSignalInfo.ssi_signo == SIGHUP)
+            {
+                LOGI("SIGHUP received, reloading");
+                if (!netherPrimaryPolicyBackend->reload())
+                    LOGW("primary backend failed to reload");
+                if (!netherBackupPolicyBackend->reload())
+                    LOGW("backup backend failed to reload");
+                if (!netherNetlink->reload())
+                    LOGW("netlink failed to reload");
+                continue;
+            }
+        }\r
+        if (FD_ISSET(netlinkDescriptor, &watchedReadDescriptorsSet))\r
+        {
+            LOGD("netlink descriptor active");
+
+            /* some data arrives on netlink, read it */\r
+            if ((packetReadSize = recv(netlinkDescriptor, packetBuffer, sizeof(packetBuffer), 0)) >= 0)\r
+            {
+                /* try to process the packet using netfilter_queue library, fetch packet info
+                    needed for making a decision about it */\r
+                if (netherNetlink->processPacket (packetBuffer, packetReadSize))
+                {
+                   continue;
+                }
+                else
+                {
+                    /* if we can't process the incoming packets, it's bad. Let's exit now */
+                    LOGE("Failed to process netlink received packet, refusing to continue");
+                    break;
+                }\r
+            }
+\r
+            if (packetReadSize < 0 && errno == ENOBUFS)\r
+            {\r
+                LOGI("NetherManager::process losing packets! [bad things might happen]");\r
+                continue;\r
+            }\r
+\r
+            LOGE("NetherManager::process recv failed " << strerror(errno));\r
+            break;\r
+        }
+        else if (FD_ISSET(backendDescriptor, &watchedReadDescriptorsSet) || FD_ISSET(backendDescriptor, &watchedWriteDescriptorsSet))
+        {
+            LOGD("policy backend descriptor active");
+            netherPrimaryPolicyBackend->processEvents();
+        }
+        else
+        {
+            LOGD("select() timeout");
+        }
+    }
+}
+
+NetherConfig &NetherManager::getConfig()
+{
+    return (netherConfig);
+}
+
+NetherPolicyBackend *NetherManager::getPolicyBackend(const NetherConfig &netherConfig, const bool primary)
+{
+    switch (primary ? netherConfig.primaryBackendType : netherConfig.backupBackendType)
+    {
+        case cynaraBackend:
+#ifdef HAVE_CYNARA
+            return new NetherCynaraBackend(netherConfig);
+#else
+            return new NetherDummyBackend(netherConfig);
+#endif
+        case fileBackend:
+            return new NetherFileBackend(netherConfig);
+        case dummyBackend:
+        default:
+            return new NetherDummyBackend(netherConfig);
+    }
+}
+
+bool NetherManager::verdictCast (const u_int32_t packetId, const NetherVerdict verdict)
+{
+    if (netherNetlink)
+    {
+        netherNetlink->setVerdict(packetId, verdict);
+    }
+    else
+    {
+        LOGE("Netlink subsystem is invalid, can't decide on packet");
+        return (false);
+    }
+
+    return (true);
+}
+
+void NetherManager::packetReceived (const NetherPacket &packet)
+{
+    LOGD(packetToString(packet).c_str());
+
+    if (netherPrimaryPolicyBackend && netherPrimaryPolicyBackend->enqueueVerdict (packet))
+    {
+        LOGD("Primary policy accepted packet");
+        return;
+    }
+
+    if (netherBackupPolicyBackend && netherBackupPolicyBackend->enqueueVerdict (packet))
+    {
+        LOGI("Primary policy backend failed, using backup policy backend");
+        return;
+    }
+
+    /* In this situation no policy backend wants to deal with this packet
+        there propably isn't any rule in either of them
+
+        we need to make a generic decision based on whatever is hard-coded
+        or passed as a parameter to the service */
+    LOGW("All policy backends failed, using DUMMY backend");
+    netherFallbackPolicyBackend->enqueueVerdict (packet);
+}
diff --git a/src/nether_Netlink.cpp b/src/nether_Netlink.cpp
new file mode 100644 (file)
index 0000000..ed2dd67
--- /dev/null
@@ -0,0 +1,150 @@
+#include "nether_Netlink.h"\r
+\r
+NetherNetlink::NetherNetlink(NetherConfig &netherConfig)\r
+    : nfqHandle(nullptr), queueHandle(nullptr), nlif(nullptr),
+        queue(netherConfig.queueNumber),
+        NetherPacketProcessor(netherConfig)\r
+{\r
+}\r
+\r
+NetherNetlink::~NetherNetlink()\r
+{
+    if (queueHandle) nfq_destroy_queue(queueHandle);\r
+    if (nfqHandle) nfq_close(nfqHandle);\r
+}\r
+\r
+const bool NetherNetlink::initialize()\r
+{\r
+    nfqHandle = nfq_open();\r
+\r
+    if (!nfqHandle)\r
+    {\r
+        LOGE("Error during nfq_open()");\r
+        return (false);\r
+    }\r
+\r
+    if (nfq_unbind_pf(nfqHandle, AF_INET) < 0)\r
+    {\r
+        LOGE("Error during nfq_unbind_pf() (no permission?)");\r
+        return (false);\r
+    }\r
+\r
+    if (nfq_bind_pf(nfqHandle, AF_INET) < 0)\r
+    {\r
+        LOGE("Error during nfq_bind_pf()");\r
+        return (false);\r
+    }\r
+\r
+    queueHandle = nfq_create_queue(nfqHandle, queue, &callback, this);
+\r
+    if (!queueHandle)\r
+    {\r
+        LOGE("Error during nfq_create_queue()");\r
+        return (false);\r
+    }\r
+
+    if (nfq_set_queue_flags(queueHandle, NFQA_CFG_F_SECCTX, NFQA_CFG_F_SECCTX))\r
+        LOGI("This kernel version does not allow to retrieve security context");
+\r
+    if (nfq_set_mode(queueHandle, NFQNL_COPY_PACKET, 0xffff) < 0)\r
+    {\r
+        LOGE("Can't set packet_copy mode");\r
+        nfq_destroy_queue (queueHandle);\r
+        return (false);\r
+    }\r
+\r
+    if (nfq_set_queue_flags(queueHandle, NFQA_CFG_F_UID_GID, NFQA_CFG_F_UID_GID))\r
+    {\r
+        LOGE("This kernel version does not allow to retrieve process UID/GID");\r
+        nfq_destroy_queue (queueHandle);\r
+        return (false);\r
+    }\r
+
+    nlif = nlif_open();
+    if (!nlif)
+        LOGI("Failed to initialize NLIF subsystem, interface information won't be available");
+\r
+    return (true);\r
+}\r
+
+int NetherNetlink::getDescriptor()
+{
+    if (nfqHandle)
+        return (nfq_fd(nfqHandle));
+    else
+        LOGE("nfq not initialized");
+}
+
+const bool NetherNetlink::processPacket (char *packetBuffer, const int packetReadSize)
+{
+    if (nfq_handle_packet (nfqHandle, packetBuffer, packetReadSize))
+    {
+        LOGE("nfq_handle_packet failed");
+        return (false);
+    }
+
+    return (true);
+}
+\r
+int NetherNetlink::callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *data)\r
+{
+    NetherNetlink *me = static_cast<NetherNetlink *>(data);
+    NetherPacket packet;
+    unsigned char *secctx;
+    int secctxSize = 0;
+    struct nfqnl_msg_packet_hdr *ph;
+    unsigned char *payload;
+\r
+    if ((ph = nfq_get_msg_packet_hdr(nfa)))
+        packet.id = ntohl(ph->packet_id);
+    else
+    {
+        LOGI("Failed to get packet id");
+        return (1);
+    }
+\r
+    if (!nfq_get_uid(nfa, &packet.uid))
+        LOGW("Failed to get uid for packet id=" << packet.id);
+\r
+    nfq_get_gid(nfa, &packet.gid);
+\r
+    secctxSize = nfq_get_secctx(nfa, &secctx);
+
+    if (secctxSize > 0)
+        packet.securityContext = std::string ((char *)secctx, secctxSize);
+    else
+        LOGD("Failed to get security context for packet id=" << packet.id);
+
+    if (nfq_get_payload(nfa, &payload) > 0)
+        decodePacket(packet, payload);
+
+    me->processNetherPacket (packet); /* this call if from the NetherPacketProcessor class */\r
+\r
+    return (0);\r
+}\r
+\r
+const bool NetherNetlink::isValid()\r
+{\r
+    return (nfqHandle && queueHandle);\r
+}
+
+void NetherNetlink::setVerdict(const u_int32_t packetId, const NetherVerdict verdict)
+{
+    int ret = 0;
+    LOGD("id=" << packetId << " verdict=" << verdictToString(verdict));
+
+    if (verdict == allow)
+        ret = nfq_set_verdict (queueHandle, packetId, NF_ACCEPT, 0, NULL);
+    if (verdict == deny)
+        ret = nfq_set_verdict2 (queueHandle, packetId, NF_ACCEPT, netherConfig.markDeny, 0, NULL);
+    if (verdict == allowAndLog)
+        ret = nfq_set_verdict2 (queueHandle, packetId, NF_ACCEPT, netherConfig.markAllowAndLog, 0, NULL);
+
+    if (ret == -1)
+        LOGW("can't set verdict for packetId=" << packetId);
+}
+
+const bool NetherNetlink::reload()
+{
+    return (true);
+}
diff --git a/src/nether_NetworkUtils.cpp b/src/nether_NetworkUtils.cpp
new file mode 100644 (file)
index 0000000..c6e1072
--- /dev/null
@@ -0,0 +1,128 @@
+#include <netdb.h>
+#include <linux/types.h>
+#include "nether_Utils.h"
+
+#define IP_PROTOCOL_UDP         (0x11)
+#define IP_PROTOCOL_TCP         (0x06)
+#define IP_PROTOCOL_ICMP        (0x01)
+#define IP_PROTOCOL_IGMP        (0x02)
+#define IP_PROTOCOL_IPV6_ROUTE  (0x2b)
+#define IP_PROTOCOL_IPV6_FRAG   (0x2c)
+#define IP_PROTOCOL_IPV6_ICMP   (0x3a)
+#define IP_PROTOCOL_IPV6_NONXT  (0x3b)
+#define IP_PROTOCOL_IPV6_OPTS   (0x3c)
+
+void decodePacket(NetherPacket &packet, unsigned char *payload)
+{
+    uint8_t ip_version = (payload[0] >> 4) & 0x0F;
+
+    switch(ip_version)
+    {
+        case 4:
+            packet.protocolType     = IPv4;
+            decodeIPv4Packet(packet, payload);
+            break;
+        case 6:
+            packet.protocolType     = IPv6;
+            decodeIPv6Packet(packet, payload);
+            break;
+        default:
+            packet.transportType    = unknownTransportType;
+            packet.protocolType     = unknownProtocolType;
+            break;
+    }
+}
+
+void decodeIPv6Packet(NetherPacket &packet, unsigned char *payload)
+{
+    const uint16_t start_of_ip_payload = 40;
+    uint8_t next_proto;
+
+    memcpy(packet.localAddress, &payload[8], NETHER_NETWORK_IPV6_ADDR_LEN);
+    memcpy(packet.remoteAddress, &payload[24], NETHER_NETWORK_IPV6_ADDR_LEN);
+
+    next_proto = payload[6];
+
+    switch(next_proto)
+    {
+        case IP_PROTOCOL_UDP:
+            packet.transportType = UDP;
+            decodeUdp(packet, &payload[start_of_ip_payload]);
+            break;
+        case IP_PROTOCOL_TCP:
+            packet.transportType = TCP;
+            decodeTcp(packet, &payload[start_of_ip_payload]);
+            break;
+        case IP_PROTOCOL_ICMP:
+            packet.transportType = ICMP;
+            break;
+        case IP_PROTOCOL_IGMP:
+            packet.transportType = IGMP;
+            break;
+        default:
+            packet.transportType = unknownTransportType;
+            break;
+    }
+}
+
+void decodeIPv4Packet(NetherPacket &packet, unsigned char *payload)
+{
+    uint16_t start_of_ip_payload = 0;
+    uint8_t next_proto;
+
+    start_of_ip_payload = (payload[0]&0x0F) << 2;
+
+    memcpy(packet.localAddress, &payload[12], NETHER_NETWORK_IPV4_ADDR_LEN);
+    memcpy(packet.remoteAddress, &payload[16], NETHER_NETWORK_IPV4_ADDR_LEN);
+
+    next_proto = payload[9];
+    switch(next_proto)
+    {
+        case IP_PROTOCOL_UDP:
+            packet.transportType = UDP;
+            decodeUdp(packet, &payload[start_of_ip_payload]);
+            break;
+        case IP_PROTOCOL_TCP:
+            packet.transportType = TCP;
+            decodeTcp(packet, &payload[start_of_ip_payload]);
+            break;
+        case IP_PROTOCOL_ICMP:
+            packet.transportType = ICMP;
+            break;
+        case IP_PROTOCOL_IGMP:
+            packet.transportType = IGMP;
+        default:
+            packet.transportType = unknownTransportType;
+            break;
+    }
+}
+
+void decodeTcp(NetherPacket &packet, unsigned char *payload)
+{
+    packet.localPort = ntohs(*(unsigned short*) &payload[0]);
+    packet.remotePort = ntohs(*(unsigned short*) &payload[2]);
+}
+
+void decodeUdp(NetherPacket &packet, unsigned char *payload)
+{
+    packet.localPort = ntohs(*(unsigned short*) &payload[0]);
+    packet.remotePort = ntohs(*(unsigned short*) &payload[2]);
+
+}
+
+const std::string ipAddressToString(const char *src, enum NetherProtocolType type)
+{
+    switch(type)
+    {
+        case IPv4:
+            return (stringFormat("%u.%u.%u.%u", src[0]&0xff,src[1]&0xff,src[2]&0xff,src[3]&0xff));
+        case IPv6:
+            return (stringFormat("%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x",
+                            ntohs(*(uint16_t*) &src[0]), ntohs(*(uint16_t*) &src[2]),
+                            ntohs(*(uint16_t*) &src[4]), ntohs(*(uint16_t*) &src[6]),
+                            ntohs(*(uint16_t*) &src[8]), ntohs(*(uint16_t*) &src[10]),
+                            ntohs(*(uint16_t*) &src[12]), ntohs(*(uint16_t*) &src[14])));
+        default:
+            return ("(unknown)");
+    }
+}