| /* |
| * Copyright (C) 2020 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "host/libs/allocd/resource_manager.h" |
| |
| #include <android-base/logging.h> |
| #include <pwd.h> |
| #include <sys/socket.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
| |
| #include <atomic> |
| #include <cstdint> |
| #include <cstdlib> |
| #include <iomanip> |
| #include <memory> |
| #include <optional> |
| #include <sstream> |
| #include <string> |
| |
| #include "common/libs/fs/shared_fd.h" |
| #include "host/libs/allocd/alloc_utils.h" |
| #include "host/libs/allocd/request.h" |
| #include "host/libs/allocd/utils.h" |
| #include "json/forwards.h" |
| #include "json/value.h" |
| #include "json/writer.h" |
| |
| namespace cuttlefish { |
| |
| uid_t GetUserIDFromSock(SharedFD client_socket); |
| |
| ResourceManager::~ResourceManager() { |
| bool success = true; |
| for (auto& res : managed_sessions_) { |
| success &= res.second->ReleaseAllResources(); |
| } |
| |
| Json::Value resp; |
| resp["request_type"] = "shutdown"; |
| auto status = success ? RequestStatus::Success : RequestStatus::Failure; |
| resp["request_status"] = StatusToStr(status); |
| SendJsonMsg(shutdown_socket_, resp); |
| LOG(INFO) << "Daemon Shutdown complete"; |
| unlink(location.c_str()); |
| } |
| |
| void ResourceManager::SetSocketLocation(const std::string& sock_name) { |
| location = sock_name; |
| } |
| |
| void ResourceManager::SetUseEbtablesLegacy(bool use_legacy) { |
| use_ebtables_legacy_ = use_legacy; |
| } |
| |
| uint32_t ResourceManager::AllocateResourceID() { |
| return global_resource_id_.fetch_add(1, std::memory_order_relaxed); |
| } |
| |
| uint32_t ResourceManager::AllocateSessionID() { |
| return session_id_.fetch_add(1, std::memory_order_relaxed); |
| } |
| |
| bool ResourceManager::AddInterface(const std::string& iface, IfaceType ty, |
| uint32_t resource_id, uid_t uid) { |
| bool allocatedIface = false; |
| std::shared_ptr<StaticResource> res = nullptr; |
| |
| bool didInsert = active_interfaces_.insert(iface).second; |
| if (didInsert) { |
| const char* idp = iface.c_str() + (iface.size() - 3); |
| int small_id = atoi(idp); |
| switch (ty) { |
| case IfaceType::mtap: |
| res = std::make_shared<MobileIface>(iface, uid, small_id, resource_id, |
| kMobileIp); |
| allocatedIface = res->AcquireResource(); |
| pending_add_.insert({resource_id, res}); |
| break; |
| case IfaceType::wtap: { |
| // TODO (paulkirth): change this to cvd-wbr, to test w/ today's |
| // debian package, this is required since the number of wireless |
| // bridges provided by the debian package has gone from 10 down to |
| // 1, but our debian packages in cloudtop are not up to date |
| auto w = std::make_shared<EthernetIface>(iface, uid, small_id, |
| resource_id, "cvd-wbr-01", |
| kWirelessIp); |
| w->SetUseEbtablesLegacy(use_ebtables_legacy_); |
| w->SetHasIpv4(use_ipv4_bridge_); |
| w->SetHasIpv6(use_ipv6_bridge_); |
| res = w; |
| allocatedIface = res->AcquireResource(); |
| pending_add_.insert({resource_id, res}); |
| break; |
| } |
| case IfaceType::etap: { |
| auto w = std::make_shared<EthernetIface>(iface, uid, small_id, |
| resource_id, "cvd-ebr", |
| kEthernetIp); |
| w->SetUseEbtablesLegacy(use_ebtables_legacy_); |
| w->SetHasIpv4(use_ipv4_bridge_); |
| w->SetHasIpv6(use_ipv6_bridge_); |
| res = w; |
| allocatedIface = res->AcquireResource(); |
| pending_add_.insert({resource_id, res}); |
| break; |
| } |
| case IfaceType::wbr: |
| case IfaceType::ebr: |
| allocatedIface = CreateBridge(iface); |
| break; |
| case IfaceType::Invalid: |
| break; |
| } |
| } else { |
| LOG(WARNING) << "Interface already in use: " << iface; |
| } |
| |
| if (didInsert && !allocatedIface) { |
| LOG(WARNING) << "Failed to allocate interface: " << iface; |
| active_interfaces_.erase(iface); |
| auto it = pending_add_.find(resource_id); |
| it->second->ReleaseResource(); |
| pending_add_.erase(it); |
| } |
| |
| LOG(INFO) << "Finish CreateInterface Request"; |
| |
| return allocatedIface; |
| } |
| |
| bool ResourceManager::RemoveInterface(const std::string& iface, IfaceType ty) { |
| bool isManagedIface = active_interfaces_.erase(iface) > 0; |
| bool removedIface = false; |
| if (isManagedIface) { |
| switch (ty) { |
| case IfaceType::mtap: { |
| const char* idp = iface.c_str() + (iface.size() - 3); |
| int id = atoi(idp); |
| removedIface = DestroyMobileIface(iface, id, kMobileIp); |
| break; |
| } |
| case IfaceType::wtap: |
| case IfaceType::etap: |
| removedIface = DestroyEthernetIface( |
| iface, use_ipv4_bridge_, use_ipv6_bridge_, use_ebtables_legacy_); |
| break; |
| case IfaceType::wbr: |
| case IfaceType::ebr: |
| removedIface = DestroyBridge(iface); |
| break; |
| case IfaceType::Invalid: |
| break; |
| } |
| |
| } else { |
| LOG(WARNING) << "Interface not managed: " << iface; |
| } |
| |
| if (isManagedIface) { |
| LOG(INFO) << "Removed interface: " << iface; |
| } else { |
| LOG(WARNING) << "Could not remove interface: " << iface; |
| } |
| |
| return isManagedIface; |
| } |
| |
| bool ResourceManager::ValidateRequestList(const Json::Value& config) { |
| if (!config.isMember("request_list") || !config["request_list"].isArray()) { |
| LOG(WARNING) << "Request has invalid 'request_list' field"; |
| return false; |
| } |
| |
| auto request_list = config["request_list"]; |
| |
| Json::ArrayIndex size = request_list.size(); |
| if (size == 0) { |
| LOG(WARNING) << "Request has empty 'request_list' field"; |
| return false; |
| } |
| |
| for (Json::ArrayIndex i = 0; i < size; ++i) { |
| if (!ValidateRequest(request_list[i])) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool ResourceManager::ValidateConfigRequest(const Json::Value& config) { |
| if (!config.isMember("config_request") || |
| !config["config_request"].isObject()) { |
| LOG(WARNING) << "Request has invalid 'config_request' field"; |
| return false; |
| } |
| |
| Json::Value config_request = config["config_request"]; |
| |
| return ValidateRequestList(config_request); |
| } |
| |
| bool ResourceManager::ValidateRequest(const Json::Value& request) { |
| if (!request.isMember("request_type") || |
| !request["request_type"].isString() || |
| StrToReqTy(request["request_type"].asString()) == RequestType::Invalid) { |
| LOG(WARNING) << "Request has invalid 'request_type' field"; |
| return false; |
| } |
| return true; |
| } |
| |
| void ResourceManager::JsonServer() { |
| LOG(INFO) << "Starting server on " << kDefaultLocation; |
| auto server = SharedFD::SocketLocalServer(kDefaultLocation, false, |
| SOCK_STREAM, kSocketMode); |
| CHECK(server->IsOpen()) << "Could not start server at " << kDefaultLocation; |
| LOG(INFO) << "Accepting client connections"; |
| |
| while (true) { |
| auto client_socket = SharedFD::Accept(*server); |
| CHECK(client_socket->IsOpen()) << "Error creating client socket"; |
| |
| struct timeval timeout; |
| timeout.tv_sec = 10; |
| timeout.tv_usec = 0; |
| |
| int err = client_socket->SetSockOpt(SOL_SOCKET, SO_RCVTIMEO, &timeout, |
| sizeof(timeout)); |
| if (err < 0) { |
| LOG(WARNING) << "Could not set socket timeout"; |
| continue; |
| } |
| |
| auto req_opt = RecvJsonMsg(client_socket); |
| |
| if (!req_opt) { |
| LOG(WARNING) << "Invalid JSON Request, closing connection"; |
| continue; |
| } |
| |
| Json::Value req = req_opt.value(); |
| |
| if (!ValidateConfigRequest(req)) { |
| continue; |
| } |
| |
| Json::Value req_list = req["config_request"]["request_list"]; |
| |
| Json::Value config_response; |
| Json::Value response_list; |
| Json::ArrayIndex req_list_size = req_list.size(); |
| |
| // sentinel value, so we can populate the list of responses correctly |
| // without trying to satisfy requests that will be aborted |
| bool transaction_failed = false; |
| |
| for (Json::ArrayIndex i = 0; i < req_list_size; ++i) { |
| LOG(INFO) << "Processing Request: " << i; |
| auto req = req_list[i]; |
| auto req_ty_str = req["request_type"].asString(); |
| auto req_ty = StrToReqTy(req_ty_str); |
| |
| Json::Value response; |
| if (transaction_failed) { |
| response["request_type"] = req_ty_str; |
| response["request_status"] = "pending"; |
| response["error"] = ""; |
| response_list.append(response); |
| continue; |
| } |
| |
| switch (req_ty) { |
| case RequestType::ID: { |
| response = JsonHandleIdRequest(); |
| break; |
| } |
| case RequestType::Shutdown: { |
| if (i != 0 || req_list_size != 1) { |
| response["request_type"] = req_ty_str; |
| response["request_status"] = "failed"; |
| response["error"] = |
| "Shutdown requests cannot be processed with other " |
| "configuration requests"; |
| response_list.append(response); |
| break; |
| } else { |
| response = JsonHandleShutdownRequest(client_socket); |
| response_list.append(response); |
| return; |
| } |
| } |
| case RequestType::CreateInterface: { |
| response = JsonHandleCreateInterfaceRequest(client_socket, req); |
| break; |
| } |
| case RequestType::DestroyInterface: { |
| response = JsonHandleDestroyInterfaceRequest(req); |
| break; |
| } |
| case RequestType::StopSession: { |
| response = JsonHandleStopSessionRequest( |
| req, GetUserIDFromSock(client_socket)); |
| break; |
| } |
| case RequestType::Invalid: { |
| LOG(WARNING) << "Invalid Request Type: " << req["request_type"]; |
| break; |
| } |
| } |
| |
| response_list.append(response); |
| if (!(response["request_status"].asString() == |
| StatusToStr(RequestStatus::Success))) { |
| LOG(INFO) << "Request failed:" << req; |
| transaction_failed = true; |
| continue; |
| } |
| } |
| |
| config_response["response_list"] = response_list; |
| |
| auto status = |
| transaction_failed ? RequestStatus::Failure : RequestStatus::Success; |
| config_response["config_status"] = StatusToStr(status); |
| |
| if (!transaction_failed) { |
| auto session_id = AllocateSessionID(); |
| config_response["session_id"] = session_id; |
| auto s = std::make_shared<Session>(session_id, |
| GetUserIDFromSock(client_socket)); |
| |
| // commit the resources |
| s->Insert(pending_add_); |
| pending_add_.clear(); |
| managed_sessions_.insert({session_id, s}); |
| } else { |
| // be sure to release anything we've acquired if the transaction failed |
| for (auto& droped_resource : pending_add_) { |
| droped_resource.second->ReleaseResource(); |
| } |
| } |
| |
| SendJsonMsg(client_socket, config_response); |
| LOG(INFO) << "Closing connection to client"; |
| client_socket->Close(); |
| } |
| server->Close(); |
| } |
| |
| uid_t GetUserIDFromSock(SharedFD client_socket) { |
| struct ucred ucred {}; |
| socklen_t len = sizeof(struct ucred); |
| |
| if (-1 == client_socket->GetSockOpt(SOL_SOCKET, SO_PEERCRED, &ucred, &len)) { |
| LOG(WARNING) << "Failed to get Socket Credentials"; |
| return -1; |
| } |
| |
| return ucred.uid; |
| } |
| |
| bool ResourceManager::CheckCredentials(SharedFD client_socket, uid_t uid) { |
| uid_t sock_uid = GetUserIDFromSock(client_socket); |
| |
| if (sock_uid == -1) { |
| LOG(WARNING) << "Invalid Socket UID: " << uid; |
| return false; |
| } |
| |
| if (uid != sock_uid) { |
| LOG(WARNING) << "Message UID: " << uid |
| << " does not match socket's EUID: " << sock_uid; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| Json::Value ResourceManager::JsonHandleIdRequest() { |
| Json::Value resp; |
| resp["request_type"] = "allocate_id"; |
| resp["request_status"] = StatusToStr(RequestStatus::Success); |
| resp["id"] = AllocateSessionID(); |
| return resp; |
| } |
| |
| Json::Value ResourceManager::JsonHandleShutdownRequest(SharedFD client_socket) { |
| LOG(INFO) << "Received Shutdown Request"; |
| shutdown_socket_ = client_socket; |
| |
| Json::Value resp; |
| resp["request_type"] = "shutdown"; |
| resp["request_status"] = "pending"; |
| resp["error"] = ""; |
| |
| return resp; |
| } |
| |
| Json::Value ResourceManager::JsonHandleCreateInterfaceRequest( |
| SharedFD client_socket, const Json::Value& request) { |
| LOG(INFO) << "Received CreateInterface Request"; |
| |
| Json::Value resp; |
| resp["request_type"] = "create_interface"; |
| resp["iface_name"] = ""; |
| resp["request_status"] = StatusToStr(RequestStatus::Failure); |
| resp["error"] = "unknown"; |
| |
| if (!request.isMember("uid") || !request["uid"].isUInt()) { |
| auto err_msg = "Input event doesn't have a valid 'uid' field"; |
| LOG(WARNING) << err_msg; |
| resp["error"] = err_msg; |
| return resp; |
| } |
| |
| if (!request.isMember("iface_type") || !request["iface_type"].isString()) { |
| auto err_msg = "Input event doesn't have a valid 'iface_type' field"; |
| LOG(WARNING) << err_msg; |
| resp["error"] = err_msg; |
| return resp; |
| } |
| |
| auto uid = request["uid"].asUInt(); |
| |
| if (!CheckCredentials(client_socket, uid)) { |
| auto err_msg = "Credential check failed"; |
| LOG(WARNING) << err_msg; |
| resp["error"] = err_msg; |
| return resp; |
| } |
| |
| auto user_opt = GetUserName(uid); |
| |
| bool addedIface = false; |
| std::stringstream ss; |
| if (!user_opt) { |
| auto err_msg = "UserName could not be matched to UID"; |
| LOG(WARNING) << err_msg; |
| resp["error"] = err_msg; |
| return resp; |
| } else { |
| auto iface_ty_name = request["iface_type"].asString(); |
| resp["iface_type"] = iface_ty_name; |
| auto iface_type = StrToIfaceTy(iface_ty_name); |
| auto attempts = kMaxIfaceNameId; |
| do { |
| auto id = AllocateResourceID(); |
| resp["resource_id"] = id; |
| ss << "cvd-" << iface_ty_name << "-" << user_opt.value().substr(0, 4) |
| << std::setfill('0') << std::setw(2) << (id % kMaxIfaceNameId); |
| addedIface = AddInterface(ss.str(), iface_type, id, uid); |
| --attempts; |
| } while (!addedIface && (attempts > 0)); |
| } |
| |
| if (addedIface) { |
| resp["request_status"] = StatusToStr(RequestStatus::Success); |
| resp["iface_name"] = ss.str(); |
| resp["error"] = ""; |
| } |
| |
| return resp; |
| } |
| |
| Json::Value ResourceManager::JsonHandleDestroyInterfaceRequest( |
| const Json::Value& request) { |
| Json::Value resp; |
| resp["request_type"] = "destroy_interface"; |
| resp["request_status"] = StatusToStr(RequestStatus::Failure); |
| if (!request.isMember("iface_name") || !request["iface_name"].isString()) { |
| auto err_msg = "Input event doesn't have a valid 'iface_name' field"; |
| LOG(WARNING) << err_msg; |
| resp["error"] = err_msg; |
| return resp; |
| } |
| |
| auto iface_name = request["iface_name"].asString(); |
| |
| bool isManagedIface = active_interfaces_.erase(iface_name) > 0; |
| |
| if (!isManagedIface) { |
| auto msg = "Interface not managed: " + iface_name; |
| LOG(WARNING) << msg; |
| resp["error"] = msg; |
| return resp; |
| } |
| |
| if (!request.isMember("session_id") || !request["session_id"].isUInt()) { |
| auto err_msg = "Input event doesn't have a valid 'session_id' field"; |
| LOG(WARNING) << err_msg; |
| resp["error"] = err_msg; |
| return resp; |
| } |
| |
| auto session_id = request["session_id"].asUInt(); |
| |
| auto resource_id = request["resource_id"].asUInt(); |
| |
| LOG(INFO) << "Received DestroyInterface Request for " << iface_name |
| << " in session: " << session_id |
| << ", resource_id: " << resource_id; |
| |
| auto sess_opt = FindSession(session_id); |
| if (!sess_opt) { |
| auto msg = "Interface " + iface_name + |
| " was not managed in session: " + std::to_string(session_id) + |
| " with resource_id: " + std::to_string(resource_id); |
| LOG(WARNING) << msg; |
| resp["error"] = msg; |
| return resp; |
| } |
| |
| auto s = sess_opt.value(); |
| |
| // while we could wait to see if any acquisitions fail and delay releasing |
| // resources until they are all finished, this operation is inherently |
| // destructive, so should a release operation fail, there is no satisfactory |
| // method for aborting the transaction. Instead, we try to release the |
| // resource and then can signal to the rest of the transaction the failure |
| // state, which can then just stop the transaction, and revert any newly |
| // acquired resources, but any successful drop requests will persist |
| auto did_drop_resource = s->ReleaseResource(resource_id); |
| |
| if (did_drop_resource) { |
| resp["request_status"] = StatusToStr(RequestStatus::Success); |
| } else { |
| auto msg = "Interface " + iface_name + |
| " was not managed in session: " + std::to_string(session_id) + |
| " with resource_id: " + std::to_string(resource_id); |
| LOG(WARNING) << msg; |
| resp["error"] = msg; |
| } |
| |
| return resp; |
| } |
| |
| Json::Value ResourceManager::JsonHandleStopSessionRequest( |
| const Json::Value& request, uid_t uid) { |
| Json::Value resp; |
| resp["request_type"] = ReqTyToStr(RequestType::StopSession); |
| resp["request_status"] = StatusToStr(RequestStatus::Failure); |
| if (!request.isMember("session_id") || !request["session_id"].isUInt()) { |
| auto err_msg = "Input event doesn't have a valid 'session_id' field"; |
| LOG(WARNING) << err_msg; |
| resp["error"] = err_msg; |
| return resp; |
| } |
| |
| auto session_id = request["session_id"].asUInt(); |
| LOG(INFO) << "Received StopSession Request for Session ID: " << session_id; |
| |
| auto it = managed_sessions_.find(session_id); |
| if (it == managed_sessions_.end()) { |
| auto msg = "Session not managed: " + std::to_string(session_id); |
| LOG(WARNING) << msg; |
| resp["error"] = msg; |
| return resp; |
| } |
| |
| if (it->second->GetUID() != uid) { |
| auto msg = "Effective user ID does not match session owner. socket uid: " + |
| std::to_string(uid); |
| LOG(WARNING) << msg; |
| resp["error"] = msg; |
| return resp; |
| } |
| |
| // while we could wait to see if any acquisitions fail and delay releasing |
| // resources until they are all finished, this operation is inherently |
| // destructive, so should a release operation fail, there is no satisfactory |
| // method for aborting the transaction. Instead, we try to release the |
| // resource and then can signal to the rest of the transaction the failure |
| // state |
| auto success = it->second->ReleaseAllResources(); |
| |
| // release the names from the global list for reuse in future requests |
| for (auto& iface : it->second->GetActiveInterfaces()) { |
| active_interfaces_.erase(iface); |
| } |
| |
| if (success) { |
| managed_sessions_.erase(it); |
| resp["request_status"] = StatusToStr(RequestStatus::Success); |
| } else { |
| resp["error"] = |
| "unknown, allocd experienced an error ending the session id: " + |
| std::to_string(session_id); |
| } |
| |
| return resp; |
| } |
| |
| std::optional<std::shared_ptr<Session>> ResourceManager::FindSession( |
| uint32_t id) { |
| auto it = managed_sessions_.find(id); |
| if (it == managed_sessions_.end()) { |
| return std::nullopt; |
| } else { |
| return it->second; |
| } |
| } |
| |
| } // namespace cuttlefish |