1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/netwerk/system/win32/nsNotifyAddrListener.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,391 @@ 1.4 +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 1.5 +/* vim:set et sw=4 ts=4: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include <stdarg.h> 1.11 +#include <windef.h> 1.12 +#include <winbase.h> 1.13 +#include <wingdi.h> 1.14 +#include <winuser.h> 1.15 +#include <ole2.h> 1.16 +#include <netcon.h> 1.17 +#include <objbase.h> 1.18 +#include <iprtrmib.h> 1.19 +#include "plstr.h" 1.20 +#include "nsThreadUtils.h" 1.21 +#include "nsIObserverService.h" 1.22 +#include "nsServiceManagerUtils.h" 1.23 +#include "nsNotifyAddrListener.h" 1.24 +#include "nsString.h" 1.25 +#include "nsAutoPtr.h" 1.26 +#include "mozilla/Services.h" 1.27 +#include "nsCRT.h" 1.28 + 1.29 +#include <iptypes.h> 1.30 +#include <iphlpapi.h> 1.31 + 1.32 +static HMODULE sNetshell; 1.33 +static decltype(NcFreeNetconProperties)* sNcFreeNetconProperties; 1.34 + 1.35 +static void InitNetshellLibrary(void) 1.36 +{ 1.37 + if (!sNetshell) { 1.38 + sNetshell = LoadLibraryW(L"Netshell.dll"); 1.39 + if (sNetshell) { 1.40 + sNcFreeNetconProperties = (decltype(NcFreeNetconProperties)*) 1.41 + GetProcAddress(sNetshell, "NcFreeNetconProperties"); 1.42 + } 1.43 + } 1.44 +} 1.45 + 1.46 +static void FreeDynamicLibraries(void) 1.47 +{ 1.48 + if (sNetshell) { 1.49 + sNcFreeNetconProperties = nullptr; 1.50 + FreeLibrary(sNetshell); 1.51 + sNetshell = nullptr; 1.52 + } 1.53 +} 1.54 + 1.55 +NS_IMPL_ISUPPORTS(nsNotifyAddrListener, 1.56 + nsINetworkLinkService, 1.57 + nsIRunnable, 1.58 + nsIObserver) 1.59 + 1.60 +nsNotifyAddrListener::nsNotifyAddrListener() 1.61 + : mLinkUp(true) // assume true by default 1.62 + , mStatusKnown(false) 1.63 + , mCheckAttempted(false) 1.64 + , mShutdownEvent(nullptr) 1.65 +{ 1.66 +} 1.67 + 1.68 +nsNotifyAddrListener::~nsNotifyAddrListener() 1.69 +{ 1.70 + NS_ASSERTION(!mThread, "nsNotifyAddrListener thread shutdown failed"); 1.71 + FreeDynamicLibraries(); 1.72 +} 1.73 + 1.74 +NS_IMETHODIMP 1.75 +nsNotifyAddrListener::GetIsLinkUp(bool *aIsUp) 1.76 +{ 1.77 + if (!mCheckAttempted && !mStatusKnown) { 1.78 + mCheckAttempted = true; 1.79 + CheckLinkStatus(); 1.80 + } 1.81 + 1.82 + *aIsUp = mLinkUp; 1.83 + return NS_OK; 1.84 +} 1.85 + 1.86 +NS_IMETHODIMP 1.87 +nsNotifyAddrListener::GetLinkStatusKnown(bool *aIsUp) 1.88 +{ 1.89 + *aIsUp = mStatusKnown; 1.90 + return NS_OK; 1.91 +} 1.92 + 1.93 +NS_IMETHODIMP 1.94 +nsNotifyAddrListener::GetLinkType(uint32_t *aLinkType) 1.95 +{ 1.96 + NS_ENSURE_ARG_POINTER(aLinkType); 1.97 + 1.98 + // XXX This function has not yet been implemented for this platform 1.99 + *aLinkType = nsINetworkLinkService::LINK_TYPE_UNKNOWN; 1.100 + return NS_OK; 1.101 +} 1.102 + 1.103 +NS_IMETHODIMP 1.104 +nsNotifyAddrListener::Run() 1.105 +{ 1.106 + PR_SetCurrentThreadName("Link Monitor"); 1.107 + 1.108 + HANDLE ev = CreateEvent(nullptr, FALSE, FALSE, nullptr); 1.109 + NS_ENSURE_TRUE(ev, NS_ERROR_OUT_OF_MEMORY); 1.110 + 1.111 + HANDLE handles[2] = { ev, mShutdownEvent }; 1.112 + OVERLAPPED overlapped = { 0 }; 1.113 + bool shuttingDown = false; 1.114 + 1.115 + overlapped.hEvent = ev; 1.116 + while (!shuttingDown) { 1.117 + HANDLE h; 1.118 + DWORD ret = NotifyAddrChange(&h, &overlapped); 1.119 + 1.120 + if (ret == ERROR_IO_PENDING) { 1.121 + ret = WaitForMultipleObjects(2, handles, FALSE, INFINITE); 1.122 + if (ret == WAIT_OBJECT_0) { 1.123 + CheckLinkStatus(); 1.124 + } else { 1.125 + shuttingDown = true; 1.126 + } 1.127 + } else { 1.128 + shuttingDown = true; 1.129 + } 1.130 + } 1.131 + CloseHandle(ev); 1.132 + 1.133 + return NS_OK; 1.134 +} 1.135 + 1.136 +NS_IMETHODIMP 1.137 +nsNotifyAddrListener::Observe(nsISupports *subject, 1.138 + const char *topic, 1.139 + const char16_t *data) 1.140 +{ 1.141 + if (!strcmp("xpcom-shutdown-threads", topic)) 1.142 + Shutdown(); 1.143 + 1.144 + return NS_OK; 1.145 +} 1.146 + 1.147 +nsresult 1.148 +nsNotifyAddrListener::Init(void) 1.149 +{ 1.150 + nsCOMPtr<nsIObserverService> observerService = 1.151 + mozilla::services::GetObserverService(); 1.152 + if (!observerService) 1.153 + return NS_ERROR_FAILURE; 1.154 + 1.155 + nsresult rv = observerService->AddObserver(this, "xpcom-shutdown-threads", 1.156 + false); 1.157 + NS_ENSURE_SUCCESS(rv, rv); 1.158 + 1.159 + mShutdownEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); 1.160 + NS_ENSURE_TRUE(mShutdownEvent, NS_ERROR_OUT_OF_MEMORY); 1.161 + 1.162 + rv = NS_NewThread(getter_AddRefs(mThread), this); 1.163 + NS_ENSURE_SUCCESS(rv, rv); 1.164 + 1.165 + return NS_OK; 1.166 +} 1.167 + 1.168 +nsresult 1.169 +nsNotifyAddrListener::Shutdown(void) 1.170 +{ 1.171 + // remove xpcom shutdown observer 1.172 + nsCOMPtr<nsIObserverService> observerService = 1.173 + mozilla::services::GetObserverService(); 1.174 + if (observerService) 1.175 + observerService->RemoveObserver(this, "xpcom-shutdown-threads"); 1.176 + 1.177 + if (!mShutdownEvent) 1.178 + return NS_OK; 1.179 + 1.180 + SetEvent(mShutdownEvent); 1.181 + 1.182 + nsresult rv = mThread->Shutdown(); 1.183 + 1.184 + // Have to break the cycle here, otherwise nsNotifyAddrListener holds 1.185 + // onto the thread and the thread holds onto the nsNotifyAddrListener 1.186 + // via its mRunnable 1.187 + mThread = nullptr; 1.188 + 1.189 + CloseHandle(mShutdownEvent); 1.190 + mShutdownEvent = nullptr; 1.191 + 1.192 + return rv; 1.193 +} 1.194 + 1.195 +/* Sends the given event to the UI thread. Assumes aEventID never goes out 1.196 + * of scope (static strings are ideal). 1.197 + */ 1.198 +nsresult 1.199 +nsNotifyAddrListener::SendEventToUI(const char *aEventID) 1.200 +{ 1.201 + if (!aEventID) 1.202 + return NS_ERROR_NULL_POINTER; 1.203 + 1.204 + nsresult rv; 1.205 + nsCOMPtr<nsIRunnable> event = new ChangeEvent(this, aEventID); 1.206 + if (NS_FAILED(rv = NS_DispatchToMainThread(event))) 1.207 + NS_WARNING("Failed to dispatch ChangeEvent"); 1.208 + return rv; 1.209 +} 1.210 + 1.211 +NS_IMETHODIMP 1.212 +nsNotifyAddrListener::ChangeEvent::Run() 1.213 +{ 1.214 + nsCOMPtr<nsIObserverService> observerService = 1.215 + mozilla::services::GetObserverService(); 1.216 + if (observerService) 1.217 + observerService->NotifyObservers( 1.218 + mService, NS_NETWORK_LINK_TOPIC, 1.219 + NS_ConvertASCIItoUTF16(mEventID).get()); 1.220 + return NS_OK; 1.221 +} 1.222 + 1.223 +bool 1.224 +nsNotifyAddrListener::CheckIsGateway(PIP_ADAPTER_ADDRESSES aAdapter) 1.225 +{ 1.226 + if (!aAdapter->FirstUnicastAddress) 1.227 + return false; 1.228 + 1.229 + LPSOCKADDR aAddress = aAdapter->FirstUnicastAddress->Address.lpSockaddr; 1.230 + if (!aAddress) 1.231 + return false; 1.232 + 1.233 + PSOCKADDR_IN in_addr = (PSOCKADDR_IN)aAddress; 1.234 + bool isGateway = (aAddress->sa_family == AF_INET && 1.235 + in_addr->sin_addr.S_un.S_un_b.s_b1 == 192 && 1.236 + in_addr->sin_addr.S_un.S_un_b.s_b2 == 168 && 1.237 + in_addr->sin_addr.S_un.S_un_b.s_b3 == 0 && 1.238 + in_addr->sin_addr.S_un.S_un_b.s_b4 == 1); 1.239 + 1.240 + if (isGateway) 1.241 + isGateway = CheckICSStatus(aAdapter->FriendlyName); 1.242 + 1.243 + return isGateway; 1.244 +} 1.245 + 1.246 +bool 1.247 +nsNotifyAddrListener::CheckICSStatus(PWCHAR aAdapterName) 1.248 +{ 1.249 + InitNetshellLibrary(); 1.250 + 1.251 + // This method enumerates all privately shared connections and checks if some 1.252 + // of them has the same name as the one provided in aAdapterName. If such 1.253 + // connection is found in the collection the adapter is used as ICS gateway 1.254 + bool isICSGatewayAdapter = false; 1.255 + 1.256 + HRESULT hr; 1.257 + nsRefPtr<INetSharingManager> netSharingManager; 1.258 + hr = CoCreateInstance( 1.259 + CLSID_NetSharingManager, 1.260 + nullptr, 1.261 + CLSCTX_INPROC_SERVER, 1.262 + IID_INetSharingManager, 1.263 + getter_AddRefs(netSharingManager)); 1.264 + 1.265 + nsRefPtr<INetSharingPrivateConnectionCollection> privateCollection; 1.266 + if (SUCCEEDED(hr)) { 1.267 + hr = netSharingManager->get_EnumPrivateConnections( 1.268 + ICSSC_DEFAULT, 1.269 + getter_AddRefs(privateCollection)); 1.270 + } 1.271 + 1.272 + nsRefPtr<IEnumNetSharingPrivateConnection> privateEnum; 1.273 + if (SUCCEEDED(hr)) { 1.274 + nsRefPtr<IUnknown> privateEnumUnknown; 1.275 + hr = privateCollection->get__NewEnum(getter_AddRefs(privateEnumUnknown)); 1.276 + if (SUCCEEDED(hr)) { 1.277 + hr = privateEnumUnknown->QueryInterface( 1.278 + IID_IEnumNetSharingPrivateConnection, 1.279 + getter_AddRefs(privateEnum)); 1.280 + } 1.281 + } 1.282 + 1.283 + if (SUCCEEDED(hr)) { 1.284 + ULONG fetched; 1.285 + VARIANT connectionVariant; 1.286 + while (!isICSGatewayAdapter && 1.287 + SUCCEEDED(hr = privateEnum->Next(1, &connectionVariant, 1.288 + &fetched)) && 1.289 + fetched) { 1.290 + if (connectionVariant.vt != VT_UNKNOWN) { 1.291 + // We should call VariantClear here but it needs to link 1.292 + // with oleaut32.lib that produces a Ts incrase about 10ms 1.293 + // that is undesired. As it is quit unlikely the result would 1.294 + // be of a different type anyway, let's pass the variant 1.295 + // unfreed here. 1.296 + NS_ERROR("Variant of unexpected type, expecting VT_UNKNOWN, we probably leak it!"); 1.297 + continue; 1.298 + } 1.299 + 1.300 + nsRefPtr<INetConnection> connection; 1.301 + if (SUCCEEDED(connectionVariant.punkVal->QueryInterface( 1.302 + IID_INetConnection, 1.303 + getter_AddRefs(connection)))) { 1.304 + connectionVariant.punkVal->Release(); 1.305 + 1.306 + NETCON_PROPERTIES *properties; 1.307 + if (SUCCEEDED(connection->GetProperties(&properties))) { 1.308 + if (!wcscmp(properties->pszwName, aAdapterName)) 1.309 + isICSGatewayAdapter = true; 1.310 + 1.311 + if (sNcFreeNetconProperties) 1.312 + sNcFreeNetconProperties(properties); 1.313 + } 1.314 + } 1.315 + } 1.316 + } 1.317 + 1.318 + return isICSGatewayAdapter; 1.319 +} 1.320 + 1.321 +DWORD 1.322 +nsNotifyAddrListener::CheckAdaptersAddresses(void) 1.323 +{ 1.324 + ULONG len = 16384; 1.325 + 1.326 + PIP_ADAPTER_ADDRESSES addresses = (PIP_ADAPTER_ADDRESSES) malloc(len); 1.327 + if (!addresses) 1.328 + return ERROR_OUTOFMEMORY; 1.329 + 1.330 + DWORD ret = GetAdaptersAddresses(AF_UNSPEC, 0, nullptr, addresses, &len); 1.331 + if (ret == ERROR_BUFFER_OVERFLOW) { 1.332 + free(addresses); 1.333 + addresses = (PIP_ADAPTER_ADDRESSES) malloc(len); 1.334 + if (!addresses) 1.335 + return ERROR_BUFFER_OVERFLOW; 1.336 + ret = GetAdaptersAddresses(AF_UNSPEC, 0, nullptr, addresses, &len); 1.337 + } 1.338 + 1.339 + if (FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED))) { 1.340 + free(addresses); 1.341 + return ERROR_NOT_SUPPORTED; 1.342 + } 1.343 + 1.344 + if (ret == ERROR_SUCCESS) { 1.345 + PIP_ADAPTER_ADDRESSES ptr; 1.346 + bool linkUp = false; 1.347 + 1.348 + for (ptr = addresses; !linkUp && ptr; ptr = ptr->Next) { 1.349 + if (ptr->OperStatus == IfOperStatusUp && 1.350 + ptr->IfType != IF_TYPE_SOFTWARE_LOOPBACK && 1.351 + !CheckIsGateway(ptr)) 1.352 + linkUp = true; 1.353 + } 1.354 + mLinkUp = linkUp; 1.355 + mStatusKnown = true; 1.356 + } 1.357 + free(addresses); 1.358 + 1.359 + CoUninitialize(); 1.360 + 1.361 + return ret; 1.362 +} 1.363 + 1.364 +/** 1.365 + * Checks the status of all network adapters. If one is up and has a valid IP 1.366 + * address, sets mLinkUp to true. Sets mStatusKnown to true if the link status 1.367 + * is definitive. 1.368 + */ 1.369 +void 1.370 +nsNotifyAddrListener::CheckLinkStatus(void) 1.371 +{ 1.372 + DWORD ret; 1.373 + const char *event; 1.374 + 1.375 + // This call is very expensive (~650 milliseconds), so we don't want to 1.376 + // call it synchronously. Instead, we just start up assuming we have a 1.377 + // network link, but we'll report that the status is unknown. 1.378 + if (NS_IsMainThread()) { 1.379 + NS_WARNING("CheckLinkStatus called on main thread! No check " 1.380 + "performed. Assuming link is up, status is unknown."); 1.381 + mLinkUp = true; 1.382 + } else { 1.383 + ret = CheckAdaptersAddresses(); 1.384 + if (ret != ERROR_SUCCESS) { 1.385 + mLinkUp = true; 1.386 + } 1.387 + } 1.388 + 1.389 + if (mStatusKnown) 1.390 + event = mLinkUp ? NS_NETWORK_LINK_DATA_UP : NS_NETWORK_LINK_DATA_DOWN; 1.391 + else 1.392 + event = NS_NETWORK_LINK_DATA_UNKNOWN; 1.393 + SendEventToUI(event); 1.394 +}