shill: Create HTTPProxy for connected services

When an a Connection is created on a Device, ask the
Service to create an HTTPProxy.  The Service will
then expose the port number for the proxy as an RPC
property.  Whenever the Service is unselected by
the Device, or the connection terminates, ask the
Service to destroy the proxy.

The Service owns the HTTPProxy so that it can report
the proxy port in its properties.

BUG=chromium-os:21664
TEST=Add expectations for DestroyProxy in unit tests.
Manual: Ensure that "list-services" shows the proxy
port on a successful connection, and "curl -x" to
this proxy succeeds.

Change-Id: I24a1f7a23c0fc4577b48aed7bec370fba6edc342
Reviewed-on: https://gerrit.chromium.org/gerrit/12625
Reviewed-by: mukesh agrawal <quiche@chromium.org>
Commit-Ready: Paul Stewart <pstew@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
diff --git a/connection.h b/connection.h
index 3b1d205..00e61f3 100644
--- a/connection.h
+++ b/connection.h
@@ -38,6 +38,9 @@
   bool is_default() const { return is_default_; }
   void SetDefault(bool is_default);
 
+  const std::string &interface_name() const { return interface_name_; }
+  const std::vector<std::string> &dns_servers() const { return dns_servers_; }
+
  private:
   friend class ConnectionTest;
   FRIEND_TEST(ConnectionTest, InitState);
diff --git a/device.cc b/device.cc
index 02013b4..52d270e 100644
--- a/device.cc
+++ b/device.cc
@@ -298,6 +298,9 @@
     CreateConnection();
     connection_->UpdateFromIPConfig(ipconfig);
     SetServiceState(Service::kStateConnected);
+    if (selected_service_.get()) {
+      selected_service_->CreateHTTPProxy(connection_);
+    }
   } else {
     // TODO(pstew): This logic gets more complex when multiple IPConfig types
     // are run in parallel (e.g. DHCP and DHCP6)
@@ -318,6 +321,9 @@
 void Device::DestroyConnection() {
   VLOG(2) << __func__;
   connection_ = NULL;
+  if (selected_service_.get()) {
+    selected_service_->DestroyHTTPProxy();
+  }
 }
 
 void Device::SelectService(const ServiceRefPtr &service) {
@@ -327,9 +333,15 @@
                            service->UniqueName().c_str(),
                            service->friendly_name().c_str()) :
               "*reset*");
-  if (selected_service_.get() &&
-      selected_service_->state() != Service::kStateFailure) {
-    selected_service_->SetState(Service::kStateIdle);
+  if (selected_service_.get()) {
+    if (selected_service_->state() != Service::kStateFailure) {
+      selected_service_->SetState(Service::kStateIdle);
+    }
+    // TODO(pstew): We need to revisit the model here: should the Device
+    // subclass be responsible for calling DestroyIPConfig() (which would
+    // trigger DestroyConnection() and Service::DestroyHTTPProxy())?
+    // Ethernet does, but WiFi currently does not.  crosbug.com/23929
+    selected_service_->DestroyHTTPProxy();
   }
   selected_service_ = service;
 }
diff --git a/device.h b/device.h
index c019a8f..8ae93dc 100644
--- a/device.h
+++ b/device.h
@@ -122,6 +122,8 @@
   FRIEND_TEST(DeviceTest, DestroyIPConfig);
   FRIEND_TEST(DeviceTest, DestroyIPConfigNULL);
   FRIEND_TEST(DeviceTest, GetProperties);
+  FRIEND_TEST(DeviceTest, IPConfigUpdatedFailure);
+  FRIEND_TEST(DeviceTest, IPConfigUpdatedSuccess);
   FRIEND_TEST(DeviceTest, Save);
   FRIEND_TEST(DeviceTest, SelectedService);
   FRIEND_TEST(DeviceTest, Stop);
diff --git a/device_unittest.cc b/device_unittest.cc
index 8f032b4..5b9cc3c 100644
--- a/device_unittest.cc
+++ b/device_unittest.cc
@@ -199,15 +199,42 @@
   EXPECT_CALL(*service.get(), state())
     .WillOnce(Return(Service::kStateUnknown));
   EXPECT_CALL(*service.get(), SetState(Service::kStateIdle));
