shill: chromeos DBus proxy for dhcpcd

Implement ChromeosDHCPCDListener by registering a filter function and
adding a match rule directly to the bus, which introduces a direct
dependency on libdbus-1.

Also dhcpcd proxy does not listen for signals, since those signals are
monitored/processed by dhcpcd listener.

BUG=chromium:507869
TEST=USE="chromeos_dbus asan clang" FEATURES=test emerge-$BOARD shill
TEST=Verify ChromeosDHCPCDListener with a simple test daemon

Change-Id: I0b5ec4acdc5c273388b0b16ffb9b6b67f45b8c89
Reviewed-on: https://chromium-review.googlesource.com/288294
Reviewed-by: Alex Vakulenko <avakulenko@chromium.org>
Reviewed-by: Paul Stewart <pstew@chromium.org>
Commit-Queue: Zeping Qiu <zqiu@chromium.org>
Tested-by: Zeping Qiu <zqiu@chromium.org>
diff --git a/dbus/chromeos_dhcpcd_listener.cc b/dbus/chromeos_dhcpcd_listener.cc
new file mode 100644
index 0000000..d61345a
--- /dev/null
+++ b/dbus/chromeos_dhcpcd_listener.cc
@@ -0,0 +1,178 @@
+// Copyright 2015 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/dbus/chromeos_dhcpcd_listener.h"
+
+#include <string.h>
+
+#include <base/bind.h>
+#include <base/callback.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/dbus/dbus_method_invoker.h>
+#include <dbus/util.h>
+
+#include "shill/dhcp/dhcp_config.h"
+#include "shill/dhcp/dhcp_provider.h"
+#include "shill/event_dispatcher.h"
+#include "shill/logging.h"
+
+using std::string;
+
+namespace shill {
+
+namespace Logging {
+static auto kModuleLogScope = ScopeLogger::kDHCP;
+static string ObjectID(ChromeosDHCPCDListener* d) {
+  return "(dhcpcd_listener)";
+}
+}
+
+const char ChromeosDHCPCDListener::kDBusInterfaceName[] = "org.chromium.dhcpcd";
+const char ChromeosDHCPCDListener::kSignalEvent[] = "Event";
+const char ChromeosDHCPCDListener::kSignalStatusChanged[] = "StatusChanged";
+
+ChromeosDHCPCDListener::ChromeosDHCPCDListener(
+    const scoped_refptr<dbus::Bus>& bus,
+    EventDispatcher* dispatcher,
+    DHCPProvider* provider)
+    : bus_(bus),
+      dispatcher_(dispatcher),
+      provider_(provider),
+      match_rule_(base::StringPrintf("type='signal', interface='%s'",
+                                     kDBusInterfaceName)) {
+  bus_->AssertOnDBusThread();
+  CHECK(bus_->SetUpAsyncOperations());
+  if (!bus_->is_connected()) {
+    LOG(FATAL) << "DBus isn't connected.";
+  }
+
+  // Register filter function to the bus.  It will be called when incoming
+  // messages are received.
+  bus_->AddFilterFunction(&ChromeosDHCPCDListener::HandleMessageThunk, this);
+
+  // Add match rule to the bus.
+  dbus::ScopedDBusError error;
+  bus_->AddMatch(match_rule_, error.get());
+  if (error.is_set()) {
+    LOG(FATAL) << "Failed to add match rule: " << error.name() << " "
+               << error.message();
+  }
+}
+
+ChromeosDHCPCDListener::~ChromeosDHCPCDListener() {
+  bus_->RemoveFilterFunction(&ChromeosDHCPCDListener::HandleMessageThunk, this);
+  dbus::ScopedDBusError error;
+  bus_->RemoveMatch(match_rule_, error.get());
+  if (error.is_set()) {
+    LOG(FATAL) << "Failed to remove match rule: " << error.name() << " "
+               << error.message();
+  }
+}
+
+// static.
+DBusHandlerResult ChromeosDHCPCDListener::HandleMessageThunk(
+    DBusConnection* connection, DBusMessage* raw_message, void* user_data) {
+  ChromeosDHCPCDListener* self =
+      static_cast<ChromeosDHCPCDListener*>(user_data);
+  return self->HandleMessage(connection, raw_message);
+}
+
+DBusHandlerResult ChromeosDHCPCDListener::HandleMessage(
+    DBusConnection* connection, DBusMessage* raw_message) {
+  bus_->AssertOnDBusThread();
+
+  // Only interested in signal message.
+  if (dbus_message_get_type(raw_message) != DBUS_MESSAGE_TYPE_SIGNAL) {
+    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+  }
+
+  // raw_message will be unrefed in Signal's parent class's (dbus::Message)
+  // destructor. Increment the reference so we can use it in Signal.
+  dbus_message_ref(raw_message);
+  std::unique_ptr<dbus::Signal> signal(
+      dbus::Signal::FromRawMessage(raw_message));
+
+  // Verify the signal comes from the interface that we interested in.
+  if (signal->GetInterface() != kDBusInterfaceName) {
+    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+  }
+
+  string sender = signal->GetSender();
+  string member_name = signal->GetMember();
+  dbus::MessageReader reader(signal.get());
+  if (member_name == kSignalEvent) {
+    uint32_t pid;
+    string reason;
+    chromeos::VariantDictionary configurations;
+    // ExtracMessageParameters will log the error if it failed.
+    if (chromeos::dbus_utils::ExtractMessageParameters(&reader,
+                                                       nullptr,
+                                                       &pid,
+                                                       &reason,
+                                                       &configurations)) {
+      dispatcher_->PostTask(
+          base::Bind(&ChromeosDHCPCDListener::EventSignal,
+                     weak_factory_.GetWeakPtr(),
+                     sender, pid, reason, configurations));
+    }
+  } else if (member_name == kSignalStatusChanged) {
+    uint32_t pid;
+    string status;
+    // ExtracMessageParameters will log the error if it failed.
+    if (chromeos::dbus_utils::ExtractMessageParameters(&reader,
+                                                       nullptr,
+                                                       &pid,
+                                                       &status)) {
+      dispatcher_->PostTask(
+          base::Bind(&ChromeosDHCPCDListener::StatusChangedSignal,
+                     weak_factory_.GetWeakPtr(),
+                     sender, pid, status));
+    }
+  } else {
+    LOG(INFO) << "Ignore signal: " << member_name;
+  }
+
+  return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+void ChromeosDHCPCDListener::EventSignal(
+    const string& sender,
+    uint32_t pid,
+    const string& reason,
+    const chromeos::VariantDictionary& configuration) {
+  DHCPConfigRefPtr config = provider_->GetConfig(pid);
+  if (!config.get()) {
+    if (provider_->IsRecentlyUnbound(pid)) {
+      SLOG(nullptr, 3)
+          << __func__ << ": ignoring message from recently unbound PID " << pid;
+    } else {
+      LOG(ERROR) << "Unknown DHCP client PID " << pid;
+    }
+    return;
+  }
+  config->InitProxy(sender);
+  KeyValueStore configuration_store;
+  KeyValueStore::ConvertFromVariantDictionary(configuration,
+                                              &configuration_store);
+  config->ProcessEventSignal(reason, configuration_store);
+}
+
+void ChromeosDHCPCDListener::StatusChangedSignal(const string& sender,
+                                                 uint32_t pid,
+                                                 const string& status) {
+  DHCPConfigRefPtr config = provider_->GetConfig(pid);
+  if (!config.get()) {
+    if (provider_->IsRecentlyUnbound(pid)) {
+      SLOG(nullptr, 3)
+          << __func__ << ": ignoring message from recently unbound PID " << pid;
+    } else {
+      LOG(ERROR) << "Unknown DHCP client PID " << pid;
+    }
+    return;
+  }
+  config->InitProxy(sender);
+  config->ProcessStatusChangeSignal(status);
+}
+
+}  // namespace shill
diff --git a/dbus/chromeos_dhcpcd_listener.h b/dbus/chromeos_dhcpcd_listener.h
new file mode 100644
index 0000000..2316c28
--- /dev/null
+++ b/dbus/chromeos_dhcpcd_listener.h
@@ -0,0 +1,69 @@
+// Copyright 2015 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.
+
+#ifndef SHILL_DBUS_CHROMEOS_DHCPCD_LISTENER_H_
+#define SHILL_DBUS_CHROMEOS_DHCPCD_LISTENER_H_
+
+#include <string>
+
+#include <dbus/dbus.h>
+
+#include <base/macros.h>
+#include <base/memory/ref_counted.h>
+#include <base/memory/weak_ptr.h>
+#include <dbus/bus.h>
+#include <dbus/message.h>
+
+#include <chromeos/variant_dictionary.h>
+
+namespace shill {
+
+class DHCPProvider;
+class EventDispatcher;
+
+// The DHCPCD listener is a singleton proxy that listens to signals from all
+// DHCP clients and dispatches them through the DHCP provider to the appropriate
+// client based on the PID.
+class ChromeosDHCPCDListener final {
+ public:
+  ChromeosDHCPCDListener(const scoped_refptr<dbus::Bus>& bus,
+                         EventDispatcher* dispatcher,
+                         DHCPProvider* provider);
+  ~ChromeosDHCPCDListener();
+
+ private:
+  static const char kDBusInterfaceName[];
+  static const char kSignalEvent[];
+  static const char kSignalStatusChanged[];
+
+  // Redirects the function call to HandleMessage
+  static DBusHandlerResult HandleMessageThunk(DBusConnection* connection,
+                                              DBusMessage* raw_message,
+                                              void* user_data);
+
+  // Handles incoming messages.
+  DBusHandlerResult HandleMessage(DBusConnection* connection,
+                                  DBusMessage* raw_message);
+
+  // Signal handlers.
+  void EventSignal(const std::string& sender,
+                   uint32_t pid,
+                   const std::string& reason,
+                   const chromeos::VariantDictionary& configurations);
+  void StatusChangedSignal(const std::string& sender,
+                           uint32_t pid,
+                           const std::string& status);
+
+  scoped_refptr<dbus::Bus> bus_;
+  EventDispatcher* dispatcher_;
+  DHCPProvider* provider_;
+  const std::string match_rule_;
+
+  base::WeakPtrFactory<ChromeosDHCPCDListener> weak_factory_{this};
+  DISALLOW_COPY_AND_ASSIGN(ChromeosDHCPCDListener);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_DBUS_CHROMEOS_DHCPCD_LISTENER_H_
diff --git a/dbus/chromeos_dhcpcd_proxy.cc b/dbus/chromeos_dhcpcd_proxy.cc
new file mode 100644
index 0000000..c876aae
--- /dev/null
+++ b/dbus/chromeos_dhcpcd_proxy.cc
@@ -0,0 +1,56 @@
+// Copyright 2015 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/dbus/chromeos_dhcpcd_proxy.h"
+
+#include "shill/logging.h"
+
+using std::string;
+namespace shill {
+
+namespace Logging {
+static auto kModuleLogScope = ScopeLogger::kDHCP;
+static string ObjectID(ChromeosDHCPCDProxy* d) { return "(dhcpcd_proxy)"; }
+}
+
+ChromeosDHCPCDProxy::ChromeosDHCPCDProxy(const scoped_refptr<dbus::Bus>& bus,
+                                         const std::string& service_name)
+    : dhcpcd_proxy_(
+        new org::chromium::dhcpcdProxy(bus, service_name)) {
+  SLOG(this, 2) << "DHCPCDProxy(service=" << service_name << ").";
+  // Do not register signal handlers, signals are processed by
+  // ChromeosDHCPCDListener.
+}
+
+ChromeosDHCPCDProxy::~ChromeosDHCPCDProxy() {}
+
+void ChromeosDHCPCDProxy::Rebind(const string& interface) {
+  SLOG(DBus, nullptr, 2) << __func__;
+  chromeos::ErrorPtr error;
+  if (!dhcpcd_proxy_->Rebind(interface, &error)) {
+    LogDBusError(error, __func__, interface);
+  }
+}
+
+void ChromeosDHCPCDProxy::Release(const string& interface) {
+  SLOG(DBus, nullptr, 2) << __func__;
+  chromeos::ErrorPtr error;
+  if (!dhcpcd_proxy_->Release(interface, &error)) {
+    LogDBusError(error, __func__, interface);
+  }
+}
+
+void ChromeosDHCPCDProxy::LogDBusError(const chromeos::ErrorPtr& error,
+                                       const string& method,
+                                       const string& interface) {
+  if (error->GetCode() == DBUS_ERROR_SERVICE_UNKNOWN ||
+      error->GetCode() == DBUS_ERROR_NO_REPLY) {
+    LOG(INFO) << method << ": dhcpcd daemon appears to have exited.";
+  } else {
+    LOG(FATAL) << "DBus error: " << method << " " << interface << ": "
+               << error->GetCode() << ": " << error->GetMessage();
+  }
+}
+
+}  // namespace shill
diff --git a/dbus/chromeos_dhcpcd_proxy.h b/dbus/chromeos_dhcpcd_proxy.h
new file mode 100644
index 0000000..38dccec
--- /dev/null
+++ b/dbus/chromeos_dhcpcd_proxy.h
@@ -0,0 +1,40 @@
+// Copyright 2015 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.
+
+#ifndef SHILL_DBUS_CHROMEOS_DHCPCD_PROXY_H_
+#define SHILL_DBUS_CHROMEOS_DHCPCD_PROXY_H_
+
+#include <string>
+
+#include <base/macros.h>
+
+#include "dhcpcd/dbus-proxies.h"
+#include "shill/dhcp/dhcp_proxy_interface.h"
+
+namespace shill {
+
+// There's a single DHCPCD proxy per DHCP client identified by its process id.
+class ChromeosDHCPCDProxy : public DHCPProxyInterface {
+ public:
+  ChromeosDHCPCDProxy(const scoped_refptr<dbus::Bus>& bus,
+                      const std::string& service_name);
+  ~ChromeosDHCPCDProxy() override;
+
+  // Inherited from DHCPProxyInterface.
+  void Rebind(const std::string& interface) override;
+  void Release(const std::string& interface) override;
+
+ private:
+  void LogDBusError(const chromeos::ErrorPtr& error,
+                    const std::string& method,
+                    const std::string& interface);
+
+  std::unique_ptr<org::chromium::dhcpcdProxy> dhcpcd_proxy_;
+
+  DISALLOW_COPY_AND_ASSIGN(ChromeosDHCPCDProxy);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_DBUS_CHROMEOS_DHCPCD_PROXY_H_
diff --git a/shill.gyp b/shill.gyp
index 7fafc8c..05cb6ba 100644
--- a/shill.gyp
+++ b/shill.gyp
@@ -33,6 +33,13 @@
           'DISABLE_CHROMEOS_DBUS',
         ],
       }],
+      ['USE_chromeos_dbus == 1', {
+        'variables': {
+          'deps': [
+            'dbus-1',
+          ],
+        },
+      }],
       ['USE_pppoe == 0', {
         'defines': [
           'DISABLE_PPPOE',
@@ -322,6 +329,8 @@
             'dbus/chromeos_dbus_control.cc',
             'dbus/chromeos_dbus_daemon.cc',
             'dbus/chromeos_device_dbus_adaptor.cc',
+            'dbus/chromeos_dhcpcd_listener.cc',
+            'dbus/chromeos_dhcpcd_proxy.cc',
             'dbus/chromeos_ipconfig_dbus_adaptor.cc',
             'dbus/chromeos_manager_dbus_adaptor.cc',
             'dbus/chromeos_profile_dbus_adaptor.cc',
@@ -332,6 +341,16 @@
           ],
           'actions': [
             {
+              'action_name': 'generate-dhcpcd-proxies',
+              'variables': {
+                'proxy_output_file': 'include/dhcpcd/dbus-proxies.h',
+              },
+              'sources': [
+                'dbus_bindings/dhcpcd.xml',
+              ],
+              'includes': ['../common-mk/generate-dbus-proxies.gypi'],
+            },
+            {
               'action_name': 'generate-upstart-proxies',
               'variables': {
                 'proxy_output_file': 'include/upstart/dbus-proxies.h',