tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsWifiMonitor.cpp (13761B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 #include "nsCOMPtr.h"
      6 #include "nsProxyRelease.h"
      7 #include "nsComponentManagerUtils.h"
      8 #include "nsServiceManagerUtils.h"
      9 #include "nsThreadUtils.h"
     10 #include "nsXPCOM.h"
     11 #include "nsXPCOMCID.h"
     12 #include "nsIObserver.h"
     13 #include "nsIObserverService.h"
     14 #include "nsWifiMonitor.h"
     15 #include "nsWifiAccessPoint.h"
     16 #include "nsINetworkLinkService.h"
     17 #include "nsQueryObject.h"
     18 #include "nsNetCID.h"
     19 
     20 #include "nsComponentManagerUtils.h"
     21 #include "mozilla/Components.h"
     22 #include "mozilla/DelayedRunnable.h"
     23 #include "mozilla/IntegerPrintfMacros.h"
     24 #include "mozilla/StaticPrefs_network.h"
     25 #include "mozilla/Services.h"
     26 
     27 #if defined(XP_WIN)
     28 #  include "WinWifiScanner.h"
     29 #endif
     30 
     31 #if defined(XP_MACOSX)
     32 #  include "MacWifiScanner.h"
     33 #endif
     34 
     35 #if defined(__DragonFly__) || defined(__FreeBSD__)
     36 #  include "FreeBsdWifiScanner.h"
     37 #endif
     38 
     39 #if defined(XP_SOLARIS)
     40 #  include "SolarisWifiScanner.h"
     41 #endif
     42 
     43 #if defined(NECKO_WIFI_DBUS)
     44 #  include "DbusWifiScanner.h"
     45 #endif
     46 
     47 using namespace mozilla;
     48 
     49 LazyLogModule gWifiMonitorLog("WifiMonitor");
     50 #define LOG(args) MOZ_LOG(gWifiMonitorLog, mozilla::LogLevel::Debug, args)
     51 
     52 NS_IMPL_ISUPPORTS(nsWifiMonitor, nsIObserver, nsIWifiMonitor)
     53 
     54 // Main thread only.
     55 static uint64_t sNextPollingIndex = 1;
     56 
     57 static uint64_t NextPollingIndex() {
     58  MOZ_ASSERT(NS_IsMainThread());
     59  ++sNextPollingIndex;
     60 
     61  // Any non-zero value is valid and we don't care about overflow beyond
     62  // that we never want the index to be zero.
     63  if (sNextPollingIndex == 0) {
     64    ++sNextPollingIndex;
     65  }
     66  return sNextPollingIndex;
     67 }
     68 
     69 // Should we poll wifi or just check it when our network changes?
     70 // We poll when we are on a network where the wifi environment
     71 // could reasonably be expected to change much -- so, on mobile.
     72 static bool ShouldPollForNetworkType(const char16_t* aLinkType) {
     73  auto linkTypeU8 = NS_ConvertUTF16toUTF8(aLinkType);
     74  return linkTypeU8 == NS_NETWORK_LINK_TYPE_WIMAX ||
     75         linkTypeU8 == NS_NETWORK_LINK_TYPE_MOBILE ||
     76         linkTypeU8 == NS_NETWORK_LINK_TYPE_UNKNOWN;
     77 }
     78 
     79 // Enum value version.
     80 static bool ShouldPollForNetworkType(uint32_t aLinkType) {
     81  return aLinkType == nsINetworkLinkService::LINK_TYPE_WIMAX ||
     82         aLinkType == nsINetworkLinkService::LINK_TYPE_MOBILE ||
     83         aLinkType == nsINetworkLinkService::LINK_TYPE_UNKNOWN;
     84 }
     85 
     86 nsWifiMonitor::nsWifiMonitor(UniquePtr<mozilla::WifiScanner>&& aScanner)
     87    : mWifiScanner(std::move(aScanner)) {
     88  LOG(("Creating nsWifiMonitor"));
     89  MOZ_ASSERT(NS_IsMainThread());
     90 
     91  nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
     92  if (obsSvc) {
     93    obsSvc->AddObserver(this, NS_NETWORK_LINK_TOPIC, false);
     94    obsSvc->AddObserver(this, NS_NETWORK_LINK_TYPE_TOPIC, false);
     95    obsSvc->AddObserver(this, "xpcom-shutdown", false);
     96  }
     97 
     98  nsresult rv;
     99  nsCOMPtr<nsINetworkLinkService> nls;
    100  nls = do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID, &rv);
    101  if (NS_SUCCEEDED(rv) && nls) {
    102    uint32_t linkType = nsINetworkLinkService::LINK_TYPE_UNKNOWN;
    103    rv = nls->GetLinkType(&linkType);
    104    if (NS_SUCCEEDED(rv)) {
    105      mShouldPollForCurrentNetwork = ShouldPollForNetworkType(linkType);
    106      if (ShouldPoll()) {
    107        mPollingId = NextPollingIndex();
    108        DispatchScanToBackgroundThread(mPollingId);
    109      }
    110      LOG(("nsWifiMonitor network type: %u | shouldPoll: %s", linkType,
    111           mShouldPollForCurrentNetwork ? "true" : "false"));
    112    }
    113  }
    114 }
    115 nsWifiMonitor::~nsWifiMonitor() { LOG(("Destroying nsWifiMonitor")); }
    116 
    117 void nsWifiMonitor::Close() {
    118  nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
    119  if (obsSvc) {
    120    obsSvc->RemoveObserver(this, NS_NETWORK_LINK_TOPIC);
    121    obsSvc->RemoveObserver(this, NS_NETWORK_LINK_TYPE_TOPIC);
    122    obsSvc->RemoveObserver(this, "xpcom-shutdown");
    123  }
    124 
    125  mPollingId = 0;
    126  if (mThread) {
    127    mThread->Shutdown();
    128  }
    129 }
    130 
    131 NS_IMETHODIMP
    132 nsWifiMonitor::Observe(nsISupports* subject, const char* topic,
    133                       const char16_t* data) {
    134  MOZ_ASSERT(NS_IsMainThread());
    135 
    136  if (!strcmp(topic, "xpcom-shutdown")) {
    137    // Make sure any wifi-polling stops.
    138    LOG(("nsWifiMonitor received shutdown"));
    139    Close();
    140  } else if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) {
    141    // Network connectivity has either been gained, lost, or changed (e.g.
    142    // by changing Wifi network).  Issue an immediate one-time scan.
    143    // If we were polling, keep polling.
    144    LOG(("nsWifiMonitor %p | mPollingId %" PRIu64
    145         " | received: " NS_NETWORK_LINK_TOPIC " with status %s",
    146         this, static_cast<uint64_t>(mPollingId),
    147         NS_ConvertUTF16toUTF8(data).get()));
    148    DispatchScanToBackgroundThread(0);
    149  } else if (!strcmp(topic, NS_NETWORK_LINK_TYPE_TOPIC)) {
    150    // Network type has changed (e.g. from wifi to mobile).  When on some
    151    // network types, we poll wifi.  This event does not indicate that a
    152    // new scan would be beneficial right now, so we only issue one if
    153    // we need to begin polling.
    154    // Use IDs to make sure only one task is polling at a time.
    155    LOG(("nsWifiMonitor %p | mPollingId %" PRIu64
    156         " | received: " NS_NETWORK_LINK_TYPE_TOPIC " with status %s",
    157         this, static_cast<uint64_t>(mPollingId),
    158         NS_ConvertUTF16toUTF8(data).get()));
    159 
    160    bool wasPolling = ShouldPoll();
    161    MOZ_ASSERT(wasPolling || mPollingId == 0);
    162 
    163    mShouldPollForCurrentNetwork = ShouldPollForNetworkType(data);
    164    if (!wasPolling && ShouldPoll()) {
    165      // We weren't polling, so start now.
    166      mPollingId = NextPollingIndex();
    167      DispatchScanToBackgroundThread(mPollingId);
    168    } else if (!ShouldPoll()) {
    169      // Stop polling if we were.
    170      mPollingId = 0;
    171    }
    172  }
    173 
    174  return NS_OK;
    175 }
    176 
    177 void nsWifiMonitor::EnsureWifiScanner() {
    178  if (mWifiScanner) {
    179    return;
    180  }
    181 
    182  LOG(("Constructing WifiScanner"));
    183  mWifiScanner = MakeUnique<mozilla::WifiScannerImpl>();
    184 }
    185 
    186 NS_IMETHODIMP nsWifiMonitor::StartWatching(nsIWifiListener* aListener,
    187                                           bool aForcePolling) {
    188  LOG(("nsWifiMonitor::StartWatching %p | listener %p | mPollingId %" PRIu64
    189       " | aForcePolling %s",
    190       this, aListener, static_cast<uint64_t>(mPollingId),
    191       aForcePolling ? "true" : "false"));
    192  MOZ_ASSERT(NS_IsMainThread());
    193 
    194  if (!aListener) {
    195    return NS_ERROR_NULL_POINTER;
    196  }
    197 
    198  if (!mListeners.InsertOrUpdate(aListener, WifiListenerData(aForcePolling),
    199                                 mozilla::fallible)) {
    200    return NS_ERROR_OUT_OF_MEMORY;
    201  }
    202 
    203  // Run a new scan to update the new listener.  If we were polling then
    204  // stop that polling and start a new polling interval now.
    205  MOZ_ASSERT(mPollingId == 0 || ShouldPoll());
    206  if (aForcePolling) {
    207    ++mNumPollingListeners;
    208  }
    209  if (ShouldPoll()) {
    210    mPollingId = NextPollingIndex();
    211  }
    212  return DispatchScanToBackgroundThread(mPollingId);
    213 }
    214 
    215 NS_IMETHODIMP nsWifiMonitor::StopWatching(nsIWifiListener* aListener) {
    216  LOG(("nsWifiMonitor::StopWatching %p | listener %p | mPollingId %" PRIu64,
    217       this, aListener, static_cast<uint64_t>(mPollingId)));
    218  MOZ_ASSERT(NS_IsMainThread());
    219 
    220  if (!aListener) {
    221    return NS_ERROR_NULL_POINTER;
    222  }
    223 
    224  auto maybeData = mListeners.MaybeGet(aListener);
    225  if (!maybeData) {
    226    return NS_ERROR_INVALID_ARG;
    227  }
    228 
    229  if (maybeData->mShouldPoll) {
    230    --mNumPollingListeners;
    231  }
    232 
    233  mListeners.Remove(aListener);
    234 
    235  if (!ShouldPoll()) {
    236    // Stop polling (if we were).
    237    LOG(("nsWifiMonitor::StopWatching clearing polling ID"));
    238    mPollingId = 0;
    239  }
    240 
    241  return NS_OK;
    242 }
    243 
    244 nsresult nsWifiMonitor::DispatchScanToBackgroundThread(uint64_t aPollingId,
    245                                                       uint32_t aWaitMs) {
    246  RefPtr<Runnable> runnable = NewRunnableMethod<uint64_t>(
    247      "WifiScannerThread", this, &nsWifiMonitor::Scan, aPollingId);
    248 
    249  if (!mThread) {
    250    MOZ_ASSERT(NS_IsMainThread());
    251 
    252 #ifndef XP_MACOSX
    253    nsIThreadManager::ThreadCreationOptions options = {};
    254 #else
    255    // If this ASSERT fails, we've increased our default stack size and
    256    // may no longer need to special-case the stack size on macOS.
    257    static_assert(kMacOSWifiMonitorStackSize >
    258                  nsIThreadManager::DEFAULT_STACK_SIZE);
    259 
    260    // Mac needs a stack size larger than the default for CoreWLAN.
    261    nsIThreadManager::ThreadCreationOptions options = {
    262        .stackSize = kMacOSWifiMonitorStackSize};
    263 #endif
    264 
    265    nsresult rv = NS_NewNamedThread("Wifi Monitor", getter_AddRefs(mThread),
    266                                    nullptr, options);
    267    NS_ENSURE_SUCCESS(rv, rv);
    268  }
    269 
    270  if (aWaitMs) {
    271    return mThread->DelayedDispatch(runnable.forget(), aWaitMs);
    272  }
    273 
    274  return mThread->Dispatch(runnable.forget());
    275 }
    276 
    277 bool nsWifiMonitor::IsBackgroundThread() {
    278  return NS_GetCurrentThread() == mThread;
    279 }
    280 
    281 void nsWifiMonitor::Scan(uint64_t aPollingId) {
    282  MOZ_ASSERT(IsBackgroundThread());
    283  LOG(("nsWifiMonitor::Scan aPollingId: %" PRIu64 " | mPollingId: %" PRIu64,
    284       aPollingId, static_cast<uint64_t>(mPollingId)));
    285 
    286  // If we are using a stale polling ID then stop.  If this request to
    287  // Scan is not for polling (aPollingId is 0) then always allow it.
    288  if (aPollingId && mPollingId != aPollingId) {
    289    LOG(("nsWifiMonitor::Scan stopping polling"));
    290    return;
    291  }
    292 
    293  LOG(("nsWifiMonitor::Scan starting DoScan with id: %" PRIu64, aPollingId));
    294  nsresult rv = DoScan();
    295  LOG(("nsWifiMonitor::Scan DoScan complete | rv = %d",
    296       static_cast<uint32_t>(rv)));
    297 
    298  if (NS_FAILED(rv)) {
    299    rv = NS_DispatchToMainThread(NewRunnableMethod<nsresult>(
    300        "PassErrorToWifiListeners", this,
    301        &nsWifiMonitor::PassErrorToWifiListeners, rv));
    302    MOZ_ASSERT(NS_SUCCEEDED(rv));
    303  }
    304 
    305  // If we are polling then we re-issue Scan after a delay.
    306  // We re-check the polling IDs since mPollingId may have changed.
    307  if (aPollingId && aPollingId == mPollingId) {
    308    uint32_t periodMs = StaticPrefs::network_wifi_scanning_period();
    309    if (periodMs) {
    310      LOG(("nsWifiMonitor::Scan requesting future scan with id: %" PRIu64
    311           " | periodMs: %u",
    312           aPollingId, periodMs));
    313      DispatchScanToBackgroundThread(aPollingId, periodMs);
    314    } else {
    315      // Polling for wifi-scans is disabled.
    316      mPollingId = 0;
    317    }
    318  }
    319 
    320  LOG(("nsWifiMonitor::Scan complete"));
    321 }
    322 
    323 nsresult nsWifiMonitor::DoScan() {
    324  MOZ_ASSERT(IsBackgroundThread());
    325 
    326  EnsureWifiScanner();
    327  MOZ_ASSERT(mWifiScanner);
    328 
    329  LOG(("Scanning Wifi for access points"));
    330  nsTArray<RefPtr<nsIWifiAccessPoint>> accessPoints;
    331  nsresult rv = mWifiScanner->GetAccessPointsFromWLAN(accessPoints);
    332  if (NS_FAILED(rv)) {
    333    return rv;
    334  }
    335 
    336  LOG(("Sorting wifi access points"));
    337  accessPoints.Sort([](const RefPtr<nsIWifiAccessPoint>& ia,
    338                       const RefPtr<nsIWifiAccessPoint>& ib) {
    339    const auto& a = static_cast<const nsWifiAccessPoint&>(*ia);
    340    const auto& b = static_cast<const nsWifiAccessPoint&>(*ib);
    341    return a.Compare(b);
    342  });
    343 
    344  // Sorted compare to see if access point list has changed.
    345  LOG(("Checking for new access points"));
    346  bool accessPointsChanged =
    347      accessPoints.Length() != mLastAccessPoints.Length();
    348  if (!accessPointsChanged) {
    349    auto itAp = accessPoints.begin();
    350    auto itLastAp = mLastAccessPoints.begin();
    351    while (itAp != accessPoints.end()) {
    352      const auto& a = static_cast<const nsWifiAccessPoint&>(**itAp);
    353      const auto& b = static_cast<const nsWifiAccessPoint&>(**itLastAp);
    354      if (a != b) {
    355        accessPointsChanged = true;
    356        break;
    357      }
    358      ++itAp;
    359      ++itLastAp;
    360    }
    361  }
    362 
    363  mLastAccessPoints = std::move(accessPoints);
    364 
    365  LOG(("Sending Wifi access points to the main thread"));
    366  auto* mainThread = GetMainThreadSerialEventTarget();
    367  if (!mainThread) {
    368    return NS_ERROR_UNEXPECTED;
    369  }
    370 
    371  return NS_DispatchToMainThread(
    372      NewRunnableMethod<nsTArray<RefPtr<nsIWifiAccessPoint>>, bool>(
    373          "CallWifiListeners", this, &nsWifiMonitor::CallWifiListeners,
    374          mLastAccessPoints.Clone(), accessPointsChanged));
    375 }
    376 
    377 template <typename CallbackFn>
    378 nsresult nsWifiMonitor::NotifyListeners(CallbackFn&& aCallback) {
    379  // Listeners may (un)register other listeners while we iterate,
    380  // so we iterate over a copy and re-check membership as we go.
    381  // Iteration order is not important.
    382  auto listenersCopy(mListeners.Clone());
    383  for (auto iter = listenersCopy.begin(); iter != listenersCopy.end(); ++iter) {
    384    auto maybeIter = mListeners.MaybeGet(iter->GetKey());
    385    if (maybeIter) {
    386      aCallback(iter->GetKey(), *maybeIter);
    387    }
    388  }
    389  return NS_OK;
    390 }
    391 
    392 nsresult nsWifiMonitor::CallWifiListeners(
    393    const nsTArray<RefPtr<nsIWifiAccessPoint>>& aAccessPoints,
    394    bool aAccessPointsChanged) {
    395  MOZ_ASSERT(NS_IsMainThread());
    396  LOG(("Sending wifi access points to the listeners"));
    397  return NotifyListeners(
    398      [&](nsIWifiListener* aListener, WifiListenerData& aListenerData) {
    399        if (!aListenerData.mHasSentData || aAccessPointsChanged) {
    400          aListenerData.mHasSentData = true;
    401          aListener->OnChange(aAccessPoints);
    402        }
    403      });
    404 }
    405 
    406 nsresult nsWifiMonitor::PassErrorToWifiListeners(nsresult rv) {
    407  MOZ_ASSERT(NS_IsMainThread());
    408  LOG(("About to send error to the wifi listeners"));
    409  return NotifyListeners([&](nsIWifiListener* aListener, WifiListenerData&) {
    410    aListener->OnError(rv);
    411  });
    412 }
    413 
    414 bool nsWifiMonitor::GetHasWifiAdapter() {
    415 #ifdef XP_WIN
    416  EnsureWifiScanner();
    417  MOZ_ASSERT(mWifiScanner);
    418  return static_cast<WifiScannerImpl*>(mWifiScanner.get())->HasWifiAdapter();
    419 #else
    420  MOZ_ASSERT_UNREACHABLE(
    421      "nsWifiMonitor::HasWifiAdapter is not available on this platform");
    422  return false;
    423 #endif
    424 }