blob: 65767f6ad33185e2f39600630c234d0c1f47650d [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"
21#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[] =
33 "http://clients3.google.com/generate_204";
34const char PortalDetector::kResponseExpected[] = "HTTP/1.1 204";
35
36const int PortalDetector::kMaxRequestAttempts = 3;
37const int PortalDetector::kMinTimeBetweenAttemptsSeconds = 3;
38const int PortalDetector::kRequestTimeoutSeconds = 10;
39
40const char PortalDetector::kPhaseConnectionString[] = "Connection";
41const char PortalDetector::kPhaseDNSString[] = "DNS";
42const char PortalDetector::kPhaseHTTPString[] = "HTTP";
43const char PortalDetector::kPhaseContentString[] = "Content";
44const char PortalDetector::kPhaseUnknownString[] = "Unknown";
45
46const char PortalDetector::kStatusFailureString[] = "Failure";
47const char PortalDetector::kStatusSuccessString[] = "Success";
48const char PortalDetector::kStatusTimeoutString[] = "Timeout";
49
50PortalDetector::PortalDetector(
51 ConnectionRefPtr connection,
52 EventDispatcher *dispatcher,
Eric Shienbrood3e20a232012-02-16 11:35:56 -050053 const Callback<void(const Result &)> &callback)
Paul Stewarte6927402012-01-23 16:11:30 -080054 : attempt_count_(0),
55 connection_(connection),
56 dispatcher_(dispatcher),
Eric Shienbrood3e20a232012-02-16 11:35:56 -050057 weak_ptr_factory_(this),
Paul Stewarte6927402012-01-23 16:11:30 -080058 portal_result_callback_(callback),
59 request_read_callback_(
Eric Shienbrood9a245532012-03-07 14:20:39 -050060 Bind(&PortalDetector::RequestReadCallback,
61 weak_ptr_factory_.GetWeakPtr())),
Paul Stewarte6927402012-01-23 16:11:30 -080062 request_result_callback_(
Eric Shienbrood9a245532012-03-07 14:20:39 -050063 Bind(&PortalDetector::RequestResultCallback,
64 weak_ptr_factory_.GetWeakPtr())),
Paul Stewarte6927402012-01-23 16:11:30 -080065 time_(Time::GetInstance()) { }
66
67PortalDetector::~PortalDetector() {
68 Stop();
69}
70
Paul Stewartc681fa02012-03-02 19:40:04 -080071bool PortalDetector::Start(const string &url_string) {
72 return StartAfterDelay(url_string, 0);
73}
74
75bool PortalDetector::StartAfterDelay(const string &url_string,
76 int delay_seconds) {
Paul Stewarte6927402012-01-23 16:11:30 -080077 VLOG(3) << "In " << __func__;
78
79 DCHECK(!request_.get());
80
81 if (!url_.ParseFromString(url_string)) {
82 LOG(ERROR) << "Failed to parse URL string: " << url_string;
83 return false;
84 }
85
86 request_.reset(new HTTPRequest(connection_, dispatcher_, &sockets_));
87 attempt_count_ = 0;
Paul Stewartc681fa02012-03-02 19:40:04 -080088 StartAttempt(delay_seconds);
Paul Stewarte6927402012-01-23 16:11:30 -080089 return true;
90}
91
92void PortalDetector::Stop() {
93 VLOG(3) << "In " << __func__;
94
95 if (!request_.get()) {
96 return;
97 }
98
Paul Stewartf582b502012-04-04 21:39:22 -070099 start_attempt_.Cancel();
Paul Stewarte6927402012-01-23 16:11:30 -0800100 StopAttempt();
101 attempt_count_ = 0;
102 request_.reset();
103}
104
Paul Stewartc681fa02012-03-02 19:40:04 -0800105bool PortalDetector::IsInProgress() {
106 return attempt_count_ != 0;
107}
108
Paul Stewarte6927402012-01-23 16:11:30 -0800109// static
110const string PortalDetector::PhaseToString(Phase phase) {
111 switch (phase) {
112 case kPhaseConnection:
113 return kPhaseConnectionString;
114 case kPhaseDNS:
115 return kPhaseDNSString;
116 case kPhaseHTTP:
117 return kPhaseHTTPString;
118 case kPhaseContent:
119 return kPhaseContentString;
120 case kPhaseUnknown:
121 default:
122 return kPhaseUnknownString;
123 }
124}
125
126// static
127const string PortalDetector::StatusToString(Status status) {
128 switch (status) {
129 case kStatusSuccess:
130 return kStatusSuccessString;
131 case kStatusTimeout:
132 return kStatusTimeoutString;
133 case kStatusFailure:
134 default:
135 return kStatusFailureString;
136 }
137}
138
139void PortalDetector::CompleteAttempt(Result result) {
140 LOG(INFO) << StringPrintf("Portal detection completed attempt %d with "
141 "phase==%s, status==%s",
142 attempt_count_,
143 PhaseToString(result.phase).c_str(),
144 StatusToString(result.status).c_str());
145 StopAttempt();
146 if (result.status != kStatusSuccess && attempt_count_ < kMaxRequestAttempts) {
Paul Stewartc681fa02012-03-02 19:40:04 -0800147 StartAttempt(0);
Paul Stewarte6927402012-01-23 16:11:30 -0800148 } else {
Thieu Le85e050b2012-03-13 15:04:38 -0700149 result.num_attempts = attempt_count_;
Paul Stewarte6927402012-01-23 16:11:30 -0800150 result.final = true;
151 Stop();
152 }
153
Eric Shienbrood3e20a232012-02-16 11:35:56 -0500154 portal_result_callback_.Run(result);
Paul Stewarte6927402012-01-23 16:11:30 -0800155}
156
157PortalDetector::Result PortalDetector::GetPortalResultForRequestResult(
158 HTTPRequest::Result result) {
159 switch (result) {
160 case HTTPRequest::kResultSuccess:
161 // The request completed without receiving the expected payload.
162 return Result(kPhaseContent, kStatusFailure);
163 case HTTPRequest::kResultDNSFailure:
164 return Result(kPhaseDNS, kStatusFailure);
165 case HTTPRequest::kResultDNSTimeout:
166 return Result(kPhaseDNS, kStatusTimeout);
167 case HTTPRequest::kResultConnectionFailure:
168 return Result(kPhaseConnection, kStatusFailure);
169 case HTTPRequest::kResultConnectionTimeout:
170 return Result(kPhaseConnection, kStatusTimeout);
171 case HTTPRequest::kResultRequestFailure:
172 case HTTPRequest::kResultResponseFailure:
173 return Result(kPhaseHTTP, kStatusFailure);
174 case HTTPRequest::kResultRequestTimeout:
175 case HTTPRequest::kResultResponseTimeout:
176 return Result(kPhaseHTTP, kStatusTimeout);
177 case HTTPRequest::kResultUnknown:
178 default:
179 return Result(kPhaseUnknown, kStatusFailure);
180 }
181}
182
Paul Stewartbdb02e62012-02-22 16:24:33 -0800183void PortalDetector::RequestReadCallback(const ByteString &response_data) {
Paul Stewarte6927402012-01-23 16:11:30 -0800184 const string response_expected(kResponseExpected);
Paul Stewarte6927402012-01-23 16:11:30 -0800185 bool expected_length_received = false;
186 int compare_length = 0;
187 if (response_data.GetLength() < response_expected.length()) {
188 // There isn't enough data yet for a final decision, but we can still
189 // test to see if the partial string matches so far.
190 expected_length_received = false;
191 compare_length = response_data.GetLength();
192 } else {
193 expected_length_received = true;
194 compare_length = response_expected.length();
195 }
196
197 if (ByteString(response_expected.substr(0, compare_length), false).Equals(
198 ByteString(response_data.GetConstData(), compare_length))) {
199 if (expected_length_received) {
200 CompleteAttempt(Result(kPhaseContent, kStatusSuccess));
201 }
202 // Otherwise, we wait for more data from the server.
203 } else {
204 CompleteAttempt(Result(kPhaseContent, kStatusFailure));
205 }
206}
207
Paul Stewartbdb02e62012-02-22 16:24:33 -0800208void PortalDetector::RequestResultCallback(
209 HTTPRequest::Result result, const ByteString &/*response_data*/) {
Paul Stewarte6927402012-01-23 16:11:30 -0800210 CompleteAttempt(GetPortalResultForRequestResult(result));
211}
212
Paul Stewartc681fa02012-03-02 19:40:04 -0800213void PortalDetector::StartAttempt(int init_delay_seconds) {
Paul Stewarte6927402012-01-23 16:11:30 -0800214 int64 next_attempt_delay = 0;
215 if (attempt_count_ > 0) {
216 // Ensure that attempts are spaced at least by a minimal interval.
217 struct timeval now, elapsed_time;
218 time_->GetTimeMonotonic(&now);
219 timersub(&now, &attempt_start_time_, &elapsed_time);
220
221 if (elapsed_time.tv_sec < kMinTimeBetweenAttemptsSeconds) {
222 struct timeval remaining_time = { kMinTimeBetweenAttemptsSeconds, 0 };
223 timersub(&remaining_time, &elapsed_time, &remaining_time);
224 next_attempt_delay =
225 remaining_time.tv_sec * 1000 + remaining_time.tv_usec / 1000;
226 }
Paul Stewartc681fa02012-03-02 19:40:04 -0800227 } else {
228 next_attempt_delay = init_delay_seconds * 1000;
Paul Stewarte6927402012-01-23 16:11:30 -0800229 }
Paul Stewartf582b502012-04-04 21:39:22 -0700230
231 start_attempt_.Reset(Bind(&PortalDetector::StartAttemptTask,
232 weak_ptr_factory_.GetWeakPtr()));
233 dispatcher_->PostDelayedTask(start_attempt_.callback(), next_attempt_delay);
Paul Stewarte6927402012-01-23 16:11:30 -0800234}
235
236void PortalDetector::StartAttemptTask() {
237 time_->GetTimeMonotonic(&attempt_start_time_);
238 ++attempt_count_;
239
Paul Stewart20088d82012-02-16 06:58:55 -0800240 LOG(INFO) << StringPrintf("Portal detection starting attempt %d of %d",
Paul Stewarte6927402012-01-23 16:11:30 -0800241 attempt_count_, kMaxRequestAttempts);
242
243 HTTPRequest::Result result =
Eric Shienbrood3e20a232012-02-16 11:35:56 -0500244 request_->Start(url_, request_read_callback_, request_result_callback_);
Paul Stewarte6927402012-01-23 16:11:30 -0800245 if (result != HTTPRequest::kResultInProgress) {
246 CompleteAttempt(GetPortalResultForRequestResult(result));
247 return;
248 }
249
Paul Stewartf582b502012-04-04 21:39:22 -0700250 attempt_timeout_.Reset(Bind(&PortalDetector::TimeoutAttemptTask,
251 weak_ptr_factory_.GetWeakPtr()));
252 dispatcher_->PostDelayedTask(attempt_timeout_.callback(),
253 kRequestTimeoutSeconds * 1000);
Paul Stewarte6927402012-01-23 16:11:30 -0800254}
255
256void PortalDetector::StopAttempt() {
257 request_->Stop();
Paul Stewartf582b502012-04-04 21:39:22 -0700258 attempt_timeout_.Cancel();
Paul Stewarte6927402012-01-23 16:11:30 -0800259}
260
261void PortalDetector::TimeoutAttemptTask() {
262 LOG(ERROR) << "Request timed out";
263 if (request_->response_data().GetLength()) {
264 CompleteAttempt(Result(kPhaseContent, kStatusTimeout));
265 } else {
266 CompleteAttempt(Result(kPhaseUnknown, kStatusTimeout));
267 }
268}
269
270} // namespace shill