blob: 14ebe9309a5b8f0c33fc34a5c388cec82627436a [file] [log] [blame]
Paul Stewarte6927402012-01-23 16:11:30 -08001// Copyright (c) 2012 The Chromium OS 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 "shill/portal_detector.h"
6
7#include <string>
8
Eric Shienbrood3e20a232012-02-16 11:35:56 -05009#include <base/bind.h>
Paul Stewarte6927402012-01-23 16:11:30 -080010#include <base/string_number_conversions.h>
11#include <base/string_util.h>
12#include <base/stringprintf.h>
13
14#include "shill/async_connection.h"
15#include "shill/connection.h"
16#include "shill/dns_client.h"
17#include "shill/event_dispatcher.h"
18#include "shill/http_url.h"
19#include "shill/ip_address.h"
Christopher Wileyb691efd2012-08-09 13:51:51 -070020#include "shill/logging.h"
Paul Stewarte6927402012-01-23 16:11:30 -080021#include "shill/sockets.h"
22
Eric Shienbrood3e20a232012-02-16 11:35:56 -050023using base::Bind;
24using base::Callback;
Paul Stewarte6927402012-01-23 16:11:30 -080025using base::StringPrintf;
26using std::string;
27
28namespace shill {
29
Paul Stewartc681fa02012-03-02 19:40:04 -080030const int PortalDetector::kDefaultCheckIntervalSeconds = 30;
Paul Stewartf555cf82012-03-15 14:42:43 -070031const char PortalDetector::kDefaultCheckPortalList[] = "ethernet,wifi,cellular";
Paul Stewarte6927402012-01-23 16:11:30 -080032const char PortalDetector::kDefaultURL[] =
Paul Stewarteae4f2b2013-04-09 07:55:31 -070033 "http://www.gstatic.com/generate_204";
Paul Stewart60519992012-09-10 17:45:01 -070034const char PortalDetector::kResponseExpected[] = "HTTP/?.? 204";
Paul Stewarte6927402012-01-23 16:11:30 -080035
36const int PortalDetector::kMaxRequestAttempts = 3;
37const int PortalDetector::kMinTimeBetweenAttemptsSeconds = 3;
38const int PortalDetector::kRequestTimeoutSeconds = 10;
Christopher Wiley6e1dc0f2012-10-17 15:38:56 -070039const int PortalDetector::kMaxFailuresInContentPhase = 2;
Paul Stewarte6927402012-01-23 16:11:30 -080040
41const char PortalDetector::kPhaseConnectionString[] = "Connection";
42const char PortalDetector::kPhaseDNSString[] = "DNS";
43const char PortalDetector::kPhaseHTTPString[] = "HTTP";
44const char PortalDetector::kPhaseContentString[] = "Content";
45const char PortalDetector::kPhaseUnknownString[] = "Unknown";
46
47const char PortalDetector::kStatusFailureString[] = "Failure";
48const char PortalDetector::kStatusSuccessString[] = "Success";
49const char PortalDetector::kStatusTimeoutString[] = "Timeout";
50
51PortalDetector::PortalDetector(
52 ConnectionRefPtr connection,
53 EventDispatcher *dispatcher,
Eric Shienbrood3e20a232012-02-16 11:35:56 -050054 const Callback<void(const Result &)> &callback)
Paul Stewarte6927402012-01-23 16:11:30 -080055 : attempt_count_(0),
Darin Petkove636c692012-05-31 10:22:17 +020056 attempt_start_time_((struct timeval){0}),
Paul Stewarte6927402012-01-23 16:11:30 -080057 connection_(connection),
58 dispatcher_(dispatcher),
Eric Shienbrood3e20a232012-02-16 11:35:56 -050059 weak_ptr_factory_(this),
Paul Stewarte6927402012-01-23 16:11:30 -080060 portal_result_callback_(callback),
61 request_read_callback_(
Eric Shienbrood9a245532012-03-07 14:20:39 -050062 Bind(&PortalDetector::RequestReadCallback,
63 weak_ptr_factory_.GetWeakPtr())),
Paul Stewarte6927402012-01-23 16:11:30 -080064 request_result_callback_(
Eric Shienbrood9a245532012-03-07 14:20:39 -050065 Bind(&PortalDetector::RequestResultCallback,
66 weak_ptr_factory_.GetWeakPtr())),
Christopher Wiley6e1dc0f2012-10-17 15:38:56 -070067 time_(Time::GetInstance()),
68 failures_in_content_phase_(0) { }
Paul Stewarte6927402012-01-23 16:11:30 -080069
70PortalDetector::~PortalDetector() {
71 Stop();
72}
73
Paul Stewartc681fa02012-03-02 19:40:04 -080074bool PortalDetector::Start(const string &url_string) {
75 return StartAfterDelay(url_string, 0);
76}
77
78bool PortalDetector::StartAfterDelay(const string &url_string,
79 int delay_seconds) {
Ben Chanfad4a0b2012-04-18 15:49:59 -070080 SLOG(Portal, 3) << "In " << __func__;
Paul Stewarte6927402012-01-23 16:11:30 -080081
82 DCHECK(!request_.get());
83
84 if (!url_.ParseFromString(url_string)) {
85 LOG(ERROR) << "Failed to parse URL string: " << url_string;
86 return false;
87 }
88
89 request_.reset(new HTTPRequest(connection_, dispatcher_, &sockets_));
90 attempt_count_ = 0;
Christopher Wiley6e1dc0f2012-10-17 15:38:56 -070091 // If we're starting a new set of attempts, discard past failure history.
92 failures_in_content_phase_ = 0;
Paul Stewartc681fa02012-03-02 19:40:04 -080093 StartAttempt(delay_seconds);
Paul Stewarte6927402012-01-23 16:11:30 -080094 return true;
95}
96
97void PortalDetector::Stop() {
Ben Chanfad4a0b2012-04-18 15:49:59 -070098 SLOG(Portal, 3) << "In " << __func__;
Paul Stewarte6927402012-01-23 16:11:30 -080099
100 if (!request_.get()) {
101 return;
102 }
103
Paul Stewartf582b502012-04-04 21:39:22 -0700104 start_attempt_.Cancel();
Paul Stewarte6927402012-01-23 16:11:30 -0800105 StopAttempt();
106 attempt_count_ = 0;
Christopher Wiley6e1dc0f2012-10-17 15:38:56 -0700107 failures_in_content_phase_ = 0;
Paul Stewarte6927402012-01-23 16:11:30 -0800108 request_.reset();
109}
110
Paul Stewartc681fa02012-03-02 19:40:04 -0800111bool PortalDetector::IsInProgress() {
112 return attempt_count_ != 0;
113}
114
Paul Stewarte6927402012-01-23 16:11:30 -0800115// static
116const string PortalDetector::PhaseToString(Phase phase) {
117 switch (phase) {
118 case kPhaseConnection:
119 return kPhaseConnectionString;
120 case kPhaseDNS:
121 return kPhaseDNSString;
122 case kPhaseHTTP:
123 return kPhaseHTTPString;
124 case kPhaseContent:
125 return kPhaseContentString;
126 case kPhaseUnknown:
127 default:
128 return kPhaseUnknownString;
129 }
130}
131
132// static
133const string PortalDetector::StatusToString(Status status) {
134 switch (status) {
135 case kStatusSuccess:
136 return kStatusSuccessString;
137 case kStatusTimeout:
138 return kStatusTimeoutString;
139 case kStatusFailure:
140 default:
141 return kStatusFailureString;
142 }
143}
144
145void PortalDetector::CompleteAttempt(Result result) {
146 LOG(INFO) << StringPrintf("Portal detection completed attempt %d with "
Christopher Wiley6e1dc0f2012-10-17 15:38:56 -0700147 "phase==%s, status==%s, failures in content==%d",
Paul Stewarte6927402012-01-23 16:11:30 -0800148 attempt_count_,
149 PhaseToString(result.phase).c_str(),
Christopher Wiley6e1dc0f2012-10-17 15:38:56 -0700150 StatusToString(result.status).c_str(),
151 failures_in_content_phase_);
Paul Stewarte6927402012-01-23 16:11:30 -0800152 StopAttempt();
Christopher Wiley6e1dc0f2012-10-17 15:38:56 -0700153
154 if (result.status == kStatusFailure &&
155 result.phase == kPhaseContent) {
156 failures_in_content_phase_++;
157 }
158 if (result.status == kStatusSuccess ||
159 attempt_count_ >= kMaxRequestAttempts ||
160 failures_in_content_phase_ >= kMaxFailuresInContentPhase) {
Thieu Le85e050b2012-03-13 15:04:38 -0700161 result.num_attempts = attempt_count_;
Paul Stewarte6927402012-01-23 16:11:30 -0800162 result.final = true;
163 Stop();
Christopher Wiley6e1dc0f2012-10-17 15:38:56 -0700164 } else {
165 StartAttempt(0);
Paul Stewarte6927402012-01-23 16:11:30 -0800166 }
167
Eric Shienbrood3e20a232012-02-16 11:35:56 -0500168 portal_result_callback_.Run(result);
Paul Stewarte6927402012-01-23 16:11:30 -0800169}
170
171PortalDetector::Result PortalDetector::GetPortalResultForRequestResult(
172 HTTPRequest::Result result) {
173 switch (result) {
174 case HTTPRequest::kResultSuccess:
175 // The request completed without receiving the expected payload.
176 return Result(kPhaseContent, kStatusFailure);
177 case HTTPRequest::kResultDNSFailure:
178 return Result(kPhaseDNS, kStatusFailure);
179 case HTTPRequest::kResultDNSTimeout:
180 return Result(kPhaseDNS, kStatusTimeout);
181 case HTTPRequest::kResultConnectionFailure:
182 return Result(kPhaseConnection, kStatusFailure);
183 case HTTPRequest::kResultConnectionTimeout:
184 return Result(kPhaseConnection, kStatusTimeout);
185 case HTTPRequest::kResultRequestFailure:
186 case HTTPRequest::kResultResponseFailure:
187 return Result(kPhaseHTTP, kStatusFailure);
188 case HTTPRequest::kResultRequestTimeout:
189 case HTTPRequest::kResultResponseTimeout:
190 return Result(kPhaseHTTP, kStatusTimeout);
191 case HTTPRequest::kResultUnknown:
192 default:
193 return Result(kPhaseUnknown, kStatusFailure);
194 }
195}
196
Paul Stewartbdb02e62012-02-22 16:24:33 -0800197void PortalDetector::RequestReadCallback(const ByteString &response_data) {
Paul Stewarte6927402012-01-23 16:11:30 -0800198 const string response_expected(kResponseExpected);
Paul Stewarte6927402012-01-23 16:11:30 -0800199 bool expected_length_received = false;
200 int compare_length = 0;
201 if (response_data.GetLength() < response_expected.length()) {
202 // There isn't enough data yet for a final decision, but we can still
203 // test to see if the partial string matches so far.
204 expected_length_received = false;
205 compare_length = response_data.GetLength();
206 } else {
207 expected_length_received = true;
208 compare_length = response_expected.length();
209 }
210
Paul Stewart60519992012-09-10 17:45:01 -0700211 if (MatchPattern(string(reinterpret_cast<const char *>(
212 response_data.GetConstData()),
213 compare_length),
214 response_expected.substr(0, compare_length))) {
Paul Stewarte6927402012-01-23 16:11:30 -0800215 if (expected_length_received) {
216 CompleteAttempt(Result(kPhaseContent, kStatusSuccess));
217 }
218 // Otherwise, we wait for more data from the server.
219 } else {
220 CompleteAttempt(Result(kPhaseContent, kStatusFailure));
221 }
222}
223
Paul Stewartbdb02e62012-02-22 16:24:33 -0800224void PortalDetector::RequestResultCallback(
225 HTTPRequest::Result result, const ByteString &/*response_data*/) {
Paul Stewarte6927402012-01-23 16:11:30 -0800226 CompleteAttempt(GetPortalResultForRequestResult(result));
227}
228
Paul Stewartc681fa02012-03-02 19:40:04 -0800229void PortalDetector::StartAttempt(int init_delay_seconds) {
Paul Stewarte6927402012-01-23 16:11:30 -0800230 int64 next_attempt_delay = 0;
231 if (attempt_count_ > 0) {
232 // Ensure that attempts are spaced at least by a minimal interval.
233 struct timeval now, elapsed_time;
234 time_->GetTimeMonotonic(&now);
235 timersub(&now, &attempt_start_time_, &elapsed_time);
236
237 if (elapsed_time.tv_sec < kMinTimeBetweenAttemptsSeconds) {
238 struct timeval remaining_time = { kMinTimeBetweenAttemptsSeconds, 0 };
239 timersub(&remaining_time, &elapsed_time, &remaining_time);
240 next_attempt_delay =
241 remaining_time.tv_sec * 1000 + remaining_time.tv_usec / 1000;
242 }
Paul Stewartc681fa02012-03-02 19:40:04 -0800243 } else {
244 next_attempt_delay = init_delay_seconds * 1000;
Paul Stewarte6927402012-01-23 16:11:30 -0800245 }
Paul Stewartf582b502012-04-04 21:39:22 -0700246
247 start_attempt_.Reset(Bind(&PortalDetector::StartAttemptTask,
248 weak_ptr_factory_.GetWeakPtr()));
249 dispatcher_->PostDelayedTask(start_attempt_.callback(), next_attempt_delay);
Paul Stewarte6927402012-01-23 16:11:30 -0800250}
251
252void PortalDetector::StartAttemptTask() {
253 time_->GetTimeMonotonic(&attempt_start_time_);
254 ++attempt_count_;
255
Paul Stewart20088d82012-02-16 06:58:55 -0800256 LOG(INFO) << StringPrintf("Portal detection starting attempt %d of %d",
Paul Stewarte6927402012-01-23 16:11:30 -0800257 attempt_count_, kMaxRequestAttempts);
258
259 HTTPRequest::Result result =
Eric Shienbrood3e20a232012-02-16 11:35:56 -0500260 request_->Start(url_, request_read_callback_, request_result_callback_);
Paul Stewarte6927402012-01-23 16:11:30 -0800261 if (result != HTTPRequest::kResultInProgress) {
262 CompleteAttempt(GetPortalResultForRequestResult(result));
263 return;
264 }
265
Paul Stewartf582b502012-04-04 21:39:22 -0700266 attempt_timeout_.Reset(Bind(&PortalDetector::TimeoutAttemptTask,
267 weak_ptr_factory_.GetWeakPtr()));
268 dispatcher_->PostDelayedTask(attempt_timeout_.callback(),
269 kRequestTimeoutSeconds * 1000);
Paul Stewarte6927402012-01-23 16:11:30 -0800270}
271
272void PortalDetector::StopAttempt() {
273 request_->Stop();
Paul Stewartf582b502012-04-04 21:39:22 -0700274 attempt_timeout_.Cancel();
Paul Stewarte6927402012-01-23 16:11:30 -0800275}
276
277void PortalDetector::TimeoutAttemptTask() {
278 LOG(ERROR) << "Request timed out";
279 if (request_->response_data().GetLength()) {
280 CompleteAttempt(Result(kPhaseContent, kStatusTimeout));
281 } else {
282 CompleteAttempt(Result(kPhaseUnknown, kStatusTimeout));
283 }
284}
285
286} // namespace shill