Merge branch 'tizen' into nether 49/140949/1
authorDariusz Michaluk <d.michaluk@samsung.com>
Thu, 27 Jul 2017 08:03:41 +0000 (10:03 +0200)
committerDariusz Michaluk <d.michaluk@samsung.com>
Thu, 27 Jul 2017 08:05:23 +0000 (10:05 +0200)
Change-Id: I8db77d4a5f4ebb57110a8707d84168fa506e95f2

12 files changed:
CMakeLists.txt
packaging/security-tests.manifest
packaging/security-tests.spec
src/CMakeLists.txt
src/nether-tests/CMakeLists.txt [new file with mode: 0644]
src/nether-tests/nether_tests.cpp [new file with mode: 0644]
src/nether-tests/setup-nether-tests-dns.sh [new file with mode: 0644]
src/nether-tests/setup-nether-tests-nns.sh [new file with mode: 0644]
src/nether-tests/teardown-nether-tests-dns.sh [new file with mode: 0644]
src/nether-tests/teardown-nether-tests-nns.sh [new file with mode: 0644]
src/security-tests-all.sh
src/security-tests.sh

index a89c09c..2687162 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2012-2015 Samsung Electronics Co., Ltd All Rights Reserved
+# Copyright (c) 2012-2017 Samsung Electronics Co., Ltd All Rights Reserved
 #
 #    Licensed under the Apache License, Version 2.0 (the "License");
 #    you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@
 # @file        CMakeLists.txt
 # @author      Bartlomiej Grzelewski (b.grzelewski@samsung.com)
 # @author      Pawel Polawski (p.polawski@partner.samsung.com)
+# @author      Piotr Sawicki (p.sawicki2@partner.samsung.com)
 # @brief
 #
 
@@ -64,6 +65,7 @@ IF(BUILD_ALL_TESTS)
     SET(BUILD_CYNARA ON)
     SET(BUILD_WEB ON)
     SET(BUILD_YACA ON)
+    SET(BUILD_NETHER ON)
 ENDIF(BUILD_ALL_TESTS)
 
 # If supported for the target machine, emit position-independent code,suitable
index a65ddaa..3dfa317 100644 (file)
@@ -11,6 +11,7 @@
         <filesystem path="/usr/bin/cynara-tests" exec_label="_" />
         <filesystem path="/usr/bin/ckm-tests" exec_label="User" />
         <filesystem path="/usr/bin/ckm-tests" exec_label="System" />
+        <filesystem path="/usr/bin/nether-tests" exec_label="System::Privileged" />
     </assign>
     <request>
         <domain name="_" />
index f815452..8243ae7 100644 (file)
@@ -32,6 +32,8 @@ BuildRequires: pkgconfig(security-privilege-manager)
 Requires: perf
 Requires: gdb
 Requires: diffutils
+Requires: iproute2
+Requires: toybox-symlinks-ping
 
 %global ckm_test_dir %{?TZ_SYS_SHARE:%TZ_SYS_SHARE/ckm-test/}%{!?TZ_SYS_SHARE:/usr/share/ckm-test/}
 %global ckm_rw_data_dir %{?TZ_SYS_DATA:%TZ_SYS_DATA/ckm/}%{!?TZ_SYS_DATA:/opt/data/ckm/}
@@ -82,6 +84,10 @@ echo "security-tests postinst done ..."
 /usr/bin/security-tests.sh
 /usr/bin/security-tests-all.sh
 /usr/bin/test-performance-check.sh
+/usr/bin/setup-nether-tests-nns.sh
+/usr/bin/teardown-nether-tests-nns.sh
+/usr/bin/setup-nether-tests-dns.sh
+/usr/bin/teardown-nether-tests-dns.sh
 
 /etc/dbus-1/system.d/security-tests.conf
 
@@ -99,6 +105,7 @@ echo "security-tests postinst done ..."
 /usr/bin/ckm-tests
 /usr/bin/ckm-integration-tests
 /usr/bin/yaca-test
