blob: 9ac5f5023d1a42c012e829d260f2b1f6a35b3d60 [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>
Hugo Benichi78148a02020-10-30 18:37:00 +090013#include <brillo/variant_dictionary.h>
Kevin Cernekee95d4ae92016-06-19 10:26:29 -070014#include <chromeos/dbus/service_constants.h>
15
Garrick Evans3388a032020-03-24 11:25:55 +090016namespace patchpanel {
Hugo Benichid17aa592019-04-26 15:15:01 +090017
Jie Jiang48c99ce2020-06-08 15:18:23 +090018namespace {
19
20ShillClient::Device::Type ParseDeviceType(const std::string& type_str) {
21 static const std::map<std::string, ShillClient::Device::Type> str2enum{
22 {shill::kTypeCellular, ShillClient::Device::Type::kCellular},
23 {shill::kTypeEthernet, ShillClient::Device::Type::kEthernet},
24 {shill::kTypeEthernetEap, ShillClient::Device::Type::kEthernetEap},
25 {shill::kTypeGuestInterface, ShillClient::Device::Type::kGuestInterface},
26 {shill::kTypeLoopback, ShillClient::Device::Type::kLoopback},
27 {shill::kTypePPP, ShillClient::Device::Type::kPPP},
28 {shill::kTypePPPoE, ShillClient::Device::Type::kPPPoE},
29 {shill::kTypeTunnel, ShillClient::Device::Type::kTunnel},
30 {shill::kTypeWifi, ShillClient::Device::Type::kWifi},
31 {shill::kTypeVPN, ShillClient::Device::Type::kVPN},
32 };
33
34 const auto it = str2enum.find(type_str);
35 return it != str2enum.end() ? it->second
36 : ShillClient::Device::Type::kUnknown;
37}
38
Hugo Benichi78148a02020-10-30 18:37:00 +090039const std::string DeviceTypeName(ShillClient::Device::Type type) {
40 static const std::map<ShillClient::Device::Type, std::string> enum2str{
41 {ShillClient::Device::Type::kUnknown, "Unknown"},
42 {ShillClient::Device::Type::kCellular, "Cellular"},
43 {ShillClient::Device::Type::kEthernet, "Ethernet"},
44 {ShillClient::Device::Type::kEthernetEap, "EthernetEap"},
45 {ShillClient::Device::Type::kGuestInterface, "GuestInterface"},
46 {ShillClient::Device::Type::kLoopback, "Loopback"},
47 {ShillClient::Device::Type::kPPP, "PPP"},
48 {ShillClient::Device::Type::kPPPoE, "PPPoE"},
49 {ShillClient::Device::Type::kTunnel, "Tunnel"},
50 {ShillClient::Device::Type::kVPN, "VPN"},
51 {ShillClient::Device::Type::kWifi, "Wifi"},
52 };
53
54 const auto it = enum2str.find(type);
55 return it != enum2str.end() ? it->second : "Unknown";
56}
57
Jie Jiang48c99ce2020-06-08 15:18:23 +090058} // namespace
59
Garrick Evans08843932019-09-17 14:41:08 +090060ShillClient::ShillClient(const scoped_refptr<dbus::Bus>& bus) : bus_(bus) {
Hidehiko Abe3a7e5132018-02-15 13:07:50 +090061 manager_proxy_.reset(new org::chromium::flimflam::ManagerProxy(bus_));
Kevin Cernekee95d4ae92016-06-19 10:26:29 -070062 manager_proxy_->RegisterPropertyChangedSignalHandler(
63 base::Bind(&ShillClient::OnManagerPropertyChange,
64 weak_factory_.GetWeakPtr()),
65 base::Bind(&ShillClient::OnManagerPropertyChangeRegistration,
66 weak_factory_.GetWeakPtr()));
67}
68
Garrick Evans1b1f67c2020-02-04 16:21:25 +090069const std::string& ShillClient::default_interface() const {
Hugo Benichi78148a02020-10-30 18:37:00 +090070 return default_device_.ifname;
Garrick Evans1b1f67c2020-02-04 16:21:25 +090071}
72
Hugo Benichi69c989d2021-03-01 00:23:39 +090073const ShillClient::Device& ShillClient::default_device() const {
74 return default_device_;
75}
76
Hugo Benichicc6850f2020-01-17 13:26:06 +090077const std::set<std::string> ShillClient::get_devices() const {
78 return devices_;
79}
80
81bool ShillClient::has_device(const std::string& ifname) const {
82 return devices_.find(ifname) != devices_.end();
83}
84
Jie Jiang84c76a12020-04-17 16:45:20 +090085void ShillClient::ScanDevices() {
Garrick Evans49879532018-12-03 13:15:36 +090086 brillo::VariantDictionary props;
87 if (!manager_proxy_->GetProperties(&props, nullptr)) {
88 LOG(ERROR) << "Unable to get manager properties";
89 return;
90 }
91 const auto it = props.find(shill::kDevicesProperty);
92 if (it == props.end()) {
93 LOG(WARNING) << "Manager properties is missing devices";
94 return;
95 }
Garrick Evans139708f2020-02-06 14:38:59 +090096 UpdateDevices(it->second);
Garrick Evans49879532018-12-03 13:15:36 +090097}
98
Hugo Benichi78148a02020-10-30 18:37:00 +090099ShillClient::Device ShillClient::GetDefaultDevice() {
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700100 brillo::VariantDictionary manager_props;
101
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700102 if (!manager_proxy_->GetProperties(&manager_props, nullptr)) {
103 LOG(ERROR) << "Unable to get manager properties";
Hugo Benichi78148a02020-10-30 18:37:00 +0900104 return {};
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700105 }
106
107 auto it = manager_props.find(shill::kDefaultServiceProperty);
108 if (it == manager_props.end()) {
Hugo Benichi78148a02020-10-30 18:37:00 +0900109 LOG(ERROR) << "Manager properties is missing default service";
110 return {};
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700111 }
112
113 dbus::ObjectPath service_path = it->second.TryGet<dbus::ObjectPath>();
114 if (!service_path.IsValid() || service_path.value() == "/") {
Hugo Benichi78148a02020-10-30 18:37:00 +0900115 LOG(ERROR) << "Invalid DBus path for the default service";
116 return {};
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700117 }
118
119 org::chromium::flimflam::ServiceProxy service_proxy(bus_, service_path);
120 brillo::VariantDictionary service_props;
121 if (!service_proxy.GetProperties(&service_props, nullptr)) {
Hugo Benichi78148a02020-10-30 18:37:00 +0900122 LOG(ERROR) << "Can't retrieve properties for default service"
123 << service_path.value();
124 return {};
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700125 }
126
Alex Khouderchah05a8b5b2019-11-20 12:18:51 -0800127 it = service_props.find(shill::kIsConnectedProperty);
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700128 if (it == service_props.end()) {
Hugo Benichi78148a02020-10-30 18:37:00 +0900129 LOG(ERROR) << "Service " << service_path.value() << " missing property "
130 << shill::kIsConnectedProperty;
131 return {};
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700132 }
Hugo Benichi78148a02020-10-30 18:37:00 +0900133
Alex Khouderchah05a8b5b2019-11-20 12:18:51 -0800134 if (!it->second.TryGet<bool>()) {
Hugo Benichi78148a02020-10-30 18:37:00 +0900135 LOG(INFO) << "Ignoring non-connected service " << service_path.value();
136 return {};
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700137 }
138
Hugo Benichi78148a02020-10-30 18:37:00 +0900139 std::string service_type = brillo::GetVariantValueOrDefault<std::string>(
140 service_props, shill::kTypeProperty);
141 if (service_type.empty()) {
142 LOG(ERROR) << "Service " << service_path.value() << " missing property "
143 << shill::kTypeProperty;
144 return {};
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700145 }
146
Hugo Benichi78148a02020-10-30 18:37:00 +0900147 Device device = {};
148 device.type = ParseDeviceType(service_type);
149
150 dbus::ObjectPath device_path =
151 brillo::GetVariantValueOrDefault<dbus::ObjectPath>(
152 service_props, shill::kDeviceProperty);
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700153 if (!device_path.IsValid()) {
Hugo Benichi78148a02020-10-30 18:37:00 +0900154 LOG(ERROR) << "Service " << service_path.value()
155 << " is missing device path";
156 return {};
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700157 }
158
159 org::chromium::flimflam::DeviceProxy device_proxy(bus_, device_path);
160 brillo::VariantDictionary device_props;
161 if (!device_proxy.GetProperties(&device_props, nullptr)) {
162 LOG(ERROR) << "Can't retrieve properties for device";
Hugo Benichi78148a02020-10-30 18:37:00 +0900163 return {};
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700164 }
165
Hugo Benichi78148a02020-10-30 18:37:00 +0900166 device.ifname = brillo::GetVariantValueOrDefault<std::string>(
167 device_props, shill::kInterfaceProperty);
168 if (device.ifname.empty()) {
169 LOG(ERROR) << "Device interface name is empty";
170 return {};
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700171 }
172
Hugo Benichi78148a02020-10-30 18:37:00 +0900173 device.service_path = service_path.value();
174 return device;
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700175}
176
177void ShillClient::OnManagerPropertyChangeRegistration(
178 const std::string& interface,
179 const std::string& signal_name,
180 bool success) {
181 if (!success)
182 LOG(FATAL) << "Unable to register for interface change events";
183}
184
185void ShillClient::OnManagerPropertyChange(const std::string& property_name,
186 const brillo::Any& property_value) {
Hugo Benichiddee2812019-05-10 16:03:43 +0900187 if (property_name == shill::kDevicesProperty) {
Garrick Evans139708f2020-02-06 14:38:59 +0900188 UpdateDevices(property_value);
Hugo Benichi78148a02020-10-30 18:37:00 +0900189 } else if (property_name != shill::kDefaultServiceProperty &&
190 property_name != shill::kConnectionStateProperty) {
Garrick Evans49879532018-12-03 13:15:36 +0900191 return;
192 }
193
Hugo Benichi78148a02020-10-30 18:37:00 +0900194 // All registered DefaultDeviceChangeHandler objects should be called if
195 // the default network has changed or if shill::kDevicesProperty has changed.
196 SetDefaultDevice(GetDefaultDevice());
Hugo Benichiddee2812019-05-10 16:03:43 +0900197}
198
Hugo Benichi78148a02020-10-30 18:37:00 +0900199void ShillClient::SetDefaultDevice(const Device& new_default) {
200 if (default_device_.ifname == new_default.ifname)
201 return;
Hugo Benichiddee2812019-05-10 16:03:43 +0900202
Hugo Benichi78148a02020-10-30 18:37:00 +0900203 LOG(INFO) << "Default device changed from " << default_device_ << " to "
204 << new_default;
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700205
Hugo Benichi78148a02020-10-30 18:37:00 +0900206 for (const auto& h : default_device_handlers_) {
Garrick Evans139708f2020-02-06 14:38:59 +0900207 if (!h.is_null())
Hugo Benichi78148a02020-10-30 18:37:00 +0900208 h.Run(new_default, default_device_);
Garrick Evansc7fea0a2020-02-04 10:46:42 +0900209 }
Hugo Benichi78148a02020-10-30 18:37:00 +0900210 default_device_ = new_default;
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700211}
212
Hugo Benichi78148a02020-10-30 18:37:00 +0900213void ShillClient::RegisterDefaultDeviceChangedHandler(
214 const DefaultDeviceChangeHandler& handler) {
215 default_device_handlers_.emplace_back(handler);
216 // Explicitly trigger the callback once to let it know of the the current
217 // default interface. The previous interface is left empty.
218 handler.Run(default_device_, {});
Garrick Evans49879532018-12-03 13:15:36 +0900219}
220
221void ShillClient::RegisterDevicesChangedHandler(
Garrick Evans139708f2020-02-06 14:38:59 +0900222 const DevicesChangeHandler& handler) {
223 device_handlers_.emplace_back(handler);
Garrick Evans49879532018-12-03 13:15:36 +0900224}
225
Jie Jiang850a4712020-04-08 21:06:36 +0900226void ShillClient::RegisterIPConfigsChangedHandler(
227 const IPConfigsChangeHandler& handler) {
228 ipconfigs_handlers_.emplace_back(handler);
229}
230
Garrick Evans139708f2020-02-06 14:38:59 +0900231void ShillClient::UpdateDevices(const brillo::Any& property_value) {
232 std::set<std::string> new_devices, added, removed;
233 for (const auto& path :
234 property_value.TryGet<std::vector<dbus::ObjectPath>>()) {
235 std::string device = path.value();
236 // Strip "/device/" prefix.
237 device = device.substr(device.find_last_of('/') + 1);
Garrick Evanse87bb4f2020-02-18 10:27:37 +0900238
Garrick Evans139708f2020-02-06 14:38:59 +0900239 new_devices.emplace(device);
240 if (devices_.find(device) == devices_.end())
241 added.insert(device);
Jie Jiang850a4712020-04-08 21:06:36 +0900242
243 // Registers handler if we see this device for the first time.
244 if (known_device_paths_.insert(std::make_pair(device, path)).second) {
245 org::chromium::flimflam::DeviceProxy proxy(bus_, path);
246 proxy.RegisterPropertyChangedSignalHandler(
247 base::Bind(&ShillClient::OnDevicePropertyChange,
248 weak_factory_.GetWeakPtr(), device),
249 base::Bind(&ShillClient::OnDevicePropertyChangeRegistration,
250 weak_factory_.GetWeakPtr()));
Jie Jiang01c1a2e2020-04-08 20:58:30 +0900251 known_device_paths_[device] = path;
Jie Jiang850a4712020-04-08 21:06:36 +0900252 }
Garrick Evans139708f2020-02-06 14:38:59 +0900253 }
254
255 for (const auto& d : devices_) {
256 if (new_devices.find(d) == new_devices.end())
257 removed.insert(d);
258 }
259
260 devices_ = new_devices;
261
262 for (const auto& h : device_handlers_)
263 h.Run(added, removed);
Garrick Evans49879532018-12-03 13:15:36 +0900264}
265
Jie Jiang850a4712020-04-08 21:06:36 +0900266ShillClient::IPConfig ShillClient::ParseIPConfigsProperty(
267 const std::string& device, const brillo::Any& property_value) {
268 IPConfig ipconfig;
269 for (const auto& path :
270 property_value.TryGet<std::vector<dbus::ObjectPath>>()) {
271 std::unique_ptr<org::chromium::flimflam::IPConfigProxy> ipconfig_proxy(
272 new org::chromium::flimflam::IPConfigProxy(bus_, path));
273 brillo::VariantDictionary ipconfig_props;
274
275 if (!ipconfig_proxy->GetProperties(&ipconfig_props, nullptr)) {
276 // It is possible that an IPConfig object is removed after we know its
277 // path, especially when the interface is going down.
278 LOG(WARNING) << "[" << device << "]: "
279 << "Unable to get properties for " << path.value();
280 continue;
281 }
282
283 // Detects the type of IPConfig. For ipv4 and ipv6 configurations, there
284 // should be at most one for each type.
285 auto it = ipconfig_props.find(shill::kMethodProperty);
286 if (it == ipconfig_props.end()) {
287 LOG(WARNING) << "[" << device << "]: "
288 << "IPConfig properties is missing Method";
289 continue;
290 }
291 const std::string& method = it->second.TryGet<std::string>();
292 const bool is_ipv4_type =
293 (method == shill::kTypeIPv4 || method == shill::kTypeDHCP ||
294 method == shill::kTypeBOOTP || method == shill::kTypeZeroConf);
295 const bool is_ipv6_type = (method == shill::kTypeIPv6);
296 if (!is_ipv4_type && !is_ipv6_type) {
297 LOG(WARNING) << "[" << device << "]: "
298 << "unknown type \"" << method << "\" for " << path.value();
299 continue;
300 }
301 if ((is_ipv4_type && !ipconfig.ipv4_address.empty()) ||
302 (is_ipv6_type && !ipconfig.ipv6_address.empty())) {
303 LOG(WARNING) << "[" << device << "]: "
304 << "Duplicated ipconfig for " << method;
305 continue;
306 }
307
308 // Gets the value of address, prefix_length, gateway, and dns_servers.
309 it = ipconfig_props.find(shill::kAddressProperty);
310 if (it == ipconfig_props.end()) {
311 LOG(WARNING) << "[" << device << "]: "
312 << "IPConfig properties is missing Address";
313 continue;
314 }
315 const std::string& address = it->second.TryGet<std::string>();
316
317 it = ipconfig_props.find(shill::kPrefixlenProperty);
318 if (it == ipconfig_props.end()) {
319 LOG(WARNING) << "[" << device << "]: "
320 << "IPConfig properties is missing Prefixlen";
321 continue;
322 }
323 int prefix_length = it->second.TryGet<int>();
324
325 it = ipconfig_props.find(shill::kGatewayProperty);
326 if (it == ipconfig_props.end()) {
327 LOG(WARNING) << "[" << device << "]: "
328 << "IPConfig properties is missing Gateway";
329 continue;
330 }
331 const std::string& gateway = it->second.TryGet<std::string>();
332
333 it = ipconfig_props.find(shill::kNameServersProperty);
334 if (it == ipconfig_props.end()) {
335 LOG(WARNING) << "[" << device << "]: "
336 << "IPConfig properties is missing NameServers";
337 // Shill will emit this property with empty value if it has no dns for
338 // this device, so missing this property indicates an error.
339 continue;
340 }
341 const std::vector<std::string>& dns_addresses =
342 it->second.TryGet<std::vector<std::string>>();
343
344 // Checks if this ipconfig is valid: address, gateway, and prefix_length
345 // should not be empty.
346 if (address.empty() || gateway.empty() || prefix_length == 0) {
347 LOG(WARNING) << "[" << device << "]: "
348 << "Skipped invalid ipconfig: "
349 << "address.length()=" << address.length()
350 << ", gateway.length()=" << gateway.length()
351 << ", prefix_length=" << prefix_length;
352 continue;
353 }
354
355 // Fills the IPConfig struct according to the type.
356 if (is_ipv4_type) {
357 ipconfig.ipv4_prefix_length = prefix_length;
358 ipconfig.ipv4_address = address;
359 ipconfig.ipv4_gateway = gateway;
360 ipconfig.ipv4_dns_addresses = dns_addresses;
361 } else { // is_ipv6_type
362 ipconfig.ipv6_prefix_length = prefix_length;
363 ipconfig.ipv6_address = address;
364 ipconfig.ipv6_gateway = gateway;
365 ipconfig.ipv6_dns_addresses = dns_addresses;
366 }
367 }
368
369 return ipconfig;
370}
371
Jie Jiang01c1a2e2020-04-08 20:58:30 +0900372bool ShillClient::GetDeviceProperties(const std::string& device,
373 Device* output) {
374 DCHECK(output);
375 const auto& device_it = known_device_paths_.find(device);
376 if (device_it == known_device_paths_.end()) {
377 LOG(ERROR) << "Unknown device " << device;
378 return false;
379 }
380
381 org::chromium::flimflam::DeviceProxy proxy(bus_, device_it->second);
382 brillo::VariantDictionary props;
383 if (!proxy.GetProperties(&props, nullptr)) {
384 LOG(WARNING) << "Unable to get device properties for " << device;
385 return false;
386 }
387
388 const auto& type_it = props.find(shill::kTypeProperty);
389 if (type_it == props.end()) {
390 LOG(WARNING) << "Device properties is missing Type for " << device;
391 return false;
392 }
Jie Jiang48c99ce2020-06-08 15:18:23 +0900393 const std::string& type_str = type_it->second.TryGet<std::string>();
394 output->type = ParseDeviceType(type_str);
395 if (output->type == Device::Type::kUnknown)
396 LOG(WARNING) << "Unknown device type " << type_str << " for " << device;
Jie Jiang01c1a2e2020-04-08 20:58:30 +0900397
398 const auto& interface_it = props.find(shill::kInterfaceProperty);
399 if (interface_it == props.end()) {
400 LOG(WARNING) << "Device properties is missing Interface for " << device;
401 return false;
402 }
403 output->ifname = interface_it->second.TryGet<std::string>();
404
405 const auto& ipconfigs_it = props.find(shill::kIPConfigsProperty);
406 if (ipconfigs_it == props.end()) {
407 LOG(WARNING) << "Device properties is missing IPConfigs for " << device;
408 return false;
409 }
410 output->ipconfig = ParseIPConfigsProperty(device, ipconfigs_it->second);
411
412 return true;
413}
414
Jie Jiang850a4712020-04-08 21:06:36 +0900415void ShillClient::OnDevicePropertyChangeRegistration(
416 const std::string& interface,
417 const std::string& signal_name,
418 bool success) {
419 if (!success)
420 LOG(ERROR) << "[" << interface << "]: "
421 << "Unable to register listener for " << signal_name;
422}
423
424void ShillClient::OnDevicePropertyChange(const std::string& device,
425 const std::string& property_name,
426 const brillo::Any& property_value) {
427 if (property_name != shill::kIPConfigsProperty)
428 return;
429
430 const IPConfig& ipconfig = ParseIPConfigsProperty(device, property_value);
431 // TODO(jiejiang): Keep a cache of the last parsed IPConfig, and only
432 // trigger handlers if there is an actual change.
433 for (const auto& handler : ipconfigs_handlers_)
434 handler.Run(device, ipconfig);
435}
436
Hugo Benichi78148a02020-10-30 18:37:00 +0900437std::ostream& operator<<(std::ostream& stream, const ShillClient::Device& dev) {
438 return stream << "{ifname: " << dev.ifname
439 << ", type: " << DeviceTypeName(dev.type)
440 << ", service: " << dev.service_path << "}";
441}
442
Hugo Benichi84d96c42021-02-26 14:20:13 +0900443std::ostream& operator<<(std::ostream& stream,
444 const ShillClient::Device::Type type) {
445 return stream << DeviceTypeName(type);
446}
447
Garrick Evans3388a032020-03-24 11:25:55 +0900448} // namespace patchpanel