+  EXPECT_CALL(*service.get(), DestroyHTTPProxy());
   device_->SelectService(NULL);
 
   // A service in the "Failure" state should not be reset to "Idle"
   device_->SelectService(service);
   EXPECT_CALL(*service.get(), state())
     .WillOnce(Return(Service::kStateFailure));
+  EXPECT_CALL(*service.get(), DestroyHTTPProxy());
   device_->SelectService(NULL);
 }
 
+TEST_F(DeviceTest, IPConfigUpdatedFailure) {
+  scoped_refptr<MockService> service(
+      new StrictMock<MockService>(control_interface(),
+                                  dispatcher(),
+                                  manager()));
+  device_->SelectService(service);
+  EXPECT_CALL(*service.get(), SetState(Service::kStateDisconnected));
+  EXPECT_CALL(*service.get(), DestroyHTTPProxy());
+  device_->IPConfigUpdatedCallback(NULL, false);
+}
+
+TEST_F(DeviceTest, IPConfigUpdatedSuccess) {
+  scoped_refptr<MockService> service(
+      new StrictMock<MockService>(control_interface(),
+                                  dispatcher(),
+                                  manager()));
+  device_->SelectService(service);
+  device_->manager_ = manager();
+  scoped_refptr<MockIPConfig> ipconfig = new MockIPConfig(control_interface(),
+                                                          kDeviceName);
+  EXPECT_CALL(*service.get(), SetState(Service::kStateConnected));
+  EXPECT_CALL(*service.get(), CreateHTTPProxy(_));
+  device_->IPConfigUpdatedCallback(ipconfig.get(), true);
+}
+
 TEST_F(DeviceTest, Stop) {
   device_->ipconfig_ = new IPConfig(&control_interface_, kDeviceName);
   scoped_refptr<MockService> service(
diff --git a/mock_service.h b/mock_service.h
index 4d771b3..3d7ce28 100644
--- a/mock_service.h
+++ b/mock_service.h
@@ -10,6 +10,7 @@
 #include <base/memory/ref_counted.h>
 #include <gmock/gmock.h>
 
+#include "shill/connection.h"
 #include "shill/refptr_types.h"
 #include "shill/service.h"
 #include "shill/technology.h"
@@ -40,6 +41,8 @@
   MOCK_CONST_METHOD0(GetStorageIdentifier, std::string());
   MOCK_METHOD1(Load, bool(StoreInterface *store_interface));
   MOCK_METHOD1(Save, bool(StoreInterface *store_interface));
+  MOCK_METHOD1(CreateHTTPProxy, void(ConnectionRefPtr connection));
+  MOCK_METHOD0(DestroyHTTPProxy, void());
   MOCK_CONST_METHOD0(technology, Technology::Identifier());
   // Set a string for this Service via |store|.  Can be wired to Save() for
   // test purposes.
diff --git a/property_store.cc b/property_store.cc
index 72c3ea8..2b6fbe7 100644
--- a/property_store.cc
+++ b/property_store.cc
@@ -311,6 +311,11 @@
   strintpair_properties_[name] = acc;
 }
 
+void PropertyStore::RegisterDerivedUint16(const std::string &name,
+                                          const Uint16Accessor &acc) {
+  uint16_properties_[name] = acc;
+}
+
 // private
 template <class V>
 bool PropertyStore::SetProperty(
diff --git a/property_store.h b/property_store.h
index caa1491..8bc4277 100644
--- a/property_store.h
+++ b/property_store.h
@@ -125,6 +125,8 @@
                               const StringsAccessor &accessor);
   void RegisterDerivedStrIntPair(const std::string &name,
                                  const StrIntPairAccessor &accessor);
+  void RegisterDerivedUint16(const std::string &name,
+                             const Uint16Accessor &accessor);
 
  private:
   template <class V>
diff --git a/service.cc b/service.cc
index 01bf613..d868b59 100644
--- a/service.cc
+++ b/service.cc
@@ -16,8 +16,10 @@
 #include <base/string_number_conversions.h>
 #include <chromeos/dbus/service_constants.h>
 
+#include "shill/connection.h"
 #include "shill/control_interface.h"
 #include "shill/error.h"
+#include "shill/http_proxy.h"
 #include "shill/manager.h"
 #include "shill/profile.h"
 #include "shill/property_accessor.h"
@@ -85,7 +87,6 @@
       available_(false),
       configured_(false),
       configuration_(NULL),
-      connection_(NULL),
       adaptor_(control_interface->CreateServiceAdaptor(this)),
       manager_(manager) {
 
@@ -127,6 +128,9 @@
 
   store_.RegisterConstString(flimflam::kErrorProperty, &error_);
   store_.RegisterConstBool(flimflam::kFavoriteProperty, &favorite_);
+  HelpRegisterDerivedUint16(shill::kHTTPProxyPortProperty,
+                            &Service::GetHTTPProxyPort,
+                            NULL);
   HelpRegisterDerivedBool(flimflam::kIsActiveProperty,
                           &Service::IsActive,
                           NULL);
@@ -314,6 +318,16 @@
   favorite_ = true;
 }
 
+void Service::CreateHTTPProxy(ConnectionRefPtr connection) {
+  http_proxy_.reset(new HTTPProxy(connection->interface_name(),
+                                  connection->dns_servers()));
+  http_proxy_->Start(dispatcher_, &sockets_);
+}
+
+void Service::DestroyHTTPProxy() {
+  http_proxy_.reset();
+}
+
 // static
 const char *Service::ConnectFailureToString(const ConnectFailure &state) {
   switch (state) {
@@ -472,6 +486,15 @@
       StringAccessor(new CustomAccessor<Service, string>(this, get, set)));
 }
 
+void Service::HelpRegisterDerivedUint16(
+    const string &name,
+    uint16(Service::*get)(Error *),
+    void(Service::*set)(const uint16&, Error *)) {
+  store_.RegisterDerivedUint16(
+      name,
+      Uint16Accessor(new CustomAccessor<Service, uint16>(this, get, set)));
+}
+
 void Service::SaveString(StoreInterface *storage,
                          const string &id,
                          const string &key,
@@ -554,4 +577,11 @@
   eap_.key_management = key_management;
 }
 
+uint16 Service::GetHTTPProxyPort(Error */*error*/) {
+  if (http_proxy_.get()) {
+    return static_cast<uint16>(http_proxy_->proxy_port());
+  }
+  return 0;
+}
+
 }  // namespace shill
