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