From 285473ef6cb9249844e31377bb1c71ce3ee2a655 Mon Sep 17 00:00:00 2001 From: RomanKubiak Date: Thu, 16 Jul 2015 16:54:22 +0200 Subject: [PATCH 2/4] Initial source code for nether 0.0.1 (source code only) Change-Id: I16970c3dedd9071c970523a478fbf35e009d13ef --- include/nether_CynaraBackend.h | 50 ++++++++ include/nether_DummyBackend.h | 35 ++++++ include/nether_FileBackend.h | 63 ++++++++++ include/nether_Manager.h | 30 +++++ include/nether_Netlink.h | 33 ++++++ include/nether_PolicyBackend.h | 24 ++++ include/nether_Types.h | 192 +++++++++++++++++++++++++++++++ include/nether_Utils.h | 164 ++++++++++++++++++++++++++ src/nether_CynaraBackend.cpp | 156 +++++++++++++++++++++++++ src/nether_FileBackend.cpp | 101 ++++++++++++++++ src/nether_Main.cpp | 167 +++++++++++++++++++++++++++ src/nether_Manager.cpp | 253 +++++++++++++++++++++++++++++++++++++++++ src/nether_Netlink.cpp | 150 ++++++++++++++++++++++++ src/nether_NetworkUtils.cpp | 128 +++++++++++++++++++++ 14 files changed, 1546 insertions(+) create mode 100644 include/nether_CynaraBackend.h create mode 100644 include/nether_DummyBackend.h create mode 100644 include/nether_FileBackend.h create mode 100644 include/nether_Manager.h create mode 100644 include/nether_Netlink.h create mode 100644 include/nether_PolicyBackend.h create mode 100644 include/nether_Types.h create mode 100644 include/nether_Utils.h create mode 100644 src/nether_CynaraBackend.cpp create mode 100644 src/nether_FileBackend.cpp create mode 100644 src/nether_Main.cpp create mode 100644 src/nether_Manager.cpp create mode 100644 src/nether_Netlink.cpp create mode 100644 src/nether_NetworkUtils.cpp diff --git a/include/nether_CynaraBackend.h b/include/nether_CynaraBackend.h new file mode 100644 index 0000000..1242797 --- /dev/null +++ b/include/nether_CynaraBackend.h @@ -0,0 +1,50 @@ +#ifndef NETHER_CYNARA_BACKEND_H +#define NETHER_CYNARA_BACKEND_H + +// #ifdef HAVE_CYNARA + +#include +#include "nether_PolicyBackend.h" +#include + +#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 responseQueue; + int cynaraLastResult; +}; + +// #endif +#endif diff --git a/include/nether_DummyBackend.h b/include/nether_DummyBackend.h new file mode 100644 index 0000000..438e0f7 --- /dev/null +++ b/include/nether_DummyBackend.h @@ -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 index 0000000..4060153 --- /dev/null +++ b/include/nether_FileBackend.h @@ -0,0 +1,63 @@ +#ifndef NETHER_FILE_BACKEND_H +#define NETHER_FILE_BACKEND_H + +#include +#include +#include +#include +#include + +#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 split(const std::string &str, const std::string &delim); + private: + std::vector policy; +}; + +#endif diff --git a/include/nether_Manager.h b/include/nether_Manager.h new file mode 100644 index 0000000..8e58d56 --- /dev/null +++ b/include/nether_Manager.h @@ -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 index 0000000..8b934a7 --- /dev/null +++ b/include/nether_Netlink.h @@ -0,0 +1,33 @@ +#ifndef NETHER_NETLINK_H +#define NETHER_NETLINK_H + +#include "nether_Types.h" +#include "nether_Utils.h" + +class NetherManager; + +class NetherNetlink : public NetherPacketProcessor +{ + public: + NetherNetlink(NetherConfig &netherConfig); + ~NetherNetlink(); + const bool initialize(); + const bool reload(); + 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(); + const bool isValid(); + + protected: + NetherPacket *processedPacket; + + private: + struct nfq_q_handle *queueHandle; + struct nfq_handle *nfqHandle; + struct nlif_handle *nlif; + int fd; + uint32_t queue; +}; + +#endif // NETLINK_H_INCLUDED diff --git a/include/nether_PolicyBackend.h b/include/nether_PolicyBackend.h new file mode 100644 index 0000000..55879bf --- /dev/null +++ b/include/nether_PolicyBackend.h @@ -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 index 0000000..624abe8 --- /dev/null +++ b/include/nether_Types.h @@ -0,0 +1,192 @@ +#ifndef NETHER_TYPES_H +#define NETHER_TYPES_H + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 index 0000000..5d654e9 --- /dev/null +++ b/include/nether_Utils.h @@ -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 +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 +std::string stringFormat( const char* format, Args ... args ) +{ + size_t size = snprintf( nullptr, 0, format, args ... ) + 1; // Extra space for '\0' + std::unique_ptr 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 index 0000000..10b426e --- /dev/null +++ b/src/nether_CynaraBackend.cpp @@ -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(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(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 "<= 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 index 0000000..b73281e --- /dev/null +++ b/src/nether_FileBackend.cpp @@ -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 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"< NetherFileBackend::split(const std::string &str, const std::string &delim) +{ + std::vector 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 index 0000000..1ab5e9b --- /dev/null +++ b/src/nether_Main.cpp @@ -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=\t\t\tSet logging backend STDERR,SYSLOG,JOURNAL (default:"<< logBackendTypeToString(NETHER_LOG_BACKEND) << ")\n"; + cout<< " -L,--log-args=\t\tSet logging backend arguments\n"; + cout<< " -V,--verdict=\t\tWhat verdict to cast when policy backend is not available\n\t\t\t\t\tACCEPT,ALLOW_LOG,DENY (default:"<\t\tPrimary policy backend\n\t\t\t\t\tCYNARA,FILE,NONE (defualt:"<< backendTypeToString(NETHER_PRIMARY_BACKEND)<<")\n"; + cout<< " -P,--primary-backend-args=\tPrimary policy backend arguments\n"; + cout<< " -b,--backup-backend=\t\tBackup policy backend\n\t\t\t\t\tCYNARA,FILE,NONE (defualt:"<< backendTypeToString(NETHER_BACKUP_BACKEND)<< ")\n"; + cout<< " -B,--backup-backend-args=\tBackup policy backend arguments\n"; + cout<< " -q,--queue-num=\t\tNFQUEUE queue number to use for receiving packets\n"; + cout<< " -m,--mark-deny=\t\t\tPacket mark to use for DENY verdicts (default:"<< NETLINK_DROP_MARK << ")\n"; + cout<< " -M,--mark-allow-log=\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 index 0000000..0ec0ae6 --- /dev/null +++ b/src/nether_Manager.cpp @@ -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; + fd_set watchedReadDescriptorsSet, watchedWriteDescriptorsSet; + struct timeval timeoutSpecification; + char packetBuffer[NETHER_PACKET_BUFFER_SIZE] __attribute__ ((aligned)); + + while (1) + { + FD_ZERO (&watchedReadDescriptorsSet); + FD_ZERO (&watchedWriteDescriptorsSet); + + /* Always listen for signals */ + FD_SET (signalDescriptor, &watchedReadDescriptorsSet); + + if ((netlinkDescriptor = netherNetlink->getDescriptor()) >= 0) + { + 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); + } + } + + timeoutSpecification.tv_sec = 240; + timeoutSpecification.tv_usec = 0; + + if (select (FD_SETSIZE, &watchedReadDescriptorsSet, &watchedWriteDescriptorsSet, NULL, &timeoutSpecification) < 0) + { + LOGE("select error " << strerror(errno)); + return (false); + } + + 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; + } + } + if (FD_ISSET(netlinkDescriptor, &watchedReadDescriptorsSet)) + { + LOGD("netlink descriptor active"); + + /* some data arrives on netlink, read it */ + if ((packetReadSize = recv(netlinkDescriptor, packetBuffer, sizeof(packetBuffer), 0)) >= 0) + { + /* try to process the packet using netfilter_queue library, fetch packet info + needed for making a decision about it */ + 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; + } + } + + if (packetReadSize < 0 && errno == ENOBUFS) + { + LOGI("NetherManager::process losing packets! [bad things might happen]"); + continue; + } + + LOGE("NetherManager::process recv failed " << strerror(errno)); + break; + } + 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 index 0000000..ed2dd67 --- /dev/null +++ b/src/nether_Netlink.cpp @@ -0,0 +1,150 @@ +#include "nether_Netlink.h" + +NetherNetlink::NetherNetlink(NetherConfig &netherConfig) + : nfqHandle(nullptr), queueHandle(nullptr), nlif(nullptr), + queue(netherConfig.queueNumber), + NetherPacketProcessor(netherConfig) +{ +} + +NetherNetlink::~NetherNetlink() +{ + if (queueHandle) nfq_destroy_queue(queueHandle); + if (nfqHandle) nfq_close(nfqHandle); +} + +const bool NetherNetlink::initialize() +{ + nfqHandle = nfq_open(); + + if (!nfqHandle) + { + LOGE("Error during nfq_open()"); + return (false); + } + + if (nfq_unbind_pf(nfqHandle, AF_INET) < 0) + { + LOGE("Error during nfq_unbind_pf() (no permission?)"); + return (false); + } + + if (nfq_bind_pf(nfqHandle, AF_INET) < 0) + { + LOGE("Error during nfq_bind_pf()"); + return (false); + } + + queueHandle = nfq_create_queue(nfqHandle, queue, &callback, this); + + if (!queueHandle) + { + LOGE("Error during nfq_create_queue()"); + return (false); + } + + if (nfq_set_queue_flags(queueHandle, NFQA_CFG_F_SECCTX, NFQA_CFG_F_SECCTX)) + LOGI("This kernel version does not allow to retrieve security context"); + + if (nfq_set_mode(queueHandle, NFQNL_COPY_PACKET, 0xffff) < 0) + { + LOGE("Can't set packet_copy mode"); + nfq_destroy_queue (queueHandle); + return (false); + } + + if (nfq_set_queue_flags(queueHandle, NFQA_CFG_F_UID_GID, NFQA_CFG_F_UID_GID)) + { + LOGE("This kernel version does not allow to retrieve process UID/GID"); + nfq_destroy_queue (queueHandle); + return (false); + } + + nlif = nlif_open(); + if (!nlif) + LOGI("Failed to initialize NLIF subsystem, interface information won't be available"); + + return (true); +} + +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); +} + +int NetherNetlink::callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *data) +{ + NetherNetlink *me = static_cast(data); + NetherPacket packet; + unsigned char *secctx; + int secctxSize = 0; + struct nfqnl_msg_packet_hdr *ph; + unsigned char *payload; + + if ((ph = nfq_get_msg_packet_hdr(nfa))) + packet.id = ntohl(ph->packet_id); + else + { + LOGI("Failed to get packet id"); + return (1); + } + + if (!nfq_get_uid(nfa, &packet.uid)) + LOGW("Failed to get uid for packet id=" << packet.id); + + nfq_get_gid(nfa, &packet.gid); + + 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 */ + + return (0); +} + +const bool NetherNetlink::isValid() +{ + return (nfqHandle && queueHandle); +} + +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 index 0000000..c6e1072 --- /dev/null +++ b/src/nether_NetworkUtils.cpp @@ -0,0 +1,128 @@ +#include +#include +#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)"); + } +} -- 2.7.4 From ba6b09873a10dc2616c89e177db798e76f59ae4a Mon Sep 17 00:00:00 2001 From: RomanKubiak Date: Thu, 16 Jul 2015 16:55:05 +0200 Subject: [PATCH 3/4] Build subsystem for nether (cmake, codeblocks, spec) Change-Id: I35e39dc7e34087126b0a8aa2999cd0f7eb733fe3 --- CMakeLists.txt | 5 ++ nether.cbp | 119 ++++++++++++++++++++++++++++++++++++++++++++++ packaging/nether.manifest | 5 ++ packaging/nether.spec | 66 +++++++++++++++++++++++++ src/CMakeLists.txt | 30 ++++++++++++ 5 files changed, 225 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 nether.cbp create mode 100644 packaging/nether.manifest create mode 100644 packaging/nether.spec create mode 100644 src/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d372f16 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,5 @@ +CMAKE_MINIMUM_REQUIRED (VERSION 2.6) +PROJECT (nether) +INCLUDE(FindPkgConfig) +SET (CMAKE_CXX_FLAGS "-std=c++11") +ADD_SUBDIRECTORY(src) diff --git a/nether.cbp b/nether.cbp new file mode 100644 index 0000000..aeedfa5 --- /dev/null +++ b/nether.cbp @@ -0,0 +1,119 @@ + + + + + + diff --git a/packaging/nether.manifest b/packaging/nether.manifest new file mode 100644 index 0000000..2a0cec5 --- /dev/null +++ b/packaging/nether.manifest @@ -0,0 +1,5 @@ + + + + + diff --git a/packaging/nether.spec b/packaging/nether.spec new file mode 100644 index 0000000..0482d03 --- /dev/null +++ b/packaging/nether.spec @@ -0,0 +1,66 @@ +Name: nether +Epoch: 1 +Version: 0.0.1 +Release: 0 +Source0: %{name}-%{version}.tar.gz +License: Apache-2.0 +Group: Security/Other +Summary: Daemon for enforcing network privileges +BuildRequires: cmake +BuildRequires: pkgconfig(glib-2.0) +BuildRequires: libnetfilter_queue-devel +Requires: iptables + +%description +This package provides a daemon used to manage zones - start, stop and switch +between them. A process from inside a zone can request a switch of context +(display, input devices) to the other zone. + +%files +%manifest packaging/nether.manifest +%defattr(644,root,root,755) +%attr(755,root,root) %{_bindir}/nether +%dir /etc/nether +%config /etc/nether/nether.policy +%config /etc/nether/setrules.sh +%config /etc/nether/nether.rules +%prep +%setup -q + +%build +%{!?build_type:%define build_type "RELEASE"} + +%if %{build_type} == "DEBUG" || %{build_type} == "PROFILING" || %{build_type} == "CCOV" + CFLAGS="$CFLAGS -Wp,-U_FORTIFY_SOURCE" + CXXFLAGS="$CXXFLAGS -Wp,-U_FORTIFY_SOURCE" +%endif + +%cmake . -DVERSION=%{version} \ + -DCMAKE_BUILD_TYPE=%{build_type} \ + -DSCRIPT_INSTALL_DIR=%{script_dir} \ + -DSYSTEMD_UNIT_DIR=%{_unitdir} +make -k %{?jobs:-j%jobs} + +%install +%make_install + +%clean +rm -rf %{buildroot} + +%post +# Refresh systemd services list after installation +if [ $1 == 1 ]; then + systemctl daemon-reload || : +fi +# set needed caps on the binary to allow restart without loosing them +setcap CAP_SYS_ADMIN,CAP_MAC_OVERRIDE+ei %{_bindir}/nether + +%preun +# Stop the service before uninstall +if [ $1 == 0 ]; then + systemctl stop nether.service || : +fi + +%postun +# Refresh systemd services list after uninstall/upgrade +systemctl daemon-reload || : diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..3585b3d --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,30 @@ +FILE (GLOB NETHER_SOURCES *.cpp) +FILE (GLOB VASUM_LOGGER logger/*.cpp) +PKG_CHECK_MODULES (CYNARA cynara-client-async QUIET) +PKG_CHECK_MODULES (NETFILTER libnetfilter_queue REQUIRED) +PKG_CHECK_MODULES (LOGGER libsystemd-journal QUIET) + +ADD_EXECUTABLE(nether ${NETHER_SOURCES} ${VASUM_LOGGER}) + +INCLUDE_DIRECTORIES ( + ../include + ${EXTERNAL_INCLUDE_DIRS} + ${CYNARA_INCLUDE_DIRS} + ${NETFILTER_INCLUDE_DIRS} + ${LOGGER_INCLUDE_DIRS} + ) +if(CYNARA_FOUND) + ADD_DEFINITIONS (-DHAVE_CYNARA=${CYNARA_FOUND}) +endif() + +if(LOGGER_FOUND) + ADD_DEFINITIONS (-DHAVE_SYSTEMD_JOURNAL=${LOGGER_FOUND}) +endif() + +IF(CMAKE_BUILD_TYPE MATCHES DEBUG) + ADD_DEFINITIONS (-D_DEBUG=1) +ENDIF(CMAKE_BUILD_TYPE MATCHES DEBUG) + +TARGET_LINK_LIBRARIES(nether ${CYNARA_LIBRARIES} + ${NETFILTER_LIBRARIES} + ${LOGGER_LIBRARIES} ) \ No newline at end of file -- 2.7.4 From 161e2ea2dbf56de7189a22be0b5faaa8de25238e Mon Sep 17 00:00:00 2001 From: RomanKubiak Date: Thu, 16 Jul 2015 16:56:05 +0200 Subject: [PATCH 4/4] Included vasum logger class. Some modifications - added an option to disable colours in stderr logger - added a syslog backend if journal is not available - added a file backend Change-Id: Id6ed1c56f871be8970879277b331b26d0e3969f3 --- include/logger/backend-file.hpp | 22 +++++++ include/logger/backend-journal.hpp | 46 +++++++++++++ include/logger/backend-null.hpp | 46 +++++++++++++ include/logger/backend-stderr.hpp | 49 ++++++++++++++ include/logger/backend-syslog.hpp | 19 ++++++ include/logger/backend.hpp | 49 ++++++++++++++ include/logger/ccolor.hpp | 53 +++++++++++++++ include/logger/config.hpp | 78 ++++++++++++++++++++++ include/logger/formatter.hpp | 50 ++++++++++++++ include/logger/level.hpp | 55 ++++++++++++++++ include/logger/logger-scope.hpp | 78 ++++++++++++++++++++++ include/logger/logger.hpp | 85 ++++++++++++++++++++++++ src/logger/backend-file.cpp | 22 +++++++ src/logger/backend-journal.cpp | 72 ++++++++++++++++++++ src/logger/backend-stderr.cpp | 61 +++++++++++++++++ src/logger/backend-syslog.cpp | 42 ++++++++++++ src/logger/ccolor.cpp | 41 ++++++++++++ src/logger/formatter.cpp | 131 +++++++++++++++++++++++++++++++++++++ src/logger/level.cpp | 71 ++++++++++++++++++++ src/logger/logger-scope.cpp | 60 +++++++++++++++++ src/logger/logger.cpp | 76 +++++++++++++++++++++ 21 files changed, 1206 insertions(+) create mode 100644 include/logger/backend-file.hpp create mode 100644 include/logger/backend-journal.hpp create mode 100644 include/logger/backend-null.hpp create mode 100644 include/logger/backend-stderr.hpp create mode 100644 include/logger/backend-syslog.hpp create mode 100644 include/logger/backend.hpp create mode 100644 include/logger/ccolor.hpp create mode 100644 include/logger/config.hpp create mode 100644 include/logger/formatter.hpp create mode 100644 include/logger/level.hpp create mode 100644 include/logger/logger-scope.hpp create mode 100644 include/logger/logger.hpp create mode 100644 src/logger/backend-file.cpp create mode 100644 src/logger/backend-journal.cpp create mode 100644 src/logger/backend-stderr.cpp create mode 100644 src/logger/backend-syslog.cpp create mode 100644 src/logger/ccolor.cpp create mode 100644 src/logger/formatter.cpp create mode 100644 src/logger/level.cpp create mode 100644 src/logger/logger-scope.cpp create mode 100644 src/logger/logger.cpp diff --git a/include/logger/backend-file.hpp b/include/logger/backend-file.hpp new file mode 100644 index 0000000..9a108d1 --- /dev/null +++ b/include/logger/backend-file.hpp @@ -0,0 +1,22 @@ +#ifndef COMMON_LOGGER_BACKEND_FILE_HPP +#define COMMON_LOGGER_BACKEND_FILE_HPP + +#include "logger/backend.hpp" + +namespace logger { + +class FileBackend : public LogBackend { +public: + FileBackend(const std::string &_filePath) : filePath(_filePath) {} + void log(LogLevel logLevel, + const std::string& file, + const unsigned int& line, + const std::string& func, + const std::string& message) override; +private: + std::string filePath; +}; + +} // namespace logger + +#endif diff --git a/include/logger/backend-journal.hpp b/include/logger/backend-journal.hpp new file mode 100644 index 0000000..e566ed1 --- /dev/null +++ b/include/logger/backend-journal.hpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * Contact: Dariusz Michaluk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +/** + * @file + * @author Dariusz Michaluk (d.michaluk@samsung.com) + * @brief Systemd journal backend for logger + */ + +#ifndef COMMON_LOGGER_BACKEND_JOURNAL_HPP +#define COMMON_LOGGER_BACKEND_JOURNAL_HPP + +#include "logger/backend.hpp" + +namespace logger { + +/** + * systemd journal logging backend + */ +class SystemdJournalBackend : public LogBackend { +public: + void log(LogLevel logLevel, + const std::string& file, + const unsigned int& line, + const std::string& func, + const std::string& message) override; +}; + +} // namespace logger + +#endif // COMMON_LOGGER_BACKEND_JOURNAL_HPP diff --git a/include/logger/backend-null.hpp b/include/logger/backend-null.hpp new file mode 100644 index 0000000..4a7e8a9 --- /dev/null +++ b/include/logger/backend-null.hpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * Contact: Pawel Broda + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +/** + * @file + * @author Pawel Broda (p.broda@partner.samsung.com) + * @brief Null backend for logger + */ + +#ifndef COMMON_LOGGER_BACKEND_NULL_HPP +#define COMMON_LOGGER_BACKEND_NULL_HPP + +#include "logger/backend.hpp" + +namespace logger { + +/** + * Null logging backend + */ +class NullLogger : public LogBackend { +public: + void log(LogLevel /*logLevel*/, + const std::string& /*file*/, + const unsigned int& /*line*/, + const std::string& /*func*/, + const std::string& /*message*/) override {} +}; + +} // namespace logger + +#endif // COMMON_LOGGER_BACKEND_NULL_HPP diff --git a/include/logger/backend-stderr.hpp b/include/logger/backend-stderr.hpp new file mode 100644 index 0000000..919e24e --- /dev/null +++ b/include/logger/backend-stderr.hpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * Contact: Pawel Broda + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +/** + * @file + * @author Pawel Broda (p.broda@partner.samsung.com) + * @brief Stderr backend for logger + */ + +#ifndef COMMON_LOGGER_BACKEND_STDERR_HPP +#define COMMON_LOGGER_BACKEND_STDERR_HPP + +#include "logger/backend.hpp" + +namespace logger { + +/** + * Stderr logging backend + */ +class StderrBackend : public LogBackend { +public: + StderrBackend(const bool _useColours=true) : useColours(_useColours) {} + void log(LogLevel logLevel, + const std::string& file, + const unsigned int& line, + const std::string& func, + const std::string& message) override; +private: + bool useColours; +}; + +} // namespace logger + +#endif // COMMON_LOGGER_BACKEND_STDERR_HPP diff --git a/include/logger/backend-syslog.hpp b/include/logger/backend-syslog.hpp new file mode 100644 index 0000000..23450b5 --- /dev/null +++ b/include/logger/backend-syslog.hpp @@ -0,0 +1,19 @@ +#ifndef COMMON_LOGGER_BACKEND_SYSLOG_HPP +#define COMMON_LOGGER_BACKEND_SYSLOG_HPP + +#include "logger/backend.hpp" + +namespace logger { + +class SyslogBackend : public LogBackend { +public: + void log(LogLevel logLevel, + const std::string& file, + const unsigned int& line, + const std::string& func, + const std::string& message) override; +}; + +} // namespace logger + +#endif diff --git a/include/logger/backend.hpp b/include/logger/backend.hpp new file mode 100644 index 0000000..99b0c49 --- /dev/null +++ b/include/logger/backend.hpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * Contact: Pawel Broda + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +/** + * @file + * @author Pawel Broda (p.broda@partner.samsung.com) + * @brief Logging backend + */ + +#ifndef COMMON_LOGGER_BACKEND_HPP +#define COMMON_LOGGER_BACKEND_HPP + +#include "logger/level.hpp" + +#include + +namespace logger { + +/** + * Abstract class for logger + */ +class LogBackend { +public: + virtual void log(LogLevel logLevel, + const std::string& file, + const unsigned int& line, + const std::string& func, + const std::string& message) = 0; + virtual ~LogBackend() {} +}; + +} // namespace logger + +#endif // COMMON_LOGGER_BACKEND_HPP diff --git a/include/logger/ccolor.hpp b/include/logger/ccolor.hpp new file mode 100644 index 0000000..47cc25e --- /dev/null +++ b/include/logger/ccolor.hpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * Contact: Dariusz Michaluk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +/** + * @file + * @author Dariusz Michaluk (d.michaluk@samsung.com) + * @brief Console color for StderrBackend logger + */ + +#ifndef COMMON_LOGGER_CCOLOR_HPP +#define COMMON_LOGGER_CCOLOR_HPP + +#include + +namespace logger { + +enum class Color : unsigned int { + DEFAULT = 0, + BLACK = 90, + RED = 91, + GREEN = 92, + YELLOW = 93, + BLUE = 94, + MAGENTA = 95, + CYAN = 96, + WHITE = 97 +}; + +enum class Attributes : unsigned int { + DEFAULT = 0, + BOLD = 1 +}; + +std::string getConsoleEscapeSequence(Attributes attr, Color color); + +} // namespace logger + +#endif // COMMON_LOGGER_CCOLOR_HPP diff --git a/include/logger/config.hpp b/include/logger/config.hpp new file mode 100644 index 0000000..753430e --- /dev/null +++ b/include/logger/config.hpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * Contact: Lukasz Pawelczyk (l.pawelczyk@partner.samsung.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +/** + * @file + * @author Lukasz Pawelczyk (l.pawelczyk@partner.samsung.com) + * @brief Configuration file for the code + */ + + +#ifndef COMMON_CONFIG_HPP +#define COMMON_CONFIG_HPP + + +#ifdef __clang__ +#define CLANG_VERSION (__clang__major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) +#endif // __clang__ + +#if defined __GNUC__ && !defined __clang__ // clang also defines GCC versions +#define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) +#endif // __GNUC__ + + +#ifdef GCC_VERSION + +#if GCC_VERSION < 40800 +// GCC 4.8 is the first where those defines are not required for +// std::this_thread::sleep_for() and ::yield(). They might exist though +// in previous versions depending on the build configuration of the GCC. +#ifndef _GLIBCXX_USE_NANOSLEEP +#define _GLIBCXX_USE_NANOSLEEP +#endif // _GLIBCXX_USE_NANOSLEEP +#ifndef _GLIBCXX_USE_SCHED_YIELD +#define _GLIBCXX_USE_SCHED_YIELD +#endif // _GLIBCXX_USE_SCHED_YIELD +#endif // GCC_VERSION < 40800 + +#if GCC_VERSION < 40700 +// Those appeared in 4.7 with full c++11 support +#define final +#define override +#define thread_local __thread // use GCC extension instead of C++11 +#define steady_clock monotonic_clock +#endif // GCC_VERSION < 40700 + +#endif // GCC_VERSION + +// Variadic macros support for boost preprocessor should be enabled +// manually for clang since they are marked as untested feature +// (boost trunk if fixed but the latest 1.55 version is not, +// see boost/preprocessor/config/config.hpp) +#ifdef __clang__ +#define BOOST_PP_VARIADICS 1 +#endif + +// This has to be defined always when the boost has not been compiled +// using C++11. Headers detect that you are compiling using C++11 and +// blindly and wrongly assume that boost has been as well. +#ifndef BOOST_NO_CXX11_SCOPED_ENUMS +#define BOOST_NO_CXX11_SCOPED_ENUMS 1 +#endif + +#endif // COMMON_CONFIG_HPP diff --git a/include/logger/formatter.hpp b/include/logger/formatter.hpp new file mode 100644 index 0000000..3af0763 --- /dev/null +++ b/include/logger/formatter.hpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * Contact: Dariusz Michaluk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +/** + * @file + * @author Dariusz Michaluk (d.michaluk@samsung.com) + * @brief Helper formatter for logger + */ + +#ifndef COMMON_LOGGER_FORMATTER_HPP +#define COMMON_LOGGER_FORMATTER_HPP + +#include "logger/level.hpp" + +#include + +namespace logger { + +class LogFormatter { +public: + static unsigned int getCurrentThread(void); + static std::string getCurrentTime(void); + static std::string getConsoleColor(LogLevel logLevel); + static std::string getDefaultConsoleColor(void); + static std::string stripProjectDir(const std::string& file, + const std::string& rootDir); + static std::string getHeader(LogLevel logLevel, + const std::string& file, + const unsigned int& line, + const std::string& func); +}; + +} // namespace logger + +#endif // COMMON_LOGGER_FORMATTER_HPP diff --git a/include/logger/level.hpp b/include/logger/level.hpp new file mode 100644 index 0000000..7902301 --- /dev/null +++ b/include/logger/level.hpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * Contact: Dariusz Michaluk (d.michaluk@samsung.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +/** + * @file + * @author Dariusz Michaluk (d.michaluk@samsung.com) + * @brief LogLevel + */ + +#ifndef COMMON_LOGGER_LEVEL_HPP +#define COMMON_LOGGER_LEVEL_HPP + +#include + +namespace logger { + +enum class LogLevel { + TRACE, + DEBUG, + INFO, + WARN, + ERROR, + HELP +}; + +/** + * @param logLevel LogLevel + * @return std::sting representation of the LogLevel value + */ +std::string toString(const LogLevel logLevel); + +/** + * @param level string representation of log level + * @return parsed LogLevel value + */ +LogLevel parseLogLevel(const std::string& level); + +} // namespace logger + +#endif // COMMON_LOGGER_LEVEL_HPP diff --git a/include/logger/logger-scope.hpp b/include/logger/logger-scope.hpp new file mode 100644 index 0000000..cefd912 --- /dev/null +++ b/include/logger/logger-scope.hpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * Contact: Lukasz Kostyra + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +/** + * @file + * @author Lukasz Kostyra (l.kostyra@samsung.com) + * @brief Scope logger class declaration + */ + +#ifndef COMMON_LOGGER_LOGGER_SCOPE_HPP +#define COMMON_LOGGER_LOGGER_SCOPE_HPP + +#include +#include + +namespace logger { + +class SStreamWrapper +{ +public: + operator std::string() const; + + template + SStreamWrapper& operator<<(const T& b) + { + this->mSStream << b; + return *this; + } + +private: + std::ostringstream mSStream; +}; + +/** + * Class specifically for scope debug logging. Should be used at the beggining of a scope. + * Constructor marks scope enterance, destructor marks scope leave. + */ +class LoggerScope +{ +public: + LoggerScope(const std::string& file, + const unsigned int line, + const std::string& func, + const std::string& message, + const std::string& rootDir); + ~LoggerScope(); + +private: + const std::string mFile; + const unsigned int mLine; + const std::string mFunc; + const std::string mMessage; + const std::string mRootDir; +}; + +} // namespace logger + +// macro to automatically create LoggerScope object +#define LOGS(MSG) logger::LoggerScope logScopeObj(__FILE__, __LINE__, __func__, \ + logger::SStreamWrapper() << MSG, \ + PROJECT_SOURCE_DIR) + +#endif // COMMON_LOGGER_LOGGER_SCOPE_HPP diff --git a/include/logger/logger.hpp b/include/logger/logger.hpp new file mode 100644 index 0000000..d596a20 --- /dev/null +++ b/include/logger/logger.hpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * Contact: Jan Olszak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +/** + * @file + * @author Jan Olszak (j.olszak@samsung.com) + * @brief Logger + */ + +#ifndef COMMON_LOGGER_LOGGER_HPP +#define COMMON_LOGGER_LOGGER_HPP + +#include "logger/level.hpp" +#include "logger/backend-file.hpp" +#include "logger/backend-stderr.hpp" + +#include +#include + +#ifndef PROJECT_SOURCE_DIR +#define PROJECT_SOURCE_DIR "" +#endif + +namespace logger { + +class LogBackend; + +class Logger { +public: + static void logMessage(LogLevel logLevel, + const std::string& message, + const std::string& file, + const unsigned int line, + const std::string& func, + const std::string& rootDir); + + static void setLogLevel(const LogLevel level); + static void setLogLevel(const std::string& level); + static LogLevel getLogLevel(void); + static void setLogBackend(LogBackend* pBackend); +}; + +} // namespace logger + +#define LOG(SEVERITY, MESSAGE) \ + do { \ + if (logger::Logger::getLogLevel() <= logger::LogLevel::SEVERITY) { \ + std::ostringstream messageStream__; \ + messageStream__ << MESSAGE; \ + logger::Logger::logMessage(logger::LogLevel::SEVERITY, \ + messageStream__.str(), \ + __FILE__, \ + __LINE__, \ + __func__, \ + PROJECT_SOURCE_DIR); \ + } \ + } while (0) + +#define LOGE(MESSAGE) LOG(ERROR, MESSAGE) +#define LOGW(MESSAGE) LOG(WARN, MESSAGE) +#define LOGI(MESSAGE) LOG(INFO, MESSAGE) +#if defined(_DEBUG) +#define LOGD(MESSAGE) LOG(DEBUG, MESSAGE) +#else +#define LOGD(MESSAGE) do {} while (0) +#endif +#define LOGH(MESSAGE) LOG(HELP, MESSAGE) +#define LOGT(MESSAGE) LOG(TRACE, MESSAGE) + +#endif // COMMON_LOGGER_LOGGER_HPP diff --git a/src/logger/backend-file.cpp b/src/logger/backend-file.cpp new file mode 100644 index 0000000..fcfd1bf --- /dev/null +++ b/src/logger/backend-file.cpp @@ -0,0 +1,22 @@ +#include "logger/config.hpp" +#include "logger/formatter.hpp" +#include "logger/backend-file.hpp" + +#include + +namespace logger { + +void FileBackend::log(LogLevel logLevel, + const std::string& file, + const unsigned int& line, + const std::string& func, + const std::string& message) +{ + std::ofstream out(filePath, std::ios::app); + out << LogFormatter::getHeader(logLevel, file, line, func); + out << message; + out << "\n"; +} + + +} diff --git a/src/logger/backend-journal.cpp b/src/logger/backend-journal.cpp new file mode 100644 index 0000000..85cb46f --- /dev/null +++ b/src/logger/backend-journal.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * Contact: Dariusz Michaluk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +/** + * @file + * @author Dariusz Michaluk (d.michaluk@samsung.com) + * @brief Systemd journal backend for logger + */ +#ifdef HAVE_SYSTEMD_JOURNAL +#include "logger/config.hpp" +#include "logger/backend-journal.hpp" + +#define SD_JOURNAL_SUPPRESS_LOCATION +#include + +namespace logger { + +namespace { + +inline int toJournalPriority(LogLevel logLevel) +{ + switch (logLevel) { + case LogLevel::ERROR: + return LOG_ERR; // 3 + case LogLevel::WARN: + return LOG_WARNING; // 4 + case LogLevel::INFO: + return LOG_INFO; // 6 + case LogLevel::DEBUG: + return LOG_DEBUG; // 7 + case LogLevel::TRACE: + return LOG_DEBUG; // 7 + case LogLevel::HELP: + return LOG_DEBUG; // 7 + default: + return LOG_DEBUG; // 7 + } +} + +} // namespace + +void SystemdJournalBackend::log(LogLevel logLevel, + const std::string& file, + const unsigned int& line, + const std::string& func, + const std::string& message) +{ + sd_journal_send("PRIORITY=%d", toJournalPriority(logLevel), + "CODE_FILE=%s", file.c_str(), + "CODE_LINE=%d", line, + "CODE_FUNC=%s", func.c_str(), + "MESSAGE=%s", message.c_str(), + NULL); +} + +} // namespace logger +#endif \ No newline at end of file diff --git a/src/logger/backend-stderr.cpp b/src/logger/backend-stderr.cpp new file mode 100644 index 0000000..36c71ac --- /dev/null +++ b/src/logger/backend-stderr.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * Contact: Pawel Broda + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +/** + * @file + * @author Pawel Broda (p.broda@partner.samsung.com) + * @brief Stderr backend for logger + */ + +#include "logger/config.hpp" +#include "logger/backend-stderr.hpp" +#include "logger/formatter.hpp" + +#include + +namespace logger { + +void StderrBackend::log(LogLevel logLevel, + const std::string& file, + const unsigned int& line, + const std::string& func, + const std::string& message) +{ + typedef boost::char_separator charSeparator; + typedef boost::tokenizer tokenizer; + + // example log string + // 06:52:35.123 [ERROR] src/util/fs.cpp:43 readFileContent: /file/file.txt is missing + + const std::string logColor = LogFormatter::getConsoleColor(logLevel); + const std::string defaultColor = LogFormatter::getDefaultConsoleColor(); + const std::string header = LogFormatter::getHeader(logLevel, file, line, func); + tokenizer tokens(message, charSeparator("\n")); + for (const auto& messageLine : tokens) { + if (!messageLine.empty()) { + fprintf(stderr, + "%s%s %s%s\n", + useColours ? logColor.c_str() : "", + header.c_str(), + messageLine.c_str(), + useColours ? defaultColor.c_str() : ""); + } + } +} + +} // namespace logger diff --git a/src/logger/backend-syslog.cpp b/src/logger/backend-syslog.cpp new file mode 100644 index 0000000..0542e46 --- /dev/null +++ b/src/logger/backend-syslog.cpp @@ -0,0 +1,42 @@ +#include "logger/config.hpp" +#include "logger/formatter.hpp" +#include "logger/backend-syslog.hpp" + +#include +#include +namespace logger { + +namespace { + +inline int toSyslogPriority(LogLevel logLevel) +{ + switch (logLevel) { + case LogLevel::ERROR: + return LOG_ERR; // 3 + case LogLevel::WARN: + return LOG_WARNING; // 4 + case LogLevel::INFO: + return LOG_INFO; // 6 + case LogLevel::DEBUG: + return LOG_DEBUG; // 7 + case LogLevel::TRACE: + return LOG_DEBUG; // 7 + case LogLevel::HELP: + return LOG_DEBUG; // 7 + default: + return LOG_DEBUG; // 7 + } +} + +} // namespace + +void SyslogBackend::log(LogLevel logLevel, + const std::string& file, + const unsigned int& line, + const std::string& func, + const std::string& message) +{ + syslog(toSyslogPriority(logLevel), "%s %s", LogFormatter::getHeader(logLevel, file, line, func).c_str(), message.c_str()); +} + +} diff --git a/src/logger/ccolor.cpp b/src/logger/ccolor.cpp new file mode 100644 index 0000000..9cc652d --- /dev/null +++ b/src/logger/ccolor.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * Contact: Dariusz Michaluk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +/** + * @file + * @author Dariusz Michaluk (d.michaluk@samsung.com) + * @brief Console color for StderrBackend logger + */ + +#include "logger/config.hpp" +#include "logger/ccolor.hpp" + +#include + +namespace logger { + +std::string getConsoleEscapeSequence(Attributes attr, Color color) +{ + char command[10]; + + // Command is the control command to the terminal + snprintf(command, sizeof(command), "%c[%u;%um", 0x1B, (unsigned int)attr, (unsigned int)color); + return std::string(command); +} + +} // namespace logger diff --git a/src/logger/formatter.cpp b/src/logger/formatter.cpp new file mode 100644 index 0000000..815a111 --- /dev/null +++ b/src/logger/formatter.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * Contact: Dariusz Michaluk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +/** + * @file + * @author Dariusz Michaluk (d.michaluk@samsung.com) + * @brief Helper formatter for logger + */ + +#include "logger/config.hpp" +#include "logger/formatter.hpp" +#include "logger/ccolor.hpp" + +#include +#include +#include +#include +#include +#include + +namespace logger { + +namespace { + +const int TIME_COLUMN_LENGTH = 12; +const int SEVERITY_COLUMN_LENGTH = 8; +const int THREAD_COLUMN_LENGTH = 3; +const int FILE_COLUMN_LENGTH = 60; + +std::atomic gNextThreadId(1); +thread_local unsigned int gThisThreadId(0); + +} // namespace + +unsigned int LogFormatter::getCurrentThread(void) +{ + unsigned int id = gThisThreadId; + if (id == 0) { + gThisThreadId = id = gNextThreadId++; + } + + return id; +} + +std::string LogFormatter::getCurrentTime(void) +{ + char time[TIME_COLUMN_LENGTH + 1]; + struct timeval tv; + gettimeofday(&tv, NULL); + struct tm* tm = localtime(&tv.tv_sec); + snprintf(time, + sizeof(time), + "%02d:%02d:%02d.%03d", + tm->tm_hour, + tm->tm_min, + tm->tm_sec, + int(tv.tv_usec / 1000)); + + return std::string(time); +} + +std::string LogFormatter::getConsoleColor(LogLevel logLevel) +{ + switch (logLevel) { + case LogLevel::ERROR: + return getConsoleEscapeSequence(Attributes::BOLD, Color::RED); + case LogLevel::WARN: + return getConsoleEscapeSequence(Attributes::BOLD, Color::YELLOW); + case LogLevel::INFO: + return getConsoleEscapeSequence(Attributes::BOLD, Color::BLUE); + case LogLevel::DEBUG: + return getConsoleEscapeSequence(Attributes::DEFAULT, Color::GREEN); + case LogLevel::TRACE: + return getConsoleEscapeSequence(Attributes::DEFAULT, Color::BLACK); + case LogLevel::HELP: + return getConsoleEscapeSequence(Attributes::BOLD, Color::MAGENTA); + default: + return getConsoleEscapeSequence(Attributes::DEFAULT, Color::DEFAULT); + } +} + +std::string LogFormatter::getDefaultConsoleColor(void) +{ + return getConsoleEscapeSequence(Attributes::DEFAULT, Color::DEFAULT); +} + +std::string LogFormatter::stripProjectDir(const std::string& file, + const std::string& rootDir) +{ + // If rootdir is empty then return full name + if (rootDir.empty()) { + return file; + } + const std::string sourceDir = rootDir + "/"; + // If file does not belong to rootDir then also return full name + if (0 != file.compare(0, sourceDir.size(), sourceDir)) { + return file; + } + return file.substr(sourceDir.size()); +} + +std::string LogFormatter::getHeader(LogLevel logLevel, + const std::string& file, + const unsigned int& line, + const std::string& func) +{ + std::ostringstream logLine; + logLine << getCurrentTime() << ' ' + << std::left << std::setw(SEVERITY_COLUMN_LENGTH) << '[' + toString(logLevel) + ']' + << std::right << std::setw(THREAD_COLUMN_LENGTH) << getCurrentThread() << ": " + << std::left << std::setw(FILE_COLUMN_LENGTH) + << file + ':' + std::to_string(line) + ' ' + func + ':'; + return logLine.str(); +} + +} // namespace logger diff --git a/src/logger/level.cpp b/src/logger/level.cpp new file mode 100644 index 0000000..3b74205 --- /dev/null +++ b/src/logger/level.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * Contact: Jan Olszak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +/** + * @file + * @author Jan Olszak (j.olszak@samsung.com) + * @brief Functions to handle LogLevel + */ + +#include "logger/config.hpp" +#include "logger/level.hpp" + +#include +#include + +namespace logger { + +LogLevel parseLogLevel(const std::string& level) +{ + if (boost::iequals(level, "ERROR")) { + return LogLevel::ERROR; + } else if (boost::iequals(level, "WARN")) { + return LogLevel::WARN; + } else if (boost::iequals(level, "INFO")) { + return LogLevel::INFO; + } else if (boost::iequals(level, "DEBUG")) { + return LogLevel::DEBUG; + } else if (boost::iequals(level, "TRACE")) { + return LogLevel::TRACE; + } else if (boost::iequals(level, "HELP")) { + return LogLevel::HELP; + } else { + throw std::runtime_error("Invalid LogLevel to parse"); + } +} + +std::string toString(const LogLevel logLevel) +{ + switch (logLevel) { + case LogLevel::ERROR: + return "ERROR"; + case LogLevel::WARN: + return "WARN"; + case LogLevel::INFO: + return "INFO"; + case LogLevel::DEBUG: + return "DEBUG"; + case LogLevel::TRACE: + return "TRACE"; + case LogLevel::HELP: + return "HELP"; + default: + return "UNKNOWN"; + } +} +} // namespace logger diff --git a/src/logger/logger-scope.cpp b/src/logger/logger-scope.cpp new file mode 100644 index 0000000..a977adc --- /dev/null +++ b/src/logger/logger-scope.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * Contact: Lukasz Kostyra + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +/** + * @file + * @author Lukasz Kostyra (l.kostyra@samsung.com) + * @brief Scope logger class implementation + */ + +#include "logger/logger-scope.hpp" +#include "logger/logger.hpp" + +namespace logger { + +SStreamWrapper::operator std::string() const +{ + return mSStream.str(); +} + +LoggerScope::LoggerScope(const std::string& file, + const unsigned int line, + const std::string& func, + const std::string& message, + const std::string& rootDir): + mFile(file), + mLine(line), + mFunc(func), + mMessage(message), + mRootDir(rootDir) +{ + if (logger::Logger::getLogLevel() <= logger::LogLevel::TRACE) { + logger::Logger::logMessage(logger::LogLevel::TRACE, "Entering: " + mMessage, + mFile, mLine, mFunc, mRootDir); + } +} + +LoggerScope::~LoggerScope() +{ + if (logger::Logger::getLogLevel() <= logger::LogLevel::TRACE) { + logger::Logger::logMessage(logger::LogLevel::TRACE, "Leaving: " + mMessage, + mFile, mLine, mFunc, mRootDir); + } +} + +} // namespace logger diff --git a/src/logger/logger.cpp b/src/logger/logger.cpp new file mode 100644 index 0000000..ec0855b --- /dev/null +++ b/src/logger/logger.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * Contact: Pawel Broda + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +/** + * @file + * @author Pawel Broda (p.broda@partner.samsung.com) + * @brief Logger + */ + +#include "logger/config.hpp" +#include "logger/logger.hpp" +#include "logger/formatter.hpp" +#include "logger/backend-null.hpp" + +#include +#include + +namespace logger { + +namespace { + +volatile LogLevel gLogLevel = LogLevel::DEBUG; +std::unique_ptr gLogBackendPtr(new NullLogger()); +std::mutex gLogMutex; + +} // namespace + +void Logger::logMessage(LogLevel logLevel, + const std::string& message, + const std::string& file, + const unsigned int line, + const std::string& func, + const std::string& rootDir) +{ + std::string sfile = LogFormatter::stripProjectDir(file, rootDir); + std::unique_lock lock(gLogMutex); + gLogBackendPtr->log(logLevel, sfile, line, func, message); +} + +void Logger::setLogLevel(const LogLevel level) +{ + gLogLevel = level; +} + +void Logger::setLogLevel(const std::string& level) +{ + gLogLevel = parseLogLevel(level); +} + +LogLevel Logger::getLogLevel(void) +{ + return gLogLevel; +} + +void Logger::setLogBackend(LogBackend* pBackend) +{ + std::unique_lock lock(gLogMutex); + gLogBackendPtr.reset(pBackend); +} + +} // namespace logger -- 2.7.4