Add tests for network protocols 40/125240/5
authorPiotr Sawicki <p.sawicki2@partner.samsung.com>
Thu, 16 Mar 2017 12:20:19 +0000 (13:20 +0100)
committerZofia Abramowska <z.abramowska@samsung.com>
Wed, 26 Apr 2017 14:52:46 +0000 (16:52 +0200)
Several test cases have been implemented to check how Nether deals with UDP,
UDPLite and TCP protocols. Additionally, tests for gethostbyname() function
have been added. It is necessary because this function can create connections
to a local DNS server. Since this local server has 'System' label attached,
it is able to bypass firewall rules and transmit data to the Internet on behalf
of a user application.

Change-Id: I31d114d2e40ef01d200d4b78223ff089b4f43d9c

packaging/security-tests.spec
src/nether-tests/CMakeLists.txt
src/nether-tests/nether_tests.cpp

index 1badd49..a2d7fa2 100644 (file)
@@ -33,6 +33,7 @@ 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/}
index 8069f12..95d9eca 100644 (file)
@@ -29,10 +29,16 @@ 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
@@ -46,6 +52,7 @@ INCLUDE_DIRECTORIES(SYSTEM
 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)
@@ -55,6 +62,8 @@ 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)
index 799f246..a76b6c5 100644 (file)
  * @brief       Tests for Nether service
  */
 
-#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <linux/netlink.h>
+#include <netdb.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 <dpl/log/log.h>
-#include <dpl/test/test_runner.h>
-#include <sm_api.h>
-#include <sm_request.h>
+#include <scoped_installer.h>
+#include <sm_commons.h>
 #include <tests_common.h>
+#include <dpl/test/safe_cleanup.h>
 
-#define NETHER_NNS_NAME "nether_test_network_ns"
 
 using namespace SecurityManagerTest;
+using namespace DPL::Test;
 
-namespace {
+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_NNS_SETUP_COMMAND = "/usr/bin/setup-nether-tests-nns.sh"
-                                             " " NETHER_NNS_NAME;
-const std::string NETHER_NNS_TEARDOWN_COMMAND = "/usr/bin/teardown-nether-tests-nns.sh"
-                                                " " NETHER_NNS_NAME;
+const ssize_t NET_BUFFER_SIZE = 1024;
+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_TEST_SERVER_ADDRESS = "10.1.0.2";
+const std::string LOCAL_TEST_SERVER_ADDRESS = "127.0.0.1";
+const std::string DNS_TEST_ADDRESS = "www.samsung.com";
+
+const int MONITOR_TIMEOUT = 1000; // ms
+
+enum class NetherInternetAccess {
+    ACCESS_GRANTED,
+    ACCESS_DENIED
+};
 
-const std::string INTERNET_ACCESS_PRIVILEGE = "http://tizen.org/privilege/internet";
 
-class ScopedSystemRunner final {
+void runShellScriptInChildAndWait(const std::string &command)
+{
+    RUNNER_ASSERT_MSG(system(command.c_str()) != -1, "Couldn't run command: " << command);
+}
+
+
+class ScopedShellScriptRunner final {
 public:
-    ScopedSystemRunner(const std::string &setupCmd, const std::string &teardownCmd)
+    ScopedShellScriptRunner(const std::string &setupCmd, const std::string &teardownCmd)
     : m_teardownCmd(teardownCmd)
     {
-        RUNNER_ASSERT_MSG(system(setupCmd.c_str()) != -1,
-                "Couldn't run " << setupCmd << " command");
+        runShellScriptInChildAndWait(setupCmd);
     }
 
-    ~ScopedSystemRunner()
+    ~ScopedShellScriptRunner()
     {
-        RUNNER_ASSERT_MSG(system(m_teardownCmd.c_str()) != -1,
-                "Couldn't run " << m_teardownCmd << " command");
+        SafeCleanup::run([this]() {
+            runShellScriptInChildAndWait(m_teardownCmd);
+        });
     }
 
-    ScopedSystemRunner(const ScopedSystemRunner &) = delete;
-    ScopedSystemRunner &operator=(const ScopedSystemRunner &) = delete;
+    ScopedShellScriptRunner(const ScopedShellScriptRunner &) = delete;
+    ScopedShellScriptRunner &operator=(const ScopedShellScriptRunner &) = delete;
 
 private:
     std::string m_teardownCmd;
 };
 
-} // namespace
 
-RUNNER_TEST_GROUP_INIT(NETHER)
+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 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);
+        }
+
+        struct sockaddr_in clntAddress;
+        size_t clntAddressLength = 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), static_cast<socklen_t *>(&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 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);
+        }
 
