michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* vim:set et sw=4 ts=4: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include "plstr.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsNotifyAddrListener.h" michael@0: #include "nsString.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "mozilla/Services.h" michael@0: #include "nsCRT.h" michael@0: michael@0: #include michael@0: #include michael@0: michael@0: static HMODULE sNetshell; michael@0: static decltype(NcFreeNetconProperties)* sNcFreeNetconProperties; michael@0: michael@0: static void InitNetshellLibrary(void) michael@0: { michael@0: if (!sNetshell) { michael@0: sNetshell = LoadLibraryW(L"Netshell.dll"); michael@0: if (sNetshell) { michael@0: sNcFreeNetconProperties = (decltype(NcFreeNetconProperties)*) michael@0: GetProcAddress(sNetshell, "NcFreeNetconProperties"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: static void FreeDynamicLibraries(void) michael@0: { michael@0: if (sNetshell) { michael@0: sNcFreeNetconProperties = nullptr; michael@0: FreeLibrary(sNetshell); michael@0: sNetshell = nullptr; michael@0: } michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsNotifyAddrListener, michael@0: nsINetworkLinkService, michael@0: nsIRunnable, michael@0: nsIObserver) michael@0: michael@0: nsNotifyAddrListener::nsNotifyAddrListener() michael@0: : mLinkUp(true) // assume true by default michael@0: , mStatusKnown(false) michael@0: , mCheckAttempted(false) michael@0: , mShutdownEvent(nullptr) michael@0: { michael@0: } michael@0: michael@0: nsNotifyAddrListener::~nsNotifyAddrListener() michael@0: { michael@0: NS_ASSERTION(!mThread, "nsNotifyAddrListener thread shutdown failed"); michael@0: FreeDynamicLibraries(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsNotifyAddrListener::GetIsLinkUp(bool *aIsUp) michael@0: { michael@0: if (!mCheckAttempted && !mStatusKnown) { michael@0: mCheckAttempted = true; michael@0: CheckLinkStatus(); michael@0: } michael@0: michael@0: *aIsUp = mLinkUp; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsNotifyAddrListener::GetLinkStatusKnown(bool *aIsUp) michael@0: { michael@0: *aIsUp = mStatusKnown; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsNotifyAddrListener::GetLinkType(uint32_t *aLinkType) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aLinkType); michael@0: michael@0: // XXX This function has not yet been implemented for this platform michael@0: *aLinkType = nsINetworkLinkService::LINK_TYPE_UNKNOWN; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsNotifyAddrListener::Run() michael@0: { michael@0: PR_SetCurrentThreadName("Link Monitor"); michael@0: michael@0: HANDLE ev = CreateEvent(nullptr, FALSE, FALSE, nullptr); michael@0: NS_ENSURE_TRUE(ev, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: HANDLE handles[2] = { ev, mShutdownEvent }; michael@0: OVERLAPPED overlapped = { 0 }; michael@0: bool shuttingDown = false; michael@0: michael@0: overlapped.hEvent = ev; michael@0: while (!shuttingDown) { michael@0: HANDLE h; michael@0: DWORD ret = NotifyAddrChange(&h, &overlapped); michael@0: michael@0: if (ret == ERROR_IO_PENDING) { michael@0: ret = WaitForMultipleObjects(2, handles, FALSE, INFINITE); michael@0: if (ret == WAIT_OBJECT_0) { michael@0: CheckLinkStatus(); michael@0: } else { michael@0: shuttingDown = true; michael@0: } michael@0: } else { michael@0: shuttingDown = true; michael@0: } michael@0: } michael@0: CloseHandle(ev); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsNotifyAddrListener::Observe(nsISupports *subject, michael@0: const char *topic, michael@0: const char16_t *data) michael@0: { michael@0: if (!strcmp("xpcom-shutdown-threads", topic)) michael@0: Shutdown(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsNotifyAddrListener::Init(void) michael@0: { michael@0: nsCOMPtr observerService = michael@0: mozilla::services::GetObserverService(); michael@0: if (!observerService) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsresult rv = observerService->AddObserver(this, "xpcom-shutdown-threads", michael@0: false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mShutdownEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); michael@0: NS_ENSURE_TRUE(mShutdownEvent, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: rv = NS_NewThread(getter_AddRefs(mThread), this); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsNotifyAddrListener::Shutdown(void) michael@0: { michael@0: // remove xpcom shutdown observer michael@0: nsCOMPtr observerService = michael@0: mozilla::services::GetObserverService(); michael@0: if (observerService) michael@0: observerService->RemoveObserver(this, "xpcom-shutdown-threads"); michael@0: michael@0: if (!mShutdownEvent) michael@0: return NS_OK; michael@0: michael@0: SetEvent(mShutdownEvent); michael@0: michael@0: nsresult rv = mThread->Shutdown(); michael@0: michael@0: // Have to break the cycle here, otherwise nsNotifyAddrListener holds michael@0: // onto the thread and the thread holds onto the nsNotifyAddrListener michael@0: // via its mRunnable michael@0: mThread = nullptr; michael@0: michael@0: CloseHandle(mShutdownEvent); michael@0: mShutdownEvent = nullptr; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: /* Sends the given event to the UI thread. Assumes aEventID never goes out michael@0: * of scope (static strings are ideal). michael@0: */ michael@0: nsresult michael@0: nsNotifyAddrListener::SendEventToUI(const char *aEventID) michael@0: { michael@0: if (!aEventID) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr event = new ChangeEvent(this, aEventID); michael@0: if (NS_FAILED(rv = NS_DispatchToMainThread(event))) michael@0: NS_WARNING("Failed to dispatch ChangeEvent"); michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsNotifyAddrListener::ChangeEvent::Run() michael@0: { michael@0: nsCOMPtr observerService = michael@0: mozilla::services::GetObserverService(); michael@0: if (observerService) michael@0: observerService->NotifyObservers( michael@0: mService, NS_NETWORK_LINK_TOPIC, michael@0: NS_ConvertASCIItoUTF16(mEventID).get()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsNotifyAddrListener::CheckIsGateway(PIP_ADAPTER_ADDRESSES aAdapter) michael@0: { michael@0: if (!aAdapter->FirstUnicastAddress) michael@0: return false; michael@0: michael@0: LPSOCKADDR aAddress = aAdapter->FirstUnicastAddress->Address.lpSockaddr; michael@0: if (!aAddress) michael@0: return false; michael@0: michael@0: PSOCKADDR_IN in_addr = (PSOCKADDR_IN)aAddress; michael@0: bool isGateway = (aAddress->sa_family == AF_INET && michael@0: in_addr->sin_addr.S_un.S_un_b.s_b1 == 192 && michael@0: in_addr->sin_addr.S_un.S_un_b.s_b2 == 168 && michael@0: in_addr->sin_addr.S_un.S_un_b.s_b3 == 0 && michael@0: in_addr->sin_addr.S_un.S_un_b.s_b4 == 1); michael@0: michael@0: if (isGateway) michael@0: isGateway = CheckICSStatus(aAdapter->FriendlyName); michael@0: michael@0: return isGateway; michael@0: } michael@0: michael@0: bool michael@0: nsNotifyAddrListener::CheckICSStatus(PWCHAR aAdapterName) michael@0: { michael@0: InitNetshellLibrary(); michael@0: michael@0: // This method enumerates all privately shared connections and checks if some michael@0: // of them has the same name as the one provided in aAdapterName. If such michael@0: // connection is found in the collection the adapter is used as ICS gateway michael@0: bool isICSGatewayAdapter = false; michael@0: michael@0: HRESULT hr; michael@0: nsRefPtr netSharingManager; michael@0: hr = CoCreateInstance( michael@0: CLSID_NetSharingManager, michael@0: nullptr, michael@0: CLSCTX_INPROC_SERVER, michael@0: IID_INetSharingManager, michael@0: getter_AddRefs(netSharingManager)); michael@0: michael@0: nsRefPtr privateCollection; michael@0: if (SUCCEEDED(hr)) { michael@0: hr = netSharingManager->get_EnumPrivateConnections( michael@0: ICSSC_DEFAULT, michael@0: getter_AddRefs(privateCollection)); michael@0: } michael@0: michael@0: nsRefPtr privateEnum; michael@0: if (SUCCEEDED(hr)) { michael@0: nsRefPtr privateEnumUnknown; michael@0: hr = privateCollection->get__NewEnum(getter_AddRefs(privateEnumUnknown)); michael@0: if (SUCCEEDED(hr)) { michael@0: hr = privateEnumUnknown->QueryInterface( michael@0: IID_IEnumNetSharingPrivateConnection, michael@0: getter_AddRefs(privateEnum)); michael@0: } michael@0: } michael@0: michael@0: if (SUCCEEDED(hr)) { michael@0: ULONG fetched; michael@0: VARIANT connectionVariant; michael@0: while (!isICSGatewayAdapter && michael@0: SUCCEEDED(hr = privateEnum->Next(1, &connectionVariant, michael@0: &fetched)) && michael@0: fetched) { michael@0: if (connectionVariant.vt != VT_UNKNOWN) { michael@0: // We should call VariantClear here but it needs to link michael@0: // with oleaut32.lib that produces a Ts incrase about 10ms michael@0: // that is undesired. As it is quit unlikely the result would michael@0: // be of a different type anyway, let's pass the variant michael@0: // unfreed here. michael@0: NS_ERROR("Variant of unexpected type, expecting VT_UNKNOWN, we probably leak it!"); michael@0: continue; michael@0: } michael@0: michael@0: nsRefPtr connection; michael@0: if (SUCCEEDED(connectionVariant.punkVal->QueryInterface( michael@0: IID_INetConnection, michael@0: getter_AddRefs(connection)))) { michael@0: connectionVariant.punkVal->Release(); michael@0: michael@0: NETCON_PROPERTIES *properties; michael@0: if (SUCCEEDED(connection->GetProperties(&properties))) { michael@0: if (!wcscmp(properties->pszwName, aAdapterName)) michael@0: isICSGatewayAdapter = true; michael@0: michael@0: if (sNcFreeNetconProperties) michael@0: sNcFreeNetconProperties(properties); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: return isICSGatewayAdapter; michael@0: } michael@0: michael@0: DWORD michael@0: nsNotifyAddrListener::CheckAdaptersAddresses(void) michael@0: { michael@0: ULONG len = 16384; michael@0: michael@0: PIP_ADAPTER_ADDRESSES addresses = (PIP_ADAPTER_ADDRESSES) malloc(len); michael@0: if (!addresses) michael@0: return ERROR_OUTOFMEMORY; michael@0: michael@0: DWORD ret = GetAdaptersAddresses(AF_UNSPEC, 0, nullptr, addresses, &len); michael@0: if (ret == ERROR_BUFFER_OVERFLOW) { michael@0: free(addresses); michael@0: addresses = (PIP_ADAPTER_ADDRESSES) malloc(len); michael@0: if (!addresses) michael@0: return ERROR_BUFFER_OVERFLOW; michael@0: ret = GetAdaptersAddresses(AF_UNSPEC, 0, nullptr, addresses, &len); michael@0: } michael@0: michael@0: if (FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED))) { michael@0: free(addresses); michael@0: return ERROR_NOT_SUPPORTED; michael@0: } michael@0: michael@0: if (ret == ERROR_SUCCESS) { michael@0: PIP_ADAPTER_ADDRESSES ptr; michael@0: bool linkUp = false; michael@0: michael@0: for (ptr = addresses; !linkUp && ptr; ptr = ptr->Next) { michael@0: if (ptr->OperStatus == IfOperStatusUp && michael@0: ptr->IfType != IF_TYPE_SOFTWARE_LOOPBACK && michael@0: !CheckIsGateway(ptr)) michael@0: linkUp = true; michael@0: } michael@0: mLinkUp = linkUp; michael@0: mStatusKnown = true; michael@0: } michael@0: free(addresses); michael@0: michael@0: CoUninitialize(); michael@0: michael@0: return ret; michael@0: } michael@0: michael@0: /** michael@0: * Checks the status of all network adapters. If one is up and has a valid IP michael@0: * address, sets mLinkUp to true. Sets mStatusKnown to true if the link status michael@0: * is definitive. michael@0: */ michael@0: void michael@0: nsNotifyAddrListener::CheckLinkStatus(void) michael@0: { michael@0: DWORD ret; michael@0: const char *event; michael@0: michael@0: // This call is very expensive (~650 milliseconds), so we don't want to michael@0: // call it synchronously. Instead, we just start up assuming we have a michael@0: // network link, but we'll report that the status is unknown. michael@0: if (NS_IsMainThread()) { michael@0: NS_WARNING("CheckLinkStatus called on main thread! No check " michael@0: "performed. Assuming link is up, status is unknown."); michael@0: mLinkUp = true; michael@0: } else { michael@0: ret = CheckAdaptersAddresses(); michael@0: if (ret != ERROR_SUCCESS) { michael@0: mLinkUp = true; michael@0: } michael@0: } michael@0: michael@0: if (mStatusKnown) michael@0: event = mLinkUp ? NS_NETWORK_LINK_DATA_UP : NS_NETWORK_LINK_DATA_DOWN; michael@0: else michael@0: event = NS_NETWORK_LINK_DATA_UNKNOWN; michael@0: SendEventToUI(event); michael@0: }