File: captive_portal_service.cc

package info (click to toggle)
chromium 139.0.7258.138-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 6,120,676 kB
  • sloc: cpp: 35,100,869; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (277 lines) | stat: -rw-r--r-- 10,006 bytes parent folder | download | duplicates (9)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/captive_portal/content/captive_portal_service.h"

#include <memory>

#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "base/time/tick_clock.h"
#include "build/build_config.h"
#include "components/captive_portal/core/captive_portal_types.h"
#include "components/embedder_support/pref_names.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/storage_partition.h"
#include "net/traffic_annotation/network_traffic_annotation.h"

#if BUILDFLAG(IS_WIN)
#include "base/win/windows_version.h"
#endif

namespace captive_portal {

CaptivePortalService::TestingState CaptivePortalService::testing_state_ =
    NOT_TESTING;

CaptivePortalService::RecheckPolicy::RecheckPolicy()
    : initial_backoff_no_portal_ms(600 * 1000),
      initial_backoff_portal_ms(20 * 1000) {
  // Receiving a new Result is considered a success.  All subsequent requests
  // that get the same Result are considered "failures", so a value of N
  // means exponential backoff starts after getting a result N + 2 times:
  // +1 for the initial success, and +1 because N failures are ignored.
  //
  // A value of 6 means to start backoff on the 7th failure, which is the 8th
  // time the same result is received.
  backoff_policy.num_errors_to_ignore = 6;

  // It doesn't matter what this is initialized to.  It will be overwritten
  // after the first captive portal detection request.
  backoff_policy.initial_delay_ms = initial_backoff_no_portal_ms;

  backoff_policy.multiply_factor = 2.0;
  backoff_policy.jitter_factor = 0.3;
  backoff_policy.maximum_backoff_ms = 2 * 60 * 1000;

  // -1 means the entry never expires.  This doesn't really matter, as the
  // service never checks for its expiration.
  backoff_policy.entry_lifetime_ms = -1;

  backoff_policy.always_use_initial_delay = true;
}

CaptivePortalService::CaptivePortalService(
    content::BrowserContext* browser_context,
    PrefService* pref_service,
    const base::TickClock* clock_for_testing,
    network::mojom::URLLoaderFactory* loader_factory_for_testing)
    : browser_context_(browser_context),
      state_(STATE_IDLE),
      enabled_(false),
      last_detection_result_(RESULT_INTERNET_CONNECTED),
      test_url_(CaptivePortalDetector::kDefaultURL),
      tick_clock_for_testing_(clock_for_testing) {
  network::mojom::URLLoaderFactory* loader_factory;
  if (loader_factory_for_testing) {
    loader_factory = loader_factory_for_testing;
  } else {
    shared_url_loader_factory_ = browser_context->GetDefaultStoragePartition()
                                     ->GetURLLoaderFactoryForBrowserProcess();
    loader_factory = shared_url_loader_factory_.get();
  }
  captive_portal_detector_ =
      std::make_unique<CaptivePortalDetector>(loader_factory);

  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  // The order matters here:
  // |resolve_errors_with_web_service_| must be initialized and |backoff_entry_|
  // created before the call to UpdateEnabledState.
  resolve_errors_with_web_service_.Init(
      embedder_support::kAlternateErrorPagesEnabled, pref_service,
      base::BindRepeating(&CaptivePortalService::UpdateEnabledState,
                          base::Unretained(this)));
  ResetBackoffEntry(last_detection_result_);

  UpdateEnabledState();
}

CaptivePortalService::~CaptivePortalService() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
}

void CaptivePortalService::DetectCaptivePortal() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  // Detection should be disabled only in tests.
  if (testing_state_ == IGNORE_REQUESTS_FOR_TESTING)
    return;

  // If a request is pending or running, do nothing.
  if (state_ == STATE_CHECKING_FOR_PORTAL || state_ == STATE_TIMER_RUNNING)
    return;

  base::TimeDelta time_until_next_check = backoff_entry_->GetTimeUntilRelease();

  // Start asynchronously.
  state_ = STATE_TIMER_RUNNING;
  check_captive_portal_timer_.Start(
      FROM_HERE, time_until_next_check,
      base::BindOnce(&CaptivePortalService::DetectCaptivePortalInternal,
                     base::Unretained(this)));
}

