From cc2353da72086feabaeeb0dc936e697c43d00110 Mon Sep 17 00:00:00 2001
From: cheloveck3-0 <cheloveck2.0@gmail.com>
Date: Tue, 26 Feb 2019 20:43:06 +0600
Subject: [PATCH] Native macos network monitor (#40)

---
 CMakeLists.txt                          |   4 +
 core/CMakeLists.txt                     |   5 +
 core/core.pri                           |   3 +
 core/darwin/CMakeLists.txt              |  17 ++
 core/darwin/darwin.pri                  |   5 +
 core/darwin/macos/dap_network_monitor.c | 329 ++++++++++++++++++++++++
 core/darwin/macos/dap_network_monitor.h |  53 ++++
 core/darwin/macos/macos.pri             |  10 +
 core/darwin/macos/pthread_barrier.h     |  65 +++++
 9 files changed, 491 insertions(+)
 create mode 100644 core/darwin/CMakeLists.txt
 create mode 100644 core/darwin/darwin.pri
 create mode 100644 core/darwin/macos/dap_network_monitor.c
 create mode 100644 core/darwin/macos/dap_network_monitor.h
 create mode 100644 core/darwin/macos/macos.pri
 create mode 100644 core/darwin/macos/pthread_barrier.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index fae4546..6c8d56f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -10,6 +10,10 @@ if(UNIX AND NOT APPLE)
     set(LINUX TRUE)
 endif()
 
+if(APPLE)
+    set(DARWIN TRUE)
+endif()
+
 if(BUILD_DAP_TESTS)
     enable_testing()
     add_subdirectory(test)
diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index c081f32..8e65cca 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -16,3 +16,8 @@ if(UNIX)
     add_subdirectory(unix)
     target_link_libraries(${PROJECT_NAME} dap_core_unix)
 endif()
+
+if(DARWIN)
+    add_subdirectory(darwin)
+    target_link_libraries(${PROJECT_NAME} dap_core_darwin)
+endif()
diff --git a/core/core.pri b/core/core.pri
index f2486be..69a0688 100644
--- a/core/core.pri
+++ b/core/core.pri
@@ -1,6 +1,9 @@
 unix {
     include(unix/unix.pri)
 }
+darwin {
+    include(darwin/darwin.pri)
+}
 
 HEADERS += $$PWD/dap_common.h \
     $$PWD/dap_config.h \
diff --git a/core/darwin/CMakeLists.txt b/core/darwin/CMakeLists.txt
new file mode 100644
index 0000000..8f981d5
--- /dev/null
+++ b/core/darwin/CMakeLists.txt
@@ -0,0 +1,17 @@
+cmake_minimum_required(VERSION 3.0)
+
+project (dap_core_darwin)
+
+file(GLOB CORE_DARWIN_SRCS *.c)
+file(GLOB CORE_DARWIN_HEADERS *.h)
+
+if(DARWIN)
+    file(GLOB CORE_MACOS_SRCS macos/*.c)
+    file(GLOB CORE_MACOS_HEADERS macos/*.h)
+endif()
+
+add_library(${PROJECT_NAME} STATIC ${CORE_DARWIN_SRCS} ${CORE_DARWIN_HEADERS}
+    ${CORE_MACOS_SRCS} ${CORE_MACOS_HEADERS})
+
+target_link_libraries(${PROJECT_NAME} dap_core pthread)
+
diff --git a/core/darwin/darwin.pri b/core/darwin/darwin.pri
new file mode 100644
index 0000000..d8afafc
--- /dev/null
+++ b/core/darwin/darwin.pri
@@ -0,0 +1,5 @@
+macos {
+    include(macos/macos.pri)
+}
+
+INCLUDEPATH += $$PWD
diff --git a/core/darwin/macos/dap_network_monitor.c b/core/darwin/macos/dap_network_monitor.c
new file mode 100644
index 0000000..f66c715
--- /dev/null
+++ b/core/darwin/macos/dap_network_monitor.c
@@ -0,0 +1,329 @@
+#include <SystemConfiguration/SystemConfiguration.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <CoreFoundation/CFDictionary.h>
+#include <CoreFoundation/CFArray.h>
+#include <CoreFoundation/CFString.h>
+#include <pthread.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "dap_network_monitor.h"
+#include "dap_common.h"
+#include "pthread_barrier.h"
+
+
+#define LOG_TAG "dap_network_monitor"
+
+static SCDynamicStoreRef s_store = NULL;
+static CFRunLoopSourceRef s_rls;
+#define __bridge
+
+static void* network_monitor_worker(void *arg);
+
+static struct {
+    CFRunLoopRef rlref;
+    pthread_t thread;
+    dap_network_monitor_notification_callback_t callback;
+} _net_notification;
+
+
+void watch_for_network_changes()
+{
+    SCDynamicStoreContext context = { 0, (void *)s_store, NULL, NULL, NULL };
+
+    s_store = SCDynamicStoreCreate(NULL, CFSTR("watch_for_network_changes"), _net_notification.callback, &context);
+    if (!s_store) {
+        log_it(L_ERROR, "Could not open session with config.error = %s\n", SCErrorString(SCError()));
+        return;
+    }
+
+/*
+* establish and register dynamic store keys to watch
+* - global IPv4 configuration changes (e.g. new default route)
+* - per-service IPv4 state changes (IP service added/removed/...)
+*/
+    CFStringRef           key1 =    SCDynamicStoreKeyCreateNetworkGlobalEntity      (NULL, kSCDynamicStoreDomainState, kSCEntNetIPv4);
+    CFStringRef           key2 =    SCDynamicStoreKeyCreateNetworkInterfaceEntity   (NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetIPv4);
+    CFStringRef           key3 =    SCDynamicStoreKeyCreateNetworkServiceEntity     (NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetIPv4);
+    CFStringRef           pattern1  = SCDynamicStoreKeyCreateNetworkInterfaceEntity (NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetIPv4);
+    CFStringRef           pattern2  = SCDynamicStoreKeyCreateNetworkInterfaceEntity (NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetInterface);
+    CFStringRef           pattern3  = SCDynamicStoreKeyCreateNetworkGlobalEntity    (NULL, kSCDynamicStoreDomainState, kSCEntNetIPv4);
+    CFStringRef           pattern4  = SCDynamicStoreKeyCreateNetworkGlobalEntity    (NULL, kSCDynamicStoreDomainState, kSCEntNetInterface);
+    CFStringRef           pattern5  = SCDynamicStoreKeyCreateNetworkServiceEntity   (NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetIPv4);
+    CFStringRef           pattern6  = SCDynamicStoreKeyCreateNetworkServiceEntity   (NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetInterface);
+    CFMutableArrayRef     keys = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+    CFMutableArrayRef     patterns = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+
+    if (!key1 || !key2 || !key3 || !keys || !pattern1 || !pattern2 || !pattern3 || !pattern4 || !pattern5 || !pattern6 || !patterns) goto error;
+
+    CFArrayAppendValue(keys, key1);
+    CFArrayAppendValue(keys, key2);
+    CFArrayAppendValue(keys, key3);
+    CFArrayAppendValue(patterns, pattern1);
+    CFArrayAppendValue(patterns, pattern2);
+    CFArrayAppendValue(patterns, pattern3);
+    CFArrayAppendValue(patterns, pattern4);
+    CFArrayAppendValue(patterns, pattern5);
+    CFArrayAppendValue(patterns, pattern6);
+
+    if (SCDynamicStoreSetNotificationKeys(s_store, keys, patterns)){
+        s_rls = SCDynamicStoreCreateRunLoopSource(NULL, s_store, 0);
+        if (s_rls) {
+            CFRunLoopAddSource(CFRunLoopGetCurrent(), s_rls, kCFRunLoopDefaultMode);
+        }else{
+            log_it(L_ERROR, "SCDynamicStoreCreateRunLoopSource failed: %s\n", SCErrorString(SCError()));
+            CFRelease(s_store);
+        }
+    }else {
+        log_it(L_ERROR, "SCDynamicStoreSetNotificationKeys failed: %s\n", SCErrorString(SCError()));
+        CFRelease(s_store);
+    }
+    goto exit;
+
+    error:
+    if (s_store)    CFRelease(s_store);
+
+    exit:
+    if (key1) CFRelease(key1);
+    if (key2) CFRelease(key2);
+    if (key3) CFRelease(key3);
+    if (pattern1) CFRelease(pattern1);
+    if (pattern2) CFRelease(pattern2);
+    if (pattern3) CFRelease(pattern3);
+    if (pattern4) CFRelease(pattern4);
+    if (pattern5) CFRelease(pattern5);
+    if (pattern6) CFRelease(pattern6);
+    if (keys) CFRelease(keys);
+    if (patterns) CFRelease(patterns);
+    return;
+}
+
+
+/**
+ * @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 cbMonitorNatification)
+{
+    memset((void*)&_net_notification, 0, sizeof(_net_notification));
+    _net_notification.callback = cbMonitorNatification;
+
+    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);
+
+    log_it(L_INFO, "dap_network_monitor was initialized");
+    return 0;
+}
+
+/**
+ * @brief dap_network_monitor_deinit
+ */
+void dap_network_monitor_deinit(void)
+{
+    CFRunLoopStop(_net_notification.rlref);
+    //log_it(L_INFO, "After stopping run loop cycle");
+    pthread_cancel(_net_notification.thread);
+    //log_it(L_INFO, "After cancelation monitor thread!");
+    pthread_join(_net_notification.thread, NULL);
+    //log_it(L_INFO, "After deinit that wonderfull monitor!");
+}
+
+
+
+static void* network_monitor_worker(void *arg)
+{
+    pthread_barrier_t *barrier = (pthread_barrier_t *)arg;
+    watch_for_network_changes();
+    pthread_barrier_wait(barrier);
+    _net_notification.rlref = CFRunLoopGetCurrent();
+    CFRunLoopRun();
+    log_it(L_WARNING, "We are in the loop activity and won't have to see this message!");
+    return NULL;
+}
+
+
+
+
+
+////////////////////////////////////////////////////////////////
+// Usefull functions for future processing changes to interfaces
+
+static OSStatus MoreSCErrorBoolean(Boolean success)
+{
+    OSStatus err;
+    int scErr;
+
+    err = noErr;
+    if ( ! success ) {
+        scErr = SCError();
+        if (scErr == kSCStatusOK) {
+            scErr = kSCStatusFailed;
+        }
+        // Return an SCF error directly as an OSStatus.
+        // That's a little cheesy.  In a real program
+        // you might want to do some mapping from SCF
+        // errors to a range within the OSStatus range.
+        err = scErr;
+    }
+    return err;
+}
+
+static OSStatus MoreSCError(const void *value)
+{
+    return MoreSCErrorBoolean(value != NULL);
+}
+
+static OSStatus CFQError(CFTypeRef cf)
+    // Maps Core Foundation error indications (such as they
+    // are) to the OSStatus domain.
+{
+    OSStatus err;
+
+    err = noErr;
+    if (cf == NULL) {
+        err = -4960;
+    }
+    return err;
+}
+
+static void CFQRelease(CFTypeRef cf)
+    // A version of CFRelease that's tolerant of NULL.
+{
+    if (cf != NULL) {
+        CFRelease(cf);
+    }
+}
+
+
+static void GetIPAddressListFromValue(const void *key,
+                                      const void *value,
+                                      void *context)
+    // This function is a callback CopyIPAddressListSCF when
+    // it calls CFDictionaryApplyFunction.  It extracts the
+    // IPv4 address list from the network service dictionary
+    // and appends it to the result dictionary (which is passed
+    // in via the context pointers).
+{
+    CFArrayRef intfAddrList;
+
+    assert( key != NULL );
+    assert( CFGetTypeID(key) == CFStringGetTypeID() );
+    assert( value != NULL );
+    assert( CFGetTypeID(value) == CFDictionaryGetTypeID() );
+    assert( context != NULL );
+    assert( CFGetTypeID(context) == CFArrayGetTypeID() );
+
+    //CFDictionaryRef _value = (CFDictionaryRef) value;
+    struct __CFDictionary * _value = (__bridge struct __CFDictionary *) value;
+    intfAddrList = (__bridge struct __CFArray *) CFDictionaryGetValue(_value,
+                            kSCPropNetIPv4Addresses);
+    if (intfAddrList != NULL) {
+        assert( CFGetTypeID(intfAddrList)
+                == CFArrayGetTypeID() );
+        struct __CFArray * _context = (__bridge struct __CFArray *) context;
+        CFArrayAppendArray(_context,
+                            intfAddrList,
+                            CFRangeMake(0, CFArrayGetCount(intfAddrList))
+                            );
+    }
+
+}
+
+static OSStatus CopyIPAddressListSCF(CFArrayRef *addrList)
+    // Returns a CFArray that contains every IPv4
+    // address on the system (as CFStrings) in no
+    // particular order.
+{
+    OSStatus            err;
+    SCDynamicStoreRef   ref;
+    CFStringRef         pattern;
+    CFArrayRef          patternList;
+    CFDictionaryRef     valueDict;
+    CFMutableArrayRef   result;
+
+    assert( addrList != NULL);
+    assert(*addrList == NULL);
+
+    ref         = NULL;
+    pattern     = NULL;
+    patternList = NULL;
+    valueDict   = NULL;
+    result      = NULL;
+
+    // Create a connection to the dynamic store, then create
+    // a search pattern that finds all IPv4 entities.
+    // The pattern is "State:/Network/Service/[^/]+/IPv4".
+    ref = SCDynamicStoreCreate( NULL,
+                                CFSTR("CopyIPAddressListSCF"),
+                                NULL,
+                                NULL);
+    err = MoreSCError(ref);
+    if (err == noErr) {
+        pattern = SCDynamicStoreKeyCreateNetworkInterfaceEntity(
+                                NULL,
+                                kSCDynamicStoreDomainState,
+                                kSCCompAnyRegex,
+                                kSCEntNetIPv4);
+        err = MoreSCError(pattern);
+    }
+
+    // Now make a pattern list out of the pattern and then
+    // call SCDynamicStoreCopyMultiple.  We use that call,
+    // rather than repeated calls to SCDynamicStoreCopyValue,
+    // because it gives us a snapshot of the state.
+    if (err == noErr) {
+        patternList = CFArrayCreate(NULL,
+                                    (const void **) &pattern,
+                                    1,
+                                    &kCFTypeArrayCallBacks);
+        err = CFQError(patternList);
+    }
+    if (err == noErr) {
+        valueDict = SCDynamicStoreCopyMultiple(ref,
+                                               NULL,
+                                               patternList);
+        err = MoreSCError(valueDict);
+    }
+
+    // For each IPv4 entity that we found, extract the list
+    // of IP addresses and append it to our results array.
+    if (err == noErr) {
+        result = CFArrayCreateMutable(NULL, 0,
+                                      &kCFTypeArrayCallBacks);
+        err = CFQError(result);
+    }
+
+    // Iterate over the values, extracting the IP address
+    // arrays and appending them to the result.
+    if (err == noErr) {
+        CFDictionaryApplyFunction(valueDict,
+                                  GetIPAddressListFromValue,
+                                  result);
+    }
+    // Clean up.
+
+    CFQRelease(ref);
+    CFQRelease(pattern);
+    CFQRelease(patternList);
+    if (err != noErr && result != NULL) {
+        CFQRelease(result);
+        result = NULL;
+        printf("10\n");
+    }
+    *addrList = result;
+
+    assert( (err == noErr) == (*addrList != NULL) );
+
+    return err;
+}
diff --git a/core/darwin/macos/dap_network_monitor.h b/core/darwin/macos/dap_network_monitor.h
new file mode 100644
index 0000000..6d570f4
--- /dev/null
+++ b/core/darwin/macos/dap_network_monitor.h
@@ -0,0 +1,53 @@
+/*
+ * Authors:
+ * Anton Isaikin <anton.isaikin@demlabs.net>
+ * 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 __cplusplus
+extern "C" {
+#endif
+
+#pragma once
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <SystemConfiguration/SystemConfiguration.h>
+#include <CoreFoundation/CFArray.h>
+
+typedef void (*dap_network_monitor_notification_callback_t)
+              (SCDynamicStoreRef store, CFArrayRef changedKeys, void *info);
+/**
+ * @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);
+
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/core/darwin/macos/macos.pri b/core/darwin/macos/macos.pri
new file mode 100644
index 0000000..1fb87f8
--- /dev/null
+++ b/core/darwin/macos/macos.pri
@@ -0,0 +1,10 @@
+HEADERS += $$PWD/dap_network_monitor.h \
+HEADERS += $$PWD/pthread_barrier.h
+
+SOURCES += $$PWD/dap_network_monitor.c
+
+INCLUDEPATH += $$PWD
+
+LIBS += -framework CoreFoundation
+LIBS += -framework SystemConfiguration
+#LIBS += -framework NetworkExtension
diff --git a/core/darwin/macos/pthread_barrier.h b/core/darwin/macos/pthread_barrier.h
new file mode 100644
index 0000000..e1f5a8d
--- /dev/null
+++ b/core/darwin/macos/pthread_barrier.h
@@ -0,0 +1,65 @@
+#ifndef PTHREAD_BARRIER_H
+#define PTHREAD_BARRIER_H
+
+#include <pthread.h>
+#include <errno.h>
+
+typedef int pthread_barrierattr_t;
+typedef struct
+{
+    pthread_mutex_t mutex;
+    pthread_cond_t cond;
+    int count;
+    int tripCount;
+} pthread_barrier_t;
+
+
+int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned int count)
+{
+    if(count == 0)
+    {
+        errno = EINVAL;
+        return -1;
+    }
+    if(pthread_mutex_init(&barrier->mutex, 0) < 0)
+    {
+        return -2;
+    }
+    if(pthread_cond_init(&barrier->cond, 0) < 0)
+    {
+        pthread_mutex_destroy(&barrier->mutex);
+        return -3;
+    }
+    barrier->tripCount = count;
+    barrier->count = 0;
+
+    return 0;
+}
+
+int pthread_barrier_destroy(pthread_barrier_t *barrier)
+{
+    pthread_cond_destroy(&barrier->cond);
+    pthread_mutex_destroy(&barrier->mutex);
+    return 0;
+}
+
+int pthread_barrier_wait(pthread_barrier_t *barrier)
+{
+    pthread_mutex_lock(&barrier->mutex);
+    ++(barrier->count);
+    if(barrier->count >= barrier->tripCount)
+    {
+        barrier->count = 0;
+        pthread_cond_broadcast(&barrier->cond);
+        pthread_mutex_unlock(&barrier->mutex);
+        return 1;
+    }
+    else
+    {
+        pthread_cond_wait(&barrier->cond, &(barrier->mutex));
+        pthread_mutex_unlock(&barrier->mutex);
+        return 0;
+    }
+}
+
+#endif // PTHREAD_BARRIER_H
-- 
GitLab