network_portal_detector_impl.cc revision 116680a4aac90f2aa7413d9095a592090648e557
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/logging.h"
12#include "base/message_loop/message_loop.h"
13#include "base/metrics/histogram.h"
14#include "chrome/browser/chrome_notification_types.h"
15#include "chromeos/dbus/dbus_thread_manager.h"
16#include "chromeos/dbus/shill_profile_client.h"
17#include "chromeos/login/login_state.h"
18#include "chromeos/network/network_state.h"
19#include "chromeos/network/network_state_handler.h"
20#include "content/public/browser/notification_service.h"
21#include "content/public/common/content_switches.h"
22#include "grit/generated_resources.h"
23#include "net/http/http_status_code.h"
24#include "third_party/cros_system_api/dbus/service_constants.h"
25#include "ui/base/l10n/l10n_util.h"
26
27using captive_portal::CaptivePortalDetector;
28
29namespace chromeos {
30
31namespace {
32
33// Delay before portal detection caused by changes in proxy settings.
34const int kProxyChangeDelaySec = 1;
35
36const NetworkState* DefaultNetwork() {
37  return NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
38}
39
40bool InSession() {
41  return LoginState::IsInitialized() && LoginState::Get()->IsUserLoggedIn();
42}
43
44void RecordDetectionResult(NetworkPortalDetector::CaptivePortalStatus status) {
45  if (InSession()) {
46    UMA_HISTOGRAM_ENUMERATION(
47        NetworkPortalDetectorImpl::kSessionDetectionResultHistogram,
48        status,
49        NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
50  } else {
51    UMA_HISTOGRAM_ENUMERATION(
52        NetworkPortalDetectorImpl::kOobeDetectionResultHistogram,
53        status,
54        NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
55  }
56}
57
58void RecordDetectionDuration(const base::TimeDelta& duration) {
59  if (InSession()) {
60    UMA_HISTOGRAM_MEDIUM_TIMES(
61        NetworkPortalDetectorImpl::kSessionDetectionDurationHistogram,
62        duration);
63  } else {
64    UMA_HISTOGRAM_MEDIUM_TIMES(
65        NetworkPortalDetectorImpl::kOobeDetectionDurationHistogram, duration);
66  }
67}
68
69void RecordDiscrepancyWithShill(
70    const NetworkState* network,
71    const NetworkPortalDetector::CaptivePortalStatus status) {
72  if (InSession()) {
73    if (network->connection_state() == shill::kStateOnline) {
74      UMA_HISTOGRAM_ENUMERATION(
75          NetworkPortalDetectorImpl::kSessionShillOnlineHistogram,
76          status,
77          NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
78    } else if (network->connection_state() == shill::kStatePortal) {
79      UMA_HISTOGRAM_ENUMERATION(
80          NetworkPortalDetectorImpl::kSessionShillPortalHistogram,
81          status,
82          NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
83    } else if (network->connection_state() == shill::kStateOffline) {
84      UMA_HISTOGRAM_ENUMERATION(
85          NetworkPortalDetectorImpl::kSessionShillOfflineHistogram,
86          status,
87          NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
88    }
89  } else {
90    if (network->connection_state() == shill::kStateOnline) {
91      UMA_HISTOGRAM_ENUMERATION(
92          NetworkPortalDetectorImpl::kOobeShillOnlineHistogram,
93          status,
94          NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
95    } else if (network->connection_state() == shill::kStatePortal) {
96      UMA_HISTOGRAM_ENUMERATION(
97          NetworkPortalDetectorImpl::kOobeShillPortalHistogram,
98          status,
99          NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
100    } else if (network->connection_state() == shill::kStateOffline) {
101      UMA_HISTOGRAM_ENUMERATION(
102          NetworkPortalDetectorImpl::kOobeShillOfflineHistogram,
103          status,
104          NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
105    }
106  }
107}
108
109void RecordPortalToOnlineTransition(const base::TimeDelta& duration) {
110  if (InSession()) {
111    UMA_HISTOGRAM_LONG_TIMES(
112        NetworkPortalDetectorImpl::kSessionPortalToOnlineHistogram,
113        duration);
114  } else {
115    UMA_HISTOGRAM_LONG_TIMES(
116        NetworkPortalDetectorImpl::kOobePortalToOnlineHistogram,
117        duration);
118  }
119}
120
121}  // namespace
122
123////////////////////////////////////////////////////////////////////////////////
124// NetworkPortalDetectorImpl::DetectionAttemptCompletedLogState
125
126NetworkPortalDetectorImpl::DetectionAttemptCompletedReport::
127    DetectionAttemptCompletedReport()
128    : result(captive_portal::RESULT_COUNT), response_code(-1) {
129}
130
131NetworkPortalDetectorImpl::DetectionAttemptCompletedReport::
132    DetectionAttemptCompletedReport(const std::string network_name,
133                                    const std::string network_id,
134                                    captive_portal::CaptivePortalResult result,
135                                    int response_code)
136    : network_name(network_name),
137      network_id(network_id),
138      result(result),
139      response_code(response_code) {
140}
141
142void NetworkPortalDetectorImpl::DetectionAttemptCompletedReport::Report()
143    const {
144  VLOG(1) << "Detection attempt completed: "
145          << "name=" << network_name << ", "
146          << "id=" << network_id << ", "
147          << "result=" << captive_portal::CaptivePortalResultToString(result)
148          << ", "
149          << "response_code=" << response_code;
150}
151
152bool NetworkPortalDetectorImpl::DetectionAttemptCompletedReport::Equals(
153    const DetectionAttemptCompletedReport& o) const {
154  return network_name == o.network_name && network_id == o.network_id &&
155         result == o.result && response_code == o.response_code;
156}
157
158////////////////////////////////////////////////////////////////////////////////
159// NetworkPortalDetectorImpl, public:
160
161const char NetworkPortalDetectorImpl::kOobeDetectionResultHistogram[] =
162    "CaptivePortal.OOBE.DetectionResult";
163const char NetworkPortalDetectorImpl::kOobeDetectionDurationHistogram[] =
164    "CaptivePortal.OOBE.DetectionDuration";
165const char NetworkPortalDetectorImpl::kOobeShillOnlineHistogram[] =
166    "CaptivePortal.OOBE.DiscrepancyWithShill_Online";
167const char NetworkPortalDetectorImpl::kOobeShillPortalHistogram[] =
168    "CaptivePortal.OOBE.DiscrepancyWithShill_RestrictedPool";
169const char NetworkPortalDetectorImpl::kOobeShillOfflineHistogram[] =
170    "CaptivePortal.OOBE.DiscrepancyWithShill_Offline";
171const char NetworkPortalDetectorImpl::kOobePortalToOnlineHistogram[] =
172    "CaptivePortal.OOBE.PortalToOnlineTransition";
173
174const char NetworkPortalDetectorImpl::kSessionDetectionResultHistogram[] =
175    "CaptivePortal.Session.DetectionResult";
176const char NetworkPortalDetectorImpl::kSessionDetectionDurationHistogram[] =
177    "CaptivePortal.Session.DetectionDuration";
178const char NetworkPortalDetectorImpl::kSessionShillOnlineHistogram[] =
179    "CaptivePortal.Session.DiscrepancyWithShill_Online";
180const char NetworkPortalDetectorImpl::kSessionShillPortalHistogram[] =
181    "CaptivePortal.Session.DiscrepancyWithShill_RestrictedPool";
182const char NetworkPortalDetectorImpl::kSessionShillOfflineHistogram[] =
183    "CaptivePortal.Session.DiscrepancyWithShill_Offline";
184const char NetworkPortalDetectorImpl::kSessionPortalToOnlineHistogram[] =
185    "CaptivePortal.Session.PortalToOnlineTransition";
186
187// static
188void NetworkPortalDetectorImpl::Initialize(
189    net::URLRequestContextGetter* url_context) {
190  if (NetworkPortalDetector::set_for_testing())
191    return;
192  CHECK(!NetworkPortalDetector::network_portal_detector())
193      << "NetworkPortalDetector was initialized twice.";
194  if (CommandLine::ForCurrentProcess()->HasSwitch(::switches::kTestType))
195    set_network_portal_detector(new NetworkPortalDetectorStubImpl());
196  else
197    set_network_portal_detector(new NetworkPortalDetectorImpl(url_context));
198}
199
200NetworkPortalDetectorImpl::NetworkPortalDetectorImpl(
201    const scoped_refptr<net::URLRequestContextGetter>& request_context)
202    : state_(STATE_IDLE),
203      test_url_(CaptivePortalDetector::kDefaultURL),
204      enabled_(false),
205      attempt_count_(0),
206      strategy_(PortalDetectorStrategy::CreateById(
207          PortalDetectorStrategy::STRATEGY_ID_LOGIN_SCREEN)),
208      weak_factory_(this) {
209  captive_portal_detector_.reset(new CaptivePortalDetector(request_context));
210  strategy_->set_delegate(this);
211
212  registrar_.Add(this,
213                 chrome::NOTIFICATION_LOGIN_PROXY_CHANGED,
214                 content::NotificationService::AllSources());
215  registrar_.Add(this,
216                 chrome::NOTIFICATION_AUTH_SUPPLIED,
217                 content::NotificationService::AllSources());
218  registrar_.Add(this,
219                 chrome::NOTIFICATION_AUTH_CANCELLED,
220                 content::NotificationService::AllSources());
221
222  NetworkHandler::Get()->network_state_handler()->AddObserver(this, FROM_HERE);
223  StartDetectionIfIdle();
224}
225
226NetworkPortalDetectorImpl::~NetworkPortalDetectorImpl() {
227  DCHECK(CalledOnValidThread());
228
229  attempt_task_.Cancel();
230  attempt_timeout_.Cancel();
231
232  captive_portal_detector_->Cancel();
233  captive_portal_detector_.reset();
234  observers_.Clear();
235  if (NetworkHandler::IsInitialized()) {
236    NetworkHandler::Get()->network_state_handler()->RemoveObserver(this,
237                                                                   FROM_HERE);
238  }
239}
240
241void NetworkPortalDetectorImpl::AddObserver(Observer* observer) {
242  DCHECK(CalledOnValidThread());
243  if (observer && !observers_.HasObserver(observer))
244    observers_.AddObserver(observer);
245}
246
247void NetworkPortalDetectorImpl::AddAndFireObserver(Observer* observer) {
248  DCHECK(CalledOnValidThread());
249  if (!observer)
250    return;
251  AddObserver(observer);
252  CaptivePortalState portal_state;
253  const NetworkState* network = DefaultNetwork();
254  if (network)
255    portal_state = GetCaptivePortalState(network->guid());
256  observer->OnPortalDetectionCompleted(network, portal_state);
257}
258
259void NetworkPortalDetectorImpl::RemoveObserver(Observer* observer) {
260  DCHECK(CalledOnValidThread());
261  if (observer)
262    observers_.RemoveObserver(observer);
263}
264
265bool NetworkPortalDetectorImpl::IsEnabled() { return enabled_; }
266
267void NetworkPortalDetectorImpl::Enable(bool start_detection) {
268  DCHECK(CalledOnValidThread());
269  if (enabled_)
270    return;
271
272  DCHECK(is_idle());
273  enabled_ = true;
274
275  const NetworkState* network = DefaultNetwork();
276  if (!start_detection || !network)
277    return;
278  VLOG(1) << "Starting detection for: "
279          << "name=" << network->name() << ", id=" << network->guid();
280  portal_state_map_.erase(network->guid());
281  StartDetection();
282}
283
284NetworkPortalDetectorImpl::CaptivePortalState
285NetworkPortalDetectorImpl::GetCaptivePortalState(const std::string& guid) {
286  DCHECK(CalledOnValidThread());
287  CaptivePortalStateMap::const_iterator it = portal_state_map_.find(guid);
288  if (it == portal_state_map_.end()) {
289    VLOG(1) << "CaptivePortalState not found for: " << guid;
290    return CaptivePortalState();
291  }
292  return it->second;
293}
294
295bool NetworkPortalDetectorImpl::StartDetectionIfIdle() {
296  if (!is_idle())
297    return false;
298  StartDetection();
299  return true;
300}
301
302void NetworkPortalDetectorImpl::SetStrategy(
303    PortalDetectorStrategy::StrategyId id) {
304  if (id == strategy_->Id())
305    return;
306  strategy_.reset(PortalDetectorStrategy::CreateById(id).release());
307  strategy_->set_delegate(this);
308  StopDetection();
309  StartDetectionIfIdle();
310}
311
312void NetworkPortalDetectorImpl::DefaultNetworkChanged(
313    const NetworkState* default_network) {
314  DCHECK(CalledOnValidThread());
315
316  if (!default_network) {
317    VLOG(1) << "DefaultNetworkChanged: None.";
318    default_network_name_.clear();
319
320    StopDetection();
321
322    CaptivePortalState state;
323    state.status = CAPTIVE_PORTAL_STATUS_OFFLINE;
324    OnDetectionCompleted(NULL, state);
325    return;
326  }
327
328  default_network_name_ = default_network->name();
329
330  bool network_changed = (default_network_id_ != default_network->guid());
331  default_network_id_ = default_network->guid();
332
333  bool connection_state_changed =
334      (default_connection_state_ != default_network->connection_state());
335  default_connection_state_ = default_network->connection_state();
336
337  VLOG(1) << "DefaultNetworkChanged: "
338          << "name=" << default_network_name_ << ", "
339          << "id=" << default_network_id_ << ", "
340          << "state=" << default_connection_state_ << ", "
341          << "changed=" << network_changed << ", "
342          << "state_changed=" << connection_state_changed;
343
344  if (network_changed || connection_state_changed)
345    StopDetection();
346
347  if (CanPerformAttempt() &&
348      NetworkState::StateIsConnected(default_connection_state_)) {
349    // Initiate Captive Portal detection if network's captive
350    // portal state is unknown (e.g. for freshly created networks),
351    // offline or if network connection state was changed.
352    CaptivePortalState state = GetCaptivePortalState(default_network->guid());
353    if (state.status == CAPTIVE_PORTAL_STATUS_UNKNOWN ||
354        state.status == CAPTIVE_PORTAL_STATUS_OFFLINE ||
355        (!network_changed && connection_state_changed)) {
356      ScheduleAttempt(base::TimeDelta());
357    }
358  }
359}
360
361int NetworkPortalDetectorImpl::AttemptCount() { return attempt_count_; }
362
363base::TimeTicks NetworkPortalDetectorImpl::AttemptStartTime() {
364  return attempt_start_time_;
365}
366
367base::TimeTicks NetworkPortalDetectorImpl::GetCurrentTimeTicks() {
368  if (time_ticks_for_testing_.is_null())
369    return base::TimeTicks::Now();
370  return time_ticks_for_testing_;
371}
372
373
374////////////////////////////////////////////////////////////////////////////////
375// NetworkPortalDetectorImpl, private:
376
377void NetworkPortalDetectorImpl::StartDetection() {
378  attempt_count_ = 0;
379  DCHECK(CanPerformAttempt());
380  detection_start_time_ = GetCurrentTimeTicks();
381  ScheduleAttempt(base::TimeDelta());
382}
383
384void NetworkPortalDetectorImpl::StopDetection() {
385  attempt_task_.Cancel();
386  attempt_timeout_.Cancel();
387  captive_portal_detector_->Cancel();
388  state_ = STATE_IDLE;
389  attempt_count_ = 0;
390}
391
392bool NetworkPortalDetectorImpl::CanPerformAttempt() const {
393  return is_idle() && strategy_->CanPerformAttempt();
394}
395
396void NetworkPortalDetectorImpl::ScheduleAttempt(const base::TimeDelta& delay) {
397  DCHECK(CanPerformAttempt());
398
399  if (!IsEnabled())
400    return;
401
402  attempt_task_.Cancel();
403  attempt_timeout_.Cancel();
404  state_ = STATE_PORTAL_CHECK_PENDING;
405
406  next_attempt_delay_ = std::max(delay, strategy_->GetDelayTillNextAttempt());
407  attempt_task_.Reset(base::Bind(&NetworkPortalDetectorImpl::StartAttempt,
408                                 weak_factory_.GetWeakPtr()));
409  base::MessageLoop::current()->PostDelayedTask(
410      FROM_HERE, attempt_task_.callback(), next_attempt_delay_);
411}
412
413void NetworkPortalDetectorImpl::StartAttempt() {
414  DCHECK(is_portal_check_pending());
415
416  state_ = STATE_CHECKING_FOR_PORTAL;
417  attempt_start_time_ = GetCurrentTimeTicks();
418
419  captive_portal_detector_->DetectCaptivePortal(
420      test_url_,
421      base::Bind(&NetworkPortalDetectorImpl::OnAttemptCompleted,
422                 weak_factory_.GetWeakPtr()));
423  attempt_timeout_.Reset(
424      base::Bind(&NetworkPortalDetectorImpl::OnAttemptTimeout,
425                 weak_factory_.GetWeakPtr()));
426
427  base::MessageLoop::current()->PostDelayedTask(
428      FROM_HERE,
429      attempt_timeout_.callback(),
430      strategy_->GetNextAttemptTimeout());
431}
432
433void NetworkPortalDetectorImpl::OnAttemptTimeout() {
434  DCHECK(CalledOnValidThread());
435  DCHECK(is_checking_for_portal());
436
437  VLOG(1) << "Portal detection timeout: name=" << default_network_name_ << ", "
438          << "id=" << default_network_id_;
439
440  captive_portal_detector_->Cancel();
441  CaptivePortalDetector::Results results;
442  results.result = captive_portal::RESULT_NO_RESPONSE;
443  OnAttemptCompleted(results);
444}
445
446void NetworkPortalDetectorImpl::OnAttemptCompleted(
447    const CaptivePortalDetector::Results& results) {
448  captive_portal::CaptivePortalResult result = results.result;
449  int response_code = results.response_code;
450
451  DCHECK(CalledOnValidThread());
452  DCHECK(is_checking_for_portal());
453
454  DetectionAttemptCompletedReport attempt_completed_report(
455      default_network_name_,
456      default_network_id_,
457      results.result,
458      results.response_code);
459  if (!attempt_completed_report_.Equals(attempt_completed_report)) {
460    attempt_completed_report_ = attempt_completed_report;
461    attempt_completed_report_.Report();
462  }
463
464  state_ = STATE_IDLE;
465  attempt_timeout_.Cancel();
466  ++attempt_count_;
467
468  const NetworkState* network = DefaultNetwork();
469
470  // If using a fake profile client, also fake being behind a captive portal
471  // if the default network is in portal state.
472  if (result != captive_portal::RESULT_NO_RESPONSE &&
473      DBusThreadManager::Get()->GetShillProfileClient()->GetTestInterface() &&
474      network && network->connection_state() == shill::kStatePortal) {
475    result = captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL;
476    response_code = 200;
477  }
478
479  CaptivePortalState state;
480  state.response_code = response_code;
481  state.time = GetCurrentTimeTicks();
482  switch (result) {
483    case captive_portal::RESULT_NO_RESPONSE:
484      if (CanPerformAttempt()) {
485        ScheduleAttempt(results.retry_after_delta);
486        return;
487      } else if (state.response_code ==
488                 net::HTTP_PROXY_AUTHENTICATION_REQUIRED) {
489        state.status = CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED;
490      } else if (network &&
491                 (network->connection_state() == shill::kStatePortal)) {
492        // Take into account shill's detection results.
493        state.status = CAPTIVE_PORTAL_STATUS_PORTAL;
494        LOG(WARNING) << "Network name=" << network->name() << ", "
495                     << "id=" << network->guid() << " "
496                     << "is marked as "
497                     << CaptivePortalStatusString(state.status) << " "
498                     << "despite the fact that CaptivePortalDetector "
499                     << "received no response";
500      } else {
501        state.status = CAPTIVE_PORTAL_STATUS_OFFLINE;
502      }
503      break;
504    case captive_portal::RESULT_INTERNET_CONNECTED:
505      state.status = CAPTIVE_PORTAL_STATUS_ONLINE;
506      break;
507    case captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL:
508      state.status = CAPTIVE_PORTAL_STATUS_PORTAL;
509      break;
510    default:
511      break;
512  }
513
514  OnDetectionCompleted(network, state);
515  if (CanPerformAttempt() && strategy_->CanPerformAttemptAfterDetection())
516    ScheduleAttempt(base::TimeDelta());
517}
518
519void NetworkPortalDetectorImpl::Observe(
520    int type,
521    const content::NotificationSource& source,
522    const content::NotificationDetails& details) {
523  if (type == chrome::NOTIFICATION_LOGIN_PROXY_CHANGED ||
524      type == chrome::NOTIFICATION_AUTH_SUPPLIED ||
525      type == chrome::NOTIFICATION_AUTH_CANCELLED) {
526    VLOG(1) << "Restarting portal detection due to proxy change.";
527    attempt_count_ = 0;
528    if (is_portal_check_pending())
529      return;
530    StopDetection();
531    ScheduleAttempt(base::TimeDelta::FromSeconds(kProxyChangeDelaySec));
532  }
533}
534
535void NetworkPortalDetectorImpl::OnDetectionCompleted(
536    const NetworkState* network,
537    const CaptivePortalState& state) {
538  if (!network) {
539    NotifyDetectionCompleted(network, state);
540    return;
541  }
542
543  CaptivePortalStateMap::const_iterator it =
544      portal_state_map_.find(network->guid());
545  if (it == portal_state_map_.end() || it->second.status != state.status ||
546      it->second.response_code != state.response_code) {
547    VLOG(1) << "Updating Chrome Captive Portal state: "
548            << "name=" << network->name() << ", "
549            << "id=" << network->guid() << ", "
550            << "status=" << CaptivePortalStatusString(state.status) << ", "
551            << "response_code=" << state.response_code;
552
553    // Record detection duration iff detection result differs from the
554    // previous one for this network. The reason is to record all stats
555    // only when network changes it's state.
556    RecordDetectionStats(network, state.status);
557    if (it != portal_state_map_.end() &&
558        it->second.status == CAPTIVE_PORTAL_STATUS_PORTAL &&
559        state.status == CAPTIVE_PORTAL_STATUS_ONLINE) {
560      RecordPortalToOnlineTransition(state.time - it->second.time);
561    }
562
563    portal_state_map_[network->guid()] = state;
564  }
565  NotifyDetectionCompleted(network, state);
566}
567
568void NetworkPortalDetectorImpl::NotifyDetectionCompleted(
569    const NetworkState* network,
570    const CaptivePortalState& state) {
571  FOR_EACH_OBSERVER(
572      Observer, observers_, OnPortalDetectionCompleted(network, state));
573  notification_controller_.OnPortalDetectionCompleted(network, state);
574}
575
576bool NetworkPortalDetectorImpl::AttemptTimeoutIsCancelledForTesting() const {
577  return attempt_timeout_.IsCancelled();
578}
579
580void NetworkPortalDetectorImpl::RecordDetectionStats(
581    const NetworkState* network,
582    CaptivePortalStatus status) {
583  // Don't record stats for offline state.
584  if (!network)
585    return;
586
587  if (!detection_start_time_.is_null())
588    RecordDetectionDuration(GetCurrentTimeTicks() - detection_start_time_);
589  RecordDetectionResult(status);
590
591  switch (status) {
592    case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_UNKNOWN:
593      NOTREACHED();
594      break;
595    case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_OFFLINE:
596      if (network->connection_state() == shill::kStateOnline ||
597          network->connection_state() == shill::kStatePortal) {
598        RecordDiscrepancyWithShill(network, status);
599      }
600      break;
601    case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE:
602      if (network->connection_state() != shill::kStateOnline)
603        RecordDiscrepancyWithShill(network, status);
604      break;
605    case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PORTAL:
606      if (network->connection_state() != shill::kStatePortal)
607        RecordDiscrepancyWithShill(network, status);
608      break;
609    case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED:
610      if (network->connection_state() != shill::kStateOnline)
611        RecordDiscrepancyWithShill(network, status);
612      break;
613    case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT:
614      NOTREACHED();
615      break;
616  }
617}
618
619}  // namespace chromeos
620