CaptivePortalService.cpp (13553B)
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 "mozilla/net/CaptivePortalService.h" 6 #include "mozilla/AppShutdown.h" 7 #include "mozilla/ClearOnShutdown.h" 8 #include "mozilla/Services.h" 9 #include "mozilla/Preferences.h" 10 #include "nsIObserverService.h" 11 #include "nsServiceManagerUtils.h" 12 #include "nsXULAppAPI.h" 13 #include "xpcpublic.h" 14 #include "xpcprivate.h" 15 16 static constexpr auto kInterfaceName = u"captive-portal-inteface"_ns; 17 18 static const char kOpenCaptivePortalLoginEvent[] = "captive-portal-login"; 19 static const char kAbortCaptivePortalLoginEvent[] = 20 "captive-portal-login-abort"; 21 static const char kCaptivePortalLoginSuccessEvent[] = 22 "captive-portal-login-success"; 23 24 namespace mozilla { 25 namespace net { 26 27 static LazyLogModule gCaptivePortalLog("CaptivePortalService"); 28 #undef LOG 29 #define LOG(args) MOZ_LOG(gCaptivePortalLog, mozilla::LogLevel::Debug, args) 30 31 NS_IMPL_ISUPPORTS(CaptivePortalService, nsICaptivePortalService, nsIObserver, 32 nsISupportsWeakReference, nsITimerCallback, 33 nsICaptivePortalCallback, nsINamed) 34 35 static StaticRefPtr<CaptivePortalService> gCPService; 36 37 // static 38 already_AddRefed<nsICaptivePortalService> CaptivePortalService::GetSingleton() { 39 if (gCPService) { 40 return do_AddRef(gCPService); 41 } 42 43 gCPService = new CaptivePortalService(); 44 ClearOnShutdown(&gCPService); 45 return do_AddRef(gCPService); 46 } 47 48 CaptivePortalService::CaptivePortalService() { 49 mLastChecked = TimeStamp::Now(); 50 } 51 52 CaptivePortalService::~CaptivePortalService() { 53 LOG(("CaptivePortalService::~CaptivePortalService isParentProcess:%d\n", 54 XRE_GetProcessType() == GeckoProcessType_Default)); 55 } 56 57 nsresult CaptivePortalService::PerformCheck() { 58 LOG( 59 ("CaptivePortalService::PerformCheck mRequestInProgress:%d " 60 "mInitialized:%d mStarted:%d\n", 61 mRequestInProgress, mInitialized, mStarted)); 62 // Don't issue another request if last one didn't complete 63 if (mRequestInProgress || !mInitialized || !mStarted) { 64 return NS_OK; 65 } 66 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { 67 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; 68 } 69 // Instantiating CaptiveDetect.sys.mjs before the JS engine is ready will 70 // lead to a crash (see bug 1800603) 71 // We can remove this restriction when we rewrite the detector in 72 // C++ or rust (bug 1809886). 73 if (!XPCJSRuntime::Get()) { 74 return NS_ERROR_NOT_INITIALIZED; 75 } 76 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); 77 nsresult rv; 78 if (!mCaptivePortalDetector) { 79 mCaptivePortalDetector = 80 do_CreateInstance("@mozilla.org/toolkit/captive-detector;1", &rv); 81 if (NS_FAILED(rv)) { 82 LOG(("Unable to get a captive portal detector\n")); 83 return rv; 84 } 85 } 86 87 LOG(("CaptivePortalService::PerformCheck - Calling CheckCaptivePortal\n")); 88 mRequestInProgress = true; 89 mCaptivePortalDetector->CheckCaptivePortal(kInterfaceName, this); 90 return NS_OK; 91 } 92 93 nsresult CaptivePortalService::RearmTimer() { 94 LOG(("CaptivePortalService::RearmTimer\n")); 95 // Start a timer to recheck 96 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); 97 if (mTimer) { 98 mTimer->Cancel(); 99 } 100 101 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { 102 mTimer = nullptr; 103 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; 104 } 105 106 // If we have successfully determined the state, and we have never detected 107 // a captive portal, we don't need to keep polling, but will rely on events 108 // to trigger detection. 109 if (mState == NOT_CAPTIVE) { 110 return NS_OK; 111 } 112 113 if (!mTimer) { 114 mTimer = NS_NewTimer(); 115 } 116 117 if (mTimer && mDelay > 0) { 118 LOG(("CaptivePortalService - Reloading timer with delay %u\n", mDelay)); 119 return mTimer->InitWithCallback(this, mDelay, nsITimer::TYPE_ONE_SHOT); 120 } 121 122 return NS_OK; 123 } 124 125 nsresult CaptivePortalService::Initialize() { 126 if (mInitialized) { 127 return NS_OK; 128 } 129 mInitialized = true; 130 131 // Only the main process service should actually do anything. The service in 132 // the content process only mirrors the CP state in the main process. 133 if (XRE_GetProcessType() != GeckoProcessType_Default) { 134 return NS_OK; 135 } 136 137 nsCOMPtr<nsIObserverService> observerService = 138 mozilla::services::GetObserverService(); 139 if (observerService) { 140 observerService->AddObserver(this, kOpenCaptivePortalLoginEvent, true); 141 observerService->AddObserver(this, kAbortCaptivePortalLoginEvent, true); 142 observerService->AddObserver(this, kCaptivePortalLoginSuccessEvent, true); 143 observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true); 144 } 145 146 LOG(("Initialized CaptivePortalService\n")); 147 return NS_OK; 148 } 149 150 nsresult CaptivePortalService::Start() { 151 if (!mInitialized) { 152 return NS_ERROR_NOT_INITIALIZED; 153 } 154 155 if (xpc::AreNonLocalConnectionsDisabled() && 156 !Preferences::GetBool("network.captive-portal-service.testMode", false)) { 157 return NS_ERROR_NOT_AVAILABLE; 158 } 159 160 if (XRE_GetProcessType() != GeckoProcessType_Default) { 161 // Doesn't do anything if called in the content process. 162 return NS_OK; 163 } 164 165 if (mStarted) { 166 return NS_OK; 167 } 168 169 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { 170 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; 171 } 172 173 MOZ_ASSERT(mState == UNKNOWN, "Initial state should be UNKNOWN"); 174 mStarted = true; 175 mEverBeenCaptive = false; 176 177 // Get the delay prefs 178 Preferences::GetUint("network.captive-portal-service.minInterval", 179 &mMinInterval); 180 Preferences::GetUint("network.captive-portal-service.maxInterval", 181 &mMaxInterval); 182 Preferences::GetFloat("network.captive-portal-service.backoffFactor", 183 &mBackoffFactor); 184 185 LOG(("CaptivePortalService::Start min:%u max:%u backoff:%.2f\n", mMinInterval, 186 mMaxInterval, mBackoffFactor)); 187 188 mSlackCount = 0; 189 mDelay = mMinInterval; 190 191 // When Start is called, perform a check immediately 192 PerformCheck(); 193 RearmTimer(); 194 return NS_OK; 195 } 196 197 nsresult CaptivePortalService::Stop() { 198 LOG(("CaptivePortalService::Stop\n")); 199 200 if (XRE_GetProcessType() != GeckoProcessType_Default) { 201 // Doesn't do anything when called in the content process. 202 return NS_OK; 203 } 204 205 if (!mStarted) { 206 return NS_OK; 207 } 208 209 if (mTimer) { 210 mTimer->Cancel(); 211 } 212 mTimer = nullptr; 213 mRequestInProgress = false; 214 mStarted = false; 215 mEverBeenCaptive = false; 216 if (mCaptivePortalDetector) { 217 mCaptivePortalDetector->Abort(kInterfaceName); 218 } 219 mCaptivePortalDetector = nullptr; 220 221 // Clear the state in case anyone queries the state while detection is off. 222 mState = UNKNOWN; 223 return NS_OK; 224 } 225 226 void CaptivePortalService::SetStateInChild(int32_t aState) { 227 // This should only be called in the content process, from ContentChild.cpp 228 // in order to mirror the captive portal state set in the chrome process. 229 MOZ_ASSERT(XRE_GetProcessType() != GeckoProcessType_Default); 230 231 mState = aState; 232 mLastChecked = TimeStamp::Now(); 233 } 234 235 //----------------------------------------------------------------------------- 236 // CaptivePortalService::nsICaptivePortalService 237 //----------------------------------------------------------------------------- 238 239 NS_IMETHODIMP 240 CaptivePortalService::GetState(int32_t* aState) { 241 *aState = mState; 242 return NS_OK; 243 } 244 245 NS_IMETHODIMP 246 CaptivePortalService::RecheckCaptivePortal() { 247 LOG(("CaptivePortalService::RecheckCaptivePortal\n")); 248 249 if (XRE_GetProcessType() != GeckoProcessType_Default) { 250 // Doesn't do anything if called in the content process. 251 return NS_OK; 252 } 253 254 // This is called for user activity. We need to reset the slack count, 255 // so the checks continue to be quite frequent. 256 mSlackCount = 0; 257 mDelay = mMinInterval; 258 259 PerformCheck(); 260 RearmTimer(); 261 return NS_OK; 262 } 263 264 NS_IMETHODIMP 265 CaptivePortalService::GetLastChecked(uint64_t* aLastChecked) { 266 double duration = (TimeStamp::Now() - mLastChecked).ToMilliseconds(); 267 *aLastChecked = static_cast<uint64_t>(duration); 268 return NS_OK; 269 } 270 271 //----------------------------------------------------------------------------- 272 // CaptivePortalService::nsITimer 273 // This callback gets called every mDelay miliseconds 274 // It issues a checkCaptivePortal operation if one isn't already in progress 275 //----------------------------------------------------------------------------- 276 NS_IMETHODIMP 277 CaptivePortalService::Notify(nsITimer* aTimer) { 278 LOG(("CaptivePortalService::Notify\n")); 279 MOZ_ASSERT(aTimer == mTimer); 280 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); 281 282 PerformCheck(); 283 284 // This is needed because we don't want to always make requests very often. 285 // Every 10 checks, we the delay is increased mBackoffFactor times 286 // to a maximum delay of mMaxInterval 287 mSlackCount++; 288 if (mSlackCount % 10 == 0) { 289 mDelay = mDelay * mBackoffFactor; 290 } 291 if (mDelay > mMaxInterval) { 292 mDelay = mMaxInterval; 293 } 294 295 // Note - if mDelay is 0, the timer will not be rearmed. 296 RearmTimer(); 297 298 return NS_OK; 299 } 300 301 //----------------------------------------------------------------------------- 302 // CaptivePortalService::nsINamed 303 //----------------------------------------------------------------------------- 304 305 NS_IMETHODIMP 306 CaptivePortalService::GetName(nsACString& aName) { 307 aName.AssignLiteral("CaptivePortalService"); 308 return NS_OK; 309 } 310 311 //----------------------------------------------------------------------------- 312 // CaptivePortalService::nsIObserver 313 //----------------------------------------------------------------------------- 314 NS_IMETHODIMP 315 CaptivePortalService::Observe(nsISupports* aSubject, const char* aTopic, 316 const char16_t* aData) { 317 if (XRE_GetProcessType() != GeckoProcessType_Default) { 318 // Doesn't do anything if called in the content process. 319 return NS_OK; 320 } 321 322 LOG(("CaptivePortalService::Observe() topic=%s\n", aTopic)); 323 if (!strcmp(aTopic, kOpenCaptivePortalLoginEvent)) { 324 // A redirect or altered content has been detected. 325 // The user needs to log in. We are in a captive portal. 326 StateTransition(LOCKED_PORTAL); 327 mLastChecked = TimeStamp::Now(); 328 mEverBeenCaptive = true; 329 } else if (!strcmp(aTopic, kCaptivePortalLoginSuccessEvent)) { 330 // The user has successfully logged in. We have connectivity. 331 StateTransition(UNLOCKED_PORTAL); 332 mLastChecked = TimeStamp::Now(); 333 mSlackCount = 0; 334 mDelay = mMinInterval; 335 336 RearmTimer(); 337 } else if (!strcmp(aTopic, kAbortCaptivePortalLoginEvent)) { 338 // The login has been aborted 339 StateTransition(UNKNOWN); 340 mLastChecked = TimeStamp::Now(); 341 mSlackCount = 0; 342 } else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { 343 Stop(); 344 return NS_OK; 345 } 346 347 // Send notification so that the captive portal state is mirrored in the 348 // content process. 349 nsCOMPtr<nsIObserverService> observerService = services::GetObserverService(); 350 if (observerService) { 351 nsCOMPtr<nsICaptivePortalService> cps(this); 352 observerService->NotifyObservers(cps, NS_IPC_CAPTIVE_PORTAL_SET_STATE, 353 nullptr); 354 } 355 356 return NS_OK; 357 } 358 359 void CaptivePortalService::NotifyConnectivityAvailable(bool aCaptive) { 360 nsCOMPtr<nsIObserverService> observerService = services::GetObserverService(); 361 if (observerService) { 362 nsCOMPtr<nsICaptivePortalService> cps(this); 363 observerService->NotifyObservers(cps, NS_CAPTIVE_PORTAL_CONNECTIVITY, 364 aCaptive ? u"captive" : u"clear"); 365 } 366 } 367 368 //----------------------------------------------------------------------------- 369 // CaptivePortalService::nsICaptivePortalCallback 370 //----------------------------------------------------------------------------- 371 NS_IMETHODIMP 372 CaptivePortalService::Prepare() { 373 LOG(("CaptivePortalService::Prepare\n")); 374 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); 375 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { 376 return NS_OK; 377 } 378 // XXX: Finish preparation shouldn't be called until dns and routing is 379 // available. 380 if (mCaptivePortalDetector) { 381 mCaptivePortalDetector->FinishPreparation(kInterfaceName); 382 } 383 return NS_OK; 384 } 385 386 NS_IMETHODIMP 387 CaptivePortalService::Complete(bool success) { 388 LOG(("CaptivePortalService::Complete(success=%d) mState=%d\n", success, 389 mState)); 390 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); 391 mLastChecked = TimeStamp::Now(); 392 393 // Note: this callback gets called when: 394 // 1. the request is completed, and content is valid (success == true) 395 // 2. when the request is aborted or times out (success == false) 396 397 if (success) { 398 if (mEverBeenCaptive) { 399 StateTransition(UNLOCKED_PORTAL); 400 NotifyConnectivityAvailable(true); 401 } else { 402 StateTransition(NOT_CAPTIVE); 403 NotifyConnectivityAvailable(false); 404 } 405 } 406 407 mRequestInProgress = false; 408 return NS_OK; 409 } 410 411 void CaptivePortalService::StateTransition(int32_t aNewState) { 412 int32_t oldState = mState; 413 mState = aNewState; 414 415 if ((oldState == UNKNOWN && mState == NOT_CAPTIVE) || 416 (oldState == LOCKED_PORTAL && mState == UNLOCKED_PORTAL)) { 417 nsCOMPtr<nsIObserverService> observerService = 418 services::GetObserverService(); 419 if (observerService) { 420 nsCOMPtr<nsICaptivePortalService> cps(this); 421 observerService->NotifyObservers( 422 cps, NS_CAPTIVE_PORTAL_CONNECTIVITY_CHANGED, nullptr); 423 } 424 } 425 } 426 427 } // namespace net 428 } // namespace mozilla