From 563ad1bd213fe82c45c77a6ae1f80ef9cff276d9 Mon Sep 17 00:00:00 2001
From: armatusmiles <akurotych@gmail.com>
Date: Fri, 11 Jan 2019 21:50:22 +0700
Subject: [PATCH] [+] dap_network_monitor with unit tests

---
 .travis.yml                               |   1 +
 core/unix/CMakeLists.txt                  |   2 +
 core/unix/dap_cpu_monitor.c               |   5 -
 core/unix/dap_network_monitor.c           | 185 +++++++++++++++++++++
 core/unix/dap_network_monitor.h           |  74 ++++++++-
 core/unix/dap_process_manager.h           |  24 +++
 test/core/CMakeLists.txt                  |   2 +-
 test/core/main.c                          |   4 +
 test/core/unix/dap_network_monitor_test.c | 188 ++++++++++++++++++++++
 test/core/unix/dap_network_monitor_test.h |   4 +
 test/libdap-test                          |   2 +-
 11 files changed, 483 insertions(+), 8 deletions(-)
 create mode 100644 test/core/unix/dap_network_monitor_test.c
 create mode 100644 test/core/unix/dap_network_monitor_test.h

diff --git a/.travis.yml b/.travis.yml
index 3e59dc6..f4b36a0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -23,4 +23,5 @@ addons:
     - ubuntu-toolchain-r-test
     packages:
     - gcc-5
+    - network-manager
 
diff --git a/core/unix/CMakeLists.txt b/core/unix/CMakeLists.txt
index 7d4465b..18c0868 100644
--- a/core/unix/CMakeLists.txt
+++ b/core/unix/CMakeLists.txt
@@ -7,4 +7,6 @@ file(GLOB CORE_UNIX_HEADERS *.h)
 
 add_library(${PROJECT_NAME} STATIC ${CORE_UNIX_SRCS} ${CORE_UNIX_HEADERS})
 
+target_link_libraries(${PROJECT_NAME} dap_core pthread)
+
 target_include_directories(dap_core_unix INTERFACE .)
diff --git a/core/unix/dap_cpu_monitor.c b/core/unix/dap_cpu_monitor.c
index 1ce77a5..749b2ec 100644
--- a/core/unix/dap_cpu_monitor.c
+++ b/core/unix/dap_cpu_monitor.c
@@ -95,8 +95,6 @@ dap_cpu_stats_t dap_cpu_get_stats()
                                                   _cpu_old_stats[i].idle_time,
                                                   _cpu_stats.cpus[i].total_time,
                                                   _cpu_old_stats[i].total_time);
-
-       // log_it(L_WARNING, "CPU %d %f", i, _cpu_stats.cpus[i].load);
     }
 
     _cpu_stats.cpu_summary.load = _calculate_load(_cpu_stats.cpu_summary.idle_time,
@@ -104,9 +102,6 @@ dap_cpu_stats_t dap_cpu_get_stats()
                     _cpu_stats.cpu_summary.total_time,
                     _cpu_summary_old.total_time);
 
