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