+/usr/bin/nether-tests
 %{ckm_test_dir}/*
 /etc/security-tests
 /usr/lib/security-tests/cynara-tests/plugins/single-policy/*
index 471c28e..fe041a6 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2011-2015 Samsung Electronics Co., Ltd All Rights Reserved
+# Copyright (c) 2011-2017 Samsung Electronics Co., Ltd All Rights Reserved
 #
 #    Licensed under the Apache License, Version 2.0 (the "License");
 #    you may not use this file except in compliance with the License.
@@ -110,3 +110,7 @@ ENDIF(BUILD_WEB)
 IF(BUILD_YACA)
     ADD_SUBDIRECTORY(yaca)
 ENDIF(BUILD_YACA)
+
+IF(BUILD_NETHER)
+    ADD_SUBDIRECTORY(nether-tests)
+ENDIF(BUILD_NETHER)
diff --git a/src/nether-tests/CMakeLists.txt b/src/nether-tests/CMakeLists.txt
new file mode 100644 (file)
index 0000000..b08c24e
--- /dev/null
@@ -0,0 +1,84 @@
+# Copyright (c) 2017 Samsung Electronics Co., Ltd All Rights Reserved
+#
+#   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        CMakeLists.txt
+# @author      Piotr Sawicki (p.sawicki2@partner.samsung.com)
+# @brief       CMake configuration file
+#
+
+INCLUDE(FindPkgConfig)
+
+# Dependencies
+PKG_CHECK_MODULES(SEC_MGR_TESTS_DEP
+    REQUIRED
+    security-manager
+)
+
+SET(TARGET_NETHER_TESTS "nether-tests")
+
+SET(NETHER_TESTS_SOURCES
+    ${PROJECT_SOURCE_DIR}/src/nether-tests/nether_tests.cpp
+    ${PROJECT_SOURCE_DIR}/src/common/tests_common.cpp
+    ${PROJECT_SOURCE_DIR}/src/security-manager-tests/common/tzplatform.cpp
+    ${PROJECT_SOURCE_DIR}/src/security-manager-tests/common/sm_api.cpp
+    ${PROJECT_SOURCE_DIR}/src/security-manager-tests/common/sm_commons.cpp
+    ${PROJECT_SOURCE_DIR}/src/security-manager-tests/common/sm_request.cpp
+    ${PROJECT_SOURCE_DIR}/src/security-manager-tests/common/sm_user_request.cpp
+    ${PROJECT_SOURCE_DIR}/src/security-manager-tests/common/sm_policy_request.cpp
+    ${PROJECT_SOURCE_DIR}/src/security-manager-tests/common/app_install_helper.cpp
+    ${PROJECT_SOURCE_DIR}/src/security-manager-tests/common/policy_configuration.cpp
+    ${PROJECT_SOURCE_DIR}/src/cynara-tests/common/cynara_test_client.cpp
+   )
+
+INCLUDE_DIRECTORIES(SYSTEM
+    ${SEC_MGR_TESTS_DEP_INCLUDE_DIRS}
+   )
+
+INCLUDE_DIRECTORIES(SYSTEM
+    ${CYNARA_TARGET_DEP_INCLUDE_DIRS}
+    )
+
+INCLUDE_DIRECTORIES(
+    ${PROJECT_SOURCE_DIR}/src/common/
+    ${PROJECT_SOURCE_DIR}/src/security-manager-tests/common/
+    ${PROJECT_SOURCE_DIR}/src/cynara-tests/common/
+   )
+
+FIND_PACKAGE(Threads)
+
+ADD_EXECUTABLE(${TARGET_NETHER_TESTS} ${NETHER_TESTS_SOURCES})
+
+TARGET_LINK_LIBRARIES(${TARGET_NETHER_TESTS}
+    ${SEC_MGR_TESTS_DEP_LIBRARIES}
+    dpl-test-framework
+    tests-common
+    ${CMAKE_THREAD_LIBS_INIT}
+    )
+
+INSTALL(TARGETS ${TARGET_NETHER_TESTS} DESTINATION /usr/bin)
+
+INSTALL(FILES
+    ${PROJECT_SOURCE_DIR}/src/nether-tests/setup-nether-tests-nns.sh
+    ${PROJECT_SOURCE_DIR}/src/nether-tests/teardown-nether-tests-nns.sh
+    ${PROJECT_SOURCE_DIR}/src/nether-tests/setup-nether-tests-dns.sh
+    ${PROJECT_SOURCE_DIR}/src/nether-tests/teardown-nether-tests-dns.sh
+    DESTINATION bin
+    PERMISSIONS OWNER_READ
+                OWNER_WRITE
+                OWNER_EXECUTE
+                GROUP_READ
+                GROUP_EXECUTE
+                WORLD_READ
+                WORLD_EXECUTE
+    )
diff --git a/src/nether-tests/nether_tests.cpp b/src/nether-tests/nether_tests.cpp
new file mode 100644 (file)
index 0000000..69bf807
--- /dev/null
@@ -0,0 +1,1441 @@
+/*
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *    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        nether_tests.cpp
+ * @author      Piotr Sawicki (p.sawicki2@partner.samsung.com)
+ * @version     1.0
+ * @brief       Tests for Nether service
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <linux/netlink.h>
+#include <netdb.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <sys/mount.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <condition_variable>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include <scoped_installer.h>
+#include <sm_commons.h>
+#include <tests_common.h>
+#include <dpl/test/safe_cleanup.h>
+
+
+using namespace SecurityManagerTest;
+using namespace DPL::Test;
+
+const std::string INTERNET_ACCESS_PRIVILEGE = "http://tizen.org/privilege/internet";
+const std::string NETHER_NETNS_NAME_NONE = "";
+const std::string NETHER_NETNS_NAME_TEST = "nether_test_network_ns";
+const std::string NETNS_RUN_DIR = "/var/run/netns"; // taken from iproute2
+const std::string NETHER_NETNS_SETUP_COMMAND = "/usr/bin/setup-nether-tests-nns.sh " + NETHER_NETNS_NAME_TEST;
+const std::string NETHER_NETNS_TEARDOWN_COMMAND = "/usr/bin/teardown-nether-tests-nns.sh " + NETHER_NETNS_NAME_TEST;
+const std::string NETHER_DNS_SETUP_COMMAND = "/usr/bin/setup-nether-tests-dns.sh";
+const std::string NETHER_DNS_TEARDOWN_COMMAND = "/usr/bin/teardown-nether-tests-dns.sh";
+
+
+const ssize_t NET_BUFFER_SIZE = 2048;
+const int UDP_MESSAGES_COUNT = 20000;
+const int TCP_MESSAGES_COUNT = 20000;
+
+const uint16_t UDP_TEST_PORT = 12000;
+const uint16_t TCP_TEST_PORT = 12000;
+
+const std::string REMOTE_INTERFACE_ADDRESS = "10.1.0.2";
+const std::string REMOTE_INTERFACE_NAME = "veth1";
+const std::string LOCAL_HOST_TEST_SERVER_ADDRESS = "127.0.0.1";
+const std::string LOCAL_TEST_MCAST_GROUP = "225.0.0.250";
+const std::string DNS_TEST_ADDRESS = "www.samsung.com";
+const std::string ANY_INTERFACE = "";
+
+const char TTL_MCAST_RESTRIC_SUBNET = 1;
+
+const int MONITOR_TIMEOUT = 1000; // ms
+
+enum class NetherInternetAccess {
+    ACCESS_GRANTED,
+    ACCESS_DENIED
+};
+
+
+void runShellScriptInChildAndWait(const std::string &command)
+{
+    RUNNER_ASSERT_MSG(system(command.c_str()) != -1, "Couldn't run command: " << command);
+}
+
+
+class ScopedShellScriptRunner final {
+public:
+    ScopedShellScriptRunner(const std::string &setupCmd, const std::string &teardownCmd)
+    : m_teardownCmd(teardownCmd)
+    {
+        runShellScriptInChildAndWait(setupCmd);
+    }
+
+    ~ScopedShellScriptRunner()
+    {
+        SafeCleanup::run([this]() {
+            runShellScriptInChildAndWait(m_teardownCmd);
+        });
+    }
+
+    ScopedShellScriptRunner(const ScopedShellScriptRunner &) = delete;
+    ScopedShellScriptRunner &operator=(const ScopedShellScriptRunner &) = delete;
+
+private:
+    std::string m_teardownCmd;
+};
+
+
+void createChildProcess(const std::function<int(int)> &procedure, pid_t &childPid, int &childPipeFd)
+{
+    int pipeFd[2];
+    RUNNER_ASSERT_ERRNO_MSG(pipe2(pipeFd, O_DIRECT) == 0, "pipe() failed");
+
+    pid_t pid = fork();
+    RUNNER_ASSERT_ERRNO_MSG(pid != -1, "fork() failed");
+
+    if (pid != 0) {
+        // parent code
+        TEMP_FAILURE_RETRY(close(pipeFd[1]));
+
+        childPipeFd = pipeFd[0];
+        childPid = pid;
+    } else {
+        // child code
+        TestRunnerSingleton::Instance().Terminate();
+
+        close(STDIN_FILENO);
+        close(STDOUT_FILENO);
+        close(STDERR_FILENO);
+
+        close(pipeFd[0]);
+
+        int retStatus = EXIT_FAILURE;
+
+        int devNullFd = TEMP_FAILURE_RETRY(open("/dev/null", O_WRONLY));
+        if (devNullFd == -1) {
+            goto end;
+        }
+
+        if (TEMP_FAILURE_RETRY(dup2(devNullFd, STDIN_FILENO)) == -1) {
+            goto end;
+        }
+
+        if (TEMP_FAILURE_RETRY(dup2(devNullFd, STDOUT_FILENO)) == -1) {
+            goto end;
+        }
+
+        if (TEMP_FAILURE_RETRY(dup2(devNullFd, STDERR_FILENO)) == -1) {
+            goto end;
+        }
+
+        retStatus = procedure(pipeFd[1]);
+end:
+        exit(retStatus);
+    }
+}
+
+
+int switchToNetworkNamespace(const std::string &netnsName)
+{
+    if (netnsName == NETHER_NETNS_NAME_NONE) {
+        return 0;
+    }
+
+    const std::string netnsPath { NETNS_RUN_DIR + "/" + netnsName };
+    if (netnsPath.length() >= PATH_MAX) {
+        return -1;
+    }
+
+    int netNsFd = open(netnsPath.c_str(), O_RDONLY);
+    if (netNsFd == -1) {
+        return -1;
+    }
+
+    // uses an existing network name-space (previously created by "ip netns" command)
+    if (setns(netNsFd, CLONE_NEWNET) == -1) {
+        close(netNsFd);
+        return -1;
+    }
+
+    return 0;
+}
+
+
+class TemporaryNormalTestUser
+{
+public:
+    TemporaryNormalTestUser(const std::string &userName)
+    : m_user(userName, GUM_USERTYPE_NORMAL, false)
+    {
+        m_user.create();
+    }
+
+    uid_t getUid() const
+    {
+        return m_user.getUid();
+    }
+
+private:
+    TemporaryTestUser m_user;
+};
+
+
+class InternetLocalAppInstallHelper : public AppInstallHelper
+{
+public:
+    InternetLocalAppInstallHelper(const std::string &namePrefix, uid_t uid, NetherInternetAccess access)
+    : AppInstallHelper(namePrefix, uid)
+    {
+        setInstallType(app_install_type::SM_APP_INSTALL_LOCAL);
+        if (access == NetherInternetAccess::ACCESS_GRANTED) {
+            addPrivilege(INTERNET_ACCESS_PRIVILEGE);
+        }
+    }
+};
+
+
+class AppContext
+{
+public:
+    AppContext()
+    : m_appId{}
+    , m_appUID(-1)
+    , m_appGID(-1)
+    {
+    }
+
+    AppContext(const std::string &appId, uid_t appUID, gid_t appGID)
+    : m_appId(appId)
+    , m_appUID(appUID)
+    , m_appGID(appGID)
+    {
+    }
+
+    const std::string &appId() const
+    {
+        return m_appId;
+    }
+
+    void setAppId(const std::string &appId)
+    {
+        m_appId = appId;
+    }
+
+    uid_t getUID() const
+    {
+        return m_appUID;
+    }
+
+    void setUID(uid_t appUID)
+    {
+        m_appUID = appUID;
+    }
+
+    gid_t getGID() const
+    {
+        return m_appGID;
+    }
+
+    void setGID(gid_t appGID)
+    {
+        m_appGID = appGID;
+    }
+
+private:
+    std::string m_appId;
+    uid_t m_appUID;
+    gid_t m_appGID;
+};
+
+
+class ScopedAppContext : public AppContext
+{
+public:
+    ScopedAppContext(const std::string &appPrefix, NetherInternetAccess access)
+    : m_user(appPrefix + "_user")
+    , m_installHelper(appPrefix + "_app_normal", m_user.getUid(), access)
+    , m_scopedInstaller(m_installHelper)
+    {
+        setAppId(m_installHelper.getAppId());
+        setUID(m_installHelper.getUID());
+        setGID(m_installHelper.getGID());
+    }
+
+private:
+    TemporaryNormalTestUser m_user;
+    InternetLocalAppInstallHelper m_installHelper;
+    ScopedInstaller m_scopedInstaller;
+};
+
+
+void runProcedureInNetAppContext(
+    const std::string &appPrefix,
+    const std::function<void(void)> &procedure,
+    const NetherInternetAccess access = NetherInternetAccess::ACCESS_DENIED)
+{
+    ScopedAppContext scopedAppContext(appPrefix, access);
+
+    auto smackLabel = scopedAppContext.appId();
+    auto appUID = scopedAppContext.getUID();
+    auto appGID = scopedAppContext.getGID();
+
+    // run client procedure in app context
+    runInChildParentWait([=]() {
+        Api::setProcessLabel(smackLabel);
+        RUNNER_ASSERT_ERRNO_MSG(drop_root_privileges(appUID, appGID) == 0,
+                                "drop_root_privileges() failed");
+        procedure();
+    });
+}
+
+
+class NetServer
+{
+public:
+    NetServer()
+    : m_monitorIsRunning(false)
+    , m_receivedBytes(0ull)
+    , m_serverPid(-1)
+    , m_serverPipeFd(-1)
+    {}
+
+    NetServer(const NetServer &) = delete;
+    NetServer &operator=(const NetServer &) = delete;
+
+    virtual ~NetServer()
+    {
+        SafeCleanup::run([this]() {
+            stop();
+        });
+    }
+
+    void start(const std::string &netnsName = NETHER_NETNS_NAME_NONE)
+    {
+        std::unique_lock<std::mutex> lock(m_monitorMutex);
+
+        RUNNER_ASSERT_MSG(!m_monitorIsRunning, "Monitor thread is already running");
+
+        createChildProcess([=] (int pipeFd) -> int {
+            if (netnsName != NETHER_NETNS_NAME_NONE) {
+                if (switchToNetworkNamespace(netnsName) == -1) {
+                    exit(EXIT_FAILURE);
+                }
+            }
+            return serverProcedure(pipeFd);
+        }, m_serverPid, m_serverPipeFd);
+
+        waitForServer();
+
+        m_monitorThread = std::move(std::thread(&NetServer::monitorThreadProc, this));
+
+        m_monitorSyncPoint.wait(lock, [this]() {
+           return m_monitorIsRunning;
+        });
+    }
+
+    void stop()
+    {
+        stopMonitor();
+        closeServerPipe();
+        stopServerProcess();
+    }
+
+    uint64_t getReceivedBytes() const
+    {
+        RUNNER_ASSERT_MSG(!isAlive(), "Cannot read statistics. Server is still running: " << (*this));
+        return m_receivedBytes;
+    }
+
+    bool isAlive() const
+    {
+        int status;
+        return waitpid(m_serverPid, &status, WNOHANG) == 0;
+    }
+
+    virtual int serverProcedure(int) = 0;
+
+    virtual std::string getDescription() const = 0;
+
+protected:
+    void notifyManager(int pipeFd)
+    {
+        char c = 'X';
+        ssize_t ret = TEMP_FAILURE_RETRY(write(pipeFd, &c, sizeof(c)));
+        if (ret == -1) {
+            exit(EXIT_FAILURE);
+        }
+    }
+
+private:
+    void waitForServer() const
+    {
+        pollfd pfd { m_serverPipeFd, POLLIN, 0 };
+
+        int ret = TEMP_FAILURE_RETRY(poll(&pfd, 1, MONITOR_TIMEOUT));
+        RUNNER_ASSERT_ERRNO_MSG(ret >= 0, "poll() failed");
+
+        if (pfd.revents & POLLIN) {
+            char c;
+            ssize_t len = TEMP_FAILURE_RETRY(read(m_serverPipeFd, &c, sizeof(c)));
+            RUNNER_ASSERT_ERRNO_MSG(len >= 0, "Read pipe failed");
+        }
+    }
+
+    ssize_t collectStatistics()
+    {
+        char buffer[NET_BUFFER_SIZE];
+
+        ssize_t ret = TEMP_FAILURE_RETRY(read(m_serverPipeFd, buffer, sizeof(buffer)));
+        if (ret > 0) {
+            m_receivedBytes += static_cast<uint64_t>(ret);
+        }
+
+        return ret;
+    }
+
+    void closeServerPipe()
+    {
+        if (m_serverPipeFd == -1) {
+            return;
+        }
+
+        close(m_serverPipeFd);
+        m_serverPipeFd = -1;
+    }
+
+    void stopServerProcess()
+    {
+        if (m_serverPid == -1) {
+            return;
+        }
+
+        if (!isAlive()) {
+            m_serverPid = -1;
+            return;
+        }
+
+        RUNNER_ASSERT_ERRNO_MSG(kill(m_serverPid, SIGTERM) == 0, "kill() failed");
+
+        int status;
+        do {
+            pid_t ret = TEMP_FAILURE_RETRY(waitpid(m_serverPid, &status, WUNTRACED | WCONTINUED));
+            RUNNER_ASSERT_ERRNO_MSG(ret != -1, "waitpid() failed");
+        } while (!WIFEXITED(status) && !WIFSIGNALED(status));
+
+        m_serverPid = -1;
+    }
+
+    void stopMonitor()
+    {
+        {
+            std::lock_guard<std::mutex> lock(m_monitorMutex);
+            m_monitorIsRunning = false;
+        }
+
+        if (m_monitorThread.joinable()) {
+            m_monitorThread.join();
+        }
+    }
+
+    void monitorThreadProc()
+    {
+        std::unique_lock<std::mutex> lock(m_monitorMutex);
+        m_monitorIsRunning = true;
+        lock.unlock();
+
+        m_monitorSyncPoint.notify_one();
+
+        lock.lock();
+
+        pollfd pollFd{ m_serverPipeFd, POLLIN, 0 };
+
+        while (m_monitorIsRunning) {
+
+            lock.unlock();
+
+            int ret = TEMP_FAILURE_RETRY(poll(&pollFd, 1, MONITOR_TIMEOUT));
+            if (ret == -1) {
+                lock.lock();
+                break;
+            }
+
+            if (ret > 0 && (pollFd.revents & POLLIN)) {
+                if (collectStatistics() == -1) {
+                    lock.lock();
+                    break;
+                }
+            }
+
+            lock.lock();
+        }
+
+        m_monitorIsRunning = false;
+        lock.unlock();
+    }
+
+    // monitor variables
+    std::thread m_monitorThread;
+    bool m_monitorIsRunning;
+    std::mutex m_monitorMutex;
+    std::condition_variable m_monitorSyncPoint;
+
+    // server variables
+    uint64_t m_receivedBytes;
+    pid_t m_serverPid;
+    int m_serverPipeFd;
+
+    friend std::ostream &operator<<(std::ostream &os, const NetServer &);
+};
+
+
+std::ostream &operator<<(std::ostream &os, const NetServer &server)
+{
+    os << server.getDescription();
+    return os;
+}
+
+
+class UDPServer : public NetServer
+{
+public:
+    UDPServer(uint16_t port, int protocol = IPPROTO_UDP)
+    : NetServer{}
+    , m_port(port)
+    , m_protocol(protocol)
+    {}
+
+    virtual ~UDPServer() {}
+
+    virtual std::string getDescription() const override
+    {
+        return "UDPServer port: " + std::to_string(m_port);
+    }
+
+    virtual bool applyExtraSocketOptions(int)
+    {
+        return true;
+    }
+
+    virtual int serverProcedure(int pipeFd) override
+    {
+        FdUniquePtr pipePtr(&pipeFd);
+
+        int sockFd = socket(AF_INET, SOCK_DGRAM, m_protocol);
+        if (sockFd == -1) {
+            exit(EXIT_FAILURE);
+        }
+
+        SockUniquePtr sockPtr(&sockFd);
+
+        int optionValue = 1;
+        if (setsockopt(sockFd, SOL_SOCKET, SO_REUSEADDR, static_cast<const void *>(&optionValue) , sizeof(int)) == -1) {
+            exit(EXIT_FAILURE);
+        }
+
+        struct sockaddr_in serverAddress;
+        memset(&serverAddress, 0, sizeof(serverAddress));
+        serverAddress.sin_family = AF_INET;
+        serverAddress.sin_addr.s_addr = htonl(INADDR_ANY);
+        serverAddress.sin_port = htons(m_port);
+
+        if (bind(sockFd, reinterpret_cast<struct sockaddr *>(&serverAddress), sizeof(serverAddress)) == -1) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!applyExtraSocketOptions(sockFd)) {
+            exit(EXIT_FAILURE);
+        }
+
+        struct sockaddr_in clntAddress;
+        socklen_t clntAddressLength = static_cast<socklen_t>(sizeof(clntAddress));
+        char receiveBuffer[NET_BUFFER_SIZE];
+
+        notifyManager(pipeFd);
+
+        while (true) {
+            ssize_t len = TEMP_FAILURE_RETRY(recvfrom(sockFd, receiveBuffer, sizeof(receiveBuffer), 0,
+                    reinterpret_cast<struct sockaddr *>(&clntAddress), &clntAddressLength));
+
+            if (len == -1) {
+                exit(EXIT_FAILURE);
+            }
+
+            if (len == 0) {
+                continue;
+            }
+
+            do {
+                ssize_t pos = 0;
+                ssize_t ret = TEMP_FAILURE_RETRY(write(pipeFd, &receiveBuffer[pos], len));
+                if (ret == -1) {
+                    exit(EXIT_FAILURE);
+                }
+
+                len -= ret;
+                pos += ret;
+            } while (len != 0);
+        }
+
+        return EXIT_SUCCESS;
+    }
+
+private:
+    uint16_t m_port;
+    int m_protocol;
+};
+
+
+class UDPServerApp : public UDPServer
+{
+public:
+    UDPServerApp(uint16_t port, int protocol, const AppContext &appContext)
+    : UDPServer(port, protocol)
+    , m_appContext(appContext)
+    {
+    }
+
+    virtual int serverProcedure(int pipeFd) override
+    {
+        Api::setProcessLabel(m_appContext.appId());
+        RUNNER_ASSERT_ERRNO_MSG(drop_root_privileges(m_appContext.getUID(), m_appContext.getGID()) == 0,
+                                "drop_root_privileges() failed");
+
+        return UDPServer::serverProcedure(pipeFd);
+    }
+
+private:
+    AppContext m_appContext;
+};
+
+class UDPMulticastServer : public UDPServer
+{
+public:
+    UDPMulticastServer(uint16_t port, int protocol,
+            const std::string &mcastGroup, const std::string &networkInterface)
+    : UDPServer(port, protocol)
+    , m_mcastGroup(mcastGroup)
+    , m_networkInterface(networkInterface)
+    {
+    }
+
+    bool applyExtraSocketOptions(int sockFd) override
+    {
+        struct ip_mreq multicastRequest;
+        memset(&multicastRequest, 0, sizeof(multicastRequest));
+
+        in_addr_t mcastGroupAddr = inet_addr(m_mcastGroup.c_str());
+        if (!IN_MULTICAST(ntohl(mcastGroupAddr))) {
+            return false;
+        }
+
+        multicastRequest.imr_multiaddr.s_addr = mcastGroupAddr;
+
+        if (m_networkInterface == ANY_INTERFACE) {
+            multicastRequest.imr_interface.s_addr = INADDR_ANY;
+        } else {
+            struct ifreq interfaceRequest;
+            memset(&interfaceRequest, 0, sizeof(interfaceRequest));
+
+            interfaceRequest.ifr_addr.sa_family = AF_INET;
+            strncpy(interfaceRequest.ifr_name, m_networkInterface.c_str(), IFNAMSIZ);
+            interfaceRequest.ifr_name[IFNAMSIZ - 1] = '\0';
+
+            if (ioctl(sockFd, SIOCGIFADDR, &interfaceRequest) == -1) {
+                return false;
+            }
+
+            struct sockaddr_in *addr = reinterpret_cast<struct sockaddr_in *>(&interfaceRequest.ifr_addr);
+            multicastRequest.imr_interface.s_addr = addr->sin_addr.s_addr;
+        }
+
+        return setsockopt(sockFd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
+                static_cast<const void *>(&multicastRequest), sizeof(multicastRequest)) == 0;
+    }
+
+private:
+    std::string m_mcastGroup;
+    std::string m_networkInterface;
+};
+
+
+class UDPMulticastServerApp : public UDPMulticastServer
+{
+public:
+    UDPMulticastServerApp(uint16_t port, int protocol, const std::string &mcastGroup, const std::string &networkInterface,
+            const AppContext &appContext)
+    : UDPMulticastServer(port, protocol, mcastGroup, networkInterface)
+    , m_appContext(appContext)
+    {
+    }
+
+    virtual int serverProcedure(int pipeFd) override
+    {
+        Api::setProcessLabel(m_appContext.appId());
+        RUNNER_ASSERT_ERRNO_MSG(drop_root_privileges(m_appContext.getUID(), m_appContext.getGID()) == 0,
+                                "drop_root_privileges() failed");
+
+        return UDPServer::serverProcedure(pipeFd);
+    }
+
+private:
+    AppContext m_appContext;
+};
+
+
+class TCPServer : public NetServer
+{
+public:
+    explicit TCPServer(uint16_t port)
+    : NetServer{}
+    , m_port(port)
+    {}
+
+    virtual ~TCPServer() {}
+
+    virtual std::string getDescription() const override
+    {
+        return "TCPServer port: " + std::to_string(m_port);
+    }
+
+    virtual int serverProcedure(int pipeFd) override
+    {
+        FdUniquePtr pipePtr(&pipeFd);
+
+        int sockFd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+        if (sockFd == -1) {
+            exit(EXIT_FAILURE);
+        }
+
+        SockUniquePtr sockPtr(&sockFd);
+
+        int optionValue = 1;
+        if (setsockopt(sockFd, SOL_SOCKET, SO_REUSEADDR, static_cast<const void *>(&optionValue) , sizeof(int)) == -1) {
+            exit(EXIT_FAILURE);
+        }
+
+        struct sockaddr_in serverAddress;
+        memset(&serverAddress, 0, sizeof(serverAddress));
+        serverAddress.sin_family = AF_INET;
+        serverAddress.sin_addr.s_addr = htonl(INADDR_ANY);
+        serverAddress.sin_port = htons(m_port);
+
+        if (bind(sockFd, reinterpret_cast<struct sockaddr *>(&serverAddress), sizeof(serverAddress)) == -1) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (listen(sockFd, 1) == -1) {
+            exit(EXIT_FAILURE);
+        }
+
+        struct sockaddr_in clntAddress;
+        socklen_t clntAddressLength = static_cast<socklen_t>(sizeof(clntAddress));
+        char receiveBuffer[NET_BUFFER_SIZE];
+
+        notifyManager(pipeFd);
+
+        while (true) {
+            int acceptedSocketFd = TEMP_FAILURE_RETRY(accept(sockFd, reinterpret_cast<struct sockaddr *>(&clntAddress),
+                    &clntAddressLength));
+            if (acceptedSocketFd == -1) {
+                exit(EXIT_FAILURE);
+            }
+
+            SockUniquePtr acceptSockPtr(&acceptedSocketFd);
+
+            while (true) {
+                ssize_t len = TEMP_FAILURE_RETRY(recv(acceptedSocketFd, receiveBuffer, sizeof(receiveBuffer), 0));
+                if (len == -1) {
+                    exit(EXIT_FAILURE);
+                }
+
+                if (len == 0) {
+                    break;
+                }
+
+                do {
+                    ssize_t pos = 0;
+                    ssize_t ret = TEMP_FAILURE_RETRY(write(pipeFd, &receiveBuffer[pos], len));
+                    if (ret == -1) {
+                        exit(EXIT_FAILURE);
+                    }
+
+                    len -= ret;
+                    pos += ret;
+                } while (len != 0);
+            }
+        }
+
+        return EXIT_SUCCESS;
+    }
+
+private:
+    uint16_t m_port;
+};
+
+
+class TCPServerApp : public TCPServer
+{
+public:
+    TCPServerApp(int port, const AppContext &appContext)
+    : TCPServer(port)
+    , m_appContext(appContext)
+    {
+    }
+
+    virtual int serverProcedure(int pipeFd) override
+    {
+        Api::setProcessLabel(m_appContext.appId());
+        RUNNER_ASSERT_ERRNO_MSG(drop_root_privileges(m_appContext.getUID(), m_appContext.getGID()) == 0,
+                                "drop_root_privileges() failed");
+
+        return TCPServer::serverProcedure(pipeFd);
+    }
+
+private:
+    AppContext m_appContext;
+};
+
+
+class NetClient
+{
+public:
+    NetClient(const std::string &appPrefix, NetherInternetAccess access, int msgCount)
+    : m_appPrefix(appPrefix)
+    , m_access(access)
+    , m_msgCount(msgCount)
+    {
+    }
+
+    NetClient(const NetClient &) = delete;
+    NetClient &operator=(const NetClient &) = delete;
+
+    void start()
+    {
+        runProcedureInNetAppContext(m_appPrefix, [this]() { clientProcedure(); } , m_access);
+    }
+
+    virtual void clientProcedure() = 0;
+
+protected:
+    std::string m_appPrefix;
+    NetherInternetAccess m_access;
+    int m_msgCount;
+};
+
+class UDPClientApp : public NetClient
+{
+public:
+    UDPClientApp(const std::string &appPrefix, NetherInternetAccess access, int msgCount,
+            int protocol, const std::string &hostName, uint16_t port)
+    : NetClient(appPrefix, access, msgCount)
+    , m_protocol(protocol)
+    , m_hostName(hostName)
+    , m_port(port)
+    {
+    }
+
+    virtual bool applyExtraSocketOptions(int)
+    {
+        return true;
+    }
+
+private:
+    virtual void clientProcedure() override
+    {
+        int sockFd = socket(AF_INET, SOCK_DGRAM, m_protocol);
+        RUNNER_ASSERT_ERRNO_MSG(sockFd >= 0, "socket() failed");
+        SockUniquePtr sockPtr(&sockFd);
+
+        struct hostent *server = gethostbyname(m_hostName.c_str());
+        RUNNER_ASSERT_MSG(server != nullptr, "Couldn't find host " << m_hostName
+                << " h_errno = " << hstrerror(h_errno));
+
+        RUNNER_ASSERT_MSG (applyExtraSocketOptions(sockFd), "Couldn't prepare socket");
+
+        struct sockaddr_in serverAddress;
+        memset(&serverAddress, 0, sizeof(serverAddress));
+        serverAddress.sin_family = AF_INET;
+        memcpy(&serverAddress.sin_addr.s_addr, server->h_addr, server->h_length);
+        serverAddress.sin_port = htons(m_port);
+
+        char sendBuffer[NET_BUFFER_SIZE];
+
+        memset(sendBuffer, 'X', sizeof(sendBuffer));
+        size_t serverAddressLength = sizeof(serverAddress);
+
+        int msgCount = m_msgCount;
+        while (msgCount-- > 0) {
+            ssize_t len = sizeof(sendBuffer);
+            ssize_t pos = 0;
+            do {
+                ssize_t ret = TEMP_FAILURE_RETRY(sendto(sockFd, &sendBuffer[pos], len, 0,
+                        reinterpret_cast<struct sockaddr *>(&serverAddress), serverAddressLength));
+                RUNNER_ASSERT_ERRNO_MSG(ret >= 0, "sendto() failed");
+                len -= ret;
+                pos += ret;
+            } while (len != 0);
+        }
+    }
+
+    int m_protocol;
+    std::string m_hostName;
+    uint16_t m_port;
+};
+
+class UDPMulticastClientApp : public UDPClientApp
+{
+public:
+    UDPMulticastClientApp(const std::string &appPrefix, NetherInternetAccess access, int msgCount,
+            int protocol, const std::string &hostName, uint16_t port, char ttl, bool enableLoop)
+    : UDPClientApp(appPrefix, access, msgCount, protocol, hostName, port)
+    , m_ttl(ttl)
+    , m_enableLoop(enableLoop)
+    {
+    }
+
+    virtual bool applyExtraSocketOptions(int sockFd) override
+    {
+        if (setsockopt(sockFd, IPPROTO_IP, IP_MULTICAST_TTL, static_cast<void *>(&m_ttl), sizeof(m_ttl)) == -1) {
+            return false;
+        }
+
+        char multicastEnableLoop = m_enableLoop ? 1 : 0;
+        return setsockopt(sockFd, IPPROTO_IP, IP_MULTICAST_LOOP, static_cast<void *>(&multicastEnableLoop),
+                sizeof(multicastEnableLoop)) == 0;
+    }
+
+private:
+    char m_ttl;
+    bool m_enableLoop;
+};
+
+class TCPClientApp : public NetClient
+{
+public:
+    TCPClientApp(const std::string &appPrefix, NetherInternetAccess access, int msgCount,
+            const std::string &hostName, int port, bool expectConnectionError)
+    : NetClient(appPrefix, access, msgCount)
+    , m_hostName(hostName)
+    , m_port(port)
+    , m_expectConnectionError(expectConnectionError)
+    {
+    }
+
+private:
+    virtual void clientProcedure() override
+    {
+        int sockFd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+        RUNNER_ASSERT_ERRNO_MSG(sockFd >= 0, "socket() failed");
+        SockUniquePtr sockPtr(&sockFd);
+
+        struct hostent *server = gethostbyname(m_hostName.c_str());
+        RUNNER_ASSERT_MSG(server != nullptr, "Couldn't find host " << m_hostName
+                << " h_errno = " << hstrerror(h_errno));
+
+        struct sockaddr_in serverAddress;
+        memset(&serverAddress, 0, sizeof(serverAddress));
+        serverAddress.sin_family = AF_INET;
+        memcpy(&serverAddress.sin_addr.s_addr, server->h_addr, server->h_length);
+        serverAddress.sin_port = htons(m_port);
+        size_t serverAddressLength = sizeof(serverAddress);
+
+        int ret = TEMP_FAILURE_RETRY(connect(sockFd,
+                reinterpret_cast<struct sockaddr *>(&serverAddress),serverAddressLength));
+        if (ret == -1 && m_expectConnectionError) {
+            return;
+        }
+
+        RUNNER_ASSERT_ERRNO_MSG(ret == 0, "Couldn't connect to " << m_hostName);
+
+        char sendBuffer[NET_BUFFER_SIZE];
+        memset(sendBuffer, 'X', sizeof(sendBuffer));
+
+        int msgCount = m_msgCount;
+        while (msgCount-- > 0) {
+            ssize_t len = sizeof(sendBuffer);
+            ssize_t pos = 0;
+            do {
+                ssize_t ret = TEMP_FAILURE_RETRY(send(sockFd, &sendBuffer[pos], len, 0));
+                RUNNER_ASSERT_ERRNO_MSG(ret >= 0, "send() failed");
+                len -= ret;
+                pos += ret;
+            } while (len != 0);
+        }
+    }
+
+    std::string m_hostName;
+    uint16_t m_port;
+    bool m_expectConnectionError;
+};
+
+
+RUNNER_TEST_GROUP_INIT(NETHER_REMOTE_CONNECTION)
+
+
+RUNNER_CHILD_TEST(nether_check_udp_connection_internet_access_granted)
+{
+    ScopedShellScriptRunner networkNSRunner(NETHER_NETNS_SETUP_COMMAND,
+                                            NETHER_NETNS_TEARDOWN_COMMAND);
+
+    UDPServer udpServer(UDP_TEST_PORT);
+    udpServer.start(NETHER_NETNS_NAME_TEST);
+
+    UDPClientApp udpClientApp("nether_test_uciag", NetherInternetAccess::ACCESS_GRANTED, UDP_MESSAGES_COUNT,
+        IPPROTO_UDP, REMOTE_INTERFACE_ADDRESS, UDP_TEST_PORT);
+    udpClientApp.start();
+
+    RUNNER_ASSERT_MSG(udpServer.isAlive(), "UDP server was not running");
+    udpServer.stop();
+    RUNNER_ASSERT_MSG(udpServer.getReceivedBytes() > 0, "UDP server didn't receive any data");
+}
+
+
+RUNNER_CHILD_TEST(nether_check_udp_connection_internet_access_denied)
+{
+    ScopedShellScriptRunner networkNSRunner(NETHER_NETNS_SETUP_COMMAND,
+                                            NETHER_NETNS_TEARDOWN_COMMAND);
+
+    UDPServer udpServer(UDP_TEST_PORT);
+    udpServer.start(NETHER_NETNS_NAME_TEST);
+
+    UDPClientApp udpClientApp("nether_test_uciad", NetherInternetAccess::ACCESS_DENIED, UDP_MESSAGES_COUNT,
+        IPPROTO_UDP, REMOTE_INTERFACE_ADDRESS, UDP_TEST_PORT);
+    udpClientApp.start();
+
+    RUNNER_ASSERT_MSG(udpServer.isAlive(), "UDP server was not running");
+    udpServer.stop();
+    RUNNER_ASSERT_MSG(udpServer.getReceivedBytes() == 0, "UDP server received some data");
+}
+
+
+RUNNER_CHILD_TEST(nether_check_udp_lite_connection_internet_access_granted)
+{
+    ScopedShellScriptRunner networkNSRunner(NETHER_NETNS_SETUP_COMMAND,
+                                            NETHER_NETNS_TEARDOWN_COMMAND);
+
+    UDPServer udpLiteServer(UDP_TEST_PORT, IPPROTO_UDPLITE);
+    udpLiteServer.start(NETHER_NETNS_NAME_TEST);
+
+    UDPClientApp udpClientApp("nether_test_ulciag", NetherInternetAccess::ACCESS_GRANTED, UDP_MESSAGES_COUNT,
+        IPPROTO_UDPLITE, REMOTE_INTERFACE_ADDRESS, UDP_TEST_PORT);
+    udpClientApp.start();
+
+    RUNNER_ASSERT_MSG(udpLiteServer.isAlive(), "UDPLite server was not running");
+    udpLiteServer.stop();
+    RUNNER_ASSERT_MSG(udpLiteServer.getReceivedBytes() > 0, "UDPLite server didn't receive any data");
+}
+
+
+RUNNER_CHILD_TEST(nether_check_udp_lite_connection_internet_access_denied)
+{
+    ScopedShellScriptRunner networkNSRunner(NETHER_NETNS_SETUP_COMMAND,
+                                            NETHER_NETNS_TEARDOWN_COMMAND);
+
+    UDPServer udpLiteServer(UDP_TEST_PORT, IPPROTO_UDPLITE);
+    udpLiteServer.start(NETHER_NETNS_NAME_TEST);
+
+    UDPClientApp udpClientApp("nether_test_ulciad", NetherInternetAccess::ACCESS_DENIED, UDP_MESSAGES_COUNT,
+        IPPROTO_UDPLITE, REMOTE_INTERFACE_ADDRESS, UDP_TEST_PORT);
+    udpClientApp.start();
+
+    RUNNER_ASSERT_MSG(udpLiteServer.isAlive(), "UDP server was not running");
+    udpLiteServer.stop();
+    RUNNER_ASSERT_MSG(udpLiteServer.getReceivedBytes() == 0, "UDP server received some data");
+}
+
+
+RUNNER_CHILD_TEST(nether_check_tcp_connection_internet_access_granted)
+{
+    ScopedShellScriptRunner networkNSRunner(NETHER_NETNS_SETUP_COMMAND,
+                                            NETHER_NETNS_TEARDOWN_COMMAND);
+
+    TCPServer tcpServer(TCP_TEST_PORT);
+    tcpServer.start(NETHER_NETNS_NAME_TEST);
+
+    TCPClientApp tcpClientApp("nether_test_tciag", NetherInternetAccess::ACCESS_GRANTED, TCP_MESSAGES_COUNT,
+        REMOTE_INTERFACE_ADDRESS, TCP_TEST_PORT, false);
+    tcpClientApp.start();
+
+    RUNNER_ASSERT_MSG(tcpServer.isAlive(), "TCP server was not running");
+    tcpServer.stop();
+    RUNNER_ASSERT_MSG(tcpServer.getReceivedBytes() > 0, "TCP server didn't receive any data");
+}
+
+
+RUNNER_CHILD_TEST(nether_check_tcp_connection_internet_access_denied)
+{
+    ScopedShellScriptRunner networkNSRunner(NETHER_NETNS_SETUP_COMMAND,
+                                            NETHER_NETNS_TEARDOWN_COMMAND);
+
+    TCPServer tcpServer(TCP_TEST_PORT);
+    tcpServer.start(NETHER_NETNS_NAME_TEST);
+
+    TCPClientApp tcpClientApp("nether_test_tciad", NetherInternetAccess::ACCESS_DENIED, TCP_MESSAGES_COUNT,
+        REMOTE_INTERFACE_ADDRESS, TCP_TEST_PORT, true);
+    tcpClientApp.start();
+
+    RUNNER_ASSERT_MSG(tcpServer.isAlive(), "TCP server was not running");
+    tcpServer.stop();
+    RUNNER_ASSERT_MSG(tcpServer.getReceivedBytes() == 0, "TCP server received some data");
+}
+
+// Communication between application and local service
+RUNNER_TEST_GROUP_INIT(NETHER_LOCAL_SERVICE_CONNECTION)
+
+
+RUNNER_CHILD_TEST(nether_check_udp_local_connection_internet_access_granted)
+{
+    UDPServer udpLiteServer(UDP_TEST_PORT, IPPROTO_UDP);
+    udpLiteServer.start();
+
+    UDPClientApp udpClientApp("nether_test_ulciag", NetherInternetAccess::ACCESS_GRANTED, UDP_MESSAGES_COUNT,
+        IPPROTO_UDP, LOCAL_HOST_TEST_SERVER_ADDRESS, UDP_TEST_PORT);
+    udpClientApp.start();
+
+    RUNNER_ASSERT_MSG(udpLiteServer.isAlive(), "UDP server was not running");
+    udpLiteServer.stop();
+    RUNNER_ASSERT_MSG(udpLiteServer.getReceivedBytes() > 0, "UDP server didn't receive any data");
+}
+
+
+RUNNER_CHILD_TEST(nether_check_udp_local_connection_internet_access_denied)
+{
+    UDPServer udpLiteServer(UDP_TEST_PORT, IPPROTO_UDP);
+    udpLiteServer.start();
+
+    UDPClientApp udpClientApp("nether_test_ulciad", NetherInternetAccess::ACCESS_DENIED, UDP_MESSAGES_COUNT,
+        IPPROTO_UDP, LOCAL_HOST_TEST_SERVER_ADDRESS, UDP_TEST_PORT);
+    udpClientApp.start();
+
+    RUNNER_ASSERT_MSG(udpLiteServer.isAlive(), "UDP server was not running");
+    udpLiteServer.stop();
+    RUNNER_ASSERT_MSG(udpLiteServer.getReceivedBytes() > 0, "UDP server didn't receive any data");
+}
+
+
+RUNNER_CHILD_TEST(nether_check_udp_lite_local_connection_internet_access_granted)
+{
+    UDPServer udpLiteServer(UDP_TEST_PORT, IPPROTO_UDPLITE);
+    udpLiteServer.start();
+
+    UDPClientApp udpClientApp("nether_test_ullciag", NetherInternetAccess::ACCESS_GRANTED, UDP_MESSAGES_COUNT,
+        IPPROTO_UDPLITE, LOCAL_HOST_TEST_SERVER_ADDRESS, UDP_TEST_PORT);
+    udpClientApp.start();
+
+    RUNNER_ASSERT_MSG(udpLiteServer.isAlive(), "UDP server was not running");
+    udpLiteServer.stop();
+    RUNNER_ASSERT_MSG(udpLiteServer.getReceivedBytes() > 0, "UDP server didn't receive any data");
+}
+
+
+RUNNER_CHILD_TEST(nether_check_udp_lite_local_connection_internet_access_denied)
+{
+    UDPServer udpLiteServer(UDP_TEST_PORT, IPPROTO_UDPLITE);
+    udpLiteServer.start();
+
+    UDPClientApp udpClientApp("nether_test_ullciad", NetherInternetAccess::ACCESS_DENIED, UDP_MESSAGES_COUNT,
+        IPPROTO_UDPLITE, LOCAL_HOST_TEST_SERVER_ADDRESS, UDP_TEST_PORT);
+    udpClientApp.start();
+
+    RUNNER_ASSERT_MSG(udpLiteServer.isAlive(), "UDP server was not running");
+    udpLiteServer.stop();
+    RUNNER_ASSERT_MSG(udpLiteServer.getReceivedBytes() > 0, "UDP server didn't receive any data");
+}
+
+
+RUNNER_CHILD_TEST(nether_check_tcp_local_connection_internet_access_granted)
+{
+    TCPServer tcpServer(TCP_TEST_PORT);
+    tcpServer.start();
+
+    TCPClientApp tcpClientApp("nether_test_tlciad", NetherInternetAccess::ACCESS_GRANTED, TCP_MESSAGES_COUNT,
+        LOCAL_HOST_TEST_SERVER_ADDRESS, TCP_TEST_PORT, false);
+    tcpClientApp.start();
+
+    RUNNER_ASSERT_MSG(tcpServer.isAlive(), "TCP server was not running");
+    tcpServer.stop();
+    RUNNER_ASSERT_MSG(tcpServer.getReceivedBytes() > 0, "TCP server didn't receive any data");
+}
+
+
+RUNNER_CHILD_TEST(nether_check_tcp_local_connection_internet_access_denied)
+{
+    TCPServer tcpServer(TCP_TEST_PORT);
+    tcpServer.start();
+
+    TCPClientApp tcpClientApp("nether_test_tlciad", NetherInternetAccess::ACCESS_DENIED, TCP_MESSAGES_COUNT,
+        LOCAL_HOST_TEST_SERVER_ADDRESS, TCP_TEST_PORT, false);
+    tcpClientApp.start();
+
+    RUNNER_ASSERT_MSG(tcpServer.isAlive(), "TCP server was not running");
+    tcpServer.stop();
+    RUNNER_ASSERT_MSG(tcpServer.getReceivedBytes() > 0, "TCP server didn't receive any data");
+}
+
+
+// Applications mustn't communicate with each other even if access to the Internet is granted.
+RUNNER_TEST_GROUP_INIT(NETHER_LOCAL_INTER_APP_CONNECTION)
+
+
+RUNNER_CHILD_TEST(nether_check_udp_local_inter_app_connection_internet_access_granted)
+{
+    ScopedAppContext appContext("nether_test_uliaciag_a", NetherInternetAccess::ACCESS_GRANTED);
+    UDPServerApp udpServerApp(UDP_TEST_PORT, IPPROTO_UDP, appContext);
+    udpServerApp.start();
+
+    UDPClientApp udpClientApp("nether_test_uliaciag_b", NetherInternetAccess::ACCESS_GRANTED, UDP_MESSAGES_COUNT,
+        IPPROTO_UDP, LOCAL_HOST_TEST_SERVER_ADDRESS, UDP_TEST_PORT);
+    udpClientApp.start();
+
+    RUNNER_ASSERT_MSG(udpServerApp.isAlive(), "UDP server was not running");
+    udpServerApp.stop();
+    RUNNER_ASSERT_MSG(udpServerApp.getReceivedBytes() == 0, "UDP server received some data");
+}
+
+
+RUNNER_CHILD_TEST(nether_check_udp_local_inter_app_connection_internet_access_denied)
+{
+    ScopedAppContext appContext("nether_test_uliaciad_a", NetherInternetAccess::ACCESS_DENIED);
+    UDPServerApp udpServerApp(UDP_TEST_PORT, IPPROTO_UDP, appContext);
+    udpServerApp.start();
+
+    UDPClientApp udpClientApp("nether_test_uliaciad_b", NetherInternetAccess::ACCESS_DENIED, UDP_MESSAGES_COUNT,
+        IPPROTO_UDP, LOCAL_HOST_TEST_SERVER_ADDRESS, UDP_TEST_PORT);
+    udpClientApp.start();
+
+    RUNNER_ASSERT_MSG(udpServerApp.isAlive(), "UDP server was not running");
+    udpServerApp.stop();
+    RUNNER_ASSERT_MSG(udpServerApp.getReceivedBytes() == 0, "UDP server received some data");
+}
+
+
+RUNNER_CHILD_TEST(nether_check_udp_lite_local_inter_app_connection_internet_access_granted)
+{
+    ScopedAppContext appContext("nether_test_ulliaciag_a", NetherInternetAccess::ACCESS_GRANTED);
+    UDPServerApp udpLiteServerApp(UDP_TEST_PORT, IPPROTO_UDPLITE, appContext);
+    udpLiteServerApp.start();
+
+    UDPClientApp udpClientApp("nether_ulliaciag_b", NetherInternetAccess::ACCESS_GRANTED, UDP_MESSAGES_COUNT,
+        IPPROTO_UDPLITE, LOCAL_HOST_TEST_SERVER_ADDRESS, UDP_TEST_PORT);
+    udpClientApp.start();
+
+    RUNNER_ASSERT_MSG(udpLiteServerApp.isAlive(), "UDP server was not running");
+    udpLiteServerApp.stop();
+    RUNNER_ASSERT_MSG(udpLiteServerApp.getReceivedBytes() == 0, "UDP server received some data");
+}
+
+
+RUNNER_CHILD_TEST(nether_check_udp_lite_local_inter_app_connection_internet_access_denied)
+{
+    ScopedAppContext appContext("nether_test_ulliaciad_a", NetherInternetAccess::ACCESS_DENIED);
+    UDPServerApp udpLiteServerApp(UDP_TEST_PORT, IPPROTO_UDPLITE, appContext);
+    udpLiteServerApp.start();
+
+    UDPClientApp udpClientApp("nether_ulliaciad_b", NetherInternetAccess::ACCESS_DENIED, UDP_MESSAGES_COUNT,
+        IPPROTO_UDPLITE, LOCAL_HOST_TEST_SERVER_ADDRESS, UDP_TEST_PORT);
+    udpClientApp.start();
+
+    RUNNER_ASSERT_MSG(udpLiteServerApp.isAlive(), "UDP server was not running");
+    udpLiteServerApp.stop();
+    RUNNER_ASSERT_MSG(udpLiteServerApp.getReceivedBytes() == 0, "UDP server received some data");
+}
+
+
+RUNNER_CHILD_TEST(nether_check_tcp_local_inter_app_connection_internet_access_granted)
+{
+    ScopedAppContext appContext("nether_test_tliaciag_a", NetherInternetAccess::ACCESS_GRANTED);
+    TCPServerApp tcpServerApp(TCP_TEST_PORT, appContext);
+    tcpServerApp.start();
+
+    TCPClientApp tcpClientApp("nether_test_tliaciag_b", NetherInternetAccess::ACCESS_GRANTED, TCP_MESSAGES_COUNT,
+        LOCAL_HOST_TEST_SERVER_ADDRESS, TCP_TEST_PORT, true);
+    tcpClientApp.start();
+
+    RUNNER_ASSERT_MSG(tcpServerApp.isAlive(), "TCP server was not running");
+    tcpServerApp.stop();
+    RUNNER_ASSERT_MSG(tcpServerApp.getReceivedBytes() == 0, "TCP server received some data");
+}
+
+
+RUNNER_CHILD_TEST(nether_check_tcp_local_inter_app_connection_internet_access_denied)
+{
+    ScopedAppContext appContext("nether_test_tliaciad_a", NetherInternetAccess::ACCESS_DENIED);
+    TCPServerApp tcpServerApp(TCP_TEST_PORT, appContext);
+    tcpServerApp.start();
+
+    TCPClientApp tcpClientApp("nether_test_tliaciad_b", NetherInternetAccess::ACCESS_DENIED, TCP_MESSAGES_COUNT,
+        LOCAL_HOST_TEST_SERVER_ADDRESS, TCP_TEST_PORT, true);
+    tcpClientApp.start();
+
+    RUNNER_ASSERT_MSG(tcpServerApp.isAlive(), "TCP server was not running");
+    tcpServerApp.stop();
+    RUNNER_ASSERT_MSG(tcpServerApp.getReceivedBytes() == 0, "TCP server received some data");
+}
+
+
+RUNNER_TEST_GROUP_INIT(NETHER_MULTICAST)
+
+
+RUNNER_CHILD_TEST(nether_check_multicast_inter_app_connection_access_granted)
+{
+    // one app acts as a server (receiver), the second as a sender
+    ScopedAppContext appContext("nether_test_miacag_a", NetherInternetAccess::ACCESS_GRANTED);
+    UDPMulticastServerApp udpMulticastServerApp(UDP_TEST_PORT, IPPROTO_UDP, LOCAL_TEST_MCAST_GROUP,
+        ANY_INTERFACE, appContext);
+    udpMulticastServerApp.start();
+
+    UDPMulticastClientApp udpMulticastClientApp("nether_test_miacag_b", NetherInternetAccess::ACCESS_GRANTED,
+        UDP_MESSAGES_COUNT, IPPROTO_UDP, LOCAL_TEST_MCAST_GROUP, UDP_TEST_PORT, TTL_MCAST_RESTRIC_SUBNET, true);
+    udpMulticastClientApp.start();
+
+    RUNNER_ASSERT_MSG(udpMulticastServerApp.isAlive(), "UDP multicast server was not running");
+    udpMulticastServerApp.stop();
+    RUNNER_ASSERT_MSG(udpMulticastServerApp.getReceivedBytes() > 0, "UDP multicast server didn't receive any data");
+}
+
+
+RUNNER_CHILD_TEST(nether_check_multicast_inter_app_connection_access_denied)
+{
+    ScopedAppContext appContext("nether_test_miacad_a", NetherInternetAccess::ACCESS_DENIED);
+    UDPMulticastServerApp udpMulticastServerApp(UDP_TEST_PORT, IPPROTO_UDP, LOCAL_TEST_MCAST_GROUP,
+        ANY_INTERFACE, appContext);
+    udpMulticastServerApp.start();
+
+    UDPMulticastClientApp udpMulticastClientApp("nether_test_miacad_b", NetherInternetAccess::ACCESS_DENIED,
+        UDP_MESSAGES_COUNT, IPPROTO_UDP, LOCAL_TEST_MCAST_GROUP, UDP_TEST_PORT, TTL_MCAST_RESTRIC_SUBNET, true);
+    udpMulticastClientApp.start();
+
+    RUNNER_ASSERT_MSG(udpMulticastServerApp.isAlive(), "UDP multicast server was not running");
+    udpMulticastServerApp.stop();
+    RUNNER_ASSERT_MSG(udpMulticastServerApp.getReceivedBytes() == 0, "UDP multicast server received some data");
+}
+
+
+RUNNER_CHILD_TEST(nether_check_multicast_remote_connection_app_sender_access_granted)
+{
+    ScopedShellScriptRunner networkNSRunner(NETHER_NETNS_SETUP_COMMAND,
+                                            NETHER_NETNS_TEARDOWN_COMMAND);
+
+    UDPMulticastServer udpMulticastServer(UDP_TEST_PORT, IPPROTO_UDP, LOCAL_TEST_MCAST_GROUP, REMOTE_INTERFACE_NAME);
+    udpMulticastServer.start(NETHER_NETNS_NAME_TEST);
+
+    UDPMulticastClientApp udpMulticastClientApp("nether_test_mrcasag", NetherInternetAccess::ACCESS_GRANTED,
+        UDP_MESSAGES_COUNT, IPPROTO_UDP, LOCAL_TEST_MCAST_GROUP, UDP_TEST_PORT, TTL_MCAST_RESTRIC_SUBNET, false);
+    udpMulticastClientApp.start();
+
+    RUNNER_ASSERT_MSG(udpMulticastServer.isAlive(), "UDP multicast server was not running");
+    udpMulticastServer.stop();
+    RUNNER_ASSERT_MSG(udpMulticastServer.getReceivedBytes() > 0, "UDP multicast server didn't receive any data");
+}
+
+
+RUNNER_CHILD_TEST(nether_check_multicast_remote_connection_app_sender_access_denied)
+{
+    ScopedShellScriptRunner networkNSRunner(NETHER_NETNS_SETUP_COMMAND,
+                                            NETHER_NETNS_TEARDOWN_COMMAND);
+
+    UDPMulticastServer udpMulticastServer(UDP_TEST_PORT, IPPROTO_UDP, LOCAL_TEST_MCAST_GROUP, REMOTE_INTERFACE_NAME);
+    udpMulticastServer.start(NETHER_NETNS_NAME_TEST);
+
+    UDPMulticastClientApp udpMulticastClientApp("nether_test_mrcasad", NetherInternetAccess::ACCESS_DENIED,
+        UDP_MESSAGES_COUNT, IPPROTO_UDP, LOCAL_TEST_MCAST_GROUP, UDP_TEST_PORT, TTL_MCAST_RESTRIC_SUBNET, false);
+    udpMulticastClientApp.start();
+
+    RUNNER_ASSERT_MSG(udpMulticastServer.isAlive(), "UDP multicast server was not running");
+    udpMulticastServer.stop();
+    RUNNER_ASSERT_MSG(udpMulticastServer.getReceivedBytes() == 0, "UDP multicast server received some data");
+}
+
+
+RUNNER_TEST_GROUP_INIT(NETHER_LOCAL_DNS_CONNECTION)
+
+
+RUNNER_CHILD_TEST(nether_check_gethostbyname_internet_access_granted)
+{
+    ScopedShellScriptRunner networkDNSRunner(NETHER_DNS_SETUP_COMMAND,
+                                             NETHER_DNS_TEARDOWN_COMMAND);
+
+    const auto getHostAddress = [](void) {
+      struct hostent *server = gethostbyname(DNS_TEST_ADDRESS.c_str());
+      RUNNER_ASSERT_MSG(server != nullptr, "Couldn't find host "
+              << DNS_TEST_ADDRESS << " h_errno = " << h_errno);
+    };
+    runProcedureInNetAppContext("nether_test_giag", getHostAddress, NetherInternetAccess::ACCESS_GRANTED);
+}
+
+
+RUNNER_CHILD_TEST(nether_check_gethostbyname_internet_access_denied)
+{
+    ScopedShellScriptRunner networkDNSRunner(NETHER_DNS_SETUP_COMMAND,
+                                             NETHER_DNS_TEARDOWN_COMMAND);
+
+    const auto getHostAddress = [](void) {
+      struct hostent *server = gethostbyname(DNS_TEST_ADDRESS.c_str());
+      RUNNER_ASSERT_MSG(server == nullptr, "Host was found " << DNS_TEST_ADDRESS);
+    };
+
+    runProcedureInNetAppContext("nether_test_giad", getHostAddress, NetherInternetAccess::ACCESS_DENIED);
+}
+
+
+void checkRawSocket(void)
+{
+    const std::vector<int> protocols {
+        IPPROTO_ICMP,
+        IPPROTO_IGMP
+    };
+
+    for (const auto protocol : protocols) {
+        int sockFd = socket(AF_INET, SOCK_RAW, protocol);
+        RUNNER_ASSERT_ERRNO_MSG(sockFd == -1, "RAW socket was successfully created for protocol " << protocol);
+    }
+};
+
+
+RUNNER_TEST_GROUP_INIT(NETHER_RAW_SOCKET)
+
+
+RUNNER_CHILD_TEST(nether_check_raw_socket_access_granted)
+{
+    runProcedureInNetAppContext("nether_test_rsag", checkRawSocket, NetherInternetAccess::ACCESS_GRANTED);
+}
+
+
+RUNNER_CHILD_TEST(nether_check_raw_socket_access_denied)
+{
+    runProcedureInNetAppContext("nether_test_rsad", checkRawSocket, NetherInternetAccess::ACCESS_DENIED);
+}
+
+
+int main(int argc, char *argv[])
+{
+    return DPL::Test::TestRunnerSingleton::Instance().ExecTestRunner(argc, argv);
+}
diff --git a/src/nether-tests/setup-nether-tests-dns.sh b/src/nether-tests/setup-nether-tests-dns.sh
new file mode 100644 (file)
index 0000000..c3e1fea
--- /dev/null
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+#####################################################################
+# Copyright (c) 2017 Samsung Electronics Co., Ltd All Rights Reserved
+#
+#    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.
+#####################################################################
+
+# make sure the filesystem is mounted in RW mode
+rw
+
+# backup old resolv.conf
+cp /etc/resolv.conf /etc/resolv.conf.back
+
+# configure Connman as a local DNS server
+echo "nameserver 127.0.0.1" > /etc/resolv.conf
diff --git a/src/nether-tests/setup-nether-tests-nns.sh b/src/nether-tests/setup-nether-tests-nns.sh
new file mode 100644 (file)
index 0000000..6ddf174
--- /dev/null
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+#####################################################################
+# Copyright (c) 2017 Samsung Electronics Co., Ltd All Rights Reserved
+#
+#    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.
+#####################################################################
+
+if [[ $# -eq 0 ]] ; then
+    scrname=`basename "$0"`
+    echo "Usage: $scrname <network namespace name>"
+    exit 0
+fi
+
+ip netns add $1
+ip link add veth0 type veth peer name veth1
+ip link set veth1 netns $1
+ip netns exec $1 ifconfig lo 127.0.0.1
+ip netns exec $1 ifconfig veth1 10.1.0.2 netmask 255.255.255.252
+ip netns exec $1 route add -net 224.0.0.0 netmask 224.0.0.0 veth1
+
+# force mtu size (we want to test that Nether's rules can deal with fragmentation)
+ifconfig veth0 mtu 1500
+ifconfig veth0 10.1.0.1 netmask 255.255.255.252
+route add -net 224.0.0.0 netmask 240.0.0.0 veth0
+
+# force ARP response
+ping -c 1 10.1.0.2 > /dev/null
diff --git a/src/nether-tests/teardown-nether-tests-dns.sh b/src/nether-tests/teardown-nether-tests-dns.sh
new file mode 100644 (file)
index 0000000..0d240f6
--- /dev/null
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+#####################################################################
+# Copyright (c) 2017 Samsung Electronics Co., Ltd All Rights Reserved
+#
+#    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.
+#####################################################################
+
+# restore old resolv.conf
+mv /etc/resolv.conf.back /etc/resolv.conf
+
+
diff --git a/src/nether-tests/teardown-nether-tests-nns.sh b/src/nether-tests/teardown-nether-tests-nns.sh
new file mode 100644 (file)
index 0000000..455db1a
--- /dev/null
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+#####################################################################
+# Copyright (c) 2017 Samsung Electronics Co., Ltd All Rights Reserved
+#
+#    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.
+#####################################################################
+
+if [[ $# -eq 0 ]] ; then
+    scrname=`basename "$0"`
+    echo "Usage: $scrname <network namespace name>"
+    exit 0
+fi
+
+ifconfig veth0 down
+ip netns exec $1 ifconfig veth1 down
+ip netns exec $1 ifconfig lo down
+
+ip link delete veth0
+ip netns delete $1
index 9ab5b5c..a4fc738 100644 (file)
@@ -57,6 +57,7 @@ runTest security-manager
 runTest cynara
 runTest ckm
 runTest yaca
+runTest nether
 
 printSummary
 
index 3956258..b86c61e 100644 (file)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 #####################################################################
-# Copyright (c) 2012-2014 Samsung Electronics Co., Ltd All Rights Reserved
+# Copyright (c) 2012-2017 Samsung Electronics Co., Ltd All Rights Reserved
 #
 #    Licensed under the Apache License, Version 2.0 (the "License");
 #    you may not use this file except in compliance with the License.
@@ -52,11 +52,17 @@ case $1 in
     echo
     yaca-test "${@:2}"
     ;;
+"nether")
+    echo "========================================================================="
+    echo "NETHER TESTS"
+    echo
+    nether-tests "${@:2}"
+    ;;
 *)
     echo "Correct using:"
     echo "    security_test.sh <module> <args_for_module>"
     echo
-    echo "modules: smack, security-manager, cynara, ckm, yaca"
+    echo "modules: smack, security-manager, cynara, ckm, yaca, nether"
     ;;
 
 esac