shill: Refactor DNS server testing code

Implement the DNSServerTester class for performing DNS server test
in shill. This class will be used for performing DNS test for fallback
DNS servers and configured DNS servers.

BUG=chromium:377056
TEST=unit tests, manual test
Manual Test:
1. Connect a chrome device to "GoogleGuest", set the DNS servers
   for the network to an unknown IP address "192.168.1.1".
2. Browse to "chrome://histograms", and verify there is a histogram
   for "Network.Shill.Wifi.FallbackDNSTestResult".

Change-Id: Iaf9c0322501ae273f4635f1d865d41f8f51b114f
Reviewed-on: https://chromium-review.googlesource.com/207136
Reviewed-by: Paul Stewart <pstew@chromium.org>
Commit-Queue: Peter Qiu <zqiu@chromium.org>
Tested-by: Peter Qiu <zqiu@chromium.org>
diff --git a/dns_server_tester.cc b/dns_server_tester.cc
new file mode 100644
index 0000000..8971852
--- /dev/null
+++ b/dns_server_tester.cc
@@ -0,0 +1,111 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "shill/dns_server_tester.h"
+
+#include <string>
+
+#include <base/bind.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include "shill/connection.h"
+#include "shill/dns_client.h"
+#include "shill/dns_client_factory.h"
+#include "shill/error.h"
+#include "shill/event_dispatcher.h"
+
+using base::Bind;
+using base::Callback;
+using std::vector;
+using std::string;
+
+namespace shill {
+
+// static
+const char DNSServerTester::kDNSTestHostname[] = "www.gstatic.com";
+// static
+const int DNSServerTester::kDNSTestRetryIntervalMilliseconds = 60000;
+// static
+const int DNSServerTester::kDNSTimeoutMilliseconds = 5000;
+
+DNSServerTester::DNSServerTester(ConnectionRefPtr connection,
+                                 EventDispatcher *dispatcher,
+                                 const vector<string> &dns_servers,
+                                 const bool retry_until_success,
+                                 const Callback<void(const Status)> &callback)
+    : connection_(connection),
+      dispatcher_(dispatcher),
+      retry_until_success_(retry_until_success),
+      weak_ptr_factory_(this),
+      dns_result_callback_(callback),
+      dns_client_callback_(Bind(&DNSServerTester::DNSClientCallback,
+                                weak_ptr_factory_.GetWeakPtr())),
+      dns_test_client_(DNSClientFactory::GetInstance()->CreateDNSClient(
+          IPAddress::kFamilyIPv4,
+          connection_->interface_name(),
+          dns_servers,
+          kDNSTimeoutMilliseconds,
+          dispatcher_,
+          dns_client_callback_)) {}
+
+DNSServerTester::~DNSServerTester() {
+  Stop();
+}
+
+void DNSServerTester::Start() {
+  // Stop existing attempt.
+  Stop();
+  // Schedule the test to start immediately.
+  StartAttempt(0);
+}
+
+void DNSServerTester::StartAttempt(int delay_ms) {
+  start_attempt_.Reset(Bind(&DNSServerTester::StartAttemptTask,
+                            weak_ptr_factory_.GetWeakPtr()));
+  dispatcher_->PostDelayedTask(start_attempt_.callback(), delay_ms);
+}
+
+void DNSServerTester::StartAttemptTask() {
+  Error error;
+  if (!dns_test_client_->Start(kDNSTestHostname, &error)) {
+    LOG(ERROR) << __func__ << ": Failed to start DNS client "
+                                << error.message();
+    CompleteAttempt(kStatusFailure);
+  }
+}
+
+void DNSServerTester::Stop() {
+  start_attempt_.Cancel();
+  StopAttempt();
+}
+
+void DNSServerTester::StopAttempt() {
+  if (dns_test_client_.get()) {
+    dns_test_client_->Stop();
+  }
+}
+
+void DNSServerTester::CompleteAttempt(Status status) {
+  if (status == kStatusFailure && retry_until_success_) {
+    // Schedule the test to restart after retry timeout interval.
+    StartAttempt(kDNSTestRetryIntervalMilliseconds);
+    return;
+  }
+
+  dns_result_callback_.Run(status);
+}
+
+void DNSServerTester::DNSClientCallback(const Error &error,
+                                        const IPAddress& ip) {
+  Status status = kStatusSuccess;
+  if (!error.IsSuccess()) {
+    status = kStatusFailure;
+  }
+
+  CompleteAttempt(status);
+}
+
+}  // namespace shill