get_server_time.cc revision bbcdd45c55eb7c4641ab97aef9889b0fc828e7d3
1// Copyright (c) 2012 The Chromium 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// This is a small utility that snarfs the server time from the 6// response headers of an http/https HEAD request and compares it to 7// the local time. 8// 9// TODO(akalin): Also snarf the server time from the TLS handshake, if 10// any (http://crbug.com/146090). 11 12#include <cstdio> 13#include <cstdlib> 14#include <string> 15 16#include "base/at_exit.h" 17#include "base/basictypes.h" 18#include "base/command_line.h" 19#include "base/compiler_specific.h" 20#include "base/format_macros.h" 21#include "base/i18n/time_formatting.h" 22#include "base/json/json_writer.h" 23#include "base/logging.h" 24#include "base/memory/ref_counted.h" 25#include "base/memory/scoped_ptr.h" 26#include "base/message_loop/message_loop.h" 27#include "base/single_thread_task_runner.h" 28#include "base/strings/string_number_conversions.h" 29#include "base/strings/utf_string_conversions.h" 30#include "base/time/time.h" 31#include "base/values.h" 32#include "build/build_config.h" 33#include "net/base/net_errors.h" 34#include "net/base/net_log.h" 35#include "net/http/http_response_headers.h" 36#include "net/url_request/url_fetcher.h" 37#include "net/url_request/url_fetcher_delegate.h" 38#include "net/url_request/url_request_context.h" 39#include "net/url_request/url_request_context_builder.h" 40#include "net/url_request/url_request_context_getter.h" 41#include "net/url_request/url_request_status.h" 42#include "url/gurl.h" 43 44#if defined(OS_MACOSX) 45#include "base/mac/scoped_nsautorelease_pool.h" 46#elif defined(OS_LINUX) 47#include "net/proxy/proxy_config.h" 48#include "net/proxy/proxy_config_service_fixed.h" 49#endif 50 51namespace { 52 53// base::TimeTicks::Now() is documented to have a resolution of 54// ~1-15ms. 55const int64 kTicksResolutionMs = 15; 56 57// For the sources that are supported (HTTP date headers, TLS 58// handshake), the resolution of the server time is 1 second. 59const int64 kServerTimeResolutionMs = 1000; 60 61// Assume base::Time::Now() has the same resolution as 62// base::TimeTicks::Now(). 63// 64// TODO(akalin): Figure out the real resolution. 65const int64 kTimeResolutionMs = kTicksResolutionMs; 66 67// Simply quits the current message loop when finished. Used to make 68// URLFetcher synchronous. 69class QuitDelegate : public net::URLFetcherDelegate { 70 public: 71 QuitDelegate() {} 72 73 virtual ~QuitDelegate() {} 74 75 // net::URLFetcherDelegate implementation. 76 virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE { 77 base::MessageLoop::current()->Quit(); 78 } 79 80 virtual void OnURLFetchDownloadProgress( 81 const net::URLFetcher* source, 82 int64 current, int64 total) OVERRIDE { 83 NOTREACHED(); 84 } 85 86 virtual void OnURLFetchDownloadData( 87 const net::URLFetcher* source, 88 scoped_ptr<std::string> download_data) OVERRIDE{ 89 NOTREACHED(); 90 } 91 92 virtual bool ShouldSendDownloadData() OVERRIDE { 93 NOTREACHED(); 94 return false; 95 } 96 97 virtual void OnURLFetchUploadProgress(const net::URLFetcher* source, 98 int64 current, int64 total) OVERRIDE { 99 NOTREACHED(); 100 } 101 102 private: 103 DISALLOW_COPY_AND_ASSIGN(QuitDelegate); 104}; 105 106// NetLog::ThreadSafeObserver implementation that simply prints events 107// to the logs. 108class PrintingLogObserver : public net::NetLog::ThreadSafeObserver { 109 public: 110 PrintingLogObserver() {} 111 112 virtual ~PrintingLogObserver() { 113 // This is guaranteed to be safe as this program is single threaded. 114 net_log()->RemoveThreadSafeObserver(this); 115 } 116 117 // NetLog::ThreadSafeObserver implementation: 118 virtual void OnAddEntry(const net::NetLog::Entry& entry) OVERRIDE { 119 // The log level of the entry is unknown, so just assume it maps 120 // to VLOG(1). 121 if (!VLOG_IS_ON(1)) 122 return; 123 124 const char* const source_type = 125 net::NetLog::SourceTypeToString(entry.source().type); 126 const char* const event_type = 127 net::NetLog::EventTypeToString(entry.type()); 128 const char* const event_phase = 129 net::NetLog::EventPhaseToString(entry.phase()); 130 scoped_ptr<base::Value> params(entry.ParametersToValue()); 131 std::string params_str; 132 if (params.get()) { 133 base::JSONWriter::Write(params.get(), ¶ms_str); 134 params_str.insert(0, ": "); 135 } 136 137 VLOG(1) << source_type << "(" << entry.source().id << "): " 138 << event_type << ": " << event_phase << params_str; 139 } 140 141 private: 142 DISALLOW_COPY_AND_ASSIGN(PrintingLogObserver); 143}; 144 145// Builds a URLRequestContext assuming there's only a single loop. 146scoped_ptr<net::URLRequestContext> 147BuildURLRequestContext(net::NetLog* net_log) { 148 net::URLRequestContextBuilder builder; 149#if defined(OS_LINUX) 150 // On Linux, use a fixed ProxyConfigService, since the default one 151 // depends on glib. 152 // 153 // TODO(akalin): Remove this once http://crbug.com/146421 is fixed. 154 builder.set_proxy_config_service( 155 new net::ProxyConfigServiceFixed(net::ProxyConfig())); 156#endif 157 scoped_ptr<net::URLRequestContext> context(builder.Build()); 158 context->set_net_log(net_log); 159 return context.Pass(); 160} 161 162class SingleThreadRequestContextGetter : public net::URLRequestContextGetter { 163 public: 164 // Since there's only a single thread, there's no need to worry 165 // about when |context_| gets created. 166 SingleThreadRequestContextGetter( 167 net::NetLog* net_log, 168 const scoped_refptr<base::SingleThreadTaskRunner>& main_task_runner) 169 : context_(BuildURLRequestContext(net_log)), 170 main_task_runner_(main_task_runner) {} 171 172 virtual net::URLRequestContext* GetURLRequestContext() OVERRIDE { 173 return context_.get(); 174 } 175 176 virtual scoped_refptr<base::SingleThreadTaskRunner> 177 GetNetworkTaskRunner() const OVERRIDE { 178 return main_task_runner_; 179 } 180 181 private: 182 virtual ~SingleThreadRequestContextGetter() {} 183 184 const scoped_ptr<net::URLRequestContext> context_; 185 const scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; 186}; 187 188// Assuming that the time |server_time| was received from a server, 189// that the request for the server was started on |start_ticks|, and 190// that it ended on |end_ticks|, fills |server_now| with an estimate 191// of the current time and |server_now_uncertainty| with a 192// conservative estimate of the uncertainty. 193void EstimateServerTimeNow(base::Time server_time, 194 base::TimeTicks start_ticks, 195 base::TimeTicks end_ticks, 196 base::Time* server_now, 197 base::TimeDelta* server_now_uncertainty) { 198 const base::TimeDelta delta_ticks = end_ticks - start_ticks; 199 const base::TimeTicks mid_ticks = start_ticks + delta_ticks / 2; 200 const base::TimeDelta estimated_elapsed = base::TimeTicks::Now() - mid_ticks; 201 202 *server_now = server_time + estimated_elapsed; 203 204 *server_now_uncertainty = 205 base::TimeDelta::FromMilliseconds(kServerTimeResolutionMs) + 206 delta_ticks + 3 * base::TimeDelta::FromMilliseconds(kTicksResolutionMs); 207} 208 209// Assuming that the time of the server is |server_now| with 210// uncertainty |server_now_uncertainty| and that the local time is 211// |now|, fills |skew| with the skew of the local clock (i.e., add 212// |*skew| to a client time to get a server time) and 213// |skew_uncertainty| with a conservative estimate of the uncertainty. 214void EstimateSkew(base::Time server_now, 215 base::TimeDelta server_now_uncertainty, 216 base::Time now, 217 base::TimeDelta now_uncertainty, 218 base::TimeDelta* skew, 219 base::TimeDelta* skew_uncertainty) { 220 *skew = server_now - now; 221 *skew_uncertainty = server_now_uncertainty + now_uncertainty; 222} 223 224} // namespace 225 226int main(int argc, char* argv[]) { 227#if defined(OS_MACOSX) 228 base::mac::ScopedNSAutoreleasePool pool; 229#endif 230 231 base::AtExitManager exit_manager; 232 CommandLine::Init(argc, argv); 233 logging::LoggingSettings settings; 234 settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG; 235 logging::InitLogging(settings); 236 237 const CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess(); 238 GURL url(parsed_command_line.GetSwitchValueASCII("url")); 239 if (!url.is_valid() || 240 (url.scheme() != "http" && url.scheme() != "https")) { 241 std::fprintf( 242 stderr, 243 "Usage: %s --url=[http|https]://www.example.com [--v=[1|2]]\n", 244 argv[0]); 245 return EXIT_FAILURE; 246 } 247 248 base::MessageLoopForIO main_loop; 249 250 // NOTE: A NetworkChangeNotifier could be instantiated here, but 251 // that interferes with the request that will be sent; some 252 // implementations always send out an OnIPAddressChanged() message, 253 // which causes the DNS resolution to abort. It's simpler to just 254 // not instantiate one, since only a single request is sent anyway. 255 256 // The declaration order for net_log and printing_log_observer is 257 // important. The destructor of PrintingLogObserver removes itself 258 // from net_log, so net_log must be available for entire lifetime of 259 // printing_log_observer. 260 net::NetLog net_log; 261 PrintingLogObserver printing_log_observer; 262 net_log.AddThreadSafeObserver(&printing_log_observer, net::NetLog::LOG_ALL); 263 scoped_refptr<SingleThreadRequestContextGetter> context_getter( 264 new SingleThreadRequestContextGetter(&net_log, 265 main_loop.message_loop_proxy())); 266 267 QuitDelegate delegate; 268 scoped_ptr<net::URLFetcher> fetcher( 269 net::URLFetcher::Create(url, net::URLFetcher::HEAD, &delegate)); 270 fetcher->SetRequestContext(context_getter.get()); 271 272 const base::Time start_time = base::Time::Now(); 273 const base::TimeTicks start_ticks = base::TimeTicks::Now(); 274 275 fetcher->Start(); 276 std::printf( 277 "Request started at %s (ticks = %" PRId64 ")\n", 278 UTF16ToUTF8(base::TimeFormatFriendlyDateAndTime(start_time)).c_str(), 279 start_ticks.ToInternalValue()); 280 281 // |delegate| quits |main_loop| when the request is done. 282 main_loop.Run(); 283 284 const base::Time end_time = base::Time::Now(); 285 const base::TimeTicks end_ticks = base::TimeTicks::Now(); 286 287 std::printf( 288 "Request ended at %s (ticks = %" PRId64 ")\n", 289 UTF16ToUTF8(base::TimeFormatFriendlyDateAndTime(end_time)).c_str(), 290 end_ticks.ToInternalValue()); 291 292 const int64 delta_ticks_internal = 293 end_ticks.ToInternalValue() - start_ticks.ToInternalValue(); 294 const base::TimeDelta delta_ticks = end_ticks - start_ticks; 295 296 std::printf( 297 "Request took %" PRId64 " ticks (%.2f ms)\n", 298 delta_ticks_internal, delta_ticks.InMillisecondsF()); 299 300 const net::URLRequestStatus status = fetcher->GetStatus(); 301 if (status.status() != net::URLRequestStatus::SUCCESS) { 302 LOG(ERROR) << "Request failed with error code: " 303 << net::ErrorToString(status.error()); 304 return EXIT_FAILURE; 305 } 306 307 const net::HttpResponseHeaders* const headers = 308 fetcher->GetResponseHeaders(); 309 if (!headers) { 310 LOG(ERROR) << "Response does not have any headers"; 311 return EXIT_FAILURE; 312 } 313 314 void* iter = NULL; 315 std::string date_header; 316 while (headers->EnumerateHeader(&iter, "Date", &date_header)) { 317 std::printf("Got date header: %s\n", date_header.c_str()); 318 } 319 320 base::Time server_time; 321 if (!headers->GetDateValue(&server_time)) { 322 LOG(ERROR) << "Could not parse time from server response headers"; 323 return EXIT_FAILURE; 324 } 325 326 std::printf( 327 "Got time %s from server\n", 328 UTF16ToUTF8(base::TimeFormatFriendlyDateAndTime(server_time)).c_str()); 329 330 base::Time server_now; 331 base::TimeDelta server_now_uncertainty; 332 EstimateServerTimeNow(server_time, start_ticks, end_ticks, 333 &server_now, &server_now_uncertainty); 334 base::Time now = base::Time::Now(); 335 336 std::printf( 337 "According to the server, it is now %s with uncertainty %.2f ms\n", 338 UTF16ToUTF8(base::TimeFormatFriendlyDateAndTime(server_now)).c_str(), 339 server_now_uncertainty.InMillisecondsF()); 340 341 base::TimeDelta skew; 342 base::TimeDelta skew_uncertainty; 343 EstimateSkew(server_now, server_now_uncertainty, now, 344 base::TimeDelta::FromMilliseconds(kTimeResolutionMs), 345 &skew, &skew_uncertainty); 346 347 std::printf( 348 "An estimate for the local clock skew is %.2f ms with " 349 "uncertainty %.2f ms\n", 350 skew.InMillisecondsF(), 351 skew_uncertainty.InMillisecondsF()); 352 353 return EXIT_SUCCESS; 354} 355