blob: 748c8b1867260016a2011fbab623c053fc55c5a4 [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 Benichicc6850f2020-01-17 13:26:06 +090073const std::set<std::string> ShillClient::get_devices() const {
74 return devices_;
75}
76
77bool ShillClient::has_device(const std::string& ifname) const {
78 return devices_.find(ifname) != devices_.end();
79}
80
Jie Jiang84c76a12020-04-17 16:45:20 +090081void ShillClient::ScanDevices() {
Garrick Evans49879532018-12-03 13:15:36 +090082 brillo::VariantDictionary props;
83 if (!manager_proxy_->GetProperties(&props, nullptr)) {
84 LOG(ERROR) << "Unable to get manager properties";
85 return;
86 }
87 const auto it = props.find(shill::kDevicesProperty);
88 if (it == props.end()) {
89 LOG(WARNING) << "Manager properties is missing devices";
90 return;
91 }
Garrick Evans139708f2020-02-06 14:38:59 +090092 UpdateDevices(it->second);
Garrick Evans49879532018-12-03 13:15:36 +090093}
94
Hugo Benichi78148a02020-10-30 18:37:00 +090095ShillClient::Device ShillClient::GetDefaultDevice() {
Kevin Cernekee95d4ae92016-06-19 10:26:29 -070096 brillo::VariantDictionary manager_props;
97
Kevin Cernekee95d4ae92016-06-19 10:26:29 -070098 if (!manager_proxy_->GetProperties(&manager_props, nullptr)) {
99 LOG(ERROR) << "Unable to get manager properties";
Hugo Benichi78148a02020-10-30 18:37:00 +0900100 return {};
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700101 }
102
103 auto it = manager_props.find(shill::kDefaultServiceProperty);
104 if (it == manager_props.end()) {
Hugo Benichi78148a02020-10-30 18:37:00 +0900105 LOG(ERROR) << "Manager properties is missing default service";
106 return {};
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700107 }
108
109 dbus::ObjectPath service_path = it->second.TryGet<dbus::ObjectPath>();
110 if (!service_path.IsValid() || service_path.value() == "/") {
Hugo Benichi78148a02020-10-30 18:37:00 +0900111 LOG(ERROR) << "Invalid DBus path for the default service";
112 return {};
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700113 }
114
115 org::chromium::flimflam::ServiceProxy service_proxy(bus_, service_path);
116 brillo::VariantDictionary service_props;
117 if (!service_proxy.GetProperties(&service_props, nullptr)) {
Hugo Benichi78148a02020-10-30 18:37:00 +0900118 LOG(ERROR) << "Can't retrieve properties for default service"
119 << service_path.value();
120 return {};
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700121 }
122
Alex Khouderchah05a8b5b2019-11-20 12:18:51 -0800123 it = service_props.find(shill::kIsConnectedProperty);
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700124 if (it == service_props.end()) {
Hugo Benichi78148a02020-10-30 18:37:00 +0900125 LOG(ERROR) << "Service " << service_path.value() << " missing property "
126 << shill::kIsConnectedProperty;
127 return {};
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700128 }
Hugo Benichi78148a02020-10-30 18:37:00 +0900129
Alex Khouderchah05a8b5b2019-11-20 12:18:51 -0800130 if (!it->second.TryGet<bool>()) {
Hugo Benichi78148a02020-10-30 18:37:00 +0900131 LOG(INFO) << "Ignoring non-connected service " << service_path.value();
132 return {};
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700133 }
134
Hugo Benichi78148a02020-10-30 18:37:00 +0900135 std::string service_type = brillo::GetVariantValueOrDefault<std::string>(
136 service_props, shill::kTypeProperty);
137 if (service_type.empty()) {
138 LOG(ERROR) << "Service " << service_path.value() << " missing property "
139 << shill::kTypeProperty;
140 return {};
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700141 }
142
Hugo Benichi78148a02020-10-30 18:37:00 +0900143 Device device = {};
144 device.type = ParseDeviceType(service_type);
145
146 dbus::ObjectPath device_path =
147 brillo::GetVariantValueOrDefault<dbus::ObjectPath>(
148 service_props, shill::kDeviceProperty);
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700149 if (!device_path.IsValid()) {
Hugo Benichi78148a02020-10-30 18:37:00 +0900150 LOG(ERROR) << "Service " << service_path.value()
151 << " is missing device path";
152 return {};
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700153 }
154
155 org::chromium::flimflam::DeviceProxy device_proxy(bus_, device_path);
156 brillo::VariantDictionary device_props;
157 if (!device_proxy.GetProperties(&device_props, nullptr)) {
158 LOG(ERROR) << "Can't retrieve properties for device";
Hugo Benichi78148a02020-10-30 18:37:00 +0900159 return {};
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700160 }
161
Hugo Benichi78148a02020-10-30 18:37:00 +0900162 device.ifname = brillo::GetVariantValueOrDefault<std::string>(
163 device_props, shill::kInterfaceProperty);
164 if (device.ifname.empty()) {
165 LOG(ERROR) << "Device interface name is empty";
166 return {};
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700167 }
168
Hugo Benichi78148a02020-10-30 18:37:00 +0900169 device.service_path = service_path.value();
170 return device;
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700171}
172
173void ShillClient::OnManagerPropertyChangeRegistration(
174 const std::string& interface,
175 const std::string& signal_name,
176 bool success) {
177 if (!success)
178 LOG(FATAL) << "Unable to register for interface change events";
179}
180
181void ShillClient::OnManagerPropertyChange(const std::string& property_name,
182 const brillo::Any& property_value) {
Hugo Benichiddee2812019-05-10 16:03:43 +0900183 if (property_name == shill::kDevicesProperty) {
Garrick Evans139708f2020-02-06 14:38:59 +0900184 UpdateDevices(property_value);
Hugo Benichi78148a02020-10-30 18:37:00 +0900185 } else if (property_name != shill::kDefaultServiceProperty &&
186 property_name != shill::kConnectionStateProperty) {
Garrick Evans49879532018-12-03 13:15:36 +0900187 return;
188 }
189
Hugo Benichi78148a02020-10-30 18:37:00 +0900190 // All registered DefaultDeviceChangeHandler objects should be called if
191 // the default network has changed or if shill::kDevicesProperty has changed.
192 SetDefaultDevice(GetDefaultDevice());
Hugo Benichiddee2812019-05-10 16:03:43 +0900193}
194
Hugo Benichi78148a02020-10-30 18:37:00 +0900195void ShillClient::SetDefaultDevice(const Device& new_default) {
196 if (default_device_.ifname == new_default.ifname)
197 return;
Hugo Benichiddee2812019-05-10 16:03:43 +0900198
Hugo Benichi78148a02020-10-30 18:37:00 +0900199 LOG(INFO) << "Default device changed from " << default_device_ << " to "
200 << new_default;
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700201
Hugo Benichi78148a02020-10-30 18:37:00 +0900202 for (const auto& h : default_device_handlers_) {
Garrick Evans139708f2020-02-06 14:38:59 +0900203 if (!h.is_null())
Hugo Benichi78148a02020-10-30 18:37:00 +0900204 h.Run(new_default, default_device_);
Garrick Evansc7fea0a2020-02-04 10:46:42 +0900205 }
Hugo Benichi78148a02020-10-30 18:37:00 +0900206 default_device_ = new_default;
Kevin Cernekee95d4ae92016-06-19 10:26:29 -0700207}
208
Hugo Benichi78148a02020-10-30 18:37:00 +0900209void ShillClient::RegisterDefaultDeviceChangedHandler(
210 const DefaultDeviceChangeHandler& handler) {
211 default_device_handlers_.emplace_back(handler);
212 // Explicitly trigger the callback once to let it know of the the current
213 // default interface. The previous interface is left empty.
214 handler.Run(default_device_, {});
Garrick Evans49879532018-12-03 13:15:36 +0900215}
216
217void ShillClient::RegisterDevicesChangedHandler(
Garrick Evans139708f2020-02-06 14:38:59 +0900218 const DevicesChangeHandler& handler) {
219 device_handlers_.emplace_back(handler);
Garrick Evans49879532018-12-03 13:15:36 +0900220}
221
Jie Jiang850a4712020-04-08 21:06:36 +0900222void ShillClient::RegisterIPConfigsChangedHandler(
223 const IPConfigsChangeHandler& handler) {
224 ipconfigs_handlers_.emplace_back(handler);
225}
226
Garrick Evans139708f2020-02-06 14:38:59 +0900227void ShillClient::UpdateDevices(const brillo::Any& property_value) {
228 std::set<std::string> new_devices, added, removed;
229 for (const auto& path :
230 property_value.TryGet<std::vector<dbus::ObjectPath>>()) {
231 std::string device = path.value();
232 // Strip "/device/" prefix.
233 device = device.substr(device.find_last_of('/') + 1);
Garrick Evanse87bb4f2020-02-18 10:27:37 +0900234
Garrick Evans139708f2020-02-06 14:38:59 +0900235 new_devices.emplace(device);
236 if (devices_.find(device) == devices_.end())
237 added.insert(device);
Jie Jiang850a4712020-04-08 21:06:36 +0900238
239 // Registers handler if we see this device for the first time.
240 if (known_device_paths_.insert(std::make_pair(device, path)).second) {
241 org::chromium::flimflam::DeviceProxy proxy(bus_, path);
242 proxy.RegisterPropertyChangedSignalHandler(
243 base::Bind(&ShillClient::OnDevicePropertyChange,
244 weak_factory_.GetWeakPtr(), device),
245 base::Bind(&ShillClient::OnDevicePropertyChangeRegistration,
246 weak_factory_.GetWeakPtr()));
Jie Jiang01c1a2e2020-04-08 20:58:30 +0900247 known_device_paths_[device] = path;
Jie Jiang850a4712020-04-08 21:06:36 +0900248 }
Garrick Evans139708f2020-02-06 14:38:59 +0900249 }
250
251 for (const auto& d : devices_) {
252 if (new_devices.find(d) == new_devices.end())
253 removed.insert(d);
254 }
255
256 devices_ = new_devices;
257
258 for (const auto& h : device_handlers_)
259 h.Run(added, removed);
Garrick Evans49879532018-12-03 13:15:36 +0900260}
261
Jie Jiang850a4712020-04-08 21:06:36 +0900262ShillClient::IPConfig ShillClient::ParseIPConfigsProperty(
263 const std::string& device, const brillo::Any& property_value) {
264 IPConfig ipconfig;
265 for (const auto& path :
266 property_value.TryGet<std::vector<dbus::ObjectPath>>()) {
267 std::unique_ptr<org::chromium::flimflam::IPConfigProxy> ipconfig_proxy(
268 new org::chromium::flimflam::IPConfigProxy(bus_, path));
269 brillo::VariantDictionary ipconfig_props;
270
271 if (!ipconfig_proxy->GetProperties(&ipconfig_props, nullptr)) {
272 // It is possible that an IPConfig object is removed after we know its
273 // path, especially when the interface is going down.
274 LOG(WARNING) << "[" << device << "]: "
275 << "Unable to get properties for " << path.value();
276 continue;
277 }
278
279 // Detects the type of IPConfig. For ipv4 and ipv6 configurations, there
280 // should be at most one for each type.
281 auto it = ipconfig_props.find(shill::kMethodProperty);
282 if (it == ipconfig_props.end()) {
283 LOG(WARNING) << "[" << device << "]: "
284 << "IPConfig properties is missing Method";
285 continue;
286 }
287 const std::string& method = it->second.TryGet<std::string>();
288 const bool is_ipv4_type =
289 (method == shill::kTypeIPv4 || method == shill::kTypeDHCP ||
290 method == shill::kTypeBOOTP || method == shill::kTypeZeroConf);
291 const bool is_ipv6_type = (method == shill::kTypeIPv6);
292 if (!is_ipv4_type && !is_ipv6_type) {
293 LOG(WARNING) << "[" << device << "]: "
294 << "unknown type \"" << method << "\" for " << path.value();
295 continue;
296 }
297 if ((is_ipv4_type && !ipconfig.ipv4_address.empty()) ||
298 (is_ipv6_type && !ipconfig.ipv6_address.empty())) {
299 LOG(WARNING) << "[" << device << "]: "
300 << "Duplicated ipconfig for " << method;
301 continue;
302 }
303
304 // Gets the value of address, prefix_length, gateway, and dns_servers.
305 it = ipconfig_props.find(shill::kAddressProperty);
306 if (it == ipconfig_props.end()) {
307 LOG(WARNING) << "[" << device << "]: "
308 << "IPConfig properties is missing Address";
309 continue;
310 }
311 const std::string& address = it->second.TryGet<std::string>();
312
313 it = ipconfig_props.find(shill::kPrefixlenProperty);
314 if (it == ipconfig_props.end()) {
315 LOG(WARNING) << "[" << device << "]: "
316 << "IPConfig properties is missing Prefixlen";
317 continue;
318 }
319 int prefix_length = it->second.TryGet<int>();
320
321 it = ipconfig_props.find(shill::kGatewayProperty);
322 if (it == ipconfig_props.end()) {
323 LOG(WARNING) << "[" << device << "]: "
324 << "IPConfig properties is missing Gateway";
325 continue;
326 }
327 const std::string& gateway = it->second.TryGet<std::string>();
328
329 it = ipconfig_props.find(shill::kNameServersProperty);
330 if (it == ipconfig_props.end()) {
331 LOG(WARNING) << "[" << device << "]: "
332 << "IPConfig properties is missing NameServers";
333 // Shill will emit this property with empty value if it has no dns for
334 // this device, so missing this property indicates an error.
335 continue;
336 }
337 const std::vector<std::string>& dns_addresses =
338 it->second.TryGet<std::vector<std::string>>();
339
340 // Checks if this ipconfig is valid: address, gateway, and prefix_length
341 // should not be empty.
342 if (address.empty() || gateway.empty() || prefix_length == 0) {
343 LOG(WARNING) << "[" << device << "]: "
344 << "Skipped invalid ipconfig: "
345 << "address.length()=" << address.length()
346 << ", gateway.length()=" << gateway.length()
347 << ", prefix_length=" << prefix_length;
348 continue;
349 }
350
351 // Fills the IPConfig struct according to the type.
352 if (is_ipv4_type) {
353 ipconfig.ipv4_prefix_length = prefix_length;
354 ipconfig.ipv4_address = address;
355 ipconfig.ipv4_gateway = gateway;
356 ipconfig.ipv4_dns_addresses = dns_addresses;
357 } else { // is_ipv6_type
358 ipconfig.ipv6_prefix_length = prefix_length;
359 ipconfig.ipv6_address = address;
360 ipconfig.ipv6_gateway = gateway;
361 ipconfig.ipv6_dns_addresses = dns_addresses;
362 }
363 }
364
365 return ipconfig;
366}
367
Jie Jiang01c1a2e2020-04-08 20:58:30 +0900368bool ShillClient::GetDeviceProperties(const std::string& device,
369 Device* output) {
370 DCHECK(output);
371 const auto& device_it = known_device_paths_.find(device);
372 if (device_it == known_device_paths_.end()) {
373 LOG(ERROR) << "Unknown device " << device;
374 return false;
375 }
376
377 org::chromium::flimflam::DeviceProxy proxy(bus_, device_it->second);
378 brillo::VariantDictionary props;
379 if (!proxy.GetProperties(&props, nullptr)) {
380 LOG(WARNING) << "Unable to get device properties for " << device;
381 return false;
382 }
383
384 const auto& type_it = props.find(shill::kTypeProperty);
385 if (type_it == props.end()) {
386 LOG(WARNING) << "Device properties is missing Type for " << device;
387 return false;
388 }
Jie Jiang48c99ce2020-06-08 15:18:23 +0900389 const std::string& type_str = type_it->second.TryGet<std::string>();
390 output->type = ParseDeviceType(type_str);
391 if (output->type == Device::Type::kUnknown)
392 LOG(WARNING) << "Unknown device type " << type_str << " for " << device;
Jie Jiang01c1a2e2020-04-08 20:58:30 +0900393
394 const auto& interface_it = props.find(shill::kInterfaceProperty);
395 if (interface_it == props.end()) {
396 LOG(WARNING) << "Device properties is missing Interface for " << device;
397 return false;
398 }
399 output->ifname = interface_it->second.TryGet<std::string>();
400
401 const auto& ipconfigs_it = props.find(shill::kIPConfigsProperty);
402 if (ipconfigs_it == props.end()) {
403 LOG(WARNING) << "Device properties is missing IPConfigs for " << device;
404 return false;
405 }
406 output->ipconfig = ParseIPConfigsProperty(device, ipconfigs_it->second);
407
408 return true;
409}
410
Jie Jiang850a4712020-04-08 21:06:36 +0900411void ShillClient::OnDevicePropertyChangeRegistration(
412 const std::string& interface,
413 const std::string& signal_name,
414 bool success) {
415 if (!success)
416 LOG(ERROR) << "[" << interface << "]: "
417 << "Unable to register listener for " << signal_name;
418}
419
420void ShillClient::OnDevicePropertyChange(const std::string& device,
421 const std::string& property_name,
422 const brillo::Any& property_value) {
423 if (property_name != shill::kIPConfigsProperty)
424 return;
425
426 const IPConfig& ipconfig = ParseIPConfigsProperty(device, property_value);
427 // TODO(jiejiang): Keep a cache of the last parsed IPConfig, and only
428 // trigger handlers if there is an actual change.
429 for (const auto& handler : ipconfigs_handlers_)
430 handler.Run(device, ipconfig);
431}
432
Hugo Benichi78148a02020-10-30 18:37:00 +0900433std::ostream& operator<<(std::ostream& stream, const ShillClient::Device& dev) {
434 return stream << "{ifname: " << dev.ifname
435 << ", type: " << DeviceTypeName(dev.type)
436 << ", service: " << dev.service_path << "}";
437}
438
Garrick Evans3388a032020-03-24 11:25:55 +0900439} // namespace patchpanel