diff --git a/service.h b/service.h
index c4a8b3e..687f5dc 100644
--- a/service.h
+++ b/service.h
@@ -16,16 +16,17 @@
 #include "shill/accessor_interface.h"
 #include "shill/property_store.h"
 #include "shill/refptr_types.h"
+#include "shill/sockets.h"
 #include "shill/technology.h"
 
 namespace shill {
 
-class Connection;
 class Configuration;
 class ControlInterface;
 class Endpoint;
 class Error;
 class EventDispatcher;
+class HTTPProxy;
 class KeyValueStore;
 class Manager;
 class ServiceAdaptorInterface;
@@ -147,6 +148,11 @@
 
   virtual void MakeFavorite();
 
+  // Create an HTTP Proxy that will utilize this service's connection
+  // (interface, DNS servers, default route) to serve requests.
+  virtual void CreateHTTPProxy(ConnectionRefPtr connection);
+  virtual void DestroyHTTPProxy();
+
   bool auto_connect() const { return auto_connect_; }
   void set_auto_connect(bool connect) { auto_connect_ = connect; }
 
@@ -208,6 +214,10 @@
       const std::string &name,
       std::string(Service::*get)(Error *error),
       void(Service::*set)(const std::string &value, Error *error));
+  void HelpRegisterDerivedUint16(
+      const std::string &name,
+      uint16(Service::*get)(Error *error),
+      void(Service::*set)(const uint16 &value, Error *error));
 
   ServiceAdaptorInterface *adaptor() const { return adaptor_.get(); }
 
@@ -269,6 +279,9 @@
     return "";  // Will need to call Profile to get this.
   }
 
+  // Returns TCP port of service's HTTP proxy in host order.
+  uint16 GetHTTPProxyPort(Error *error);
+
   // Utility function that returns true if a is different from b.  When they
   // are, "decision" is populated with the boolean value of "a > b".
   static bool DecideBetween(int a, int b, bool *decision);
@@ -299,9 +312,10 @@
   bool available_;
   bool configured_;
   Configuration *configuration_;
-  Connection *connection_;
   scoped_ptr<ServiceAdaptorInterface> adaptor_;
+  scoped_ptr<HTTPProxy> http_proxy_;
   Manager *manager_;
+  Sockets sockets_;
 
   DISALLOW_COPY_AND_ASSIGN(Service);
 };