cashew: Use an http proxy server if specified by shill

Use an http proxy server if shill specifies one by setting an http
proxy port property on the service.  Either a sticky host route or a
proxy server can be used when the cellular service is not the default.

Adapt cashew to work with both shill and flimflam by no longer
requiring that the service object names have the string "cellular_" in
them.

BUG=chromium-os:23047
TEST=run cashew with shill, ensure data plan is fetched when connected via Ethernet & Cellular

Change-Id: I2d129b9e2c524158c2d6f9fd0e0f535246568575
Reviewed-on: https://gerrit.chromium.org/gerrit/19374
Tested-by: Jason Glasgow <jglasgow@chromium.org>
Reviewed-by: Nathan J. Williams <njw@chromium.org>
Commit-Ready: Jason Glasgow <jglasgow@chromium.org>
diff --git a/.gitignore b/.gitignore
index 0673a6b..ebbe1df 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,6 @@
 *.o
+*.lo
+*.la
 src/cashewd
 cashew-*.tar.gz
 Makefile.in
@@ -15,3 +17,5 @@
 missing
 stamp-h1
 .deps/
+.libs/
+src/*glue.h
diff --git a/src/data_plan_provider.cc b/src/data_plan_provider.cc
index e2f0733..c5f5cc1 100644
--- a/src/data_plan_provider.cc
+++ b/src/data_plan_provider.cc
@@ -44,6 +44,14 @@
   }
 }
 
+void DataPlanProvider::SetHttpProxy(const std::string& http_proxy) {
+  LOG(INFO) << carrier_ << ": SetHttpProxy: http_proxy = " << http_proxy;
+  if (http_proxy_ != http_proxy) {
+    http_proxy_ = http_proxy;
+    CancelPendingRequests();
+  }
+}
+
 void DataPlanProvider::SetDelegate(DataPlanProviderDelegate *delegate) {
   LOG(INFO) << carrier_ << ": SetDelegate";
   delegate_ = delegate;
@@ -74,7 +82,7 @@
   DCHECK(response_buffer_.empty());
   LOG(INFO) << carrier_ << ": RequestUsageUpdate: initiating request";
   request_in_progress_ = true;
-  fetcher_->BeginTransfer(usage_url_);
+  fetcher_->BeginTransfer(usage_url_, http_proxy_);
   return true;
 }
 
diff --git a/src/data_plan_provider.h b/src/data_plan_provider.h
index 690ac15..6175d35 100644
--- a/src/data_plan_provider.h
+++ b/src/data_plan_provider.h
@@ -33,6 +33,9 @@
     // set usage API URL for which this proxy is configured
     virtual void SetUsageUrl(const std::string& usage_url);
 
+    // set http proxy to use when fetching requests
+    virtual void SetHttpProxy(const std::string& http_proxy);
+
     // set delegate interface that will receive callbacks from this proxy
     // it's ok to clear this by setting it to NULL
     virtual void SetDelegate(DataPlanProviderDelegate *delegate);
@@ -74,6 +77,9 @@
     // usage API URL
     std::string usage_url_;
 
+    // http proxy
+    std::string http_proxy_;
+
     // delegate
     DataPlanProviderDelegate *delegate_;
 
diff --git a/src/http_fetcher.h b/src/http_fetcher.h
index d0fd827..9a91d69 100644
--- a/src/http_fetcher.h
+++ b/src/http_fetcher.h
@@ -53,7 +53,9 @@
   }
 
   // Begins the transfer to the specified URL.
-  virtual void BeginTransfer(const std::string& url) = 0;
+  virtual void BeginTransfer(const std::string& url,
+                             const std::string& proxy
+                             ) = 0;
 
   // Aborts the transfer. TransferComplete() will not be called on the
   // delegate.
@@ -73,6 +75,9 @@
   // The URL we're actively fetching from
   std::string url_;
 
+  // The proxy to use, if any.
+  std::string proxy_;
+
   // The name servers to use (if not empty).
   std::vector<std::string> nameservers_;
 
diff --git a/src/libcurl_http_fetcher.cc b/src/libcurl_http_fetcher.cc
index 59352b5..951e042 100644
--- a/src/libcurl_http_fetcher.cc
+++ b/src/libcurl_http_fetcher.cc
@@ -35,10 +35,12 @@
   CleanUp();
 }
 
-void LibcurlHttpFetcher::ResumeTransfer(const std::string& url) {
+void LibcurlHttpFetcher::ResumeTransfer(const std::string& url,
+                                        const std::string& proxy) {
   LOG(INFO) << "Starting/Resuming transfer";
   CHECK(!transfer_in_progress_);
   url_ = url;
+  proxy_ = proxy;
   curl_multi_handle_ = curl_multi_init();
   CHECK(curl_multi_handle_);
 
@@ -113,19 +115,26 @@
                             kRequestTimeoutSeconds),
            CURLE_OK);
 
+  // Set the proxy
+  if (!proxy_.empty()) {
+    CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_PROXY, proxy_.c_str()),
+             CURLE_OK);
+  }
+
   CHECK_EQ(curl_multi_add_handle(curl_multi_handle_, curl_handle_), CURLM_OK);
   transfer_in_progress_ = true;
 }
 
 // Begins the transfer, which must not have already been started.
-void LibcurlHttpFetcher::BeginTransfer(const std::string& url) {
+void LibcurlHttpFetcher::BeginTransfer(const std::string& url,
+                                       const std::string& proxy) {
   LOG(INFO) << "BeginTransfer";
   transfer_size_ = -1;
   bytes_downloaded_ = 0;
   resume_offset_ = 0;
   retry_count_ = 0;
   http_response_code_ = 0;
-  ResumeTransfer(url);
+  ResumeTransfer(url, proxy);
   CurlPerformOnce();
 }
 
@@ -312,7 +321,7 @@
 
 gboolean LibcurlHttpFetcher::RetryTimeoutCallback() {
   LOG(INFO) << "RetryTimeoutCallback";
-  ResumeTransfer(url_);
+  ResumeTransfer(url_, proxy_);
   CurlPerformOnce();
   return FALSE;  // Don't have glib auto call this callback again
 }
diff --git a/src/libcurl_http_fetcher.h b/src/libcurl_http_fetcher.h
index b8179b4..cd76c49 100644
--- a/src/libcurl_http_fetcher.h
+++ b/src/libcurl_http_fetcher.h
@@ -41,7 +41,7 @@
   ~LibcurlHttpFetcher();
 
   // Begins the transfer if it hasn't already begun.
-  virtual void BeginTransfer(const std::string& url);
+  virtual void BeginTransfer(const std::string& url, const std::string& proxy);
 
   // If the transfer is in progress, aborts the transfer early.
   // The transfer cannot be resumed.
@@ -77,7 +77,7 @@
   // Resumes a transfer where it left off. This will use the
   // HTTP Range: header to make a new connection from where the last
   // left off.
-  virtual void ResumeTransfer(const std::string& url);
+  virtual void ResumeTransfer(const std::string& url, const std::string& proxy);
 
   // These two methods are for glib main loop callbacks. They are called
   // when either a file descriptor is ready for work or when a timer
diff --git a/src/service.h b/src/service.h
index 5cb01aa..37f108c 100644
--- a/src/service.h
+++ b/src/service.h
@@ -65,7 +65,6 @@
     } Type;
 
     // get type for this service
-    // NOTE: for now, should always be kTypeCellular
     virtual Type GetType() const = 0;
 
     // convert type string to Type enum value
diff --git a/src/service_impl.cc b/src/service_impl.cc
index 0936253..9d8c21a 100644
--- a/src/service_impl.cc
+++ b/src/service_impl.cc
@@ -6,6 +6,8 @@
 
 #include <glog/logging.h>
 
+#include <iostream>
+#include <sstream>
 #include <vector>
 
 #include "src/aggregator.h"
@@ -29,6 +31,9 @@
 static const char *kFlimflamServiceUsageUrlProperty = "Cellular.UsageUrl";
 static const char *kFlimflamServiceStickyHostRouteProperty = "StickyHostRoute";
 
+// Shill Service property names
+static const char kShillServiceHTTPProxyPortProperty[] = "HTTPProxyPort";
+
 // Flimflam Service on-the-wire State values
 static const char *kFlimflamServiceStateIdle = "idle";
 static const char *kFlimflamServiceStateCarrier = "carrier";
@@ -211,6 +216,8 @@
     OnUsageUrlUpdate(new_value.reader().get_string());
   } else if (property_name == kFlimflamServiceStickyHostRouteProperty) {
     OnStickyHostRouteUpdate(new_value.reader().get_string());
+  } else if (property_name == kShillServiceHTTPProxyPortProperty) {
+    OnHTTPProxyPortUpdate(new_value.reader().get_uint16());
   } else {
     // we don't care about this property
   }
@@ -255,6 +262,9 @@
     return;
   }
 
+  // Make sure the proxy is set correctly
+  provider_->SetHttpProxy(http_proxy_);
+
   // if we already have usage url, set new provider in motion
   if (!usage_url_.empty()) {
     provider_->SetUsageUrl(usage_url_);
@@ -565,6 +575,11 @@
     return;
   }
 
+  // if this is not a cellular service, do not bother.
+  if (type_ != kTypeCellular) {
+    return;
+  }
+
   // if there's no existing device: make one
   device_ = Device::NewDevice(this, connection_, device_path);
   if (device_ == NULL) {
@@ -632,6 +647,21 @@
   ReconsiderSendingUsageRequests();
 }
 
+void ServiceImpl::OnHTTPProxyPortUpdate(uint16_t port) {
+  LOG(INFO) << path_ << ": OnHTTPProxyPortUpdate: port = " << port;
+  std::ostringstream http_proxy;
+  if (port > 0) {
+    http_proxy << "localhost:" << port;
+  }
+  if (http_proxy_ == http_proxy.str())
+    return;
+  http_proxy_ = http_proxy.str();
+  if (provider_) {
+    provider_->SetHttpProxy(http_proxy_);
+    ReconsiderSendingUsageRequests();
+  }
+}
+
 // static
 gboolean ServiceImpl::StaticGetServicePropertiesCallback(gpointer data) {
   ServiceImpl *service = reinterpret_cast<ServiceImpl*>(data);
@@ -690,8 +720,17 @@
   LOG(INFO) << path_ << ": GetServiceProperties: Received "
       << properties.size() << " properties";
 
-  // grab the properties in which we're interested
+  // Grab the properties in which we're interested.  Order is somewhat
+  // important.  Type should be fetched first because it is used by
+  // OnDeviceUpdate().
   PropertyMap::const_iterator it;
+  it = properties.find(kFlimflamServiceTypeProperty);
+  if (it != properties.end()) {
+    const DBus::Variant& value = static_cast<DBus::Variant>(it->second);
+    OnTypeUpdate(value.reader().get_string());
+  } else {
+    LOG(WARNING) << path_ << ": GetServiceProperties: no Type property";
+  }
   it = properties.find(kFlimflamServiceDeviceProperty);
   if (it != properties.end()) {
     const DBus::Variant& value = static_cast<DBus::Variant>(it->second);
@@ -706,13 +745,6 @@
   } else {
     LOG(WARNING) << path_ << ": GetServiceProperties: no State property";
   }
-  it = properties.find(kFlimflamServiceTypeProperty);
-  if (it != properties.end()) {
-    const DBus::Variant& value = static_cast<DBus::Variant>(it->second);
-    OnTypeUpdate(value.reader().get_string());
-  } else {
-    LOG(WARNING) << path_ << ": GetServiceProperties: no Type property";
-  }
   it = properties.find(kFlimflamServiceStickyHostRouteProperty);
   if (it != properties.end()) {
     const DBus::Variant& value = static_cast<DBus::Variant>(it->second);
@@ -721,6 +753,15 @@
     LOG(WARNING) << path_
         << ": GetServiceProperties: no StickyHostRoute property";
   }
+  it = properties.find(kShillServiceHTTPProxyPortProperty);
+  if (it != properties.end()) {
+    const DBus::Variant& value = static_cast<DBus::Variant>(it->second);
+    OnHTTPProxyPortUpdate(value.reader().get_uint16());
+  } else {
+    LOG(WARNING) << path_
+        << ": GetServiceProperties: no HTTPProxyPort property";
+  }
+
   // don't expect to find Cellular.* properties if we're not a cellular service
   if (type_ != kTypeCellular) {
     return true;
@@ -968,9 +1009,10 @@
     LOG(INFO) << path_ << ": ShouldSendUsageRequests: no: not connected";
     return false;
   }
-  if (sticky_host_route_.empty() && !IsDefaultService()) {
+  if (sticky_host_route_.empty() && http_proxy_.empty()
+      && !IsDefaultService()) {
     LOG(INFO) << path_ << ": ShouldSendUsageRequests: "
-        << "no: no sticky host route and not default service";
+        << "no: no sticky host route, no proxy and not default service";
     return false;
   }
   LOG(INFO) << path_ << ": ShouldSendUsageRequests: yes";
diff --git a/src/service_impl.h b/src/service_impl.h
index 12f0fa8..d676855 100644
--- a/src/service_impl.h
+++ b/src/service_impl.h
@@ -150,6 +150,9 @@
     // do we think that we're the default service?
     bool is_default_service_;
 
+    // the http proxy used to fetch URLs.
+    std::string http_proxy_;
+
     // GetProperties timer glib source id
     // 0 means no source
     guint get_properties_source_id_;
@@ -185,6 +188,9 @@
     // We've received updated StickyHostRoute info from Flimflam
     void OnStickyHostRouteUpdate(const std::string& sticky_host_route);
 
+    // We've received updated HTTPProxyPort info from Shill
+  void OnHTTPProxyPortUpdate(uint16_t port);
+
     // glib integration: static wrapper for GetServiceProperties
     // takes object ptr as data and invokes object->GetServiceProperties()
     static gboolean StaticGetServicePropertiesCallback(gpointer data);
diff --git a/src/service_manager_impl.cc b/src/service_manager_impl.cc
index 3917cbe..6a59a47 100644
--- a/src/service_manager_impl.cc
+++ b/src/service_manager_impl.cc
@@ -20,9 +20,6 @@
 static const char *kServicesProperty = "Services";
 static const char *kStateProperty = "State";
 
-// Flimflam service types
-static const char *kTypeCellular = "cellular";
-
 // GetFlimflamProperties retry interval
 static const guint kSecondsPerMinute = 60;
 static const guint kGetFlimflamPropertiesIntervalSeconds =
@@ -265,22 +262,12 @@
   ServiceMap old_services = services_;
   services_.clear();
 
-  // Flimflam service types are embedded in their path names, so we can use
-  // the pattern "/cellular_" to decide which paths are cellular services.
-  static const std::string cellular_pattern = std::string("/") + kTypeCellular
-      + "_";
-
   // Walk paths in this update and see if they're new to us
   LOG(INFO) << "OnServicesUpdate: " << paths.size() << " service paths";
   ServicePathList::const_iterator it;
   for (it = paths.begin(); it != paths.end(); ++it) {
     const DBus::Path& path = *it;
 
-    // We only care about cellular services
-    if (path.find(cellular_pattern) == std::string::npos) {
-      continue;
-    }
-
     ServiceMap::iterator old_it = old_services.find(path);
     if (old_it != old_services.end()) {
       // service is old: move it from old_services to services_
@@ -335,12 +322,13 @@
       << ((path == NULL) ? "None" : *path);
   // check if new default service path (if any) corresponds to one of our
   // cellular services
-  Service *new_default_cellular_service = NULL;
+  Service *new_default_service = NULL;
   if (path != NULL) {
-    new_default_cellular_service = const_cast<Service*>(GetService(*path));
+    new_default_service = const_cast<Service*>(GetService(*path));
   }
-  if (new_default_cellular_service != NULL) {
-    SetDefaultCellularService(new_default_cellular_service);
+  if (new_default_service != NULL &&
+      new_default_service->GetType() == Service::kTypeCellular) {
+    SetDefaultCellularService(new_default_service);
   } else {
     ClearDefaultCellularService();
   }