void CaptivePortalService::DetectCaptivePortalInternal() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  DCHECK(state_ == STATE_TIMER_RUNNING || state_ == STATE_IDLE);
  DCHECK(!TimerRunning());

  state_ = STATE_CHECKING_FOR_PORTAL;

  // When not enabled, just claim there's an Internet connection.
  if (!enabled_) {
    // Count this as a success, so the backoff entry won't apply exponential
    // backoff, but will apply the standard delay.
    backoff_entry_->InformOfRequest(true);
    OnResult(RESULT_INTERNET_CONNECTED, GURL());
    return;
  }

  net::NetworkTrafficAnnotationTag traffic_annotation =
      net::DefineNetworkTrafficAnnotation("captive_portal_service", R"(
        semantics {
          sender: "Captive Portal Service"
          description:
            "Checks if the system is behind a captive portal. To do so, makes"
            "an unlogged, dataless connection to a Google server and checks"
            "the response."
          trigger:
            "It is triggered on multiple cases: It is run on certain SSL "
            "errors (ERR_CONNECTION_TIMED_OUT, ERR_SSL_PROTOCOL_ERROR, and all "
            "SSL interstitials)."
          data: "None."
          destination: GOOGLE_OWNED_SERVICE
        }
        policy {
          cookies_allowed: NO
          setting:
            "Users can enable/disable this feature by toggling 'Use a web "
            "service to resolve network errors' in Chromium settings under "
            "Privacy. This feature is enabled by default."
          chrome_policy {
            AlternateErrorPagesEnabled {
              policy_options {mode: MANDATORY}
              AlternateErrorPagesEnabled: false
            }
          }
        })");

  captive_portal_detector_->DetectCaptivePortal(
      test_url_,
      base::BindOnce(&CaptivePortalService::OnPortalDetectionCompleted,
                     base::Unretained(this)),
      traffic_annotation);
}

void CaptivePortalService::OnPortalDetectionCompleted(
    const CaptivePortalDetector::Results& results) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  DCHECK_EQ(STATE_CHECKING_FOR_PORTAL, state_);
  DCHECK(!TimerRunning());
  DCHECK(enabled_);

  CaptivePortalResult result = results.result;
  const base::TimeDelta& retry_after_delta = results.retry_after_delta;
  base::TimeTicks now = GetCurrentTimeTicks();

  if (last_check_time_.is_null() || result != last_detection_result_) {
    // Reset the backoff entry both to update the default time and clear
    // previous failures.
    ResetBackoffEntry(result);

    backoff_entry_->SetCustomReleaseTime(now + retry_after_delta);
    // The BackoffEntry is not informed of this request, so there's no delay
    // before the next request.  This allows for faster login when a captive
    // portal is first detected.  It can also help when moving between captive
    // portals.
  } else {
    // Requests that have the same Result as the last one are considered
    // "failures", to trigger backoff.
    backoff_entry_->SetCustomReleaseTime(now + retry_after_delta);
    backoff_entry_->InformOfRequest(false);
  }

  last_check_time_ = now;

  OnResult(result, results.landing_url);
}

void CaptivePortalService::Shutdown() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
}

void CaptivePortalService::OnResult(CaptivePortalResult result,
                                    const GURL& landing_url) {
  DCHECK_EQ(STATE_CHECKING_FOR_PORTAL, state_);
  state_ = STATE_IDLE;

  Results results;
  results.previous_result = last_detection_result_;
  results.result = result;
  results.landing_url = landing_url;
  last_detection_result_ = result;

  callback_list_.Notify(results);
}

void CaptivePortalService::ResetBackoffEntry(CaptivePortalResult result) {
  if (!enabled_ || result == RESULT_BEHIND_CAPTIVE_PORTAL) {
    // Use the shorter time when the captive portal service is not enabled, or
    // behind a captive portal.
    recheck_policy_.backoff_policy.initial_delay_ms =
        recheck_policy_.initial_backoff_portal_ms;
  } else {
    recheck_policy_.backoff_policy.initial_delay_ms =
        recheck_policy_.initial_backoff_no_portal_ms;
  }

  backoff_entry_ = std::make_unique<net::BackoffEntry>(
      &recheck_policy().backoff_policy, tick_clock_for_testing_);
}

void CaptivePortalService::UpdateEnabledState() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  bool enabled_before = enabled_;
  enabled_ = testing_state_ != DISABLED_FOR_TESTING &&
             resolve_errors_with_web_service_.GetValue();

  if (enabled_before == enabled_)
    return;

  last_check_time_ = base::TimeTicks();
  ResetBackoffEntry(last_detection_result_);

  if (state_ == STATE_CHECKING_FOR_PORTAL || state_ == STATE_TIMER_RUNNING) {
    // If a captive portal check was running or pending, cancel check
    // and the timer.
    check_captive_portal_timer_.Stop();
    captive_portal_detector_->Cancel();
    state_ = STATE_IDLE;

    // Since a captive portal request was queued or running, something may be
    // expecting to receive a captive portal result.
    DetectCaptivePortal();
  }
}

base::TimeTicks CaptivePortalService::GetCurrentTimeTicks() const {
  if (tick_clock_for_testing_)
    return tick_clock_for_testing_->NowTicks();
  return base::TimeTicks::Now();
}

bool CaptivePortalService::DetectionInProgress() const {
  return state_ == STATE_CHECKING_FOR_PORTAL;
}

bool CaptivePortalService::TimerRunning() const {
  return check_captive_portal_timer_.IsRunning();
}

}  // namespace captive_portal