blob: 745a323b25995c6adc91e683cd7a2e1fe399bacf [file] [edit]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "shill/cellular/carrier_entitlement.h"
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include <base/check.h>
#include <base/functional/callback_helpers.h>
#include <base/strings/string_util.h>
#include <base/values.h>
#include <brillo/http/http_utils.h>
#include <brillo/http/http_request.h>
#include "shill/cellular/cellular.h"
#include "shill/event_dispatcher.h"
#include "shill/logging.h"
#include "shill/metrics.h"
#include "shill/network/network.h"
namespace shill {
namespace Logging {
static auto kModuleLogScope = ScopeLogger::kCellular;
} // namespace Logging
CarrierEntitlement::CarrierEntitlement(
Cellular* cellular,
Metrics* metrics,
patchpanel::Client* patchpanel_client,
base::RepeatingCallback<void(Result)> check_cb)
: cellular_(cellular),
metrics_(metrics),
patchpanel_client_(patchpanel_client),
check_cb_(check_cb),
weak_ptr_factory_(this) {}
CarrierEntitlement::~CarrierEntitlement() {
// cancel pending request if it exists
if (transport_) {
transport_->CancelRequest(request_id_);
}
background_check_cancelable.Cancel();
}
EventDispatcher* CarrierEntitlement::dispatcher() {
return cellular_->dispatcher();
}
void CarrierEntitlement::Check(
const MobileOperatorMapper::EntitlementConfig& config) {
config_ = config;
CheckInternal(/* user_triggered */ true);
}
void CarrierEntitlement::CheckInternal(bool user_triggered) {
SLOG(3) << __func__;
if (transport_) {
LOG(WARNING)
<< "Entitlement check already in progress. New request ignored.";
metrics_->NotifyCellularEntitlementCheckResult(
Metrics::kCellularEntitlementCheckInProgress);
// The new request is ignored, but the client will receive an update
// when the previous request completes.
return;
}
// Reset the cache value on a background check.
if (!user_triggered) {
last_result_ = Result::kGenericError;
LOG(INFO) << "Initiating a background entitlement check.";
}
if (config_.url.empty()) {
SLOG(3) << "Carrier doesn't require an entitlement check.";
// Skip reporting metrics, since this result would dominate the results,
// and it's not a very useful value to know.
SendResult(Result::kAllowed);
return;
}
std::unique_ptr<base::Value> content = BuildContentPayload(config_.params);
if (!content) {
LOG(ERROR) << "Failed to build entitlement check message.";
SendResult(Result::kGenericError);
metrics_->NotifyCellularEntitlementCheckResult(
Metrics::kCellularEntitlementCheckFailedToBuildPayload);
return;
}
auto network = cellular_->GetPrimaryNetwork();
if (!network) {
LOG(ERROR)
<< "Cannot run entitlement check because Network object is missing";
SendResult(Result::kNetworkNotReady);
metrics_->NotifyCellularEntitlementCheckResult(
Metrics::kCellularEntitlementCheckNoNetwork);
return;
}
if (!network->IsConnected()) {
LOG(ERROR)
<< "Cannot run entitlement check because the network is not connected";
SendResult(Result::kNetworkNotReady);
metrics_->NotifyCellularEntitlementCheckResult(
Metrics::kCellularEntitlementCheckNetworkNotConnected);
return;
}
if (!network->HasInternetConnectivity()) {
LOG(ERROR) << "Cannot run entitlement check because cellular is not online";
SendResult(Result::kNetworkNotReady);
metrics_->NotifyCellularEntitlementCheckResult(
Metrics::kCellularEntitlementCheckNetworkNotOnline);
return;
}
SLOG(3) << __func__ << " launching request at interface: " << network;
std::vector<std::string> dns_list_str;
for (auto& ip : network->GetDNSServers()) {
dns_list_str.push_back(ip.ToString());
}
if (transport_for_testing_) {
transport_ = transport_for_testing_;
} else {
transport_ = brillo::http::Transport::CreateDefault();
}
patchpanel::Client::TrafficAnnotation annotation;
annotation.id =
patchpanel::Client::TrafficAnnotationId::kShillCarrierEntitlement;
patchpanel_client_->PrepareTagSocket(std::move(annotation), transport_);
transport_->SetDnsServers(dns_list_str);
transport_->SetDnsInterface(network->interface_name());
transport_->SetInterface(network->interface_name());
transport_->UseCustomCertificate(brillo::http::Transport::Certificate::kNss);
transport_->SetDefaultTimeout(kHttpRequestTimeout);
auto cb_http_success =
base::BindOnce(&CarrierEntitlement::HttpRequestSuccessCallback,
weak_ptr_factory_.GetWeakPtr());
auto cb_http_error =
base::BindOnce(&CarrierEntitlement::HttpRequestErrorCallback,
weak_ptr_factory_.GetWeakPtr());
if (config_.method == brillo::http::request_type::kGet) {
// No content is sent.
request_id_ =
brillo::http::Get(config_.url, {} /*headers*/, transport_,
std::move(cb_http_success), std::move(cb_http_error));
} else {
request_id_ = brillo::http::PostJson(
config_.url, std::move(content), {} /*headers*/, transport_,
std::move(cb_http_success), std::move(cb_http_error));
}
}
void CarrierEntitlement::PostBackgroundCheck() {
background_check_cancelable.Reset(base::BindOnce(
&CarrierEntitlement::CheckInternal, weak_ptr_factory_.GetWeakPtr(),
/* user_triggered */ false));
dispatcher()->PostDelayedTask(FROM_HERE,
background_check_cancelable.callback(),
kBackgroundCheckPeriod);
}
void CarrierEntitlement::Reset() {
SLOG(3) << __func__;
// cancel pending request if it exists
if (transport_) {
transport_->CancelRequest(request_id_);
transport_.reset();
}
last_result_ = Result::kGenericError;
background_check_cancelable.Cancel();
}
std::unique_ptr<base::Value> CarrierEntitlement::BuildContentPayload(
const Stringmap& params) {
base::Value::Dict dict;
for (auto pair : params)
dict.Set(pair.first, pair.second);
return std::make_unique<base::Value>(std::move(dict));
}
void CarrierEntitlement::SendResult(Result result) {
if (transport_)
transport_.reset();
dispatcher()->PostTask(FROM_HERE, base::BindOnce(check_cb_, result));
}
void CarrierEntitlement::HttpRequestSuccessCallback(
brillo::http::RequestID request_id,
std::unique_ptr<brillo::http::Response> response) {
DCHECK(request_id == request_id_);
if (request_id != request_id_) {
LOG(ERROR) << "EntitlementCheck: Expected request ID " << request_id_
<< " but got " << request_id;
SendResult(Result::kGenericError);
metrics_->NotifyCellularEntitlementCheckResult(
Metrics::kCellularEntitlementCheckUnexpectedRequestId);
return;
}
int http_status_code = response->GetStatusCode();
std::string response_code;
base::TrimString(response->ExtractDataAsString(), "\r\n ", &response_code);
SLOG(3) << __func__ << " status_code:" << http_status_code
<< ". response text:" << response_code;
switch (http_status_code) {
case brillo::http::status_code::Ok:
last_result_ = Result::kAllowed;
metrics_->NotifyCellularEntitlementCheckResult(
Metrics::kCellularEntitlementCheckAllowed);
PostBackgroundCheck();
break;
case brillo::http::status_code::Forbidden:
if (response_code == kServerCodeUserNotAllowedToTether) {
last_result_ = Result::kUserNotAllowedToTether;
LOG(INFO) << __func__ << ": User not allowed to tether.";
metrics_->NotifyCellularEntitlementCheckResult(
Metrics::kCellularEntitlementCheckUserNotAllowedToTether);
} else if (response_code == kServerCodeHttpSyntaxError) {
last_result_ = Result::kGenericError;
LOG(INFO) << __func__ << ": Syntax error of HTTP Request.";
metrics_->NotifyCellularEntitlementCheckResult(
Metrics::kCellularEntitlementCheckHttpSyntaxErrorOnServer);
} else if (response_code == kServerCodeUnrecognizedUser) {
last_result_ = Result::kUnrecognizedUser;
LOG(INFO) << __func__ << ": Unrecognized User.";
metrics_->NotifyCellularEntitlementCheckResult(
Metrics::kCellularEntitlementCheckUnrecognizedUser);
} else if (response_code == kServerCodeInternalError) {
LOG(INFO) << __func__ << ": Server error. Using cached value";
metrics_->NotifyCellularEntitlementCheckResult(
Metrics::kCellularEntitlementCheckInternalErrorOnServer);
} else {
last_result_ = Result::kGenericError;
LOG(INFO) << __func__ << ": Unrecognized error:" << response_code;
metrics_->NotifyCellularEntitlementCheckResult(
Metrics::kCellularEntitlementCheckUnrecognizedErrorCode);
}
break;
default:
LOG(INFO) << __func__ << ": Unrecognized http status code.";
metrics_->NotifyCellularEntitlementCheckResult(
Metrics::kCellularEntitlementCheckUnrecognizedHttpStatusCode);
last_result_ = Result::kGenericError;
break;
}
SendResult(last_result_);
}
void CarrierEntitlement::HttpRequestErrorCallback(
brillo::http::RequestID request_id, const brillo::Error* error) {
// On a request failure, the result will be the cached value.
if (request_id != request_id_) {
LOG(ERROR) << "EntitlementCheck: Expected request ID " << request_id_
<< " but got " << request_id;
} else {
LOG(ERROR) << "Entitlement check failed with error code :"
<< error->GetCode() << ":" << error->GetMessage();
}
// Reschedule a new background check if the cached value is Allowed.
if (last_result_ == Result::kAllowed) {
PostBackgroundCheck();
}
SendResult(last_result_);
metrics_->NotifyCellularEntitlementCheckResult(
Metrics::kCellularEntitlementCheckHttpRequestError);
}
} // namespace shill