blob: be560eee7d024447b3c14eb3c152071f2cb9e00d [file] [log] [blame]
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -07001// 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/connectivity_trial.h"
6
7#include <string>
8
9#include <base/bind.h>
10#include <base/strings/string_number_conversions.h>
11#include <base/strings/string_util.h>
12#include <base/strings/stringprintf.h>
13#include <chromeos/dbus/service_constants.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_request.h"
20#include "shill/http_url.h"
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -070021#include "shill/logging.h"
Peter Qiu8d6b5972014-10-28 15:33:34 -070022#include "shill/net/ip_address.h"
23#include "shill/net/sockets.h"
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -070024
25using base::Bind;
26using base::Callback;
27using base::StringPrintf;
28using std::string;
29
30namespace shill {
31
Rebecca Silbersteinc9c31d82014-10-21 15:01:00 -070032namespace Logging {
33static auto kModuleLogScope = ScopeLogger::kPortal;
Paul Stewarta794cd62015-06-16 13:13:10 -070034static string ObjectID(Connection* c) { return c->interface_name(); }
Rebecca Silbersteinc9c31d82014-10-21 15:01:00 -070035}
36
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -070037const char ConnectivityTrial::kDefaultURL[] =
38 "http://www.gstatic.com/generate_204";
39const char ConnectivityTrial::kResponseExpected[] = "HTTP/?.? 204";
40
41ConnectivityTrial::ConnectivityTrial(
42 ConnectionRefPtr connection,
Paul Stewarta794cd62015-06-16 13:13:10 -070043 EventDispatcher* dispatcher,
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -070044 int trial_timeout_seconds,
Paul Stewarta794cd62015-06-16 13:13:10 -070045 const Callback<void(Result)>& callback)
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -070046 : connection_(connection),
47 dispatcher_(dispatcher),
48 trial_timeout_seconds_(trial_timeout_seconds),
49 trial_callback_(callback),
50 weak_ptr_factory_(this),
51 request_read_callback_(
52 Bind(&ConnectivityTrial::RequestReadCallback,
53 weak_ptr_factory_.GetWeakPtr())),
54 request_result_callback_(
55 Bind(&ConnectivityTrial::RequestResultCallback,
56 weak_ptr_factory_.GetWeakPtr())),
57 is_active_(false) { }
58
59ConnectivityTrial::~ConnectivityTrial() {
60 Stop();
61}
62
63bool ConnectivityTrial::Retry(int start_delay_milliseconds) {
Alex Vakulenko0951ccb2014-12-10 12:52:31 -080064 SLOG(connection_.get(), 3) << "In " << __func__;
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -070065 if (request_.get())
66 CleanupTrial(false);
67 else
68 return false;
69 StartTrialAfterDelay(start_delay_milliseconds);
70 return true;
71}
72
Paul Stewarta794cd62015-06-16 13:13:10 -070073bool ConnectivityTrial::Start(const string& url_string,
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -070074 int start_delay_milliseconds) {
Alex Vakulenko0951ccb2014-12-10 12:52:31 -080075 SLOG(connection_.get(), 3) << "In " << __func__;
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -070076
77 if (!url_.ParseFromString(url_string)) {
78 LOG(ERROR) << "Failed to parse URL string: " << url_string;
79 return false;
80 }
81 if (request_.get()) {
82 CleanupTrial(false);
83 } else {
84 request_.reset(new HTTPRequest(connection_, dispatcher_, &sockets_));
85 }
86 StartTrialAfterDelay(start_delay_milliseconds);
87 return true;
88}
89
90void ConnectivityTrial::Stop() {
Alex Vakulenko0951ccb2014-12-10 12:52:31 -080091 SLOG(connection_.get(), 3) << "In " << __func__;
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -070092
93 if (!request_.get()) {
94 return;
95 }
96
97 CleanupTrial(true);
98}
99
100void ConnectivityTrial::StartTrialAfterDelay(int start_delay_milliseconds) {
Alex Vakulenko0951ccb2014-12-10 12:52:31 -0800101 SLOG(connection_.get(), 4) << "In " << __func__
102 << " delay = " << start_delay_milliseconds
103 << "ms.";
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -0700104 trial_.Reset(Bind(&ConnectivityTrial::StartTrialTask,
105 weak_ptr_factory_.GetWeakPtr()));
106 dispatcher_->PostDelayedTask(trial_.callback(), start_delay_milliseconds);
107}
108
109void ConnectivityTrial::StartTrialTask() {
110 HTTPRequest::Result result =
111 request_->Start(url_, request_read_callback_, request_result_callback_);
112 if (result != HTTPRequest::kResultInProgress) {
113 CompleteTrial(ConnectivityTrial::GetPortalResultForRequestResult(result));
114 return;
115 }
116 is_active_ = true;
117
118 trial_timeout_.Reset(Bind(&ConnectivityTrial::TimeoutTrialTask,
119 weak_ptr_factory_.GetWeakPtr()));
120 dispatcher_->PostDelayedTask(trial_timeout_.callback(),
121 trial_timeout_seconds_ * 1000);
122}
123
124bool ConnectivityTrial::IsActive() {
125 return is_active_;
126}
127
Paul Stewarta794cd62015-06-16 13:13:10 -0700128void ConnectivityTrial::RequestReadCallback(const ByteString& response_data) {
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -0700129 const string response_expected(kResponseExpected);
130 bool expected_length_received = false;
131 int compare_length = 0;
132 if (response_data.GetLength() < response_expected.length()) {
133 // There isn't enough data yet for a final decision, but we can still
134 // test to see if the partial string matches so far.
135 expected_length_received = false;
136 compare_length = response_data.GetLength();
137 } else {
138 expected_length_received = true;
139 compare_length = response_expected.length();
140 }
141
142 if (MatchPattern(
Paul Stewarta794cd62015-06-16 13:13:10 -0700143 string(reinterpret_cast<const char*>(response_data.GetConstData()),
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -0700144 compare_length),
145 response_expected.substr(0, compare_length))) {
146 if (expected_length_received) {
147 CompleteTrial(Result(kPhaseContent, kStatusSuccess));
148 }
149 // Otherwise, we wait for more data from the server.
150 } else {
151 CompleteTrial(Result(kPhaseContent, kStatusFailure));
152 }
153}
154
155void ConnectivityTrial::RequestResultCallback(
Paul Stewarta794cd62015-06-16 13:13:10 -0700156 HTTPRequest::Result result, const ByteString& /*response_data*/) {
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -0700157 CompleteTrial(GetPortalResultForRequestResult(result));
158}
159
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -0700160void ConnectivityTrial::CompleteTrial(Result result) {
Alex Vakulenko0951ccb2014-12-10 12:52:31 -0800161 SLOG(connection_.get(), 3)
162 << StringPrintf("Connectivity Trial completed with phase==%s, status==%s",
163 PhaseToString(result.phase).c_str(),
164 StatusToString(result.status).c_str());
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -0700165 CleanupTrial(false);
166 trial_callback_.Run(result);
167}
168
169void ConnectivityTrial::CleanupTrial(bool reset_request) {
170 trial_timeout_.Cancel();
171
172 if (request_.get())
173 request_->Stop();
174
175 is_active_ = false;
176
177 if (!reset_request || !request_.get())
178 return;
179
180 request_.reset();
181}
182
183void ConnectivityTrial::TimeoutTrialTask() {
184 LOG(ERROR) << "Connectivity Trial - Request timed out";
185 if (request_->response_data().GetLength()) {
186 CompleteTrial(ConnectivityTrial::Result(ConnectivityTrial::kPhaseContent,
187 ConnectivityTrial::kStatusTimeout));
188 } else {
189 CompleteTrial(ConnectivityTrial::Result(ConnectivityTrial::kPhaseUnknown,
190 ConnectivityTrial::kStatusTimeout));
191 }
192}
193
194// statiic
195const string ConnectivityTrial::PhaseToString(Phase phase) {
196 switch (phase) {
197 case kPhaseConnection:
198 return kPortalDetectionPhaseConnection;
199 case kPhaseDNS:
200 return kPortalDetectionPhaseDns;
201 case kPhaseHTTP:
202 return kPortalDetectionPhaseHttp;
203 case kPhaseContent:
204 return kPortalDetectionPhaseContent;
205 case kPhaseUnknown:
206 default:
207 return kPortalDetectionPhaseUnknown;
208 }
209}
210
211// static
212const string ConnectivityTrial::StatusToString(Status status) {
213 switch (status) {
214 case kStatusSuccess:
215 return kPortalDetectionStatusSuccess;
216 case kStatusTimeout:
217 return kPortalDetectionStatusTimeout;
218 case kStatusFailure:
219 default:
220 return kPortalDetectionStatusFailure;
221 }
222}
223
224ConnectivityTrial::Result ConnectivityTrial::GetPortalResultForRequestResult(
225 HTTPRequest::Result result) {
226 switch (result) {
227 case HTTPRequest::kResultSuccess:
228 // The request completed without receiving the expected payload.
229 return Result(kPhaseContent, kStatusFailure);
230 case HTTPRequest::kResultDNSFailure:
231 return Result(kPhaseDNS, kStatusFailure);
232 case HTTPRequest::kResultDNSTimeout:
233 return Result(kPhaseDNS, kStatusTimeout);
234 case HTTPRequest::kResultConnectionFailure:
235 return Result(kPhaseConnection, kStatusFailure);
236 case HTTPRequest::kResultConnectionTimeout:
237 return Result(kPhaseConnection, kStatusTimeout);
238 case HTTPRequest::kResultRequestFailure:
239 case HTTPRequest::kResultResponseFailure:
240 return Result(kPhaseHTTP, kStatusFailure);
241 case HTTPRequest::kResultRequestTimeout:
242 case HTTPRequest::kResultResponseTimeout:
243 return Result(kPhaseHTTP, kStatusTimeout);
244 case HTTPRequest::kResultUnknown:
245 default:
246 return Result(kPhaseUnknown, kStatusFailure);
247 }
248}
249
250} // namespace shill