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