-RUNNER_CHILD_TEST(nether_01_check_app_install_uninstall)
+        if (listen(sockFd, 1) == -1) {
+            exit(EXIT_FAILURE);
+        }
+
+        struct sockaddr_in clntAddress;
+        size_t clntAddressLength = sizeof(clntAddress);
+        char receiveBuffer[NET_BUFFER_SIZE];
+
+        notifyManager(pipeFd);
+
+        while (true) {
+            int acceptedSocketFd = TEMP_FAILURE_RETRY(accept(sockFd, reinterpret_cast<struct sockaddr *>(&clntAddress),
+                    static_cast<socklen_t *>(&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
 {
-    const std::string nether_app_id = "nether_test_01_app";
-    const std::string nether_pkg_id = "nether_test_01_pkg";
+public:
+    TCPServerApp(int port, const AppContext &appContext)
+    : TCPServer(port)
+    , m_appContext(appContext)
+    {
+    }
 
-    ScopedSystemRunner networkNSRunner(NETHER_NNS_SETUP_COMMAND,
-                                       NETHER_NNS_TEARDOWN_COMMAND);
+    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");
 
-    InstallRequest requestInst;
-    requestInst.setAppId(nether_app_id);
-    requestInst.setPkgId(nether_pkg_id);
-    requestInst.addPrivilege(INTERNET_ACCESS_PRIVILEGE);
+        return TCPServer::serverProcedure(pipeFd);
+    }
 
-    Api::install(requestInst);
+private:
+    AppContext m_appContext;
+};
 
-    // TODO test code goes here
 
-    InstallRequest requestUninst;
-    requestUninst.setAppId(nether_app_id);
+class NetClient
+{
+public:
+    NetClient(const std::string &appPrefix, NetherInternetAccess access, int msgCount)
+    : m_appPrefix(appPrefix)
+    , m_access(access)
+    , m_msgCount(msgCount)
+    {
+    }
 
-    Api::uninstall(requestUninst);
+    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)
+    {
+    }
+
+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));
+
+        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 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);
+        }
+    }
+
+    int m_protocol;
+    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)
+{
+    RUNNER_IGNORED_MSG("Disabled until the implementation of handling of UDP traffic is finished.");
+    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_01", NetherInternetAccess::ACCESS_GRANTED, UDP_MESSAGES_COUNT,
+        IPPROTO_UDP, REMOTE_TEST_SERVER_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)
+{
+    RUNNER_IGNORED_MSG("Disabled until the implementation of handling of UDP traffic is finished.");
+    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_02", NetherInternetAccess::ACCESS_DENIED, UDP_MESSAGES_COUNT,
+        IPPROTO_UDP, REMOTE_TEST_SERVER_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)
+{
+    RUNNER_IGNORED_MSG("Disabled until the implementation of handling of UDP traffic is finished.");
+    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_03", NetherInternetAccess::ACCESS_GRANTED, UDP_MESSAGES_COUNT,
+        IPPROTO_UDPLITE, REMOTE_TEST_SERVER_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)
+{
+    RUNNER_IGNORED_MSG("Disabled until the implementation of handling of UDP traffic is finished.");
+    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_04", NetherInternetAccess::ACCESS_DENIED, UDP_MESSAGES_COUNT,
+        IPPROTO_UDPLITE, REMOTE_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 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_05", NetherInternetAccess::ACCESS_GRANTED, TCP_MESSAGES_COUNT,
+        REMOTE_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_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_06", NetherInternetAccess::ACCESS_DENIED, TCP_MESSAGES_COUNT,
+        REMOTE_TEST_SERVER_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");
+}
+
+
+RUNNER_TEST_GROUP_INIT(NETHER_LOCAL_SERVICE_CONNECTION)
+
+
+RUNNER_CHILD_TEST(nether_check_udp_local_connection_internet_access_denied)
+{
+    RUNNER_IGNORED_MSG("Disabled until the implementation of handling of UDP traffic is finished.");
+    UDPServer udpLiteServer(UDP_TEST_PORT, IPPROTO_UDP);
+    udpLiteServer.start();
+
+    UDPClientApp udpClientApp("nether_test_07", NetherInternetAccess::ACCESS_DENIED, UDP_MESSAGES_COUNT,
+        IPPROTO_UDP, LOCAL_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)
+{
+    RUNNER_IGNORED_MSG("Disabled until the implementation of handling of UDP traffic is finished.");
+    UDPServer udpLiteServer(UDP_TEST_PORT, IPPROTO_UDPLITE);
+    udpLiteServer.start();
+
+    UDPClientApp udpClientApp("nether_test_08", NetherInternetAccess::ACCESS_DENIED, UDP_MESSAGES_COUNT,
+        IPPROTO_UDPLITE, LOCAL_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_denied)
+{
+    TCPServer tcpServer(TCP_TEST_PORT);
+    tcpServer.start();
+
+    TCPClientApp tcpClientApp("nether_test_09", NetherInternetAccess::ACCESS_DENIED, TCP_MESSAGES_COUNT,
+        LOCAL_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_TEST_GROUP_INIT(NETHER_LOCAL_INTER_APP_CONNECTION)
+
+
+RUNNER_CHILD_TEST(nether_check_udp_local_inter_app_connection_internet_access_denied)
+{
+    RUNNER_IGNORED_MSG("Disabled until the implementation of handling of UDP traffic is finished.");
+    ScopedAppContext appContext("nether_test_10a", NetherInternetAccess::ACCESS_DENIED);
+    UDPServerApp udpServerApp(UDP_TEST_PORT, IPPROTO_UDP, appContext);
+    udpServerApp.start();
+
+    UDPClientApp udpClientApp("nether_test_10b", NetherInternetAccess::ACCESS_DENIED, UDP_MESSAGES_COUNT,
+        IPPROTO_UDP, LOCAL_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 didn't receive any data");
+}
+
+
+RUNNER_CHILD_TEST(nether_check_udp_lite_local_inter_app_connection_internet_access_denied)
+{
+    RUNNER_IGNORED_MSG("Disabled until the implementation of handling of UDP traffic is finished.");
+    ScopedAppContext appContext("nether_test_11a", NetherInternetAccess::ACCESS_DENIED);
+    UDPServerApp udpLiteServerApp(UDP_TEST_PORT, IPPROTO_UDPLITE, appContext);
+    udpLiteServerApp.start();
+
+    UDPClientApp udpClientApp("nether_test_11b", NetherInternetAccess::ACCESS_DENIED, UDP_MESSAGES_COUNT,
+        IPPROTO_UDPLITE, LOCAL_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 didn't receive any data");
+}
+
+
+RUNNER_CHILD_TEST(nether_check_tcp_local_inter_app_connection_internet_access_denied)
+{
+    ScopedAppContext appContext("nether_test_12a", NetherInternetAccess::ACCESS_DENIED);
+    TCPServerApp tcpServerApp(TCP_TEST_PORT, appContext);
+    tcpServerApp.start();
+
+    TCPClientApp tcpClientApp("nether_test_12b", NetherInternetAccess::ACCESS_DENIED, TCP_MESSAGES_COUNT,
+        LOCAL_TEST_SERVER_ADDRESS, TCP_TEST_PORT, false);
+    tcpClientApp.start();
+
+    RUNNER_ASSERT_MSG(tcpServerApp.isAlive(), "TCP server was not running");
+    tcpServerApp.stop();
+    RUNNER_ASSERT_MSG(tcpServerApp.getReceivedBytes() > 0, "TCP server didn't receive any data");
+}
+
+
+RUNNER_TEST_GROUP_INIT(NETHER_LOCAL_DNS_CONNECTION)
+
+
+RUNNER_CHILD_TEST(nether_check_gethostbyname_internet_access_granted)
+{
+    RUNNER_IGNORED_MSG("Disabled until the implementation of handling of DNS queries is finished.");
+    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_13", getHostAddress, NetherInternetAccess::ACCESS_GRANTED);
+}
+
+
+RUNNER_CHILD_TEST(nether_check_gethostbyname_internet_access_denied)
+{
+    RUNNER_IGNORED_MSG("Disabled until the implementation of handling of DNS queries is finished.");
+    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_14", 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_15", checkRawSocket, NetherInternetAccess::ACCESS_GRANTED);
+}
+
+
+RUNNER_CHILD_TEST(nether_check_raw_socket_access_denied)
+{
+    runProcedureInNetAppContext("nether_test_16", checkRawSocket, NetherInternetAccess::ACCESS_DENIED);
+}
+
+
 int main(int argc, char *argv[])
 {
     return DPL::Test::TestRunnerSingleton::Instance().ExecTestRunner(argc, argv);