blob: 7faf7c3e1ac80c576d2f16fdc2f75995787e669c [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),
56 connection_(connection),
57 dispatcher_(dispatcher),
Eric Shienbrood3e20a232012-02-16 11:35:56 -050058 weak_ptr_factory_(this),
Paul Stewarte6927402012-01-23 16:11:30 -080059 portal_result_callback_(callback),
60 request_read_callback_(
Eric Shienbrood9a245532012-03-07 14:20:39 -050061 Bind(&PortalDetector::RequestReadCallback,
62 weak_ptr_factory_.GetWeakPtr())),
Paul Stewarte6927402012-01-23 16:11:30 -080063 request_result_callback_(
Eric Shienbrood9a245532012-03-07 14:20:39 -050064 Bind(&PortalDetector::RequestResultCallback,
65 weak_ptr_factory_.GetWeakPtr())),
Paul Stewarte6927402012-01-23 16:11:30 -080066 time_(Time::GetInstance()) { }
67
68PortalDetector::~PortalDetector() {
69 Stop();
70}
71
Paul Stewartc681fa02012-03-02 19:40:04 -080072bool PortalDetector::Start(const string &url_string) {
73 return StartAfterDelay(url_string, 0);
74}
75
76bool PortalDetector::StartAfterDelay(const string &url_string,
77 int delay_seconds) {
Ben Chanfad4a0b2012-04-18 15:49:59 -070078 SLOG(Portal, 3) << "In " << __func__;
Paul Stewarte6927402012-01-23 16:11:30 -080079
80 DCHECK(!request_.get());
81
82 if (!url_.ParseFromString(url_string)) {
83 LOG(ERROR) << "Failed to parse URL string: " << url_string;
84 return false;
85 }
86
87 request_.reset(new HTTPRequest(connection_, dispatcher_, &sockets_));
88 attempt_count_ = 0;
Paul Stewartc681fa02012-03-02 19:40:04 -080089 StartAttempt(delay_seconds);
Paul Stewarte6927402012-01-23 16:11:30 -080090 return true;
91}
92
93void PortalDetector::Stop() {
Ben Chanfad4a0b2012-04-18 15:49:59 -070094 SLOG(Portal, 3) << "In " << __func__;
Paul Stewarte6927402012-01-23 16:11:30 -080095
96 if (!request_.get()) {
97 return;
98 }
99
Paul Stewartf582b502012-04-04 21:39:22 -0700100 start_attempt_.Cancel();
Paul Stewarte6927402012-01-23 16:11:30 -0800101 StopAttempt();
102 attempt_count_ = 0;
103 request_.reset();
104}
105
Paul Stewartc681fa02012-03-02 19:40:04 -0800106bool PortalDetector::IsInProgress() {
107 return attempt_count_ != 0;
108}
109
Paul Stewarte6927402012-01-23 16:11:30 -0800110// static
111const string PortalDetector::PhaseToString(Phase phase) {
112 switch (phase) {
113 case kPhaseConnection:
114 return kPhaseConnectionString;
115 case kPhaseDNS:
116 return kPhaseDNSString;
117 case kPhaseHTTP:
118 return kPhaseHTTPString;
119 case kPhaseContent:
120 return kPhaseContentString;
121 case kPhaseUnknown:
122 default:
123 return kPhaseUnknownString;
124 }
125}
126
127// static
128const string PortalDetector::StatusToString(Status status) {
129 switch (status) {
130 case kStatusSuccess:
131 return kStatusSuccessString;
132 case kStatusTimeout:
133 return kStatusTimeoutString;
134 case kStatusFailure:
135 default:
136 return kStatusFailureString;
137 }
138}
139
140void PortalDetector::CompleteAttempt(Result result) {
141 LOG(INFO) << StringPrintf("Portal detection completed attempt %d with "
142 "phase==%s, status==%s",
143 attempt_count_,
144 PhaseToString(result.phase).c_str(),
145 StatusToString(result.status).c_str());
146 StopAttempt();
147 if (result.status != kStatusSuccess && attempt_count_ < kMaxRequestAttempts) {
Paul Stewartc681fa02012-03-02 19:40:04 -0800148 StartAttempt(0);
Paul Stewarte6927402012-01-23 16:11:30 -0800149 } else {
Thieu Le85e050b2012-03-13 15:04:38 -0700150 result.num_attempts = attempt_count_;
Paul Stewarte6927402012-01-23 16:11:30 -0800151 result.final = true;
152 Stop();
153 }
154
Eric Shienbrood3e20a232012-02-16 11:35:56 -0500155 portal_result_callback_.Run(result);
Paul Stewarte6927402012-01-23 16:11:30 -0800156}
157
158PortalDetector::Result PortalDetector::GetPortalResultForRequestResult(
159 HTTPRequest::Result result) {
160 switch (result) {
161 case HTTPRequest::kResultSuccess:
162 // The request completed without receiving the expected payload.
163 return Result(kPhaseContent, kStatusFailure);
164 case HTTPRequest::kResultDNSFailure:
165 return Result(kPhaseDNS, kStatusFailure);
166 case HTTPRequest::kResultDNSTimeout:
167 return Result(kPhaseDNS, kStatusTimeout);
168 case HTTPRequest::kResultConnectionFailure:
169 return Result(kPhaseConnection, kStatusFailure);
170 case HTTPRequest::kResultConnectionTimeout:
171 return Result(kPhaseConnection, kStatusTimeout);
172 case HTTPRequest::kResultRequestFailure:
173 case HTTPRequest::kResultResponseFailure:
174 return Result(kPhaseHTTP, kStatusFailure);
175 case HTTPRequest::kResultRequestTimeout:
176 case HTTPRequest::kResultResponseTimeout:
177 return Result(kPhaseHTTP, kStatusTimeout);
178 case HTTPRequest::kResultUnknown:
179 default:
180 return Result(kPhaseUnknown, kStatusFailure);
181 }
182}
183
Paul Stewartbdb02e62012-02-22 16:24:33 -0800184void PortalDetector::RequestReadCallback(const ByteString &response_data) {
Paul Stewarte6927402012-01-23 16:11:30 -0800185 const string response_expected(kResponseExpected);
Paul Stewarte6927402012-01-23 16:11:30 -0800186 bool expected_length_received = false;
187 int compare_length = 0;
188 if (response_data.GetLength() < response_expected.length()) {
189 // There isn't enough data yet for a final decision, but we can still
190 // test to see if the partial string matches so far.
191 expected_length_received = false;
192 compare_length = response_data.GetLength();
193 } else {
194 expected_length_received = true;
195 compare_length = response_expected.length();
196 }
197
198 if (ByteString(response_expected.substr(0, compare_length), false).Equals(
199 ByteString(response_data.GetConstData(), compare_length))) {
200 if (expected_length_received) {
201 CompleteAttempt(Result(kPhaseContent, kStatusSuccess));
202 }
203 // Otherwise, we wait for more data from the server.
204 } else {
205 CompleteAttempt(Result(kPhaseContent, kStatusFailure));
206 }
207}
208
Paul Stewartbdb02e62012-02-22 16:24:33 -0800209void PortalDetector::RequestResultCallback(
210 HTTPRequest::Result result, const ByteString &/*response_data*/) {
Paul Stewarte6927402012-01-23 16:11:30 -0800211 CompleteAttempt(GetPortalResultForRequestResult(result));
212}
213
Paul Stewartc681fa02012-03-02 19:40:04 -0800214void PortalDetector::StartAttempt(int init_delay_seconds) {
Paul Stewarte6927402012-01-23 16:11:30 -0800215 int64 next_attempt_delay = 0;
216 if (attempt_count_ > 0) {
217 // Ensure that attempts are spaced at least by a minimal interval.
218 struct timeval now, elapsed_time;
219 time_->GetTimeMonotonic(&now);
220 timersub(&now, &attempt_start_time_, &elapsed_time);
221
222 if (elapsed_time.tv_sec < kMinTimeBetweenAttemptsSeconds) {
223 struct timeval remaining_time = { kMinTimeBetweenAttemptsSeconds, 0 };
224 timersub(&remaining_time, &elapsed_time, &remaining_time);
225 next_attempt_delay =
226 remaining_time.tv_sec * 1000 + remaining_time.tv_usec / 1000;
227 }
Paul Stewartc681fa02012-03-02 19:40:04 -0800228 } else {
229 next_attempt_delay = init_delay_seconds * 1000;
Paul Stewarte6927402012-01-23 16:11:30 -0800230 }
Paul Stewartf582b502012-04-04 21:39:22 -0700231
232 start_attempt_.Reset(Bind(&PortalDetector::StartAttemptTask,
233 weak_ptr_factory_.GetWeakPtr()));
234 dispatcher_->PostDelayedTask(start_attempt_.callback(), next_attempt_delay);
Paul Stewarte6927402012-01-23 16:11:30 -0800235}
236
237void PortalDetector::StartAttemptTask() {
238 time_->GetTimeMonotonic(&attempt_start_time_);
239 ++attempt_count_;
240
Paul Stewart20088d82012-02-16 06:58:55 -0800241 LOG(INFO) << StringPrintf("Portal detection starting attempt %d of %d",
Paul Stewarte6927402012-01-23 16:11:30 -0800242 attempt_count_, kMaxRequestAttempts);
243
244 HTTPRequest::Result result =
Eric Shienbrood3e20a232012-02-16 11:35:56 -0500245 request_->Start(url_, request_read_callback_, request_result_callback_);
Paul Stewarte6927402012-01-23 16:11:30 -0800246 if (result != HTTPRequest::kResultInProgress) {
247 CompleteAttempt(GetPortalResultForRequestResult(result));
248 return;
249 }
250
Paul Stewartf582b502012-04-04 21:39:22 -0700251 attempt_timeout_.Reset(Bind(&PortalDetector::TimeoutAttemptTask,
252 weak_ptr_factory_.GetWeakPtr()));
253 dispatcher_->PostDelayedTask(attempt_timeout_.callback(),
254 kRequestTimeoutSeconds * 1000);
Paul Stewarte6927402012-01-23 16:11:30 -0800255}
256
257void PortalDetector::StopAttempt() {
258 request_->Stop();
Paul Stewartf582b502012-04-04 21:39:22 -0700259 attempt_timeout_.Cancel();
Paul Stewarte6927402012-01-23 16:11:30 -0800260}
261
262void PortalDetector::TimeoutAttemptTask() {
263 LOG(ERROR) << "Request timed out";
264 if (request_->response_data().GetLength()) {
265 CompleteAttempt(Result(kPhaseContent, kStatusTimeout));
266 } else {
267 CompleteAttempt(Result(kPhaseUnknown, kStatusTimeout));
268 }
269}
270
271} // namespace shill