1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/chromeos/net/network_portal_detector_impl.h"
6
7 #include <algorithm>
8
9 #include "base/bind.h"
10 #include "base/command_line.h"
11 #include "base/location.h"
12 #include "base/logging.h"
13 #include "base/metrics/histogram_macros.h"
14 #include "base/single_thread_task_runner.h"
15 #include "base/threading/thread_task_runner_handle.h"
16 #include "chrome/browser/browser_process.h"
17 #include "chrome/browser/chrome_notification_types.h"
18 #include "chrome/browser/net/system_network_context_manager.h"
19 #include "chromeos/constants/chromeos_switches.h"
20 #include "chromeos/dbus/dbus_thread_manager.h"
21 #include "chromeos/dbus/shill/shill_profile_client.h"
22 #include "chromeos/login/login_state/login_state.h"
23 #include "chromeos/network/network_event_log.h"
24 #include "chromeos/network/network_state.h"
25 #include "chromeos/network/network_state_handler.h"
26 #include "content/public/browser/notification_service.h"
27 #include "net/http/http_status_code.h"
28 #include "net/traffic_annotation/network_traffic_annotation.h"
29 #include "services/network/public/cpp/shared_url_loader_factory.h"
30 #include "third_party/cros_system_api/dbus/service_constants.h"
31
32 using captive_portal::CaptivePortalDetector;
33
34 namespace chromeos {
35
36 namespace {
37
38 // Delay before portal detection caused by changes in proxy settings.
39 constexpr int kProxyChangeDelaySec = 1;
40
41 // Maximum number of reports from captive portal detector about
42 // offline state in a row before notification is sent to observers.
43 constexpr int kMaxOfflineResultsBeforeReport = 3;
44
45 // Delay before portal detection attempt after !ONLINE -> !ONLINE
46 // transition.
47 constexpr int kShortInitialDelayBetweenAttemptsMs = 600;
48
49 // Maximum timeout before portal detection attempts after !ONLINE ->
50 // !ONLINE transition.
51 constexpr int kShortMaximumDelayBetweenAttemptsMs = 2 * 60 * 1000;
52
53 // Delay before portal detection attempt after !ONLINE -> ONLINE
54 // transition.
55 constexpr int kLongInitialDelayBetweenAttemptsMs = 30 * 1000;
56
57 // Maximum timeout before portal detection attempts after !ONLINE ->
58 // ONLINE transition.
59 constexpr int kLongMaximumDelayBetweenAttemptsMs = 5 * 60 * 1000;
60
DefaultNetwork()61 const NetworkState* DefaultNetwork() {
62 return NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
63 }
64
SetNetworkPortalDetected(const NetworkState * network,bool portal_detected)65 void SetNetworkPortalDetected(const NetworkState* network,
66 bool portal_detected) {
67 NetworkHandler::Get()
68 ->network_state_handler()
69 ->SetNetworkChromePortalDetected(network->path(), portal_detected);
70 }
71
72 } // namespace
73
74 ////////////////////////////////////////////////////////////////////////////////
75 // NetworkPortalDetectorImpl, public:
76
NetworkPortalDetectorImpl(network::mojom::URLLoaderFactory * loader_factory_for_testing)77 NetworkPortalDetectorImpl::NetworkPortalDetectorImpl(
78 network::mojom::URLLoaderFactory* loader_factory_for_testing)
79 : strategy_(PortalDetectorStrategy::CreateById(
80 PortalDetectorStrategy::STRATEGY_ID_LOGIN_SCREEN,
81 this)) {
82 NET_LOG(EVENT) << "NetworkPortalDetectorImpl::NetworkPortalDetectorImpl()";
83 network::mojom::URLLoaderFactory* loader_factory;
84 if (loader_factory_for_testing) {
85 loader_factory = loader_factory_for_testing;
86 } else {
87 shared_url_loader_factory_ =
88 g_browser_process->system_network_context_manager()
89 ->GetSharedURLLoaderFactory();
90 loader_factory = shared_url_loader_factory_.get();
91 }
92 captive_portal_detector_.reset(new CaptivePortalDetector(loader_factory));
93
94 registrar_.Add(this, chrome::NOTIFICATION_AUTH_SUPPLIED,
95 content::NotificationService::AllSources());
96 registrar_.Add(this, chrome::NOTIFICATION_AUTH_CANCELLED,
97 content::NotificationService::AllSources());
98
99 NetworkHandler::Get()->network_state_handler()->AddObserver(this, FROM_HERE);
100 StartPortalDetection();
101 }
102
~NetworkPortalDetectorImpl()103 NetworkPortalDetectorImpl::~NetworkPortalDetectorImpl() {
104 NET_LOG(EVENT) << "NetworkPortalDetectorImpl::~NetworkPortalDetectorImpl()";
105 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
106
107 attempt_task_.Cancel();
108 attempt_timeout_.Cancel();
109
110 captive_portal_detector_->Cancel();
111 captive_portal_detector_.reset();
112 observers_.Clear();
113 if (NetworkHandler::IsInitialized()) {
114 NetworkHandler::Get()->network_state_handler()->RemoveObserver(this,
115 FROM_HERE);
116 }
117 for (auto& observer : observers_)
118 observer.OnShutdown();
119 }
120
AddObserver(Observer * observer)121 void NetworkPortalDetectorImpl::AddObserver(Observer* observer) {
122 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
123 if (observer && !observers_.HasObserver(observer))
124 observers_.AddObserver(observer);
125 }
126
AddAndFireObserver(Observer * observer)127 void NetworkPortalDetectorImpl::AddAndFireObserver(Observer* observer) {
128 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
129 if (!observer)
130 return;
131 AddObserver(observer);
132 observer->OnPortalDetectionCompleted(DefaultNetwork(),
133 GetCaptivePortalStatus());
134 }
135
RemoveObserver(Observer * observer)136 void NetworkPortalDetectorImpl::RemoveObserver(Observer* observer) {
137 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
138 if (observer)
139 observers_.RemoveObserver(observer);
140 }
141
IsEnabled()142 bool NetworkPortalDetectorImpl::IsEnabled() {
143 return enabled_;
144 }
145
Enable(bool start_detection)146 void NetworkPortalDetectorImpl::Enable(bool start_detection) {
147 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
148 if (enabled_)
149 return;
150
151 DCHECK(is_idle());
152 enabled_ = true;
153
154 const NetworkState* network = DefaultNetwork();
155 if (!start_detection || !network)
156 return;
157 NET_LOG(EVENT) << "Starting detection attempt:"
158 << " id=" << NetworkId(network);
159 SetNetworkPortalDetected(network, false /* portal_detected */);
160 StartDetection();
161 }
162
163 NetworkPortalDetector::CaptivePortalStatus
GetCaptivePortalStatus()164 NetworkPortalDetectorImpl::GetCaptivePortalStatus() {
165 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
166 return default_portal_status_;
167 }
168
StartPortalDetection()169 void NetworkPortalDetectorImpl::StartPortalDetection() {
170 if (!is_idle())
171 return;
172 StartDetection();
173 return;
174 }
175
SetStrategy(PortalDetectorStrategy::StrategyId id)176 void NetworkPortalDetectorImpl::SetStrategy(
177 PortalDetectorStrategy::StrategyId id) {
178 if (id == strategy_->Id())
179 return;
180 strategy_ = PortalDetectorStrategy::CreateById(id, this);
181 if (!is_idle())
182 StopDetection();
183 StartPortalDetection();
184 }
185
DefaultNetworkChanged(const NetworkState * default_network)186 void NetworkPortalDetectorImpl::DefaultNetworkChanged(
187 const NetworkState* default_network) {
188 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
189
190 if (!default_network) {
191 NET_LOG(EVENT) << "Default network changed: None";
192
193 default_network_id_ = std::string();
194 default_proxy_config_ = base::Value();
195
196 StopDetection();
197
198 DetectionCompleted(nullptr, CAPTIVE_PORTAL_STATUS_OFFLINE, -1);
199 return;
200 }
201
202 bool network_changed = (default_network_id_ != default_network->guid());
203 if (network_changed)
204 default_network_id_ = default_network->guid();
205
206 bool connection_state_changed =
207 (default_connection_state_ != default_network->connection_state());
208 default_connection_state_ = default_network->connection_state();
209
210 bool proxy_config_changed = false;
211 const base::Value& default_network_proxy_config =
212 default_network->proxy_config();
213 if (default_network_proxy_config.is_none()) {
214 if (!default_proxy_config_.is_none()) {
215 proxy_config_changed = true;
216 default_proxy_config_ = base::Value();
217 }
218 } else if (network_changed ||
219 default_proxy_config_ != default_network_proxy_config) {
220 proxy_config_changed = true;
221 default_proxy_config_ = default_network_proxy_config.Clone();
222 }
223
224 NET_LOG(EVENT) << "Default network changed:"
225 << " id=" << NetworkGuidId(default_network_id_)
226 << " state=" << default_connection_state_
227 << " changed=" << network_changed
228 << " proxy_config_changed=" << proxy_config_changed
229 << " state_changed=" << connection_state_changed;
230
231 if (network_changed || connection_state_changed || proxy_config_changed)
232 StopDetection();
233
234 if (!NetworkState::StateIsConnected(default_connection_state_))
235 return;
236
237 if (proxy_config_changed) {
238 ScheduleAttempt(base::TimeDelta::FromSeconds(kProxyChangeDelaySec));
239 return;
240 }
241
242 if (is_idle()) {
243 // Initiate Captive Portal detection if network's captive
244 // portal state is unknown (e.g. for freshly created networks),
245 // offline or if network connection state was changed.
246 CaptivePortalStatus status = GetCaptivePortalStatus();
247 if (status == CAPTIVE_PORTAL_STATUS_UNKNOWN ||
248 status == CAPTIVE_PORTAL_STATUS_OFFLINE ||
249 (!network_changed && connection_state_changed)) {
250 ScheduleAttempt(base::TimeDelta());
251 }
252 }
253 }
254
NoResponseResultCount()255 int NetworkPortalDetectorImpl::NoResponseResultCount() {
256 return no_response_result_count_;
257 }
258
AttemptStartTime()259 base::TimeTicks NetworkPortalDetectorImpl::AttemptStartTime() {
260 return attempt_start_time_;
261 }
262
NowTicks() const263 base::TimeTicks NetworkPortalDetectorImpl::NowTicks() const {
264 if (time_ticks_for_testing_.is_null())
265 return base::TimeTicks::Now();
266 return time_ticks_for_testing_;
267 }
268
269 ////////////////////////////////////////////////////////////////////////////////
270 // NetworkPortalDetectorImpl, private:
271
StartDetection()272 void NetworkPortalDetectorImpl::StartDetection() {
273 DCHECK(is_idle());
274
275 ResetStrategyAndCounters();
276 default_portal_status_ = CAPTIVE_PORTAL_STATUS_UNKNOWN;
277 detection_start_time_ = NowTicks();
278 ScheduleAttempt(base::TimeDelta());
279 }
280
StopDetection()281 void NetworkPortalDetectorImpl::StopDetection() {
282 attempt_task_.Cancel();
283 attempt_timeout_.Cancel();
284 captive_portal_detector_->Cancel();
285 default_portal_status_ = CAPTIVE_PORTAL_STATUS_UNKNOWN;
286 state_ = STATE_IDLE;
287 ResetStrategyAndCounters();
288 }
289
ScheduleAttempt(const base::TimeDelta & delay)290 void NetworkPortalDetectorImpl::ScheduleAttempt(const base::TimeDelta& delay) {
291 DCHECK(is_idle());
292
293 if (!IsEnabled())
294 return;
295
296 attempt_task_.Cancel();
297 attempt_timeout_.Cancel();
298 state_ = STATE_PORTAL_CHECK_PENDING;
299
300 next_attempt_delay_ = std::max(delay, strategy_->GetDelayTillNextAttempt());
301 attempt_task_.Reset(base::Bind(&NetworkPortalDetectorImpl::StartAttempt,
302 weak_factory_.GetWeakPtr()));
303 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
304 FROM_HERE, attempt_task_.callback(), next_attempt_delay_);
305 }
306
StartAttempt()307 void NetworkPortalDetectorImpl::StartAttempt() {
308 DCHECK(is_portal_check_pending());
309
310 state_ = STATE_CHECKING_FOR_PORTAL;
311 attempt_start_time_ = NowTicks();
312
313 NET_LOG(EVENT) << "Starting captive portal detection.";
314 captive_portal_detector_->DetectCaptivePortal(
315 GURL(CaptivePortalDetector::kDefaultURL),
316 base::BindOnce(&NetworkPortalDetectorImpl::OnAttemptCompleted,
317 weak_factory_.GetWeakPtr()),
318 NO_TRAFFIC_ANNOTATION_YET);
319 attempt_timeout_.Reset(
320 base::Bind(&NetworkPortalDetectorImpl::OnAttemptTimeout,
321 weak_factory_.GetWeakPtr()));
322
323 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
324 FROM_HERE, attempt_timeout_.callback(),
325 strategy_->GetNextAttemptTimeout());
326 }
327
OnAttemptTimeout()328 void NetworkPortalDetectorImpl::OnAttemptTimeout() {
329 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
330 DCHECK(is_checking_for_portal());
331
332 NET_LOG(EVENT) << "Portal detection timeout: "
333 << " id=" << NetworkGuidId(default_network_id_);
334
335 captive_portal_detector_->Cancel();
336 CaptivePortalDetector::Results results;
337 results.result = captive_portal::RESULT_NO_RESPONSE;
338 OnAttemptCompleted(results);
339 }
340
OnAttemptCompleted(const CaptivePortalDetector::Results & results)341 void NetworkPortalDetectorImpl::OnAttemptCompleted(
342 const CaptivePortalDetector::Results& results) {
343 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
344 DCHECK(is_checking_for_portal());
345
346 captive_portal::CaptivePortalResult result = results.result;
347 int response_code = results.response_code;
348
349 const NetworkState* network = DefaultNetwork();
350
351 // If using a fake profile client, also fake being behind a captive portal
352 // if the default network is in portal state.
353 if (result != captive_portal::RESULT_NO_RESPONSE &&
354 DBusThreadManager::Get()->GetShillProfileClient()->GetTestInterface() &&
355 network && network->IsShillCaptivePortal()) {
356 result = captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL;
357 response_code = 200;
358 }
359
360 state_ = STATE_IDLE;
361 attempt_timeout_.Cancel();
362
363 CaptivePortalStatus status = CAPTIVE_PORTAL_STATUS_UNKNOWN;
364 bool no_response_since_portal = false;
365 switch (result) {
366 case captive_portal::RESULT_NO_RESPONSE:
367 if (response_code == net::HTTP_PROXY_AUTHENTICATION_REQUIRED) {
368 status = CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED;
369 } else if (network && network->IsShillCaptivePortal()) {
370 // Take into account shill's detection results.
371 status = CAPTIVE_PORTAL_STATUS_PORTAL;
372 no_response_since_portal = true;
373 } else {
374 status = CAPTIVE_PORTAL_STATUS_OFFLINE;
375 }
376 break;
377 case captive_portal::RESULT_INTERNET_CONNECTED:
378 status = CAPTIVE_PORTAL_STATUS_ONLINE;
379 break;
380 case captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL:
381 status = CAPTIVE_PORTAL_STATUS_PORTAL;
382 break;
383 default:
384 break;
385 }
386
387 NET_LOG(EVENT) << "NetworkPortalDetector completed: id="
388 << NetworkGuidId(default_network_id_) << ", result="
389 << captive_portal::CaptivePortalResultToString(result)
390 << ", status=" << status
391 << ", response_code=" << response_code;
392
393 UMA_HISTOGRAM_ENUMERATION("CaptivePortal.NetworkPortalDetectorResult",
394 status);
395 NetworkState::NetworkTechnologyType type =
396 NetworkState::NetworkTechnologyType::kUnknown;
397 if (status == CAPTIVE_PORTAL_STATUS_PORTAL) {
398 if (network)
399 type = network->GetNetworkTechnologyType();
400 UMA_HISTOGRAM_ENUMERATION("CaptivePortal.NetworkPortalDetectorType", type);
401 }
402
403 if (last_detection_result_ != status) {
404 last_detection_result_ = status;
405 same_detection_result_count_ = 1;
406 net::BackoffEntry::Policy policy = strategy_->policy();
407 if (status == CAPTIVE_PORTAL_STATUS_ONLINE) {
408 policy.initial_delay_ms = kLongInitialDelayBetweenAttemptsMs;
409 policy.maximum_backoff_ms = kLongMaximumDelayBetweenAttemptsMs;
410 } else {
411 policy.initial_delay_ms = kShortInitialDelayBetweenAttemptsMs;
412 policy.maximum_backoff_ms = kShortMaximumDelayBetweenAttemptsMs;
413 }
414 strategy_->SetPolicyAndReset(policy);
415 } else {
416 ++same_detection_result_count_;
417 }
418 strategy_->OnDetectionCompleted();
419
420 if (result == captive_portal::RESULT_NO_RESPONSE)
421 ++no_response_result_count_;
422 else
423 no_response_result_count_ = 0;
424
425 if (status != CAPTIVE_PORTAL_STATUS_OFFLINE ||
426 same_detection_result_count_ >= kMaxOfflineResultsBeforeReport) {
427 DetectionCompleted(network, status, response_code);
428 }
429
430 // Observers (via DetectionCompleted) may already schedule new attempt.
431 if (is_idle())
432 ScheduleAttempt(results.retry_after_delta);
433 }
434
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)435 void NetworkPortalDetectorImpl::Observe(
436 int type,
437 const content::NotificationSource& source,
438 const content::NotificationDetails& details) {
439 if (type == chrome::NOTIFICATION_AUTH_SUPPLIED ||
440 type == chrome::NOTIFICATION_AUTH_CANCELLED) {
441 NET_LOG(EVENT) << "Restarting portal detection due to auth change"
442 << " id=" << NetworkGuidId(default_network_id_);
443 StopDetection();
444 ScheduleAttempt(base::TimeDelta::FromSeconds(kProxyChangeDelaySec));
445 }
446 }
447
DetectionCompleted(const NetworkState * network,const CaptivePortalStatus & status,int response_code)448 void NetworkPortalDetectorImpl::DetectionCompleted(
449 const NetworkState* network,
450 const CaptivePortalStatus& status,
451 int response_code) {
452 default_portal_status_ = status;
453 response_code_for_testing_ = response_code;
454 if (network) {
455 SetNetworkPortalDetected(
456 network, status == NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PORTAL);
457 }
458 for (auto& observer : observers_)
459 observer.OnPortalDetectionCompleted(network, status);
460 }
461
AttemptTimeoutIsCancelledForTesting() const462 bool NetworkPortalDetectorImpl::AttemptTimeoutIsCancelledForTesting() const {
463 return attempt_timeout_.IsCancelled();
464 }
465
ResetStrategyAndCounters()466 void NetworkPortalDetectorImpl::ResetStrategyAndCounters() {
467 last_detection_result_ = CAPTIVE_PORTAL_STATUS_UNKNOWN;
468 same_detection_result_count_ = 0;
469 no_response_result_count_ = 0;
470 strategy_->Reset();
471 }
472
473 } // namespace chromeos
474