blob: 69c7ab693df250fa8fcc3dc3c1561394a9648477 [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>
Qijiang Fan713061e2021-03-08 15:45:12 +090011#include <base/check.h>
Kevin Cernekee95d4ae92016-06-19 10:26:29 -070012#include <base/logging.h>
Garrick Evanse87bb4f2020-02-18 10:27:37 +090013#include <base/strings/string_util.h>
Hugo Benichi78148a02020-10-30 18:37:00 +090014#include <brillo/variant_dictionary.h>
Kevin Cernekee95d4ae92016-06-19 10:26:29 -070015#include <chromeos/dbus/service_constants.h>
16
Garrick Evans3388a032020-03-24 11:25:55 +090017namespace patchpanel {
Hugo Benichid17aa592019-04-26 15:15:01 +090018
Jie Jiang48c99ce2020-06-08 15:18:23 +090019namespace {
20
21ShillClient::Device::Type ParseDeviceType(const std::string& type_str) {
22 static const std::map<std::string, ShillClient::Device::Type> str2enum{
23 {shill::kTypeCellular, ShillClient::Device::Type::kCellular},
24 {shill::kTypeEthernet, ShillClient::Device::Type::kEthernet},
25 {shill::kTypeEthernetEap, ShillClient::Device::Type::kEthernetEap},
26 {shill::kTypeGuestInterface, ShillClient::Device::Type::kGuestInterface},
27 {shill::kTypeLoopback, ShillClient::Device::Type::kLoopback},
28 {shill::kTypePPP, ShillClient::Device::Type::kPPP},
29 {shill::kTypePPPoE, ShillClient::Device::Type::kPPPoE},
30 {shill::kTypeTunnel, ShillClient::Device::Type::kTunnel},
31 {shill::kTypeWifi, ShillClient::Device::Type::kWifi},
32 {shill::kTypeVPN, ShillClient::Device::Type::kVPN},
33 };
34
35 const auto it = str2enum.find(type_str);
36 return it != str2enum.end() ? it->second
37 : ShillClient::Device::Type::kUnknown;
38}
39
Hugo Benichi78148a02020-10-30 18:37:00 +090040const std::string DeviceTypeName(ShillClient::Device::Type type) {
41 static const std::map<ShillClient::Device::Type, std::string> enum2str{
42 {ShillClient::Device::Type::kUnknown, "Unknown"},
43 {ShillClient::Device::Type::kCellular, "Cellular"},
44 {ShillClient::Device::Type::kEthernet, "Ethernet"},
45 {ShillClient::Device::Type::kEthernetEap, "EthernetEap"},
46 {ShillClient::Device::Type::kGuestInterface, "GuestInterface"},
47 {ShillClient::Device::Type::kLoopback, "Loopback"},
48 {ShillClient::Device::Type::kPPP, "PPP"},
49 {ShillClient::Device::Type::kPPPoE, "PPPoE"},
50 {ShillClient::Device::Type::kTunnel, "Tunnel"},
51 {ShillClient::Device::Type::kVPN, "VPN"},
52 {ShillClient::Device::Type::kWifi, "Wifi"},
53 };
54
55 const auto it = enum2str.find(type);
56 return it != enum2str.end() ? it->second : "Unknown";
57}
58
Jie Jiang48c99ce2020-06-08 15:18:23 +090059} // namespace
60
Garrick Evans08843932019-09-17 14:41:08 +090061ShillClient::ShillClient(const scoped_refptr<dbus::Bus>& bus) : bus_(bus) {
Hidehiko Abe3a7e5132018-02-15 13:07:50 +090062 manager_proxy_.reset(new org::chromium::flimflam::ManagerProxy(bus_));
Kevin Cernekee95d4ae92016-06-19 10:26:29 -070063 manager_proxy_->RegisterPropertyChangedSignalHandler(
64 base::Bind(&ShillClient::OnManagerPropertyChange,
65 weak_factory_.GetWeakPtr()),
66 base::Bind(&ShillClient::OnManagerPropertyChangeRegistration,
67 weak_factory_.GetWeakPtr()));
68}
69
Garrick Evans1b1f67c2020-02-04 16:21:25 +090070const std::string& ShillClient::default_interface() const {
Hugo Benichi78148a02020-10-30 18:37:00 +090071 return default_device_.ifname;
Garrick Evans1b1f67c2020-02-04 16:21:25 +090072}
73
Hugo Benichi69c989d2021-03-01 00:23:39 +090074const ShillClient::Device& ShillClient::default_device() const {
75 return default_device_;
76}
77
Hugo Benichicc6850f2020-01-17 13:26:06 +090078const std::set<std::string> ShillClient::get_devices() const {
79 return devices_;
80}
81
82bool ShillClient::has_device(const std::string& ifname) const {
83 return devices_.find(ifname) != devices_.end();
84}
85
Jie Jiang84c76a12020-04-17 16:45:20 +090086void ShillClient::ScanDevices() {
Garrick Evans49879532018-12-03 13:15:36 +090087 brillo::VariantDictionary props;
88 if (!manager_proxy_->GetProperties(&props, nullptr)) {
89 LOG(ERROR) << "Unable to get manager properties";
90 return;
91 }
92 const auto it = props.find(shill::kDevicesProperty);
93 if (it == props.end()) {
94 LOG(WARNING) << "Manager properties is missing devices";
95 return;
96 }
Garrick Evans139708f2020-02-06 14:38:59 +090097 UpdateDevices(it->second);
Garrick Evans49879532018-12-03 13:15:36 +090098}
99
Hugo Benichi78148a02020-10-30 18:37:00 +0900100ShillClient::Device ShillClient::GetDefaultDevice() {
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700101 brillo::VariantDictionary manager_props;
102
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700103 if (!manager_proxy_->GetProperties(&manager_props, nullptr)) {
104 LOG(ERROR) << "Unable to get manager properties";
Hugo Benichi78148a02020-10-30 18:37:00 +0900105 return {};
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700106 }
107
108 auto it = manager_props.find(shill::kDefaultServiceProperty);
109 if (it == manager_props.end()) {
Hugo Benichi78148a02020-10-30 18:37:00 +0900110 LOG(ERROR) << "Manager properties is missing default service";
111 return {};
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700112 }
113
114 dbus::ObjectPath service_path = it->second.TryGet<dbus::ObjectPath>();
115 if (!service_path.IsValid() || service_path.value() == "/") {
Hugo Benichi78148a02020-10-30 18:37:00 +0900116 LOG(ERROR) << "Invalid DBus path for the default service";
117 return {};
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700118 }
119
120 org::chromium::flimflam::ServiceProxy service_proxy(bus_, service_path);
121 brillo::VariantDictionary service_props;
122 if (!service_proxy.GetProperties(&service_props, nullptr)) {
Hugo Benichi78148a02020-10-30 18:37:00 +0900123 LOG(ERROR) << "Can't retrieve properties for default service"
124 << service_path.value();
125 return {};
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700126 }
127
Alex Khouderchah05a8b5b2019-11-20 12:18:51 -0800128 it = service_props.find(shill::kIsConnectedProperty);
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700129 if (it == service_props.end()) {
Hugo Benichi78148a02020-10-30 18:37:00 +0900130 LOG(ERROR) << "Service " << service_path.value() << " missing property "
131 << shill::kIsConnectedProperty;
132 return {};
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700133 }
Hugo Benichi78148a02020-10-30 18:37:00 +0900134
Alex Khouderchah05a8b5b2019-11-20 12:18:51 -0800135 if (!it->second.TryGet<bool>()) {
Hugo Benichi78148a02020-10-30 18:37:00 +0900136 LOG(INFO) << "Ignoring non-connected service " << service_path.value();
137 return {};
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700138 }
139
Hugo Benichi78148a02020-10-30 18:37:00 +0900140 std::string service_type = brillo::GetVariantValueOrDefault<std::string>(
141 service_props, shill::kTypeProperty);
142 if (service_type.empty()) {
143 LOG(ERROR) << "Service " << service_path.value() << " missing property "
144 << shill::kTypeProperty;
145 return {};
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700146 }
147
Hugo Benichi78148a02020-10-30 18:37:00 +0900148 Device device = {};
149 device.type = ParseDeviceType(service_type);
150
151 dbus::ObjectPath device_path =
152 brillo::GetVariantValueOrDefault<dbus::ObjectPath>(
153 service_props, shill::kDeviceProperty);
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700154 if (!device_path.IsValid()) {
Hugo Benichi78148a02020-10-30 18:37:00 +0900155 LOG(ERROR) << "Service " << service_path.value()
156 << " is missing device path";
157 return {};
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700158 }
159
160 org::chromium::flimflam::DeviceProxy device_proxy(bus_, device_path);
161 brillo::VariantDictionary device_props;
162 if (!device_proxy.GetProperties(&device_props, nullptr)) {
163 LOG(ERROR) << "Can't retrieve properties for device";
Hugo Benichi78148a02020-10-30 18:37:00 +0900164 return {};
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700165 }
166
Hugo Benichi78148a02020-10-30 18:37:00 +0900167 device.ifname = brillo::GetVariantValueOrDefault<std::string>(
168 device_props, shill::kInterfaceProperty);
169 if (device.ifname.empty()) {
170 LOG(ERROR) << "Device interface name is empty";
171 return {};
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700172 }
173
Hugo Benichi78148a02020-10-30 18:37:00 +0900174 device.service_path = service_path.value();
175 return device;
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700176}
177
178void ShillClient::OnManagerPropertyChangeRegistration(
179 const std::string& interface,
180 const std::string& signal_name,
181 bool success) {
182 if (!success)
183 LOG(FATAL) << "Unable to register for interface change events";
184}
185
186void ShillClient::OnManagerPropertyChange(const std::string& property_name,
187 const brillo::Any& property_value) {
Hugo Benichiddee2812019-05-10 16:03:43 +0900188 if (property_name == shill::kDevicesProperty) {
Garrick Evans139708f2020-02-06 14:38:59 +0900189 UpdateDevices(property_value);
Hugo Benichi78148a02020-10-30 18:37:00 +0900190 } else if (property_name != shill::kDefaultServiceProperty &&
191 property_name != shill::kConnectionStateProperty) {
Garrick Evans49879532018-12-03 13:15:36 +0900192 return;
193 }
194
Hugo Benichi78148a02020-10-30 18:37:00 +0900195 // All registered DefaultDeviceChangeHandler objects should be called if
196 // the default network has changed or if shill::kDevicesProperty has changed.
197 SetDefaultDevice(GetDefaultDevice());
Hugo Benichiddee2812019-05-10 16:03:43 +0900198}
199
Hugo Benichi78148a02020-10-30 18:37:00 +0900200void ShillClient::SetDefaultDevice(const Device& new_default) {
201 if (default_device_.ifname == new_default.ifname)
202 return;
Hugo Benichiddee2812019-05-10 16:03:43 +0900203
Hugo Benichi78148a02020-10-30 18:37:00 +0900204 LOG(INFO) << "Default device changed from " << default_device_ << " to "
205 << new_default;
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700206
Hugo Benichi78148a02020-10-30 18:37:00 +0900207 for (const auto& h : default_device_handlers_) {
Garrick Evans139708f2020-02-06 14:38:59 +0900208 if (!h.is_null())
Hugo Benichi78148a02020-10-30 18:37:00 +0900209 h.Run(new_default, default_device_);
Garrick Evansc7fea0a2020-02-04 10:46:42 +0900210 }
Hugo Benichi78148a02020-10-30 18:37:00 +0900211 default_device_ = new_default;
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700212}
213
Hugo Benichi78148a02020-10-30 18:37:00 +0900214void ShillClient::RegisterDefaultDeviceChangedHandler(
215 const DefaultDeviceChangeHandler& handler) {
216 default_device_handlers_.emplace_back(handler);
217 // Explicitly trigger the callback once to let it know of the the current
218 // default interface. The previous interface is left empty.
219 handler.Run(default_device_, {});
Garrick Evans49879532018-12-03 13:15:36 +0900220}
221
222void ShillClient::RegisterDevicesChangedHandler(
Garrick Evans139708f2020-02-06 14:38:59 +0900223 const DevicesChangeHandler& handler) {
224 device_handlers_.emplace_back(handler);
Garrick Evans49879532018-12-03 13:15:36 +0900225}
226
Jie Jiang850a4712020-04-08 21:06:36 +0900227void ShillClient::RegisterIPConfigsChangedHandler(
228 const IPConfigsChangeHandler& handler) {
229 ipconfigs_handlers_.emplace_back(handler);
230}
231
Garrick Evans139708f2020-02-06 14:38:59 +0900232void ShillClient::UpdateDevices(const brillo::Any& property_value) {
233 std::set<std::string> new_devices, added, removed;
234 for (const auto& path :
235 property_value.TryGet<std::vector<dbus::ObjectPath>>()) {
236 std::string device = path.value();
237 // Strip "/device/" prefix.
238 device = device.substr(device.find_last_of('/') + 1);
Garrick Evanse87bb4f2020-02-18 10:27:37 +0900239
Garrick Evans139708f2020-02-06 14:38:59 +0900240 new_devices.emplace(device);
241 if (devices_.find(device) == devices_.end())
242 added.insert(device);
Jie Jiang850a4712020-04-08 21:06:36 +0900243
244 // Registers handler if we see this device for the first time.
245 if (known_device_paths_.insert(std::make_pair(device, path)).second) {
246 org::chromium::flimflam::DeviceProxy proxy(bus_, path);
247 proxy.RegisterPropertyChangedSignalHandler(
248 base::Bind(&ShillClient::OnDevicePropertyChange,
249 weak_factory_.GetWeakPtr(), device),
250 base::Bind(&ShillClient::OnDevicePropertyChangeRegistration,
251 weak_factory_.GetWeakPtr()));
Jie Jiang01c1a2e2020-04-08 20:58:30 +0900252 known_device_paths_[device] = path;
Jie Jiang850a4712020-04-08 21:06:36 +0900253 }
Garrick Evans139708f2020-02-06 14:38:59 +0900254 }
255
256 for (const auto& d : devices_) {
257 if (new_devices.find(d) == new_devices.end())
258 removed.insert(d);
259 }
260
Hugo Benichi9fa20e92021-03-23 15:29:56 +0900261 // This can happen if the default network switched from one device to another.
262 if (added.empty() && removed.empty())
263 return;
264
Garrick Evans139708f2020-02-06 14:38:59 +0900265 devices_ = new_devices;
266
Hugo Benichi9fa20e92021-03-23 15:29:56 +0900267 LOG(INFO) << "Shill devices changed: added={"
268 << base::JoinString(std::vector(added.begin(), added.end()), ",")
269 << "}, removed={"
270 << base::JoinString(std::vector(removed.begin(), removed.end()),
271 ",")
272 << "}";
273
Garrick Evans139708f2020-02-06 14:38:59 +0900274 for (const auto& h : device_handlers_)
275 h.Run(added, removed);
Garrick Evans49879532018-12-03 13:15:36 +0900276}
277
Jie Jiang850a4712020-04-08 21:06:36 +0900278ShillClient::IPConfig ShillClient::ParseIPConfigsProperty(
279 const std::string& device, const brillo::Any& property_value) {
280 IPConfig ipconfig;
281 for (const auto& path :
282 property_value.TryGet<std::vector<dbus::ObjectPath>>()) {
283 std::unique_ptr<org::chromium::flimflam::IPConfigProxy> ipconfig_proxy(
284 new org::chromium::flimflam::IPConfigProxy(bus_, path));
285 brillo::VariantDictionary ipconfig_props;
286
287 if (!ipconfig_proxy->GetProperties(&ipconfig_props, nullptr)) {
288 // It is possible that an IPConfig object is removed after we know its
289 // path, especially when the interface is going down.
290 LOG(WARNING) << "[" << device << "]: "
291 << "Unable to get properties for " << path.value();
292 continue;
293 }
294
295 // Detects the type of IPConfig. For ipv4 and ipv6 configurations, there
296 // should be at most one for each type.
297 auto it = ipconfig_props.find(shill::kMethodProperty);
298 if (it == ipconfig_props.end()) {
299 LOG(WARNING) << "[" << device << "]: "
300 << "IPConfig properties is missing Method";
301 continue;
302 }
303 const std::string& method = it->second.TryGet<std::string>();
304 const bool is_ipv4_type =
305 (method == shill::kTypeIPv4 || method == shill::kTypeDHCP ||
306 method == shill::kTypeBOOTP || method == shill::kTypeZeroConf);
307 const bool is_ipv6_type = (method == shill::kTypeIPv6);
308 if (!is_ipv4_type && !is_ipv6_type) {
309 LOG(WARNING) << "[" << device << "]: "
310 << "unknown type \"" << method << "\" for " << path.value();
311 continue;
312 }
313 if ((is_ipv4_type && !ipconfig.ipv4_address.empty()) ||
314 (is_ipv6_type && !ipconfig.ipv6_address.empty())) {
315 LOG(WARNING) << "[" << device << "]: "
316 << "Duplicated ipconfig for " << method;
317 continue;
318 }
319
320 // Gets the value of address, prefix_length, gateway, and dns_servers.
321 it = ipconfig_props.find(shill::kAddressProperty);
322 if (it == ipconfig_props.end()) {
323 LOG(WARNING) << "[" << device << "]: "
324 << "IPConfig properties is missing Address";
325 continue;
326 }
327 const std::string& address = it->second.TryGet<std::string>();
328
329 it = ipconfig_props.find(shill::kPrefixlenProperty);
330 if (it == ipconfig_props.end()) {
331 LOG(WARNING) << "[" << device << "]: "
332 << "IPConfig properties is missing Prefixlen";
333 continue;
334 }
335 int prefix_length = it->second.TryGet<int>();
336
337 it = ipconfig_props.find(shill::kGatewayProperty);
338 if (it == ipconfig_props.end()) {
339 LOG(WARNING) << "[" << device << "]: "
340 << "IPConfig properties is missing Gateway";
341 continue;
342 }
343 const std::string& gateway = it->second.TryGet<std::string>();
344
345 it = ipconfig_props.find(shill::kNameServersProperty);
346 if (it == ipconfig_props.end()) {
347 LOG(WARNING) << "[" << device << "]: "
348 << "IPConfig properties is missing NameServers";
349 // Shill will emit this property with empty value if it has no dns for
350 // this device, so missing this property indicates an error.
351 continue;
352 }
353 const std::vector<std::string>& dns_addresses =
354 it->second.TryGet<std::vector<std::string>>();
355
356 // Checks if this ipconfig is valid: address, gateway, and prefix_length
357 // should not be empty.
358 if (address.empty() || gateway.empty() || prefix_length == 0) {
359 LOG(WARNING) << "[" << device << "]: "
360 << "Skipped invalid ipconfig: "
361 << "address.length()=" << address.length()
362 << ", gateway.length()=" << gateway.length()
363 << ", prefix_length=" << prefix_length;
364 continue;
365 }
366
367 // Fills the IPConfig struct according to the type.
368 if (is_ipv4_type) {
369 ipconfig.ipv4_prefix_length = prefix_length;
370 ipconfig.ipv4_address = address;
371 ipconfig.ipv4_gateway = gateway;
372 ipconfig.ipv4_dns_addresses = dns_addresses;
373 } else { // is_ipv6_type
374 ipconfig.ipv6_prefix_length = prefix_length;
375 ipconfig.ipv6_address = address;
376 ipconfig.ipv6_gateway = gateway;
377 ipconfig.ipv6_dns_addresses = dns_addresses;
378 }
379 }
380
381 return ipconfig;
382}
383
Jie Jiang01c1a2e2020-04-08 20:58:30 +0900384bool ShillClient::GetDeviceProperties(const std::string& device,
385 Device* output) {
386 DCHECK(output);
387 const auto& device_it = known_device_paths_.find(device);
388 if (device_it == known_device_paths_.end()) {
389 LOG(ERROR) << "Unknown device " << device;
390 return false;
391 }
392
393 org::chromium::flimflam::DeviceProxy proxy(bus_, device_it->second);
394 brillo::VariantDictionary props;
395 if (!proxy.GetProperties(&props, nullptr)) {
396 LOG(WARNING) << "Unable to get device properties for " << device;
397 return false;
398 }
399
400 const auto& type_it = props.find(shill::kTypeProperty);
401 if (type_it == props.end()) {
402 LOG(WARNING) << "Device properties is missing Type for " << device;
403 return false;
404 }
Jie Jiang48c99ce2020-06-08 15:18:23 +0900405 const std::string& type_str = type_it->second.TryGet<std::string>();
406 output->type = ParseDeviceType(type_str);
407 if (output->type == Device::Type::kUnknown)
408 LOG(WARNING) << "Unknown device type " << type_str << " for " << device;
Jie Jiang01c1a2e2020-04-08 20:58:30 +0900409
410 const auto& interface_it = props.find(shill::kInterfaceProperty);
411 if (interface_it == props.end()) {
412 LOG(WARNING) << "Device properties is missing Interface for " << device;
413 return false;
414 }
415 output->ifname = interface_it->second.TryGet<std::string>();
416
417 const auto& ipconfigs_it = props.find(shill::kIPConfigsProperty);
418 if (ipconfigs_it == props.end()) {
419 LOG(WARNING) << "Device properties is missing IPConfigs for " << device;
420 return false;
421 }
422 output->ipconfig = ParseIPConfigsProperty(device, ipconfigs_it->second);
423
424 return true;
425}
426
Jie Jiang850a4712020-04-08 21:06:36 +0900427void ShillClient::OnDevicePropertyChangeRegistration(
428 const std::string& interface,
429 const std::string& signal_name,
430 bool success) {
431 if (!success)
432 LOG(ERROR) << "[" << interface << "]: "
433 << "Unable to register listener for " << signal_name;
434}
435
436void ShillClient::OnDevicePropertyChange(const std::string& device,
437 const std::string& property_name,
438 const brillo::Any& property_value) {
439 if (property_name != shill::kIPConfigsProperty)
440 return;
441
442 const IPConfig& ipconfig = ParseIPConfigsProperty(device, property_value);
443 // TODO(jiejiang): Keep a cache of the last parsed IPConfig, and only
444 // trigger handlers if there is an actual change.
445 for (const auto& handler : ipconfigs_handlers_)
446 handler.Run(device, ipconfig);
447}
448
Hugo Benichi78148a02020-10-30 18:37:00 +0900449std::ostream& operator<<(std::ostream& stream, const ShillClient::Device& dev) {
450 return stream << "{ifname: " << dev.ifname
451 << ", type: " << DeviceTypeName(dev.type)
452 << ", service: " << dev.service_path << "}";
453}
454
Hugo Benichi84d96c42021-02-26 14:20:13 +0900455std::ostream& operator<<(std::ostream& stream,
456 const ShillClient::Device::Type type) {
457 return stream << DeviceTypeName(type);
458}
459
Garrick Evans3388a032020-03-24 11:25:55 +0900460} // namespace patchpanel