blob: f879e8ef06c14c9d5a02239b82a91dfc9c8c50a7 [file] [log] [blame]
Garrick Evans47c19272019-11-21 10:58:21 +09001// Copyright 2019 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/crostini_service.h"
Garrick Evans47c19272019-11-21 10:58:21 +09006
7#include <memory>
8#include <utility>
9
10#include <base/strings/string_number_conversions.h>
Garrick Evans02e6e872020-11-30 11:53:13 +090011#include <base/strings/string_split.h>
Garrick Evans47c19272019-11-21 10:58:21 +090012#include <base/strings/string_util.h>
13#include <base/strings/stringprintf.h>
Jason Jeremy Imanfa8b6d22020-02-20 03:44:21 +000014#include "base/threading/thread_task_runner_handle.h"
Garrick Evans47c19272019-11-21 10:58:21 +090015#include <chromeos/constants/vm_tools.h>
Jason Jeremy Imanfa8b6d22020-02-20 03:44:21 +000016#include <chromeos/dbus/service_constants.h>
17#include <dbus/message.h>
18#include <dbus/object_path.h>
Garrick Evans47c19272019-11-21 10:58:21 +090019
Garrick Evans3388a032020-03-24 11:25:55 +090020#include "patchpanel/adb_proxy.h"
21#include "patchpanel/device.h"
Garrick Evans47c19272019-11-21 10:58:21 +090022
Garrick Evans3388a032020-03-24 11:25:55 +090023namespace patchpanel {
Garrick Evans47c19272019-11-21 10:58:21 +090024namespace {
Garrick Evans51d5b552020-01-30 10:42:06 +090025constexpr int32_t kInvalidID = 0;
Jason Jeremy Imanfa8b6d22020-02-20 03:44:21 +000026constexpr int kDbusTimeoutMs = 200;
Jason Jeremy Iman10314a82020-05-12 00:09:53 +090027// The maximum number of ADB sideloading query failures before stopping.
28constexpr int kAdbSideloadMaxTry = 5;
Jason Jeremy Imanfa8b6d22020-02-20 03:44:21 +000029constexpr base::TimeDelta kAdbSideloadUpdateDelay =
30 base::TimeDelta::FromMilliseconds(5000);
Jason Jeremy Imanbb8787e2019-12-13 14:08:20 +090031
Garrick Evans51d5b552020-01-30 10:42:06 +090032std::string MakeKey(uint64_t vm_id, bool is_termina) {
33 return base::StringPrintf("%s:%s", is_termina ? "t" : "p",
34 base::NumberToString(vm_id).c_str());
35}
Garrick Evans1b1f67c2020-02-04 16:21:25 +090036
Garrick Evans02e6e872020-11-30 11:53:13 +090037bool ParseKey(const std::string& key, uint64_t* vm_id, bool* is_termina) {
38 auto s =
39 base::SplitString(key, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
40 if (s.size() != 2 || !base::StringToUint64(s[1], vm_id) || s[0] != "t" ||
41 s[0] != "p") {
42 LOG(DFATAL) << "Invalid key: " + key;
43 return false;
44 }
45 *is_termina = s[0] == "t";
46 return true;
47}
48
Hugo Benichi78148a02020-10-30 18:37:00 +090049bool IsEthernetOrWifiDevice(const ShillClient::Device& device) {
50 return device.type == ShillClient::Device::Type::kEthernet ||
51 device.type == ShillClient::Device::Type::kWifi;
52}
53
Garrick Evans47c19272019-11-21 10:58:21 +090054} // namespace
55
Garrick Evans209a80a2020-11-30 14:42:40 +090056CrostiniService::CrostiniService(
57 ShillClient* shill_client,
58 AddressManager* addr_mgr,
59 Datapath* datapath,
60 TrafficForwarder* forwarder,
61 Device::ChangeEventHandler device_changed_handler)
Garrick Evans1b1f67c2020-02-04 16:21:25 +090062 : shill_client_(shill_client),
63 addr_mgr_(addr_mgr),
64 datapath_(datapath),
Jason Jeremy Imanfa8b6d22020-02-20 03:44:21 +000065 forwarder_(forwarder),
Garrick Evans209a80a2020-11-30 14:42:40 +090066 device_changed_handler_(device_changed_handler),
Jason Jeremy Imanfa8b6d22020-02-20 03:44:21 +000067 adb_sideloading_enabled_(false) {
Garrick Evans1b1f67c2020-02-04 16:21:25 +090068 DCHECK(shill_client_);
69 DCHECK(addr_mgr_);
Garrick Evansf29f5a32019-12-06 11:34:25 +090070 DCHECK(datapath_);
Garrick Evans1b1f67c2020-02-04 16:21:25 +090071 DCHECK(forwarder_);
Garrick Evansf29f5a32019-12-06 11:34:25 +090072
Jason Jeremy Imana7273a32020-08-04 11:25:31 +090073 dbus::Bus::Options options;
74 options.bus_type = dbus::Bus::SYSTEM;
75
76 bus_ = new dbus::Bus(options);
77 if (!bus_->Connect()) {
78 LOG(ERROR) << "Failed to connect to system bus";
Jason Jeremy Imanfa8b6d22020-02-20 03:44:21 +000079 } else {
80 CheckAdbSideloadingStatus();
81 }
82
Hugo Benichi78148a02020-10-30 18:37:00 +090083 shill_client_->RegisterDefaultDeviceChangedHandler(base::Bind(
84 &CrostiniService::OnDefaultDeviceChanged, weak_factory_.GetWeakPtr()));
Garrick Evansf29f5a32019-12-06 11:34:25 +090085}
Garrick Evans47c19272019-11-21 10:58:21 +090086
Garrick Evansc1ac5c42020-03-31 15:31:22 +090087CrostiniService::~CrostiniService() {
88 if (bus_)
89 bus_->ShutdownAndBlock();
90}
91
Garrick Evans51d5b552020-01-30 10:42:06 +090092bool CrostiniService::Start(uint64_t vm_id, bool is_termina, int subnet_index) {
93 if (vm_id == kInvalidID) {
94 LOG(ERROR) << "Invalid VM id";
Garrick Evansb1c93712020-01-22 09:28:25 +090095 return false;
96 }
Garrick Evans47c19272019-11-21 10:58:21 +090097
Garrick Evans51d5b552020-01-30 10:42:06 +090098 const auto key = MakeKey(vm_id, is_termina);
99 if (taps_.find(key) != taps_.end()) {
100 LOG(WARNING) << "Already started for {id: " << vm_id << "}";
Garrick Evansb1c93712020-01-22 09:28:25 +0900101 return false;
Garrick Evans51d5b552020-01-30 10:42:06 +0900102 }
Garrick Evansb1c93712020-01-22 09:28:25 +0900103
Garrick Evans51d5b552020-01-30 10:42:06 +0900104 auto tap = AddTAP(is_termina, subnet_index);
105 if (!tap) {
106 LOG(ERROR) << "Cannot start for {id: " << vm_id << "}";
107 return false;
108 }
Jason Jeremy Imanbb8787e2019-12-13 14:08:20 +0900109
Garrick Evans51d5b552020-01-30 10:42:06 +0900110 LOG(INFO) << "Crostini network service started for {id: " << vm_id << "}";
Garrick Evans6c7dcb82020-03-16 15:21:05 +0900111 StartForwarding(shill_client_->default_interface(), tap->host_ifname());
Hugo Benichic6ae67c2020-08-14 15:02:13 +0900112 auto source = is_termina ? TrafficSource::CROSVM : TrafficSource::PLUGINVM;
113 datapath_->StartRoutingDevice("", tap->host_ifname(),
Hugo Benichi93306e52020-12-04 16:08:00 +0900114 tap->config().host_ipv4_addr(), source,
115 true /*route_on_vpn*/);
Garrick Evans6c7dcb82020-03-16 15:21:05 +0900116
Jason Jeremy Imanfa8b6d22020-02-20 03:44:21 +0000117 if (adb_sideloading_enabled_)
Garrick Evans6c7dcb82020-03-16 15:21:05 +0900118 StartAdbPortForwarding(tap->phys_ifname());
119
Garrick Evans209a80a2020-11-30 14:42:40 +0900120 device_changed_handler_.Run(
121 *tap, Device::ChangeEvent::ADDED,
122 is_termina ? GuestMessage::TERMINA_VM : GuestMessage::PLUGIN_VM);
123
Garrick Evans51d5b552020-01-30 10:42:06 +0900124 taps_.emplace(key, std::move(tap));
Garrick Evans47c19272019-11-21 10:58:21 +0900125 return true;
126}
127
Garrick Evans51d5b552020-01-30 10:42:06 +0900128void CrostiniService::Stop(uint64_t vm_id, bool is_termina) {
129 const auto key = MakeKey(vm_id, is_termina);
130 const auto it = taps_.find(key);
Garrick Evansb1c93712020-01-22 09:28:25 +0900131 if (it == taps_.end()) {
Garrick Evans51d5b552020-01-30 10:42:06 +0900132 LOG(WARNING) << "Unknown {id: " << vm_id << "}";
Garrick Evans47c19272019-11-21 10:58:21 +0900133 return;
134 }
135
Garrick Evans209a80a2020-11-30 14:42:40 +0900136 device_changed_handler_.Run(
137 *it->second, Device::ChangeEvent::REMOVED,
138 is_termina ? GuestMessage::TERMINA_VM : GuestMessage::PLUGIN_VM);
139
Garrick Evans6c7dcb82020-03-16 15:21:05 +0900140 const auto& ifname = it->second->host_ifname();
Hugo Benichic6ae67c2020-08-14 15:02:13 +0900141 auto source = is_termina ? TrafficSource::CROSVM : TrafficSource::PLUGINVM;
142 datapath_->StopRoutingDevice("", ifname,
Hugo Benichi93306e52020-12-04 16:08:00 +0900143 it->second->config().host_ipv4_addr(), source,
144 true /*route_on_vpn*/);
Garrick Evans1b1f67c2020-02-04 16:21:25 +0900145 StopForwarding(shill_client_->default_interface(), ifname);
Jason Jeremy Imanfa8b6d22020-02-20 03:44:21 +0000146 if (adb_sideloading_enabled_)
147 StopAdbPortForwarding(ifname);
Garrick Evans1b1f67c2020-02-04 16:21:25 +0900148 datapath_->RemoveInterface(ifname);
Garrick Evans51d5b552020-01-30 10:42:06 +0900149 taps_.erase(key);
Garrick Evans47c19272019-11-21 10:58:21 +0900150
Garrick Evans51d5b552020-01-30 10:42:06 +0900151 LOG(INFO) << "Crostini network service stopped for {id: " << vm_id << "}";
Garrick Evans47c19272019-11-21 10:58:21 +0900152}
153
Garrick Evans51d5b552020-01-30 10:42:06 +0900154const Device* const CrostiniService::TAP(uint64_t vm_id,
155 bool is_termina) const {
156 const auto it = taps_.find(MakeKey(vm_id, is_termina));
Garrick Evansb1c93712020-01-22 09:28:25 +0900157 if (it == taps_.end()) {
158 return nullptr;
159 }
160 return it->second.get();
161}
Garrick Evans47c19272019-11-21 10:58:21 +0900162
Garrick Evans02e6e872020-11-30 11:53:13 +0900163void CrostiniService::ScanDevices(
164 base::RepeatingCallback<void(uint64_t, bool, const Device&)> callback)
165 const {
166 for (const auto& [key, dev] : taps_) {
167 uint64_t vm_id;
168 bool is_termina;
169 if (ParseKey(key, &vm_id, &is_termina))
170 callback.Run(vm_id, is_termina, *dev.get());
171 }
172}
173
Garrick Evans51d5b552020-01-30 10:42:06 +0900174std::unique_ptr<Device> CrostiniService::AddTAP(bool is_termina,
175 int subnet_index) {
Garrick Evans1b1f67c2020-02-04 16:21:25 +0900176 auto ipv4_subnet = addr_mgr_->AllocateIPv4Subnet(
Garrick Evans51d5b552020-01-30 10:42:06 +0900177 is_termina ? AddressManager::Guest::VM_TERMINA
Garrick Evans1fa4e642020-03-13 11:43:41 +0900178 : AddressManager::Guest::VM_PLUGIN,
Garrick Evans51d5b552020-01-30 10:42:06 +0900179 subnet_index);
Garrick Evansb1c93712020-01-22 09:28:25 +0900180 if (!ipv4_subnet) {
Garrick Evans51d5b552020-01-30 10:42:06 +0900181 LOG(ERROR) << "Subnet already in use or unavailable.";
182 return nullptr;
Garrick Evansb1c93712020-01-22 09:28:25 +0900183 }
184 auto host_ipv4_addr = ipv4_subnet->AllocateAtOffset(0);
185 if (!host_ipv4_addr) {
Garrick Evans51d5b552020-01-30 10:42:06 +0900186 LOG(ERROR) << "Host address already in use or unavailable.";
187 return nullptr;
Garrick Evansb1c93712020-01-22 09:28:25 +0900188 }
189 auto guest_ipv4_addr = ipv4_subnet->AllocateAtOffset(1);
190 if (!guest_ipv4_addr) {
Garrick Evans51d5b552020-01-30 10:42:06 +0900191 LOG(ERROR) << "VM address already in use or unavailable.";
192 return nullptr;
Garrick Evansb1c93712020-01-22 09:28:25 +0900193 }
Garrick Evans51d5b552020-01-30 10:42:06 +0900194 std::unique_ptr<Subnet> lxd_subnet;
195 if (is_termina) {
Garrick Evans1b1f67c2020-02-04 16:21:25 +0900196 lxd_subnet =
197 addr_mgr_->AllocateIPv4Subnet(AddressManager::Guest::CONTAINER);
Garrick Evans51d5b552020-01-30 10:42:06 +0900198 if (!lxd_subnet) {
199 LOG(ERROR) << "lxd subnet already in use or unavailable.";
200 return nullptr;
201 }
Garrick Evans47c19272019-11-21 10:58:21 +0900202 }
203
Garrick Evans7d9a2322020-04-02 11:59:56 +0900204 const auto mac_addr = addr_mgr_->GenerateMacAddress(subnet_index);
Garrick Evansb1c93712020-01-22 09:28:25 +0900205 const std::string tap =
Garrick Evans47c19272019-11-21 10:58:21 +0900206 datapath_->AddTAP("" /* auto-generate name */, &mac_addr,
Garrick Evansb1c93712020-01-22 09:28:25 +0900207 host_ipv4_addr.get(), vm_tools::kCrosVmUser);
Garrick Evans47c19272019-11-21 10:58:21 +0900208 if (tap.empty()) {
Garrick Evans51d5b552020-01-30 10:42:06 +0900209 LOG(ERROR) << "Failed to create TAP device.";
210 return nullptr;
Garrick Evans47c19272019-11-21 10:58:21 +0900211 }
212
Garrick Evans3d97a392020-02-21 15:24:37 +0900213 if (lxd_subnet) {
214 // Setup lxd route for the container using the VM as a gateway.
215 if (!datapath_->AddIPv4Route(ipv4_subnet->AddressAtOffset(1),
216 lxd_subnet->AddressAtOffset(0),
217 lxd_subnet->Netmask())) {
218 LOG(ERROR) << "Failed to setup lxd route";
219 return nullptr;
220 }
221 }
222
Garrick Evansb1c93712020-01-22 09:28:25 +0900223 auto config = std::make_unique<Device::Config>(
Garrick Evans6c7dcb82020-03-16 15:21:05 +0900224 mac_addr, std::move(ipv4_subnet), std::move(host_ipv4_addr),
Garrick Evansb1c93712020-01-22 09:28:25 +0900225 std::move(guest_ipv4_addr), std::move(lxd_subnet));
Garrick Evans47c19272019-11-21 10:58:21 +0900226
Garrick Evansb1c93712020-01-22 09:28:25 +0900227 Device::Options opts{
228 .fwd_multicast = true,
229 .ipv6_enabled = true,
Garrick Evansb1c93712020-01-22 09:28:25 +0900230 };
Garrick Evans47c19272019-11-21 10:58:21 +0900231
Garrick Evans6c7dcb82020-03-16 15:21:05 +0900232 return std::make_unique<Device>(tap, tap, "", std::move(config), opts);
Garrick Evans47c19272019-11-21 10:58:21 +0900233}
234
Hugo Benichi78148a02020-10-30 18:37:00 +0900235void CrostiniService::OnDefaultDeviceChanged(
236 const ShillClient::Device& new_device,
237 const ShillClient::Device& prev_device) {
238 // Only take into account interface switches and ignore layer 3 property
239 // changes.
240 if (prev_device.ifname == new_device.ifname)
241 return;
242
243 if (IsEthernetOrWifiDevice(prev_device)) {
244 for (const auto& t : taps_)
245 StopForwarding(prev_device.ifname, t.second->host_ifname());
246 }
247
248 if (IsEthernetOrWifiDevice(new_device)) {
249 for (const auto& t : taps_)
250 StartForwarding(new_device.ifname, t.second->host_ifname());
Garrick Evans1b1f67c2020-02-04 16:21:25 +0900251 }
252}
Garrick Evans47c19272019-11-21 10:58:21 +0900253
Garrick Evans1b1f67c2020-02-04 16:21:25 +0900254void CrostiniService::StartForwarding(const std::string& phys_ifname,
Jason Jeremy Iman0e9f8262020-03-06 14:50:49 +0900255 const std::string& virt_ifname) {
Garrick Evans1b1f67c2020-02-04 16:21:25 +0900256 if (!phys_ifname.empty())
Jason Jeremy Iman0e9f8262020-03-06 14:50:49 +0900257 forwarder_->StartForwarding(phys_ifname, virt_ifname, true /*ipv6*/,
258 true /*multicast*/);
Garrick Evans1b1f67c2020-02-04 16:21:25 +0900259}
Garrick Evans47c19272019-11-21 10:58:21 +0900260
Garrick Evans1b1f67c2020-02-04 16:21:25 +0900261void CrostiniService::StopForwarding(const std::string& phys_ifname,
262 const std::string& virt_ifname) {
263 if (!phys_ifname.empty())
264 forwarder_->StopForwarding(phys_ifname, virt_ifname, true /*ipv6*/,
265 true /*multicast*/);
Garrick Evans47c19272019-11-21 10:58:21 +0900266}
267
Jason Jeremy Imanfa8b6d22020-02-20 03:44:21 +0000268void CrostiniService::StartAdbPortForwarding(const std::string& ifname) {
Jason Jeremy Imana7273a32020-08-04 11:25:31 +0900269 if (!datapath_->AddAdbPortForwardRule(ifname)) {
270 LOG(ERROR) << "Error adding ADB port forwarding rule for " << ifname;
Jason Jeremy Imanfa8b6d22020-02-20 03:44:21 +0000271 return;
272 }
273
Jason Jeremy Imana7273a32020-08-04 11:25:31 +0900274 if (!datapath_->AddAdbPortAccessRule(ifname)) {
275 datapath_->DeleteAdbPortForwardRule(ifname);
276 LOG(ERROR) << "Error adding ADB port access rule for " << ifname;
Jason Jeremy Imanfa8b6d22020-02-20 03:44:21 +0000277 return;
278 }
279
280 if (datapath_->runner().sysctl_w(
281 "net.ipv4.conf." + ifname + ".route_localnet", "1") != 0) {
282 LOG(ERROR) << "Failed to set up route localnet for " << ifname;
283 return;
284 }
Jason Jeremy Imanfa8b6d22020-02-20 03:44:21 +0000285}
286
287void CrostiniService::StopAdbPortForwarding(const std::string& ifname) {
Jason Jeremy Imana7273a32020-08-04 11:25:31 +0900288 datapath_->DeleteAdbPortForwardRule(ifname);
289 datapath_->DeleteAdbPortAccessRule(ifname);
Jason Jeremy Imanfa8b6d22020-02-20 03:44:21 +0000290}
291
292void CrostiniService::CheckAdbSideloadingStatus() {
Jason Jeremy Iman10314a82020-05-12 00:09:53 +0900293 static int num_try = 0;
Jason Jeremy Imandd372fd2020-08-11 00:07:58 +0900294 if (num_try >= kAdbSideloadMaxTry) {
295 LOG(WARNING) << "Failed to get ADB sideloading status after " << num_try
296 << " tries. ADB sideloading will not work";
Jason Jeremy Iman10314a82020-05-12 00:09:53 +0900297 return;
Jason Jeremy Imandd372fd2020-08-11 00:07:58 +0900298 }
Jason Jeremy Iman10314a82020-05-12 00:09:53 +0900299
Jason Jeremy Imanfa8b6d22020-02-20 03:44:21 +0000300 dbus::ObjectProxy* proxy = bus_->GetObjectProxy(
301 login_manager::kSessionManagerServiceName,
302 dbus::ObjectPath(login_manager::kSessionManagerServicePath));
303 dbus::MethodCall method_call(login_manager::kSessionManagerInterface,
304 login_manager::kSessionManagerQueryAdbSideload);
305 std::unique_ptr<dbus::Response> dbus_response =
306 proxy->CallMethodAndBlock(&method_call, kDbusTimeoutMs);
307
308 if (!dbus_response) {
Jason Jeremy Imanfa8b6d22020-02-20 03:44:21 +0000309 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
310 FROM_HERE,
311 base::BindOnce(&CrostiniService::CheckAdbSideloadingStatus,
312 weak_factory_.GetWeakPtr()),
313 kAdbSideloadUpdateDelay);
Jason Jeremy Iman10314a82020-05-12 00:09:53 +0900314 num_try++;
Jason Jeremy Imanfa8b6d22020-02-20 03:44:21 +0000315 return;
316 }
317
318 dbus::MessageReader reader(dbus_response.get());
319 reader.PopBool(&adb_sideloading_enabled_);
320 if (!adb_sideloading_enabled_)
321 return;
322
323 // If ADB sideloading is enabled, start ADB forwarding on all configured
324 // Crostini's TAP interfaces.
325 for (const auto& tap : taps_) {
Garrick Evans6c7dcb82020-03-16 15:21:05 +0900326 StartAdbPortForwarding(tap.second->phys_ifname());
Jason Jeremy Imanfa8b6d22020-02-20 03:44:21 +0000327 }
328}
329
Garrick Evans3388a032020-03-24 11:25:55 +0900330} // namespace patchpanel