1 /*
2 * Copyright (C) 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "host/libs/allocd/resource_manager.h"
18
19 #include <android-base/logging.h>
20 #include <pwd.h>
21 #include <sys/socket.h>
22 #include <sys/types.h>
23 #include <sys/wait.h>
24 #include <unistd.h>
25
26 #include <atomic>
27 #include <cstdint>
28 #include <cstdlib>
29 #include <iomanip>
30 #include <memory>
31 #include <optional>
32 #include <sstream>
33 #include <string>
34
35 #include "common/libs/fs/shared_fd.h"
36 #include "host/libs/allocd/alloc_utils.h"
37 #include "host/libs/allocd/request.h"
38 #include "host/libs/allocd/utils.h"
39 #include "json/forwards.h"
40 #include "json/value.h"
41 #include "json/writer.h"
42
43 namespace cuttlefish {
44
45 uid_t GetUserIDFromSock(SharedFD client_socket);
46
~ResourceManager()47 ResourceManager::~ResourceManager() {
48 bool success = true;
49 for (auto& res : managed_sessions_) {
50 success &= res.second->ReleaseAllResources();
51 }
52
53 Json::Value resp;
54 resp["request_type"] = "shutdown";
55 auto status = success ? RequestStatus::Success : RequestStatus::Failure;
56 resp["request_status"] = StatusToStr(status);
57 SendJsonMsg(shutdown_socket_, resp);
58 LOG(INFO) << "Daemon Shutdown complete";
59 unlink(location.c_str());
60 }
61
SetSocketLocation(const std::string & sock_name)62 void ResourceManager::SetSocketLocation(const std::string& sock_name) {
63 location = sock_name;
64 }
65
SetUseEbtablesLegacy(bool use_legacy)66 void ResourceManager::SetUseEbtablesLegacy(bool use_legacy) {
67 use_ebtables_legacy_ = use_legacy;
68 }
69
AllocateResourceID()70 uint32_t ResourceManager::AllocateResourceID() {
71 return global_resource_id_.fetch_add(1, std::memory_order_relaxed);
72 }
73
AllocateSessionID()74 uint32_t ResourceManager::AllocateSessionID() {
75 return session_id_.fetch_add(1, std::memory_order_relaxed);
76 }
77
AddInterface(const std::string & iface,IfaceType ty,uint32_t resource_id,uid_t uid)78 bool ResourceManager::AddInterface(const std::string& iface, IfaceType ty,
79 uint32_t resource_id, uid_t uid) {
80 bool allocatedIface = false;
81 std::shared_ptr<StaticResource> res = nullptr;
82
83 bool didInsert = active_interfaces_.insert(iface).second;
84 if (didInsert) {
85 const char* idp = iface.c_str() + (iface.size() - 3);
86 int small_id = atoi(idp);
87 switch (ty) {
88 case IfaceType::mtap:
89 res = std::make_shared<MobileIface>(iface, uid, small_id, resource_id,
90 kMobileIp);
91 allocatedIface = res->AcquireResource();
92 pending_add_.insert({resource_id, res});
93 break;
94 case IfaceType::wtap: {
95 // TODO (paulkirth): change this to cvd-wbr, to test w/ today's
96 // debian package, this is required since the number of wireless
97 // bridges provided by the debian package has gone from 10 down to
98 // 1, but our debian packages in cloudtop are not up to date
99 auto w = std::make_shared<EthernetIface>(iface, uid, small_id,
100 resource_id, "cvd-wbr-01",
101 kWirelessIp);
102 w->SetUseEbtablesLegacy(use_ebtables_legacy_);
103 w->SetHasIpv4(use_ipv4_bridge_);
104 w->SetHasIpv6(use_ipv6_bridge_);
105 res = w;
106 allocatedIface = res->AcquireResource();
107 pending_add_.insert({resource_id, res});
108 break;
109 }
110 case IfaceType::etap: {
111 auto w = std::make_shared<EthernetIface>(iface, uid, small_id,
112 resource_id, "cvd-ebr",
113 kEthernetIp);
114 w->SetUseEbtablesLegacy(use_ebtables_legacy_);
115 w->SetHasIpv4(use_ipv4_bridge_);
116 w->SetHasIpv6(use_ipv6_bridge_);
117 res = w;
118 allocatedIface = res->AcquireResource();
119 pending_add_.insert({resource_id, res});
120 break;
121 }
122 case IfaceType::wbr:
123 case IfaceType::ebr:
124 allocatedIface = CreateBridge(iface);
125 break;
126 case IfaceType::Invalid:
127 break;
128 }
129 } else {
130 LOG(WARNING) << "Interface already in use: " << iface;
131 }
132
133 if (didInsert && !allocatedIface) {
134 LOG(WARNING) << "Failed to allocate interface: " << iface;
135 active_interfaces_.erase(iface);
136 auto it = pending_add_.find(resource_id);
137 it->second->ReleaseResource();
138 pending_add_.erase(it);
139 }
140
141 LOG(INFO) << "Finish CreateInterface Request";
142
143 return allocatedIface;
144 }
145
RemoveInterface(const std::string & iface,IfaceType ty)146 bool ResourceManager::RemoveInterface(const std::string& iface, IfaceType ty) {
147 bool isManagedIface = active_interfaces_.erase(iface) > 0;
148 bool removedIface = false;
149 if (isManagedIface) {
150 switch (ty) {
151 case IfaceType::mtap: {
152 const char* idp = iface.c_str() + (iface.size() - 3);
153 int id = atoi(idp);
154 removedIface = DestroyMobileIface(iface, id, kMobileIp);
155 break;
156 }
157 case IfaceType::wtap:
158 case IfaceType::etap:
159 removedIface = DestroyEthernetIface(
160 iface, use_ipv4_bridge_, use_ipv6_bridge_, use_ebtables_legacy_);
161 break;
162 case IfaceType::wbr:
163 case IfaceType::ebr:
164 removedIface = DestroyBridge(iface);
165 break;
166 case IfaceType::Invalid:
167 break;
168 }
169
170 } else {
171 LOG(WARNING) << "Interface not managed: " << iface;
172 }
173
174 if (isManagedIface) {
175 LOG(INFO) << "Removed interface: " << iface;
176 } else {
177 LOG(WARNING) << "Could not remove interface: " << iface;
178 }
179
180 return isManagedIface;
181 }
182
ValidateRequestList(const Json::Value & config)183 bool ResourceManager::ValidateRequestList(const Json::Value& config) {
184 if (!config.isMember("request_list") || !config["request_list"].isArray()) {
185 LOG(WARNING) << "Request has invalid 'request_list' field";
186 return false;
187 }
188
189 auto request_list = config["request_list"];
190
191 Json::ArrayIndex size = request_list.size();
192 if (size == 0) {
193 LOG(WARNING) << "Request has empty 'request_list' field";
194 return false;
195 }
196
197 for (Json::ArrayIndex i = 0; i < size; ++i) {
198 if (!ValidateRequest(request_list[i])) {
199 return false;
200 }
201 }
202
203 return true;
204 }
205
ValidateConfigRequest(const Json::Value & config)206 bool ResourceManager::ValidateConfigRequest(const Json::Value& config) {
207 if (!config.isMember("config_request") ||
208 !config["config_request"].isObject()) {
209 LOG(WARNING) << "Request has invalid 'config_request' field";
210 return false;
211 }
212
213 Json::Value config_request = config["config_request"];
214
215 return ValidateRequestList(config_request);
216 }
217
ValidateRequest(const Json::Value & request)218 bool ResourceManager::ValidateRequest(const Json::Value& request) {
219 if (!request.isMember("request_type") ||
220 !request["request_type"].isString() ||
221 StrToReqTy(request["request_type"].asString()) == RequestType::Invalid) {
222 LOG(WARNING) << "Request has invalid 'request_type' field";
223 return false;
224 }
225 return true;
226 }
227
JsonServer()228 void ResourceManager::JsonServer() {
229 LOG(INFO) << "Starting server on " << kDefaultLocation;
230 auto server = SharedFD::SocketLocalServer(kDefaultLocation, false,
231 SOCK_STREAM, kSocketMode);
232 CHECK(server->IsOpen()) << "Could not start server at " << kDefaultLocation;
233 LOG(INFO) << "Accepting client connections";
234
235 while (true) {
236 auto client_socket = SharedFD::Accept(*server);
237 CHECK(client_socket->IsOpen()) << "Error creating client socket";
238
239 struct timeval timeout;
240 timeout.tv_sec = 10;
241 timeout.tv_usec = 0;
242
243 int err = client_socket->SetSockOpt(SOL_SOCKET, SO_RCVTIMEO, &timeout,
244 sizeof(timeout));
245 if (err < 0) {
246 LOG(WARNING) << "Could not set socket timeout";
247 continue;
248 }
249
250 auto req_opt = RecvJsonMsg(client_socket);
251
252 if (!req_opt) {
253 LOG(WARNING) << "Invalid JSON Request, closing connection";
254 continue;
255 }
256
257 Json::Value req = req_opt.value();
258
259 if (!ValidateConfigRequest(req)) {
260 continue;
261 }
262
263 Json::Value req_list = req["config_request"]["request_list"];
264
265 Json::Value config_response;
266 Json::Value response_list;
267 Json::ArrayIndex req_list_size = req_list.size();
268
269 // sentinel value, so we can populate the list of responses correctly
270 // without trying to satisfy requests that will be aborted
271 bool transaction_failed = false;
272
273 for (Json::ArrayIndex i = 0; i < req_list_size; ++i) {
274 LOG(INFO) << "Processing Request: " << i;
275 auto req = req_list[i];
276 auto req_ty_str = req["request_type"].asString();
277 auto req_ty = StrToReqTy(req_ty_str);
278
279 Json::Value response;
280 if (transaction_failed) {
281 response["request_type"] = req_ty_str;
282 response["request_status"] = "pending";
283 response["error"] = "";
284 response_list.append(response);
285 continue;
286 }
287
288 switch (req_ty) {
289 case RequestType::ID: {
290 response = JsonHandleIdRequest();
291 break;
292 }
293 case RequestType::Shutdown: {
294 if (i != 0 || req_list_size != 1) {
295 response["request_type"] = req_ty_str;
296 response["request_status"] = "failed";
297 response["error"] =
298 "Shutdown requests cannot be processed with other "
299 "configuration requests";
300 response_list.append(response);
301 break;
302 } else {
303 response = JsonHandleShutdownRequest(client_socket);
304 response_list.append(response);
305 return;
306 }
307 }
308 case RequestType::CreateInterface: {
309 response = JsonHandleCreateInterfaceRequest(client_socket, req);
310 break;
311 }
312 case RequestType::DestroyInterface: {
313 response = JsonHandleDestroyInterfaceRequest(req);
314 break;
315 }
316 case RequestType::StopSession: {
317 response = JsonHandleStopSessionRequest(
318 req, GetUserIDFromSock(client_socket));
319 break;
320 }
321 case RequestType::Invalid: {
322 LOG(WARNING) << "Invalid Request Type: " << req["request_type"];
323 break;
324 }
325 }
326
327 response_list.append(response);
328 if (!(response["request_status"].asString() ==
329 StatusToStr(RequestStatus::Success))) {
330 LOG(INFO) << "Request failed:" << req;
331 transaction_failed = true;
332 continue;
333 }
334 }
335
336 config_response["response_list"] = response_list;
337
338 auto status =
339 transaction_failed ? RequestStatus::Failure : RequestStatus::Success;
340 config_response["config_status"] = StatusToStr(status);
341
342 if (!transaction_failed) {
343 auto session_id = AllocateSessionID();
344 config_response["session_id"] = session_id;
345 auto s = std::make_shared<Session>(session_id,
346 GetUserIDFromSock(client_socket));
347
348 // commit the resources
349 s->Insert(pending_add_);
350 pending_add_.clear();
351 managed_sessions_.insert({session_id, s});
352 } else {
353 // be sure to release anything we've acquired if the transaction failed
354 for (auto& droped_resource : pending_add_) {
355 droped_resource.second->ReleaseResource();
356 }
357 }
358
359 SendJsonMsg(client_socket, config_response);
360 LOG(INFO) << "Closing connection to client";
361 client_socket->Close();
362 }
363 server->Close();
364 }
365
GetUserIDFromSock(SharedFD client_socket)366 uid_t GetUserIDFromSock(SharedFD client_socket) {
367 struct ucred ucred {};
368 socklen_t len = sizeof(struct ucred);
369
370 if (-1 == client_socket->GetSockOpt(SOL_SOCKET, SO_PEERCRED, &ucred, &len)) {
371 LOG(WARNING) << "Failed to get Socket Credentials";
372 return -1;
373 }
374
375 return ucred.uid;
376 }
377
CheckCredentials(SharedFD client_socket,uid_t uid)378 bool ResourceManager::CheckCredentials(SharedFD client_socket, uid_t uid) {
379 uid_t sock_uid = GetUserIDFromSock(client_socket);
380
381 if (sock_uid == -1) {
382 LOG(WARNING) << "Invalid Socket UID: " << uid;
383 return false;
384 }
385
386 if (uid != sock_uid) {
387 LOG(WARNING) << "Message UID: " << uid
388 << " does not match socket's EUID: " << sock_uid;
389 return false;
390 }
391
392 return true;
393 }
394
JsonHandleIdRequest()395 Json::Value ResourceManager::JsonHandleIdRequest() {
396 Json::Value resp;
397 resp["request_type"] = "allocate_id";
398 resp["request_status"] = StatusToStr(RequestStatus::Success);
399 resp["id"] = AllocateSessionID();
400 return resp;
401 }
402
JsonHandleShutdownRequest(SharedFD client_socket)403 Json::Value ResourceManager::JsonHandleShutdownRequest(SharedFD client_socket) {
404 LOG(INFO) << "Received Shutdown Request";
405 shutdown_socket_ = client_socket;
406
407 Json::Value resp;
408 resp["request_type"] = "shutdown";
409 resp["request_status"] = "pending";
410 resp["error"] = "";
411
412 return resp;
413 }
414
JsonHandleCreateInterfaceRequest(SharedFD client_socket,const Json::Value & request)415 Json::Value ResourceManager::JsonHandleCreateInterfaceRequest(
416 SharedFD client_socket, const Json::Value& request) {
417 LOG(INFO) << "Received CreateInterface Request";
418
419 Json::Value resp;
420 resp["request_type"] = "create_interface";
421 resp["iface_name"] = "";
422 resp["request_status"] = StatusToStr(RequestStatus::Failure);
423 resp["error"] = "unknown";
424
425 if (!request.isMember("uid") || !request["uid"].isUInt()) {
426 auto err_msg = "Input event doesn't have a valid 'uid' field";
427 LOG(WARNING) << err_msg;
428 resp["error"] = err_msg;
429 return resp;
430 }
431
432 if (!request.isMember("iface_type") || !request["iface_type"].isString()) {
433 auto err_msg = "Input event doesn't have a valid 'iface_type' field";
434 LOG(WARNING) << err_msg;
435 resp["error"] = err_msg;
436 return resp;
437 }
438
439 auto uid = request["uid"].asUInt();
440
441 if (!CheckCredentials(client_socket, uid)) {
442 auto err_msg = "Credential check failed";
443 LOG(WARNING) << err_msg;
444 resp["error"] = err_msg;
445 return resp;
446 }
447
448 auto user_opt = GetUserName(uid);
449
450 bool addedIface = false;
451 std::stringstream ss;
452 if (!user_opt) {
453 auto err_msg = "UserName could not be matched to UID";
454 LOG(WARNING) << err_msg;
455 resp["error"] = err_msg;
456 return resp;
457 } else {
458 auto iface_ty_name = request["iface_type"].asString();
459 resp["iface_type"] = iface_ty_name;
460 auto iface_type = StrToIfaceTy(iface_ty_name);
461 auto attempts = kMaxIfaceNameId;
462 do {
463 auto id = AllocateResourceID();
464 resp["resource_id"] = id;
465 ss << "cvd-" << iface_ty_name << "-" << user_opt.value().substr(0, 4)
466 << std::setfill('0') << std::setw(2) << (id % kMaxIfaceNameId);
467 addedIface = AddInterface(ss.str(), iface_type, id, uid);
468 --attempts;
469 } while (!addedIface && (attempts > 0));
470 }
471
472 if (addedIface) {
473 resp["request_status"] = StatusToStr(RequestStatus::Success);
474 resp["iface_name"] = ss.str();
475 resp["error"] = "";
476 }
477
478 return resp;
479 }
480
JsonHandleDestroyInterfaceRequest(const Json::Value & request)481 Json::Value ResourceManager::JsonHandleDestroyInterfaceRequest(
482 const Json::Value& request) {
483 Json::Value resp;
484 resp["request_type"] = "destroy_interface";
485 resp["request_status"] = StatusToStr(RequestStatus::Failure);
486 if (!request.isMember("iface_name") || !request["iface_name"].isString()) {
487 auto err_msg = "Input event doesn't have a valid 'iface_name' field";
488 LOG(WARNING) << err_msg;
489 resp["error"] = err_msg;
490 return resp;
491 }
492
493 auto iface_name = request["iface_name"].asString();
494
495 bool isManagedIface = active_interfaces_.erase(iface_name) > 0;
496
497 if (!isManagedIface) {
498 auto msg = "Interface not managed: " + iface_name;
499 LOG(WARNING) << msg;
500 resp["error"] = msg;
501 return resp;
502 }
503
504 if (!request.isMember("session_id") || !request["session_id"].isUInt()) {
505 auto err_msg = "Input event doesn't have a valid 'session_id' field";
506 LOG(WARNING) << err_msg;
507 resp["error"] = err_msg;
508 return resp;
509 }
510
511 auto session_id = request["session_id"].asUInt();
512
513 auto resource_id = request["resource_id"].asUInt();
514
515 LOG(INFO) << "Received DestroyInterface Request for " << iface_name
516 << " in session: " << session_id
517 << ", resource_id: " << resource_id;
518
519 auto sess_opt = FindSession(session_id);
520 if (!sess_opt) {
521 auto msg = "Interface " + iface_name +
522 " was not managed in session: " + std::to_string(session_id) +
523 " with resource_id: " + std::to_string(resource_id);
524 LOG(WARNING) << msg;
525 resp["error"] = msg;
526 return resp;
527 }
528
529 auto s = sess_opt.value();
530
531 // while we could wait to see if any acquisitions fail and delay releasing
532 // resources until they are all finished, this operation is inherently
533 // destructive, so should a release operation fail, there is no satisfactory
534 // method for aborting the transaction. Instead, we try to release the
535 // resource and then can signal to the rest of the transaction the failure
536 // state, which can then just stop the transaction, and revert any newly
537 // acquired resources, but any successful drop requests will persist
538 auto did_drop_resource = s->ReleaseResource(resource_id);
539
540 if (did_drop_resource) {
541 resp["request_status"] = StatusToStr(RequestStatus::Success);
542 } else {
543 auto msg = "Interface " + iface_name +
544 " was not managed in session: " + std::to_string(session_id) +
545 " with resource_id: " + std::to_string(resource_id);
546 LOG(WARNING) << msg;
547 resp["error"] = msg;
548 }
549
550 return resp;
551 }
552
JsonHandleStopSessionRequest(const Json::Value & request,uid_t uid)553 Json::Value ResourceManager::JsonHandleStopSessionRequest(
554 const Json::Value& request, uid_t uid) {
555 Json::Value resp;
556 resp["request_type"] = ReqTyToStr(RequestType::StopSession);
557 resp["request_status"] = StatusToStr(RequestStatus::Failure);
558 if (!request.isMember("session_id") || !request["session_id"].isUInt()) {
559 auto err_msg = "Input event doesn't have a valid 'session_id' field";
560 LOG(WARNING) << err_msg;
561 resp["error"] = err_msg;
562 return resp;
563 }
564
565 auto session_id = request["session_id"].asUInt();
566 LOG(INFO) << "Received StopSession Request for Session ID: " << session_id;
567
568 auto it = managed_sessions_.find(session_id);
569 if (it == managed_sessions_.end()) {
570 auto msg = "Session not managed: " + std::to_string(session_id);
571 LOG(WARNING) << msg;
572 resp["error"] = msg;
573 return resp;
574 }
575
576 if (it->second->GetUID() != uid) {
577 auto msg = "Effective user ID does not match session owner. socket uid: " +
578 std::to_string(uid);
579 LOG(WARNING) << msg;
580 resp["error"] = msg;
581 return resp;
582 }
583
584 // while we could wait to see if any acquisitions fail and delay releasing
585 // resources until they are all finished, this operation is inherently
586 // destructive, so should a release operation fail, there is no satisfactory
587 // method for aborting the transaction. Instead, we try to release the
588 // resource and then can signal to the rest of the transaction the failure
589 // state
590 auto success = it->second->ReleaseAllResources();
591
592 // release the names from the global list for reuse in future requests
593 for (auto& iface : it->second->GetActiveInterfaces()) {
594 active_interfaces_.erase(iface);
595 }
596
597 if (success) {
598 managed_sessions_.erase(it);
599 resp["request_status"] = StatusToStr(RequestStatus::Success);
600 } else {
601 resp["error"] =
602 "unknown, allocd experienced an error ending the session id: " +
603 std::to_string(session_id);
604 }
605
606 return resp;
607 }
608
FindSession(uint32_t id)609 std::optional<std::shared_ptr<Session>> ResourceManager::FindSession(
610 uint32_t id) {
611 auto it = managed_sessions_.find(id);
612 if (it == managed_sessions_.end()) {
613 return std::nullopt;
614 } else {
615 return it->second;
616 }
617 }
618
619 } // namespace cuttlefish
620