-  //  log_it(L_WARNING, "%f", _cpu_stats.cpu_summary.load);
-
-
     memcpy(&_cpu_summary_old, &_cpu_stats.cpu_summary, sizeof (dap_cpu_t));
 
     memcpy(_cpu_old_stats, _cpu_stats.cpus,
diff --git a/core/unix/dap_network_monitor.c b/core/unix/dap_network_monitor.c
index e69de29..d84dfe7 100644
--- a/core/unix/dap_network_monitor.c
+++ b/core/unix/dap_network_monitor.c
@@ -0,0 +1,185 @@
+#include <linux/netlink.h>
+#include <pthread.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+
+#include "dap_network_monitor.h"
+#include "dap_common.h"
+
+
+#define LOG_TAG "dap_network_monitor"
+
+static struct {
+    int socket;
+    pthread_t thread;
+    dap_network_monitor_notification_callback_t callback;
+} _net_notification;
+
+static void* network_monitor_worker(void *arg);
+
+int dap_network_monitor_init(dap_network_monitor_notification_callback_t cb)
+{
+    memset((void*)&_net_notification, 0, sizeof(_net_notification));
+
+    if ((_net_notification.socket = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) == -1) {
+        log_it(L_ERROR, "Can't open notification socket");
+        return -1;
+    }
+
+    struct sockaddr_nl addr = {0};
+    addr.nl_family = AF_NETLINK;
+    addr.nl_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV4_ROUTE;
+    if (bind(_net_notification.socket, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
+        log_it(L_ERROR, "Can't bind notification socket");
+        return -2;
+    }
+
+    pthread_barrier_t barrier;
+
+    pthread_barrier_init(&barrier, NULL, 2);
+    if(pthread_create(&_net_notification.thread, NULL, network_monitor_worker, &barrier) != 0) {
+        log_it(L_ERROR, "Error create notification thread");
+        return -3;
+    }
+
+    pthread_barrier_wait(&barrier);
+
+    pthread_barrier_destroy(&barrier);
+
+    _net_notification.callback = cb;
+    log_it(L_DEBUG, "dap_network_monitor was initialized");
+    return 0;
+}
+
+void dap_network_monitor_deinit(void)
+{
+    if(_net_notification.socket == 0 || _net_notification.socket == -1) {
+        log_it(L_ERROR, "Network monitor not be inited");
+        return;
+    }
+    close(_net_notification.socket);
+    pthread_cancel(_net_notification.thread);
+    pthread_join(_net_notification.thread, NULL);
+}
+
+static void _ip_addr_msg_handler(struct nlmsghdr *nlh,
+                                 dap_network_notification_t* result)
+{
+    struct ifaddrmsg *ifa = (struct ifaddrmsg *)NLMSG_DATA(nlh);
+    struct rtattr *rth = IFA_RTA(ifa);
+    size_t rtl = IFA_PAYLOAD(nlh);
+    for (; rtl && RTA_OK(rth, rtl); rth = RTA_NEXT(rth,rtl)) {
+        char *inet_str = inet_ntoa(*((struct in_addr *)RTA_DATA(rth)));
+
+        if (rth->rta_type != IFA_LOCAL) continue;
+
+        /* fill result */
+        result->addr.ip = htonl(*((uint32_t *)RTA_DATA(rth)));
+        strcpy(result->addr.s_ip, inet_str);
+        if_indextoname(ifa->ifa_index, result->addr.interface_name);
+    }
+}
+
+static void _route_msg_handler(struct nlmsghdr *nlh,
+                               dap_network_notification_t* result,
+                               int received_bytes)
+{
+    struct  rtmsg *route_entry;  /* This struct represent a route entry
+                                        in the routing table */
+    struct  rtattr *route_attribute; /* This struct contain route
+                                                attributes (route type) */
+    int     route_attribute_len = 0;
+
+    route_attribute_len = RTM_PAYLOAD(nlh);
+
+    for ( ; NLMSG_OK(nlh, received_bytes); \
+                       nlh = NLMSG_NEXT(nlh, received_bytes))
+       {
+           /* Get the route data */
+           route_entry = (struct rtmsg *) NLMSG_DATA(nlh);
+
+           result->route.netmask = route_entry->rtm_dst_len;
+           result->route.protocol = route_entry->rtm_protocol;
+
+           /* Get attributes of route_entry */
+           route_attribute = (struct rtattr *) RTM_RTA(route_entry);
+
+           /* Get the route atttibutes len */
+           route_attribute_len = RTM_PAYLOAD(nlh);
+           /* Loop through all attributes */
+           for ( ; RTA_OK(route_attribute, route_attribute_len); \
+               route_attribute = RTA_NEXT(route_attribute, route_attribute_len))
+           {
+               /* Get the destination address */
+               if (route_attribute->rta_type == RTA_DST)
+               {
+                   result->route.destination_address = htonl(*(uint32_t*)RTA_DATA(route_attribute));
+
+                   inet_ntop(AF_INET, RTA_DATA(route_attribute),
+                                                result->route.s_destination_address,
+                                                sizeof(result->route.s_destination_address));
+               }
+               /* Get the gateway (Next hop) */
+               if (route_attribute->rta_type == RTA_GATEWAY)
+               {
+                   result->route.gateway_address = htonl(*(uint32_t*)RTA_DATA(route_attribute));
+;
+                   inet_ntop(AF_INET, RTA_DATA(route_attribute),
+                                                result->route.s_gateway_address,
+                                                sizeof(result->route.s_gateway_address));
+               }
+           }
+   }
+
+}
+
+static void clear_results(dap_network_notification_t* cb_result) {
+    bzero(cb_result, sizeof (dap_network_notification_t));
+    cb_result->route.destination_address = (uint64_t) -1;
+    cb_result->route.gateway_address = (uint64_t) -1;
+}
+
+static void* network_monitor_worker(void *arg)
+{
+    pthread_barrier_t *barrier = (pthread_barrier_t *)arg;
+    log_it(L_DEBUG, "Network monitor worker started");
+    if (_net_notification.socket == -1) {
+        log_it(L_ERROR, "Net socket not running. Can't start worker");
+        return  NULL;
+    }
+
+    char buffer[4096];
+    dap_network_notification_t callback_result;
+
+    struct nlmsghdr *nlh = (struct nlmsghdr *)buffer;
+    int len;
+
+    pthread_barrier_wait(barrier);
+
+    while ((len = recv(_net_notification.socket, nlh, sizeof(buffer), 0)) > 0) {
+        for (; (NLMSG_OK(nlh, len)) && (nlh->nlmsg_type != NLMSG_DONE); nlh = NLMSG_NEXT(nlh, len)) {
+
+            clear_results(&callback_result);
+
+            callback_result.type = nlh->nlmsg_type;
+            if (nlh->nlmsg_type == RTM_NEWADDR || nlh->nlmsg_type == RTM_DELADDR) {
+                _ip_addr_msg_handler(nlh, &callback_result);
+            } else if(nlh->nlmsg_type == RTM_NEWROUTE || nlh->nlmsg_type == RTM_DELROUTE) {
+                _route_msg_handler(nlh, &callback_result, len);
+            } else {
+                log_it(L_DEBUG, "Not supported msg type %d", nlh->nlmsg_type);
+                continue;
+            }
+
+            if (_net_notification.callback) {
+                _net_notification.callback(callback_result);
+            } else {
+                log_it(L_ERROR, "callback is NULL");
+            }
+        }
+    }
+}
diff --git a/core/unix/dap_network_monitor.h b/core/unix/dap_network_monitor.h
index b0ae8cb..b81e588 100644
--- a/core/unix/dap_network_monitor.h
+++ b/core/unix/dap_network_monitor.h
@@ -1 +1,73 @@
-#include "linux/netlink.h"
+/*
+ * Authors:
+ * Anatolii Kurotych <akurotych@gmail.com>
+ * DeM Labs Inc.   https://demlabs.net
+ * DeM Labs Open source community https://github.com/demlabsinc
+ * Copyright  (c) 2017-2019
+ * All rights reserved.
+
+ This file is part of DAP (Deus Applications Prototypes) the open source project
+
+    DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    DAP is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with any DAP based project.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <net/if.h>
+#include <linux/rtnetlink.h>
+
+#define MAX_IP_STR_LEN 15
+
+typedef enum {
+    // like in rtnetlink defines
+    IP_ADDR_ADD = RTM_NEWADDR,
+    IP_ADDR_REMOVE,
+    IP_ROUTE_ADD = RTM_NEWROUTE,
+    IP_ROUTE_REMOVE
+} dap_network_monitor_notification_type_t;
+
+typedef struct {
+    dap_network_monitor_notification_type_t type;
+    union {
+        struct {
+            char interface_name[IF_NAMESIZE];
+            char s_ip[MAX_IP_STR_LEN + 1];
+            uint32_t ip; // inet_ntoa(*((struct in_addr *)&ipaddr)) for cast to char*
+        } addr; // for IP_ADDR_ADD, IP_ADDR_REMOVE
+        struct {
+            uint64_t destination_address; // 64 bit for checking -1 like not filled variable
+            char s_destination_address[MAX_IP_STR_LEN + 1];
+            uint64_t gateway_address;
+            char s_gateway_address[MAX_IP_STR_LEN + 1];
+            uint8_t protocol;
+            uint8_t netmask;
+        } route; // for IP_ROUTE_ADD, IP_ROUTE_REMOVE
+    };
+} dap_network_notification_t;
+
+typedef void (*dap_network_monitor_notification_callback_t)
+              (const dap_network_notification_t notification);
+
+/**
+ * @brief dap_network_monitor_init
+ * @param callback
+ * @details starts network monitorting
+ * @return 0 if successful
+ */
+int dap_network_monitor_init(dap_network_monitor_notification_callback_t callback);
+
+/**
+ * @brief dap_network_monitor_deinit
+ */
+void dap_network_monitor_deinit(void);
diff --git a/core/unix/dap_process_manager.h b/core/unix/dap_process_manager.h
index 2fb47d8..b35bfeb 100755
--- a/core/unix/dap_process_manager.h
+++ b/core/unix/dap_process_manager.h
@@ -1,3 +1,27 @@
+/*
+ * Authors:
+ * Anatolii Kurotych <akurotych@gmail.com>
+ * DeM Labs Inc.   https://demlabs.net
+ * DeM Labs Open source community https://github.com/demlabsinc
+ * Copyright  (c) 2017-2019
+ * All rights reserved.
+
+ This file is part of DAP (Deus Applications Prototypes) the open source project
+
+    DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    DAP is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with any DAP based project.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
 #ifdef __linux__
 
 #include <stdbool.h>
diff --git a/test/core/CMakeLists.txt b/test/core/CMakeLists.txt
index 1704153..314e96e 100755
--- a/test/core/CMakeLists.txt
+++ b/test/core/CMakeLists.txt
@@ -10,7 +10,7 @@ endif()
 
 add_executable(${PROJECT_NAME} ${SRCS} ${PLATROFM_DEP_SRC})
 
-target_link_libraries(core_test dap_test dap_core)
+target_link_libraries(core_test dap_test dap_core pthread)
 
 add_test(
     NAME core-test
diff --git a/test/core/main.c b/test/core/main.c
index 6571276..53ec99e 100755
--- a/test/core/main.c
+++ b/test/core/main.c
@@ -1,7 +1,9 @@
 #include "dap_config_test.h"
 #include "dap_common_test.h"
+#include "dap_network_monitor_test.h"
 #include "dap_common.h"
 
+
 int main(void) {
     // switch off debug info from library
     set_log_level(L_CRITICAL);
@@ -10,7 +12,9 @@ int main(void) {
 #ifdef __linux__
 #include "dap_process_mem_test.h"
 #include "dap_cpu_monitor_test.h"
+#include "dap_network_monitor.h"
     dap_process_mem_test_run();
     dap_cpu_monitor_test_run();
+    dap_network_monitor_test_run();
 #endif
 }
diff --git a/test/core/unix/dap_network_monitor_test.c b/test/core/unix/dap_network_monitor_test.c
new file mode 100644
index 0000000..07d7d92
--- /dev/null
+++ b/test/core/unix/dap_network_monitor_test.c
@@ -0,0 +1,188 @@
+#include <arpa/inet.h>
+#include "linux/rtnetlink.h"
+
+#include "dap_network_monitor.h"
+#include "dap_network_monitor_test.h"
+
+enum events {
+    NEW_INTERFACE_EV,
+    NEW_GATEWAY_EV,
+    REMOVE_INTERFACE_EV,
+    REMOVE_GATEWAY_EV,
+    REMOVE_ROUTE_EV
+};
+
+#define COUNT_TEST_EVENT_CASES 5
+
+
+static dap_network_notification_t _test_event_cases[COUNT_TEST_EVENT_CASES];
+
+
+static bool list_events_done[COUNT_TEST_EVENT_CASES] = {0};
+
+void _network_callback(const dap_network_notification_t result)
+{
+    if(result.type == IP_ADDR_ADD || result.type == IP_ADDR_REMOVE)
+    {
+        dap_test_msg("Interface %s %s has IP address %s",
+               result.addr.interface_name, (result.type == IP_ADDR_ADD ? "now" : "no longer"),
+               result.addr.s_ip);
+        enum events event;
+        if(result.type == IP_ADDR_ADD) {
+            event = NEW_INTERFACE_EV;
+        } else {
+            event = REMOVE_INTERFACE_EV;
+        }
+
+        dap_test_msg("Checking %s" , (event == NEW_INTERFACE_EV ?
+                                          "add new interface callback" : "remove interface callback"));
+
+        dap_assert(result.addr.ip == _test_event_cases[event].addr.ip,
+                   "Check dest ip");
+
+        dap_assert(dap_str_equals(result.addr.s_ip, _test_event_cases[event].addr.s_ip),
+                   "Check dest str ip");
+
+        dap_assert(dap_str_equals(result.addr.interface_name,
+                                  _test_event_cases[event].addr.interface_name),
+                   "Check interface name");
+
+        list_events_done[event] = true;
+
+    } else if(result.type == IP_ROUTE_ADD || result.type == IP_ROUTE_REMOVE) {
+
+        if (result.type == IP_ROUTE_REMOVE) {
+
+            if(result.route.destination_address == _test_event_cases[REMOVE_GATEWAY_EV].route.gateway_address) {
+                dap_pass_msg("Gateway addr removed");
+                dap_assert(dap_str_equals(result.route.s_destination_address,
+                                          _test_event_cases[REMOVE_GATEWAY_EV].route.s_gateway_address),
+                           "Check gateway str ip");
+
+                dap_assert(result.route.protocol == _test_event_cases[REMOVE_GATEWAY_EV].route.protocol,
+                           "Check protocol");
+
+                list_events_done[REMOVE_GATEWAY_EV] = true;
+            } else if(result.route.destination_address ==
+                      _test_event_cases[REMOVE_ROUTE_EV].route.destination_address) {
+                dap_pass_msg("Destination address removed");
+
+                dap_assert(dap_str_equals(result.route.s_destination_address,
+                                          _test_event_cases[REMOVE_ROUTE_EV].route.s_destination_address),
+                           "Check dest str ip");
+
+                dap_assert(result.route.protocol == _test_event_cases[REMOVE_ROUTE_EV].route.protocol,
+                           "Check protocol");
+
+                list_events_done[REMOVE_ROUTE_EV] = true;
+            }
+
+//            dap_test_msg("Deleting route to destination --> %s/%d proto %d and gateway %s\n",
+//                         result.route.s_destination_address,
+//                         result.route.netmask,
+//                         result.route.protocol,
+//                         result.route.s_gateway_address);
+
+        } else  if (result.type == IP_ROUTE_ADD) {
+            if(result.route.gateway_address != -1) { // gateway address is present
+                dap_test_msg("Checking new gateway addr");
+                dap_assert(result.route.gateway_address ==
+                           _test_event_cases[NEW_GATEWAY_EV].route.gateway_address,
+                           "Check gateway ip");
+
+                dap_assert(dap_str_equals(result.route.s_gateway_address,
+                                          _test_event_cases[NEW_GATEWAY_EV].route.s_gateway_address),
+                           "Check gateway str ip");
+
+                dap_assert(result.route.protocol == _test_event_cases[NEW_GATEWAY_EV].route.protocol,
+                           "Check protocol");
+
+                list_events_done[NEW_GATEWAY_EV] = true;
+            }
+//            dap_test_msg("Adding route to destination --> %s/%d proto %d and gateway %s\n",
+//                         result.route.s_destination_address,
+//                         result.route.netmask,
+//                         result.route.protocol,
+//                         result.route.s_gateway_address);
+        }
+    }
+}
+
+
+static void init_test_case()
+{
+    bzero(_test_event_cases, sizeof (_test_event_cases));
+
+    dap_network_notification_t * res;
+
+    // new_interface
+    res = &_test_event_cases[NEW_INTERFACE_EV];
+    res->type = IP_ADDR_ADD;
+    strcpy(res->addr.s_ip, "10.1.0.111");
+    strcpy(res->addr.interface_name, "tun10");
+    res->addr.ip = 167837807;
+
+    // new_gateway
+    res = &_test_event_cases[NEW_GATEWAY_EV];
+    res->type = IP_ROUTE_ADD;
+    strcpy(res->route.s_gateway_address, "10.1.0.1");
+    res->route.gateway_address = 167837697;
+    res->route.protocol = RTPROT_STATIC;
+
+    res = &_test_event_cases[REMOVE_GATEWAY_EV];
+    res->type = IP_ROUTE_REMOVE;
+    strcpy(res->route.s_gateway_address, "10.1.0.1");
+    res->route.gateway_address = 167837697;
+    res->route.protocol = RTPROT_STATIC;
+
+
+    // remove interface
+    res = &_test_event_cases[REMOVE_INTERFACE_EV];
+    res->type = IP_ADDR_REMOVE;
+    strcpy(res->addr.s_ip, "10.1.0.111");
+    strcpy(res->addr.interface_name, "tun10");
+    res->addr.ip = 167837807;
+
+    // remote route
+    res = &_test_event_cases[REMOVE_ROUTE_EV];
+    res->type = IP_ROUTE_REMOVE;
+    strcpy(res->route.s_destination_address, "10.1.0.111");
+    res->route.destination_address = 167837807;
+    res->route.protocol = RTPROT_KERNEL;
+}
+
+static void cleanup_test_case()
+{
+
+}
+
+void dap_network_monitor_test_run(void)
+{
+    dap_print_module_name("dap_network_monitor");
+
+    init_test_case();
+
+    dap_network_monitor_init(_network_callback);
+
+    const char *add_test_interfece = "nmcli connection add type tun con-name "
+                                     "DiveVPNTest autoconnect false ifname tun10 "
+                                     "mode tun ip4 10.1.0.111 gw4 10.1.0.1";
+    const char *up_test_interfece = "nmcli connection up DiveVPNTest";
+    const char *down_test_interfece = "nmcli connection down DiveVPNTest";
+    const char *delete_test_interfece = "nmcli connection delete DiveVPNTest 2> /dev/null";
+
+    system(delete_test_interfece);
+    system(add_test_interfece);
+    system(up_test_interfece);
+    system(down_test_interfece);
+    system(delete_test_interfece);
+
+    for(int i = 0; i < COUNT_TEST_EVENT_CASES; i++) {
+        if(list_events_done[i] == false) {
+            dap_fail("Not all events were processed");
+        }
+    }
+
+    dap_network_monitor_deinit();
+    cleanup_test_case();
+}
diff --git a/test/core/unix/dap_network_monitor_test.h b/test/core/unix/dap_network_monitor_test.h
new file mode 100644
index 0000000..1dae522
--- /dev/null
+++ b/test/core/unix/dap_network_monitor_test.h
@@ -0,0 +1,4 @@
+#pragma once
+#include "dap_test.h"
+#include "dap_common.h"
+void dap_network_monitor_test_run(void);
diff --git a/test/libdap-test b/test/libdap-test
index 7b40a4c..6d69cc4 160000
--- a/test/libdap-test
+++ b/test/libdap-test
@@ -1 +1 @@
-Subproject commit 7b40a4cdfae222f84ccedb11ecae471d01f5e33b
+Subproject commit 6d69cc4be2ccbc5fc978adadab4fbcc52bc31d36
-- 
GitLab