blob: f8dff0a2fde12dbd44fd04f356e71432d6db5b84 [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/logging.h>
11#include <base/string_number_conversions.h>
12#include <base/string_util.h>
13#include <base/stringprintf.h>
14
15#include "shill/async_connection.h"
16#include "shill/connection.h"
17#include "shill/dns_client.h"
18#include "shill/event_dispatcher.h"
19#include "shill/http_url.h"
20#include "shill/ip_address.h"
Ben Chanfad4a0b2012-04-18 15:49:59 -070021#include "shill/scope_logger.h"
Paul Stewarte6927402012-01-23 16:11:30 -080022#include "shill/sockets.h"
23
Eric Shienbrood3e20a232012-02-16 11:35:56 -050024using base::Bind;
25using base::Callback;
Paul Stewarte6927402012-01-23 16:11:30 -080026using base::StringPrintf;
27using std::string;
28
29namespace shill {
30
Paul Stewartc681fa02012-03-02 19:40:04 -080031const int PortalDetector::kDefaultCheckIntervalSeconds = 30;
Paul Stewartf555cf82012-03-15 14:42:43 -070032const char PortalDetector::kDefaultCheckPortalList[] = "ethernet,wifi,cellular";
Paul Stewarte6927402012-01-23 16:11:30 -080033const char PortalDetector::kDefaultURL[] =
34 "http://clients3.google.com/generate_204";
35const char PortalDetector::kResponseExpected[] = "HTTP/1.1 204";
36
37const int PortalDetector::kMaxRequestAttempts = 3;
38const int PortalDetector::kMinTimeBetweenAttemptsSeconds = 3;
39const int PortalDetector::kRequestTimeoutSeconds = 10;
40
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())),
Paul Stewarte6927402012-01-23 16:11:30 -080067 time_(Time::GetInstance()) { }
68
69PortalDetector::~PortalDetector() {
70 Stop();
71}
72
Paul Stewartc681fa02012-03-02 19:40:04 -080073bool PortalDetector::Start(const string &url_string) {
74 return StartAfterDelay(url_string, 0);
75}
76
77bool PortalDetector::StartAfterDelay(const string &url_string,
78 int delay_seconds) {
Ben Chanfad4a0b2012-04-18 15:49:59 -070079 SLOG(Portal, 3) << "In " << __func__;
Paul Stewarte6927402012-01-23 16:11:30 -080080
81 DCHECK(!request_.get());
82
83 if (!url_.ParseFromString(url_string)) {
84 LOG(ERROR) << "Failed to parse URL string: " << url_string;
85 return false;
86 }
87
88 request_.reset(new HTTPRequest(connection_, dispatcher_, &sockets_));
89 attempt_count_ = 0;
Paul Stewartc681fa02012-03-02 19:40:04 -080090 StartAttempt(delay_seconds);
Paul Stewarte6927402012-01-23 16:11:30 -080091 return true;
92}
93
94void PortalDetector::Stop() {
Ben Chanfad4a0b2012-04-18 15:49:59 -070095 SLOG(Portal, 3) << "In " << __func__;
Paul Stewarte6927402012-01-23 16:11:30 -080096
97 if (!request_.get()) {
98 return;
99 }
100
Paul Stewartf582b502012-04-04 21:39:22 -0700101 start_attempt_.Cancel();
Paul Stewarte6927402012-01-23 16:11:30 -0800102 StopAttempt();
103 attempt_count_ = 0;
104 request_.reset();
105}
106
Paul Stewartc681fa02012-03-02 19:40:04 -0800107bool PortalDetector::IsInProgress() {
108 return attempt_count_ != 0;
109}
110
Paul Stewarte6927402012-01-23 16:11:30 -0800111// static
112const string PortalDetector::PhaseToString(Phase phase) {
113 switch (phase) {
114 case kPhaseConnection:
115 return kPhaseConnectionString;
116 case kPhaseDNS:
117 return kPhaseDNSString;
118 case kPhaseHTTP:
119 return kPhaseHTTPString;
120 case kPhaseContent:
121 return kPhaseContentString;
122 case kPhaseUnknown:
123 default:
124 return kPhaseUnknownString;
125 }
126}
127
128// static
129const string PortalDetector::StatusToString(Status status) {
130 switch (status) {
131 case kStatusSuccess:
132 return kStatusSuccessString;
133 case kStatusTimeout:
134 return kStatusTimeoutString;
135 case kStatusFailure:
136 default:
137 return kStatusFailureString;
138 }
139}
140
141void PortalDetector::CompleteAttempt(Result result) {
142 LOG(INFO) << StringPrintf("Portal detection completed attempt %d with "
143 "phase==%s, status==%s",
144 attempt_count_,
145 PhaseToString(result.phase).c_str(),
146 StatusToString(result.status).c_str());
147 StopAttempt();
148 if (result.status != kStatusSuccess && attempt_count_ < kMaxRequestAttempts) {
Paul Stewartc681fa02012-03-02 19:40:04 -0800149 StartAttempt(0);
Paul Stewarte6927402012-01-23 16:11:30 -0800150 } else {
Thieu Le85e050b2012-03-13 15:04:38 -0700151 result.num_attempts = attempt_count_;
Paul Stewarte6927402012-01-23 16:11:30 -0800152 result.final = true;
153 Stop();
154 }
155
Eric Shienbrood3e20a232012-02-16 11:35:56 -0500156 portal_result_callback_.Run(result);
Paul Stewarte6927402012-01-23 16:11:30 -0800157}
158
159PortalDetector::Result PortalDetector::GetPortalResultForRequestResult(
160 HTTPRequest::Result result) {
161 switch (result) {
162 case HTTPRequest::kResultSuccess:
163 // The request completed without receiving the expected payload.
164 return Result(kPhaseContent, kStatusFailure);
165 case HTTPRequest::kResultDNSFailure:
166 return Result(kPhaseDNS, kStatusFailure);
167 case HTTPRequest::kResultDNSTimeout:
168 return Result(kPhaseDNS, kStatusTimeout);
169 case HTTPRequest::kResultConnectionFailure:
170 return Result(kPhaseConnection, kStatusFailure);
171 case HTTPRequest::kResultConnectionTimeout:
172 return Result(kPhaseConnection, kStatusTimeout);
173 case HTTPRequest::kResultRequestFailure:
174 case HTTPRequest::kResultResponseFailure:
175 return Result(kPhaseHTTP, kStatusFailure);
176 case HTTPRequest::kResultRequestTimeout:
177 case HTTPRequest::kResultResponseTimeout:
178 return Result(kPhaseHTTP, kStatusTimeout);
179 case HTTPRequest::kResultUnknown:
180 default:
181 return Result(kPhaseUnknown, kStatusFailure);
182 }
183}
184
Paul Stewartbdb02e62012-02-22 16:24:33 -0800185void PortalDetector::RequestReadCallback(const ByteString &response_data) {
Paul Stewarte6927402012-01-23 16:11:30 -0800186 const string response_expected(kResponseExpected);
Paul Stewarte6927402012-01-23 16:11:30 -0800187 bool expected_length_received = false;
188 int compare_length = 0;
189 if (response_data.GetLength() < response_expected.length()) {
190 // There isn't enough data yet for a final decision, but we can still
191 // test to see if the partial string matches so far.
192 expected_length_received = false;
193 compare_length = response_data.GetLength();
194 } else {
195 expected_length_received = true;
196 compare_length = response_expected.length();
197 }
198
199 if (ByteString(response_expected.substr(0, compare_length), false).Equals(
200 ByteString(response_data.GetConstData(), compare_length))) {
201 if (expected_length_received) {
202 CompleteAttempt(Result(kPhaseContent, kStatusSuccess));
203 }
204 // Otherwise, we wait for more data from the server.
205 } else {
206 CompleteAttempt(Result(kPhaseContent, kStatusFailure));
207 }
208}
209
Paul Stewartbdb02e62012-02-22 16:24:33 -0800210void PortalDetector::RequestResultCallback(
211 HTTPRequest::Result result, const ByteString &/*response_data*/) {
Paul Stewarte6927402012-01-23 16:11:30 -0800212 CompleteAttempt(GetPortalResultForRequestResult(result));
213}
214
Paul Stewartc681fa02012-03-02 19:40:04 -0800215void PortalDetector::StartAttempt(int init_delay_seconds) {
Paul Stewarte6927402012-01-23 16:11:30 -0800216 int64 next_attempt_delay = 0;
217 if (attempt_count_ > 0) {
218 // Ensure that attempts are spaced at least by a minimal interval.
219 struct timeval now, elapsed_time;
220 time_->GetTimeMonotonic(&now);
221 timersub(&now, &attempt_start_time_, &elapsed_time);
222
223 if (elapsed_time.tv_sec < kMinTimeBetweenAttemptsSeconds) {
224 struct timeval remaining_time = { kMinTimeBetweenAttemptsSeconds, 0 };
225 timersub(&remaining_time, &elapsed_time, &remaining_time);
226 next_attempt_delay =
227 remaining_time.tv_sec * 1000 + remaining_time.tv_usec / 1000;
228 }
Paul Stewartc681fa02012-03-02 19:40:04 -0800229 } else {
230 next_attempt_delay = init_delay_seconds * 1000;
Paul Stewarte6927402012-01-23 16:11:30 -0800231 }
Paul Stewartf582b502012-04-04 21:39:22 -0700232
233 start_attempt_.Reset(Bind(&PortalDetector::StartAttemptTask,
234 weak_ptr_factory_.GetWeakPtr()));
235 dispatcher_->PostDelayedTask(start_attempt_.callback(), next_attempt_delay);
Paul Stewarte6927402012-01-23 16:11:30 -0800236}
237
238void PortalDetector::StartAttemptTask() {
239 time_->GetTimeMonotonic(&attempt_start_time_);
240 ++attempt_count_;
241
Paul Stewart20088d82012-02-16 06:58:55 -0800242 LOG(INFO) << StringPrintf("Portal detection starting attempt %d of %d",
Paul Stewarte6927402012-01-23 16:11:30 -0800243 attempt_count_, kMaxRequestAttempts);
244
245 HTTPRequest::Result result =
Eric Shienbrood3e20a232012-02-16 11:35:56 -0500246 request_->Start(url_, request_read_callback_, request_result_callback_);
Paul Stewarte6927402012-01-23 16:11:30 -0800247 if (result != HTTPRequest::kResultInProgress) {
248 CompleteAttempt(GetPortalResultForRequestResult(result));
249 return;
250 }
251
Paul Stewartf582b502012-04-04 21:39:22 -0700252 attempt_timeout_.Reset(Bind(&PortalDetector::TimeoutAttemptTask,
253 weak_ptr_factory_.GetWeakPtr()));
254 dispatcher_->PostDelayedTask(attempt_timeout_.callback(),
255 kRequestTimeoutSeconds * 1000);
Paul Stewarte6927402012-01-23 16:11:30 -0800256}
257
258void PortalDetector::StopAttempt() {
259 request_->Stop();
Paul Stewartf582b502012-04-04 21:39:22 -0700260 attempt_timeout_.Cancel();
Paul Stewarte6927402012-01-23 16:11:30 -0800261}
262
263void PortalDetector::TimeoutAttemptTask() {
264 LOG(ERROR) << "Request timed out";
265 if (request_->response_data().GetLength()) {
266 CompleteAttempt(Result(kPhaseContent, kStatusTimeout));
267 } else {
268 CompleteAttempt(Result(kPhaseUnknown, kStatusTimeout));
269 }
270}
271
272} // namespace shill