blob: 016756e6ac4b85e10d9782336c038acf7c17115a [file] [log] [blame]
Kevin Cernekee95d4ae92016-06-19 10:26:29 -07001// Copyright 2016 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Garrick Evans3388a032020-03-24 11:25:55 +09005#include "patchpanel/shill_client.h"
Kevin Cernekee95d4ae92016-06-19 10:26:29 -07006
Jie Jiang850a4712020-04-08 21:06:36 +09007#include <utility>
Garrick Evans49879532018-12-03 13:15:36 +09008#include <vector>
9
Kevin Cernekee95d4ae92016-06-19 10:26:29 -070010#include <base/bind.h>
11#include <base/logging.h>
Garrick Evanse87bb4f2020-02-18 10:27:37 +090012#include <base/strings/string_util.h>
Kevin Cernekee95d4ae92016-06-19 10:26:29 -070013#include <chromeos/dbus/service_constants.h>
14
Garrick Evans3388a032020-03-24 11:25:55 +090015namespace patchpanel {
Hugo Benichid17aa592019-04-26 15:15:01 +090016
Garrick Evans08843932019-09-17 14:41:08 +090017ShillClient::ShillClient(const scoped_refptr<dbus::Bus>& bus) : bus_(bus) {
Hidehiko Abe3a7e5132018-02-15 13:07:50 +090018 manager_proxy_.reset(new org::chromium::flimflam::ManagerProxy(bus_));
Kevin Cernekee95d4ae92016-06-19 10:26:29 -070019 manager_proxy_->RegisterPropertyChangedSignalHandler(
20 base::Bind(&ShillClient::OnManagerPropertyChange,
21 weak_factory_.GetWeakPtr()),
22 base::Bind(&ShillClient::OnManagerPropertyChangeRegistration,
23 weak_factory_.GetWeakPtr()));
24}
25
Garrick Evans1b1f67c2020-02-04 16:21:25 +090026const std::string& ShillClient::default_interface() const {
27 return default_interface_;
28}
29
Hugo Benichicc6850f2020-01-17 13:26:06 +090030const std::set<std::string> ShillClient::get_devices() const {
31 return devices_;
32}
33
34bool ShillClient::has_device(const std::string& ifname) const {
35 return devices_.find(ifname) != devices_.end();
36}
37
Jie Jiang84c76a12020-04-17 16:45:20 +090038void ShillClient::ScanDevices() {
Garrick Evans49879532018-12-03 13:15:36 +090039 brillo::VariantDictionary props;
40 if (!manager_proxy_->GetProperties(&props, nullptr)) {
41 LOG(ERROR) << "Unable to get manager properties";
42 return;
43 }
44 const auto it = props.find(shill::kDevicesProperty);
45 if (it == props.end()) {
46 LOG(WARNING) << "Manager properties is missing devices";
47 return;
48 }
Garrick Evans139708f2020-02-06 14:38:59 +090049 UpdateDevices(it->second);
Garrick Evans49879532018-12-03 13:15:36 +090050}
51
Hugo Benichiddee2812019-05-10 16:03:43 +090052std::string ShillClient::GetDefaultInterface() {
Kevin Cernekee95d4ae92016-06-19 10:26:29 -070053 brillo::VariantDictionary manager_props;
54
Kevin Cernekee95d4ae92016-06-19 10:26:29 -070055 if (!manager_proxy_->GetProperties(&manager_props, nullptr)) {
56 LOG(ERROR) << "Unable to get manager properties";
Hugo Benichiddee2812019-05-10 16:03:43 +090057 return "";
Kevin Cernekee95d4ae92016-06-19 10:26:29 -070058 }
59
60 auto it = manager_props.find(shill::kDefaultServiceProperty);
61 if (it == manager_props.end()) {
62 LOG(WARNING) << "Manager properties is missing default service";
Hugo Benichiddee2812019-05-10 16:03:43 +090063 return "";
Kevin Cernekee95d4ae92016-06-19 10:26:29 -070064 }
65
66 dbus::ObjectPath service_path = it->second.TryGet<dbus::ObjectPath>();
67 if (!service_path.IsValid() || service_path.value() == "/") {
Hugo Benichiddee2812019-05-10 16:03:43 +090068 return "";
Kevin Cernekee95d4ae92016-06-19 10:26:29 -070069 }
70
71 org::chromium::flimflam::ServiceProxy service_proxy(bus_, service_path);
72 brillo::VariantDictionary service_props;
73 if (!service_proxy.GetProperties(&service_props, nullptr)) {
74 LOG(ERROR) << "Can't retrieve properties for service";
Hugo Benichiddee2812019-05-10 16:03:43 +090075 return "";
Kevin Cernekee95d4ae92016-06-19 10:26:29 -070076 }
77
Alex Khouderchah05a8b5b2019-11-20 12:18:51 -080078 it = service_props.find(shill::kIsConnectedProperty);
Kevin Cernekee95d4ae92016-06-19 10:26:29 -070079 if (it == service_props.end()) {
Alex Khouderchah05a8b5b2019-11-20 12:18:51 -080080 LOG(WARNING) << "Service properties is missing \"IsConnected\"";
Hugo Benichiddee2812019-05-10 16:03:43 +090081 return "";
Kevin Cernekee95d4ae92016-06-19 10:26:29 -070082 }
Alex Khouderchah05a8b5b2019-11-20 12:18:51 -080083 if (!it->second.TryGet<bool>()) {
84 LOG(INFO) << "Ignoring non-connected service";
Hugo Benichiddee2812019-05-10 16:03:43 +090085 return "";
Kevin Cernekee95d4ae92016-06-19 10:26:29 -070086 }
87
88 it = service_props.find(shill::kDeviceProperty);
89 if (it == service_props.end()) {
90 LOG(WARNING) << "Service properties is missing device path";
Hugo Benichiddee2812019-05-10 16:03:43 +090091 return "";
Kevin Cernekee95d4ae92016-06-19 10:26:29 -070092 }
93
94 dbus::ObjectPath device_path = it->second.TryGet<dbus::ObjectPath>();
95 if (!device_path.IsValid()) {
96 LOG(WARNING) << "Invalid device path";
Hugo Benichiddee2812019-05-10 16:03:43 +090097 return "";
Kevin Cernekee95d4ae92016-06-19 10:26:29 -070098 }
99
100 org::chromium::flimflam::DeviceProxy device_proxy(bus_, device_path);
101 brillo::VariantDictionary device_props;
102 if (!device_proxy.GetProperties(&device_props, nullptr)) {
103 LOG(ERROR) << "Can't retrieve properties for device";
Hugo Benichiddee2812019-05-10 16:03:43 +0900104 return "";
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700105 }
106
107 it = device_props.find(shill::kInterfaceProperty);
108 if (it == device_props.end()) {
109 LOG(WARNING) << "Device properties is missing interface name";
Hugo Benichiddee2812019-05-10 16:03:43 +0900110 return "";
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700111 }
112
113 std::string interface = it->second.TryGet<std::string>();
114 if (interface.empty()) {
115 LOG(WARNING) << "Device interface name is empty";
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700116 }
117
Hugo Benichiddee2812019-05-10 16:03:43 +0900118 return interface;
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700119}
120
121void ShillClient::OnManagerPropertyChangeRegistration(
122 const std::string& interface,
123 const std::string& signal_name,
124 bool success) {
125 if (!success)
126 LOG(FATAL) << "Unable to register for interface change events";
127}
128
129void ShillClient::OnManagerPropertyChange(const std::string& property_name,
130 const brillo::Any& property_value) {
Hugo Benichiddee2812019-05-10 16:03:43 +0900131 if (property_name == shill::kDevicesProperty) {
Garrick Evans139708f2020-02-06 14:38:59 +0900132 UpdateDevices(property_value);
Hugo Benichiddee2812019-05-10 16:03:43 +0900133
134 // Choose a fallback interface when any network device exist. Update the
135 // fallback interface if it that device does not exist anymore.
Garrick Evansd4742832019-05-20 11:51:40 +0900136 if (!devices_.empty() &&
137 devices_.find(fallback_default_interface_) == devices_.end()) {
Hugo Benichiddee2812019-05-10 16:03:43 +0900138 fallback_default_interface_ = *devices_.begin();
139 // When the system appears to have no default interface, use the fallback
140 // interface instead.
Garrick Evansd4742832019-05-20 11:51:40 +0900141 if (default_interface_.empty() ||
142 default_interface_ != fallback_default_interface_)
Hugo Benichiddee2812019-05-10 16:03:43 +0900143 SetDefaultInterface(fallback_default_interface_);
144 }
145
Hugo Benichiddee2812019-05-10 16:03:43 +0900146 // Remove the fallback interface when no network device is managed by shill.
147 if (!fallback_default_interface_.empty() && devices_.empty()) {
148 fallback_default_interface_ = "";
149 SetDefaultInterface("");
150 }
151
Garrick Evans49879532018-12-03 13:15:36 +0900152 return;
153 }
154
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700155 if (property_name != shill::kDefaultServiceProperty &&
156 property_name != shill::kConnectionStateProperty)
157 return;
158
Hugo Benichiddee2812019-05-10 16:03:43 +0900159 SetDefaultInterface(GetDefaultInterface());
160}
161
Garrick Evans1b1f67c2020-02-04 16:21:25 +0900162std::string ShillClient::SetDefaultInterface(std::string new_default) {
Hugo Benichiddee2812019-05-10 16:03:43 +0900163 // When the system default is lost, use the fallback interface instead.
164 if (new_default.empty())
165 new_default = fallback_default_interface_;
166
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700167 if (default_interface_ == new_default)
Garrick Evans1b1f67c2020-02-04 16:21:25 +0900168 return default_interface_;
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700169
Garrick Evans139708f2020-02-06 14:38:59 +0900170 LOG(INFO) << "Default interface changed from [" << default_interface_
171 << "] to [" << new_default << "]";
172
Garrick Evans1b1f67c2020-02-04 16:21:25 +0900173 const std::string prev_default = default_interface_;
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700174 default_interface_ = new_default;
Garrick Evans139708f2020-02-06 14:38:59 +0900175 for (const auto& h : default_interface_handlers_) {
176 if (!h.is_null())
177 h.Run(default_interface_, prev_default);
Garrick Evansc7fea0a2020-02-04 10:46:42 +0900178 }
Garrick Evans1b1f67c2020-02-04 16:21:25 +0900179 return prev_default;
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700180}
181
182void ShillClient::RegisterDefaultInterfaceChangedHandler(
Garrick Evans139708f2020-02-06 14:38:59 +0900183 const DefaultInterfaceChangeHandler& handler) {
184 default_interface_handlers_.emplace_back(handler);
Garrick Evans1b1f67c2020-02-04 16:21:25 +0900185 const auto prev_default = SetDefaultInterface(GetDefaultInterface());
Garrick Evans139708f2020-02-06 14:38:59 +0900186 handler.Run(default_interface_, prev_default);
Garrick Evans49879532018-12-03 13:15:36 +0900187}
188
189void ShillClient::RegisterDevicesChangedHandler(
Garrick Evans139708f2020-02-06 14:38:59 +0900190 const DevicesChangeHandler& handler) {
191 device_handlers_.emplace_back(handler);
Garrick Evans49879532018-12-03 13:15:36 +0900192}
193
Jie Jiang850a4712020-04-08 21:06:36 +0900194void ShillClient::RegisterIPConfigsChangedHandler(
195 const IPConfigsChangeHandler& handler) {
196 ipconfigs_handlers_.emplace_back(handler);
197}
198
Garrick Evans139708f2020-02-06 14:38:59 +0900199void ShillClient::UpdateDevices(const brillo::Any& property_value) {
200 std::set<std::string> new_devices, added, removed;
201 for (const auto& path :
202 property_value.TryGet<std::vector<dbus::ObjectPath>>()) {
203 std::string device = path.value();
204 // Strip "/device/" prefix.
205 device = device.substr(device.find_last_of('/') + 1);
Garrick Evanse87bb4f2020-02-18 10:27:37 +0900206
Garrick Evans139708f2020-02-06 14:38:59 +0900207 new_devices.emplace(device);
208 if (devices_.find(device) == devices_.end())
209 added.insert(device);
Jie Jiang850a4712020-04-08 21:06:36 +0900210
211 // Registers handler if we see this device for the first time.
212 if (known_device_paths_.insert(std::make_pair(device, path)).second) {
213 org::chromium::flimflam::DeviceProxy proxy(bus_, path);
214 proxy.RegisterPropertyChangedSignalHandler(
215 base::Bind(&ShillClient::OnDevicePropertyChange,
216 weak_factory_.GetWeakPtr(), device),
217 base::Bind(&ShillClient::OnDevicePropertyChangeRegistration,
218 weak_factory_.GetWeakPtr()));
Jie Jiang01c1a2e2020-04-08 20:58:30 +0900219 known_device_paths_[device] = path;
Jie Jiang850a4712020-04-08 21:06:36 +0900220 }
Garrick Evans139708f2020-02-06 14:38:59 +0900221 }
222
223 for (const auto& d : devices_) {
224 if (new_devices.find(d) == new_devices.end())
225 removed.insert(d);
226 }
227
228 devices_ = new_devices;
229
230 for (const auto& h : device_handlers_)
231 h.Run(added, removed);
Garrick Evans49879532018-12-03 13:15:36 +0900232}
233
Jie Jiang850a4712020-04-08 21:06:36 +0900234ShillClient::IPConfig ShillClient::ParseIPConfigsProperty(
235 const std::string& device, const brillo::Any& property_value) {
236 IPConfig ipconfig;
237 for (const auto& path :
238 property_value.TryGet<std::vector<dbus::ObjectPath>>()) {
239 std::unique_ptr<org::chromium::flimflam::IPConfigProxy> ipconfig_proxy(
240 new org::chromium::flimflam::IPConfigProxy(bus_, path));
241 brillo::VariantDictionary ipconfig_props;
242
243 if (!ipconfig_proxy->GetProperties(&ipconfig_props, nullptr)) {
244 // It is possible that an IPConfig object is removed after we know its
245 // path, especially when the interface is going down.
246 LOG(WARNING) << "[" << device << "]: "
247 << "Unable to get properties for " << path.value();
248 continue;
249 }
250
251 // Detects the type of IPConfig. For ipv4 and ipv6 configurations, there
252 // should be at most one for each type.
253 auto it = ipconfig_props.find(shill::kMethodProperty);
254 if (it == ipconfig_props.end()) {
255 LOG(WARNING) << "[" << device << "]: "
256 << "IPConfig properties is missing Method";
257 continue;
258 }
259 const std::string& method = it->second.TryGet<std::string>();
260 const bool is_ipv4_type =
261 (method == shill::kTypeIPv4 || method == shill::kTypeDHCP ||
262 method == shill::kTypeBOOTP || method == shill::kTypeZeroConf);
263 const bool is_ipv6_type = (method == shill::kTypeIPv6);
264 if (!is_ipv4_type && !is_ipv6_type) {
265 LOG(WARNING) << "[" << device << "]: "
266 << "unknown type \"" << method << "\" for " << path.value();
267 continue;
268 }
269 if ((is_ipv4_type && !ipconfig.ipv4_address.empty()) ||
270 (is_ipv6_type && !ipconfig.ipv6_address.empty())) {
271 LOG(WARNING) << "[" << device << "]: "
272 << "Duplicated ipconfig for " << method;
273 continue;
274 }
275
276 // Gets the value of address, prefix_length, gateway, and dns_servers.
277 it = ipconfig_props.find(shill::kAddressProperty);
278 if (it == ipconfig_props.end()) {
279 LOG(WARNING) << "[" << device << "]: "
280 << "IPConfig properties is missing Address";
281 continue;
282 }
283 const std::string& address = it->second.TryGet<std::string>();
284
285 it = ipconfig_props.find(shill::kPrefixlenProperty);
286 if (it == ipconfig_props.end()) {
287 LOG(WARNING) << "[" << device << "]: "
288 << "IPConfig properties is missing Prefixlen";
289 continue;
290 }
291 int prefix_length = it->second.TryGet<int>();
292
293 it = ipconfig_props.find(shill::kGatewayProperty);
294 if (it == ipconfig_props.end()) {
295 LOG(WARNING) << "[" << device << "]: "
296 << "IPConfig properties is missing Gateway";
297 continue;
298 }
299 const std::string& gateway = it->second.TryGet<std::string>();
300
301 it = ipconfig_props.find(shill::kNameServersProperty);
302 if (it == ipconfig_props.end()) {
303 LOG(WARNING) << "[" << device << "]: "
304 << "IPConfig properties is missing NameServers";
305 // Shill will emit this property with empty value if it has no dns for
306 // this device, so missing this property indicates an error.
307 continue;
308 }
309 const std::vector<std::string>& dns_addresses =
310 it->second.TryGet<std::vector<std::string>>();
311
312 // Checks if this ipconfig is valid: address, gateway, and prefix_length
313 // should not be empty.
314 if (address.empty() || gateway.empty() || prefix_length == 0) {
315 LOG(WARNING) << "[" << device << "]: "
316 << "Skipped invalid ipconfig: "
317 << "address.length()=" << address.length()
318 << ", gateway.length()=" << gateway.length()
319 << ", prefix_length=" << prefix_length;
320 continue;
321 }
322
323 // Fills the IPConfig struct according to the type.
324 if (is_ipv4_type) {
325 ipconfig.ipv4_prefix_length = prefix_length;
326 ipconfig.ipv4_address = address;
327 ipconfig.ipv4_gateway = gateway;
328 ipconfig.ipv4_dns_addresses = dns_addresses;
329 } else { // is_ipv6_type
330 ipconfig.ipv6_prefix_length = prefix_length;
331 ipconfig.ipv6_address = address;
332 ipconfig.ipv6_gateway = gateway;
333 ipconfig.ipv6_dns_addresses = dns_addresses;
334 }
335 }
336
337 return ipconfig;
338}
339
Jie Jiang01c1a2e2020-04-08 20:58:30 +0900340bool ShillClient::GetDeviceProperties(const std::string& device,
341 Device* output) {
342 DCHECK(output);
343 const auto& device_it = known_device_paths_.find(device);
344 if (device_it == known_device_paths_.end()) {
345 LOG(ERROR) << "Unknown device " << device;
346 return false;
347 }
348
349 org::chromium::flimflam::DeviceProxy proxy(bus_, device_it->second);
350 brillo::VariantDictionary props;
351 if (!proxy.GetProperties(&props, nullptr)) {
352 LOG(WARNING) << "Unable to get device properties for " << device;
353 return false;
354 }
355
356 const auto& type_it = props.find(shill::kTypeProperty);
357 if (type_it == props.end()) {
358 LOG(WARNING) << "Device properties is missing Type for " << device;
359 return false;
360 }
361 output->type = type_it->second.TryGet<std::string>();
362
363 const auto& interface_it = props.find(shill::kInterfaceProperty);
364 if (interface_it == props.end()) {
365 LOG(WARNING) << "Device properties is missing Interface for " << device;
366 return false;
367 }
368 output->ifname = interface_it->second.TryGet<std::string>();
369
370 const auto& ipconfigs_it = props.find(shill::kIPConfigsProperty);
371 if (ipconfigs_it == props.end()) {
372 LOG(WARNING) << "Device properties is missing IPConfigs for " << device;
373 return false;
374 }
375 output->ipconfig = ParseIPConfigsProperty(device, ipconfigs_it->second);
376
377 return true;
378}
379
Jie Jiang850a4712020-04-08 21:06:36 +0900380void ShillClient::OnDevicePropertyChangeRegistration(
381 const std::string& interface,
382 const std::string& signal_name,
383 bool success) {
384 if (!success)
385 LOG(ERROR) << "[" << interface << "]: "
386 << "Unable to register listener for " << signal_name;
387}
388
389void ShillClient::OnDevicePropertyChange(const std::string& device,
390 const std::string& property_name,
391 const brillo::Any& property_value) {
392 if (property_name != shill::kIPConfigsProperty)
393 return;
394
395 const IPConfig& ipconfig = ParseIPConfigsProperty(device, property_value);
396 // TODO(jiejiang): Keep a cache of the last parsed IPConfig, and only
397 // trigger handlers if there is an actual change.
398 for (const auto& handler : ipconfigs_handlers_)
399 handler.Run(device, ipconfig);
400}
401
Garrick Evans3388a032020-03-24 11:25:55 +0900402} // namespace patchpanel