diff options
| author | Odd Stranne <odd@mullvad.net> | 2019-05-10 13:19:12 +0200 |
|---|---|---|
| committer | Emīls Piņķis <emils@mullvad.net> | 2019-05-21 11:07:34 +0100 |
| commit | da0e3356fde77a2183b47bc8b20d05b402a075bf (patch) | |
| tree | 609b496bf3b0f676a08b7c8ac60838245718e6f2 | |
| parent | 4073b27046c34e86c1117bbf70f5305e76c52a22 (diff) | |
| download | mullvadvpn-da0e3356fde77a2183b47bc8b20d05b402a075bf.tar.xz mullvadvpn-da0e3356fde77a2183b47bc8b20d05b402a075bf.zip | |
Add WinNet_ActivateConnectivityMonitor()
| -rw-r--r-- | windows/winnet/src/winnet/netmonitor.cpp | 201 | ||||
| -rw-r--r-- | windows/winnet/src/winnet/netmonitor.h | 55 | ||||
| -rw-r--r-- | windows/winnet/src/winnet/winnet.cpp | 74 | ||||
| -rw-r--r-- | windows/winnet/src/winnet/winnet.h | 26 | ||||
| -rw-r--r-- | windows/winnet/src/winnet/winnet.vcxproj | 2 | ||||
| -rw-r--r-- | windows/winnet/src/winnet/winnet.vcxproj.filters | 2 |
6 files changed, 360 insertions, 0 deletions
diff --git a/windows/winnet/src/winnet/netmonitor.cpp b/windows/winnet/src/winnet/netmonitor.cpp new file mode 100644 index 0000000000..6ccaf3d64a --- /dev/null +++ b/windows/winnet/src/winnet/netmonitor.cpp @@ -0,0 +1,201 @@ +#include "stdafx.h" +#include "netmonitor.h" +#include <libcommon/error.h> +#include <libcommon/memory.h> +#include <libcommon/synchronization.h> + +namespace +{ + +bool ValidInterfaceType(const MIB_IF_ROW2 &iface) +{ + switch (iface.InterfaceLuid.Info.IfType) + { + case IF_TYPE_SOFTWARE_LOOPBACK: + case IF_TYPE_TUNNEL: + { + return false; + } + } + + if (FALSE == iface.InterfaceAndOperStatusFlags.ConnectorPresent + || FALSE != iface.InterfaceAndOperStatusFlags.EndPointInterface) + { + return false; + } + + return true; +} + +} // anonyomus namespace + +NetMonitor::NetMonitor(NetMonitor::Notifier notifier, bool ¤tConnectivity) + : m_notifier(notifier) + , m_connected(false) + , m_notificationHandle(nullptr) +{ + createCache(); + updateConnectivity(); + + currentConnectivity = m_connected; + + const auto status = NotifyIpInterfaceChange(AF_UNSPEC, callback, this, FALSE, &m_notificationHandle); + + THROW_UNLESS(NO_ERROR, status, "Register interface change notification"); +} + +NetMonitor::~NetMonitor() +{ + CancelMibChangeNotify2(m_notificationHandle); +} + +void NetMonitor::createCache() +{ + MIB_IF_TABLE2 *table; + + const auto status = GetIfTable2(&table); + + THROW_UNLESS(NO_ERROR, status, "Acquire network interface table"); + + common::memory::ScopeDestructor sd; + + sd += [table]() + { + FreeMibTable(table); + }; + + for (ULONG i = 0; i < table->NumEntries; ++i) + { + addCacheEntry(table->Table[i]); + } +} + +void NetMonitor::addCacheEntry(const MIB_IF_ROW2 &iface) +{ + CacheEntry e; + + if (false == ValidInterfaceType(iface)) + { + e.luid = iface.InterfaceLuid.Value; + e.valid = false; + e.connected = false; + } + else + { + e.luid = iface.InterfaceLuid.Value; + e.valid = true; + e.connected = (MediaConnectStateConnected == iface.MediaConnectState); + } + + m_cache.insert(std::make_pair(e.luid, e)); +} + +void NetMonitor::updateConnectivity() +{ + for (const auto cacheEntryIter : m_cache) + { + const auto entry = cacheEntryIter.second; + + if (entry.valid && entry.connected) + { + m_connected = true; + return; + } + } + + m_connected = false; +} + +//static +void __stdcall NetMonitor::callback(void *context, MIB_IPINTERFACE_ROW *hint, MIB_NOTIFICATION_TYPE updateType) +{ + auto thiz = reinterpret_cast<NetMonitor *>(context); + + common::sync::ScopeLock<> processingLock(thiz->m_processingMutex); + + switch (updateType) + { + case MibAddInstance: + { + MIB_IF_ROW2 iface = { 0 }; + iface.InterfaceLuid = hint->InterfaceLuid; + + if (NO_ERROR != GetIfEntry2(&iface)) + { + // Failed to query interface. + return; + } + + thiz->addCacheEntry(iface); + + break; + } + case MibDeleteInstance: + { + const auto cacheEntry = thiz->m_cache.find(hint->InterfaceLuid.Value); + + if (thiz->m_cache.end() != cacheEntry) + { + cacheEntry->second.connected = false; + } + + break; + } + case MibParameterNotification: + { + auto cacheEntry = thiz->m_cache.find(hint->InterfaceLuid.Value); + + if (thiz->m_cache.end() == cacheEntry) + { + // + // A change occurred on an interface that we're not tracking. + // Perhaps the MibAddInstance logic failed for some reason. + // + + MIB_IF_ROW2 iface = { 0 }; + iface.InterfaceLuid = hint->InterfaceLuid; + + if (NO_ERROR != GetIfEntry2(&iface)) + { + // Failed to query interface. + return; + } + + thiz->addCacheEntry(iface); + } + else + { + // + // Abort processing if this is a known interface that we don't care about. + // + if (false == cacheEntry->second.valid) + { + return; + } + + // + // Update cache. + // + + MIB_IF_ROW2 iface = { 0 }; + iface.InterfaceLuid = hint->InterfaceLuid; + + const auto status = GetIfEntry2(&iface); + + cacheEntry->second.connected = + (NO_ERROR == status ? MediaConnectStateConnected == iface.MediaConnectState : false); + } + + break; + } + } + + const auto previousConnectivity = thiz->m_connected; + + thiz->updateConnectivity(); + + if (previousConnectivity != thiz->m_connected) + { + thiz->m_notifier(thiz->m_connected); + } +} diff --git a/windows/winnet/src/winnet/netmonitor.h b/windows/winnet/src/winnet/netmonitor.h new file mode 100644 index 0000000000..b8e418b82d --- /dev/null +++ b/windows/winnet/src/winnet/netmonitor.h @@ -0,0 +1,55 @@ +#pragma once + +#include <map> +#include <string> +#include <cstdint> +#include <mutex> +#include <functional> +#include <winsock2.h> +#include <ws2ipdef.h> +#include <iphlpapi.h> +#include <windows.h> + +class NetMonitor +{ +public: + + // + // Connectivity changed. + // true = connected, false = disconnected. + // + using Notifier = std::function<void(bool)>; + + NetMonitor(Notifier notifier, bool ¤tConnectivity); + ~NetMonitor(); + +private: + + std::mutex m_processingMutex; + + Notifier m_notifier; + + struct CacheEntry + { + // Unique interface identifier. + uint64_t luid; + + // Whether this is a physical adapter or not. + bool valid; + + // Last known state. + bool connected; + }; + + std::map<uint64_t, CacheEntry> m_cache; + + bool m_connected; + + HANDLE m_notificationHandle; + + void createCache(); + void addCacheEntry(const MIB_IF_ROW2 &iface); + void updateConnectivity(); + + static void __stdcall callback(void *context, MIB_IPINTERFACE_ROW *hint, MIB_NOTIFICATION_TYPE updateType); +}; diff --git a/windows/winnet/src/winnet/winnet.cpp b/windows/winnet/src/winnet/winnet.cpp index ba14c737da..9741613457 100644 --- a/windows/winnet/src/winnet/winnet.cpp +++ b/windows/winnet/src/winnet/winnet.cpp @@ -3,9 +3,17 @@ #include "NetworkInterfaces.h"
#include "interfaceutils.h"
#include "libcommon/error.h"
+#include "netmonitor.h"
#include <cstdint>
#include <stdexcept>
+namespace
+{
+
+NetMonitor *g_NetMonitor = nullptr;
+
+} //anonymous namespace
+
extern "C"
WINNET_LINKAGE
WINNET_ETM_STATUS
@@ -134,3 +142,69 @@ WinNet_ReleaseString( {
}
}
+
+extern "C" +WINNET_LINKAGE +WINNET_ACM_STATUS +WINNET_API +WinNet_ActivateConnectivityMonitor( + WinNetConnectivityMonitorCallback callback, + uint8_t *currentConnectivity, + WinNetErrorSink errorSink, + void* errorSinkContext +) +{ + try
+ {
+ if (nullptr != g_NetMonitor)
+ {
+ throw std::runtime_error("Cannot activate connectivity monitor twice");
+ }
+
+ auto forwarder = [callback](bool connected)
+ {
+ callback(static_cast<uint8_t>(connected));
+ };
+
+ bool connected = false;
+
+ g_NetMonitor = new NetMonitor(forwarder, connected);
+
+ if (nullptr != currentConnectivity)
+ {
+ *currentConnectivity = static_cast<uint8_t>(connected);
+ }
+
+ return WINNET_ACM_STATUS::SUCCESS;
+ }
+ catch (std::exception &err)
+ {
+ if (nullptr != errorSink)
+ {
+ errorSink(err.what(), errorSinkContext);
+ }
+
+ return WINNET_ACM_STATUS::FAILURE;
+ }
+ catch (...)
+ {
+ return WINNET_ACM_STATUS::FAILURE;
+ }
+} + +extern "C" +WINNET_LINKAGE +void +WINNET_API +WinNet_DeactivateConnectivityMonitor( +) +{ + try + { + delete g_NetMonitor; + g_NetMonitor = nullptr; + } + catch (...) + { + } +} diff --git a/windows/winnet/src/winnet/winnet.h b/windows/winnet/src/winnet/winnet.h index b5771f5b5a..c3464d4c03 100644 --- a/windows/winnet/src/winnet/winnet.h +++ b/windows/winnet/src/winnet/winnet.h @@ -71,3 +71,29 @@ WINNET_API WinNet_ReleaseString( wchar_t *str ); + +typedef void (WINNET_API *WinNetConnectivityMonitorCallback)(uint8_t connected); + +enum class WINNET_ACM_STATUS : uint32_t +{ + SUCCESS = 0, + FAILURE = 1, +}; + +extern "C" +WINNET_LINKAGE +WINNET_ACM_STATUS +WINNET_API +WinNet_ActivateConnectivityMonitor( + WinNetConnectivityMonitorCallback callback, + uint8_t *currentConnectivity, + WinNetErrorSink errorSink, + void* errorSinkContext +); + +extern "C" +WINNET_LINKAGE +void +WINNET_API +WinNet_DeactivateConnectivityMonitor( +); diff --git a/windows/winnet/src/winnet/winnet.vcxproj b/windows/winnet/src/winnet/winnet.vcxproj index e43b928d6c..fd10ec40fd 100644 --- a/windows/winnet/src/winnet/winnet.vcxproj +++ b/windows/winnet/src/winnet/winnet.vcxproj @@ -22,6 +22,7 @@ <ClCompile Include="dllmain.cpp" /> <ClCompile Include="InterfacePair.cpp" /> <ClCompile Include="interfaceutils.cpp" /> + <ClCompile Include="netmonitor.cpp" /> <ClCompile Include="NetworkInterfaces.cpp" /> <ClCompile Include="stdafx.cpp" /> <ClCompile Include="winnet.cpp" /> @@ -29,6 +30,7 @@ <ItemGroup> <ClInclude Include="InterfacePair.h" /> <ClInclude Include="interfaceutils.h" /> + <ClInclude Include="netmonitor.h" /> <ClInclude Include="NetworkInterfaces.h" /> <ClInclude Include="stdafx.h" /> <ClInclude Include="targetver.h" /> diff --git a/windows/winnet/src/winnet/winnet.vcxproj.filters b/windows/winnet/src/winnet/winnet.vcxproj.filters index 2d320e7908..3ac5a14717 100644 --- a/windows/winnet/src/winnet/winnet.vcxproj.filters +++ b/windows/winnet/src/winnet/winnet.vcxproj.filters @@ -7,6 +7,7 @@ <ClCompile Include="NetworkInterfaces.cpp" /> <ClCompile Include="InterfacePair.cpp" /> <ClCompile Include="interfaceutils.cpp" /> + <ClCompile Include="netmonitor.cpp" /> </ItemGroup> <ItemGroup> <ClInclude Include="stdafx.h" /> @@ -15,6 +16,7 @@ <ClInclude Include="NetworkInterfaces.h" /> <ClInclude Include="InterfacePair.h" /> <ClInclude Include="interfaceutils.h" /> + <ClInclude Include="netmonitor.h" /> </ItemGroup> <ItemGroup> <None Include="winnet.def" /> |
