1//
2// Copyright (C) 2015 The Android Open Source Project
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//      http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15//
16
17#include "shill/dbus/chromeos_dhcpcd_listener.h"
18
19#include <string.h>
20
21#include <base/bind.h>
22#include <base/callback.h>
23#include <base/strings/stringprintf.h>
24#include <brillo/dbus/dbus_method_invoker.h>
25#include <dbus/util.h>
26
27#include "shill/dhcp/dhcp_config.h"
28#include "shill/dhcp/dhcp_provider.h"
29#include "shill/event_dispatcher.h"
30#include "shill/logging.h"
31
32using std::string;
33
34namespace shill {
35
36namespace Logging {
37static auto kModuleLogScope = ScopeLogger::kDHCP;
38static string ObjectID(ChromeosDHCPCDListener* d) {
39  return "(dhcpcd_listener)";
40}
41}
42
43const char ChromeosDHCPCDListener::kDBusInterfaceName[] = "org.chromium.dhcpcd";
44const char ChromeosDHCPCDListener::kSignalEvent[] = "Event";
45const char ChromeosDHCPCDListener::kSignalStatusChanged[] = "StatusChanged";
46
47ChromeosDHCPCDListener::ChromeosDHCPCDListener(
48    const scoped_refptr<dbus::Bus>& bus,
49    EventDispatcher* dispatcher,
50    DHCPProvider* provider)
51    : bus_(bus),
52      dispatcher_(dispatcher),
53      provider_(provider),
54      match_rule_(base::StringPrintf("type='signal', interface='%s'",
55                                     kDBusInterfaceName)) {
56  bus_->AssertOnDBusThread();
57  CHECK(bus_->SetUpAsyncOperations());
58  if (!bus_->is_connected()) {
59    LOG(FATAL) << "DBus isn't connected.";
60  }
61
62  // Register filter function to the bus.  It will be called when incoming
63  // messages are received.
64  bus_->AddFilterFunction(&ChromeosDHCPCDListener::HandleMessageThunk, this);
65
66  // Add match rule to the bus.
67  dbus::ScopedDBusError error;
68  bus_->AddMatch(match_rule_, error.get());
69  if (error.is_set()) {
70    LOG(FATAL) << "Failed to add match rule: " << error.name() << " "
71               << error.message();
72  }
73}
74
75ChromeosDHCPCDListener::~ChromeosDHCPCDListener() {
76  bus_->RemoveFilterFunction(&ChromeosDHCPCDListener::HandleMessageThunk, this);
77  dbus::ScopedDBusError error;
78  bus_->RemoveMatch(match_rule_, error.get());
79  if (error.is_set()) {
80    LOG(FATAL) << "Failed to remove match rule: " << error.name() << " "
81               << error.message();
82  }
83}
84
85// static.
86DBusHandlerResult ChromeosDHCPCDListener::HandleMessageThunk(
87    DBusConnection* connection, DBusMessage* raw_message, void* user_data) {
88  ChromeosDHCPCDListener* self =
89      static_cast<ChromeosDHCPCDListener*>(user_data);
90  return self->HandleMessage(connection, raw_message);
91}
92
93DBusHandlerResult ChromeosDHCPCDListener::HandleMessage(
94    DBusConnection* connection, DBusMessage* raw_message) {
95  bus_->AssertOnDBusThread();
96
97  // Only interested in signal message.
98  if (dbus_message_get_type(raw_message) != DBUS_MESSAGE_TYPE_SIGNAL) {
99    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
100  }
101
102  // raw_message will be unrefed in Signal's parent class's (dbus::Message)
103  // destructor. Increment the reference so we can use it in Signal.
104  dbus_message_ref(raw_message);
105  std::unique_ptr<dbus::Signal> signal(
106      dbus::Signal::FromRawMessage(raw_message));
107
108  // Verify the signal comes from the interface that we interested in.
109  if (signal->GetInterface() != kDBusInterfaceName) {
110    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
111  }
112
113  string sender = signal->GetSender();
114  string member_name = signal->GetMember();
115  dbus::MessageReader reader(signal.get());
116  if (member_name == kSignalEvent) {
117    uint32_t pid;
118    string reason;
119    brillo::VariantDictionary configurations;
120    // ExtracMessageParameters will log the error if it failed.
121    if (brillo::dbus_utils::ExtractMessageParameters(&reader,
122                                                     nullptr,
123                                                     &pid,
124                                                     &reason,
125                                                     &configurations)) {
126      dispatcher_->PostTask(
127          base::Bind(&ChromeosDHCPCDListener::EventSignal,
128                     weak_factory_.GetWeakPtr(),
129                     sender, pid, reason, configurations));
130    }
131  } else if (member_name == kSignalStatusChanged) {
132    uint32_t pid;
133    string status;
134    // ExtracMessageParameters will log the error if it failed.
135    if (brillo::dbus_utils::ExtractMessageParameters(&reader,
136                                                     nullptr,
137                                                     &pid,
138                                                     &status)) {
139      dispatcher_->PostTask(
140          base::Bind(&ChromeosDHCPCDListener::StatusChangedSignal,
141                     weak_factory_.GetWeakPtr(),
142                     sender, pid, status));
143    }
144  } else {
145    LOG(INFO) << "Ignore signal: " << member_name;
146  }
147
148  return DBUS_HANDLER_RESULT_HANDLED;
149}
150
151void ChromeosDHCPCDListener::EventSignal(
152    const string& sender,
153    uint32_t pid,
154    const string& reason,
155    const brillo::VariantDictionary& configuration) {
156  DHCPConfigRefPtr config = provider_->GetConfig(pid);
157  if (!config.get()) {
158    if (provider_->IsRecentlyUnbound(pid)) {
159      SLOG(nullptr, 3)
160          << __func__ << ": ignoring message from recently unbound PID " << pid;
161    } else {
162      LOG(ERROR) << "Unknown DHCP client PID " << pid;
163    }
164    return;
165  }
166  config->InitProxy(sender);
167  KeyValueStore configuration_store;
168  KeyValueStore::ConvertFromVariantDictionary(configuration,
169                                              &configuration_store);
170  config->ProcessEventSignal(reason, configuration_store);
171}
172
173void ChromeosDHCPCDListener::StatusChangedSignal(const string& sender,
174                                                 uint32_t pid,
175                                                 const string& status) {
176  DHCPConfigRefPtr config = provider_->GetConfig(pid);
177  if (!config.get()) {
178    if (provider_->IsRecentlyUnbound(pid)) {
179      SLOG(nullptr, 3)
180          << __func__ << ": ignoring message from recently unbound PID " << pid;
181    } else {
182      LOG(ERROR) << "Unknown DHCP client PID " << pid;
183    }
184    return;
185  }
186  config->InitProxy(sender);
187  config->ProcessStatusChangeSignal(status);
188}
189
190}  // namespace shill
191