Merge "trusty: Helper library for metrics"

This commit is contained in:
Tri Vo 2021-05-04 14:17:05 +00:00 committed by Gerrit Code Review
commit e23e85bee5
5 changed files with 492 additions and 0 deletions

51
trusty/metrics/Android.bp Normal file
View file

@ -0,0 +1,51 @@
// Copyright (C) 2021 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.
package {
default_applicable_licenses: ["Android-Apache-2.0"],
}
cc_library {
name: "libtrusty_metrics",
vendor: true,
srcs: [
"metrics.cpp",
],
export_include_dirs: [
"include",
],
shared_libs: [
"libbase",
"liblog",
"libtrusty",
],
}
cc_test {
name: "libtrusty_metrics_test",
vendor: true,
srcs: [
"metrics_test.cpp",
],
static_libs: [
"libtrusty_metrics",
],
shared_libs: [
"libbase",
"libbinder",
"liblog",
"libtrusty",
],
require_root: true,
}

View file

@ -0,0 +1,57 @@
/*
* Copyright (C) 2021 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.
*/
#pragma once
#include <functional>
#include <memory>
#include <string>
#include <android-base/result.h>
#include <android-base/unique_fd.h>
namespace android {
namespace trusty {
namespace metrics {
using android::base::Result;
using android::base::unique_fd;
class TrustyMetrics {
public:
/* Wait for next event with a given timeout. Negative timeout means infinite timeout. */
Result<void> WaitForEvent(int timeout_ms = -1);
/* Attempt to handle an event from Metrics TA in a non-blocking manner. */
Result<void> HandleEvent();
/* Expose TIPC channel so that client can integrate it into an event loop with other fds. */
int GetRawFd() { return metrics_fd_; };
protected:
TrustyMetrics(std::string tipc_dev) : tipc_dev_(std::move(tipc_dev)), metrics_fd_(-1) {}
virtual ~TrustyMetrics(){};
Result<void> Open();
virtual void HandleCrash(const std::string& app_id) = 0;
virtual void HandleEventDrop() = 0;
private:
std::string tipc_dev_;
unique_fd metrics_fd_;
};
} // namespace metrics
} // namespace trusty
} // namespace android

View file

@ -0,0 +1,103 @@
/*
* Copyright 2021, 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.
*/
#pragma once
#include <stdint.h>
/**
* DOC: Metrics
*
* Metrics interface provides a way for Android to get Trusty metrics data.
*
* Currently, only "push" model is supported. Clients are expected to connect to
* metrics service, listen for events, e.g. app crash events, and respond to
* every event with a &struct metrics_req.
*
* Communication is driven by metrics service, i.e. requests/responses are all
* sent from/to metrics service.
*
* Note that the type of the event is not known to the client ahead of time.
*
* In the future, if we need to have Android "pull" metrics data from Trusty,
* that can be done by introducing a separate port.
*
* This interface is shared between Android and Trusty. There is a copy in each
* repository. They must be kept in sync.
*/
#define METRICS_PORT "com.android.trusty.metrics"
/**
* enum metrics_cmd - command identifiers for metrics interface
* @METRICS_CMD_RESP_BIT: message is a response
* @METRICS_CMD_REQ_SHIFT: number of bits used by @METRICS_CMD_RESP_BIT
* @METRICS_CMD_REPORT_EVENT_DROP: report gaps in the event stream
* @METRICS_CMD_REPORT_CRASH: report an app crash event
*/
enum metrics_cmd {
METRICS_CMD_RESP_BIT = 1,
METRICS_CMD_REQ_SHIFT = 1,
METRICS_CMD_REPORT_EVENT_DROP = (1 << METRICS_CMD_REQ_SHIFT),
METRICS_CMD_REPORT_CRASH = (2 << METRICS_CMD_REQ_SHIFT),
};
/**
* enum metrics_error - metrics error codes
* @METRICS_NO_ERROR: no error
* @METRICS_ERR_UNKNOWN_CMD: unknown or not implemented command
*/
enum metrics_error {
METRICS_NO_ERROR = 0,
METRICS_ERR_UNKNOWN_CMD = 1,
};
/**
* struct metrics_req - common structure for metrics requests
* @cmd: command identifier - one of &enum metrics_cmd
* @reserved: must be 0
*/
struct metrics_req {
uint32_t cmd;
uint32_t reserved;
} __attribute__((__packed__));
/**
* struct metrics_resp - common structure for metrics responses
* @cmd: command identifier - %METRICS_CMD_RESP_BIT or'ed with a cmd in
* one of &enum metrics_cmd
* @status: response status, one of &enum metrics_error
*/
struct metrics_resp {
uint32_t cmd;
uint32_t status;
} __attribute__((__packed__));
/**
* struct metrics_report_crash_req - arguments of %METRICS_CMD_REPORT_CRASH
* requests
* @app_id_len: length of app ID that follows this structure
*/
struct metrics_report_crash_req {
uint32_t app_id_len;
} __attribute__((__packed__));
#define METRICS_MAX_APP_ID_LEN 256
#define METRICS_MAX_MSG_SIZE \
(sizeof(struct metrics_req) + sizeof(struct metrics_report_crash_req) + \
METRICS_MAX_APP_ID_LEN)

142
trusty/metrics/metrics.cpp Normal file
View file

@ -0,0 +1,142 @@
/*
* Copyright (C) 2021 The Android Open Sourete 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.
*/
#define LOG_TAG "metrics"
#include <android-base/logging.h>
#include <fcntl.h>
#include <poll.h>
#include <trusty/metrics/metrics.h>
#include <trusty/metrics/tipc.h>
#include <trusty/tipc.h>
#include <unistd.h>
namespace android {
namespace trusty {
namespace metrics {
using android::base::ErrnoError;
using android::base::Error;
Result<void> TrustyMetrics::Open() {
int fd = tipc_connect(tipc_dev_.c_str(), METRICS_PORT);
if (fd < 0) {
return ErrnoError() << "failed to connect to Trusty metrics TA";
}
int flags = fcntl(fd, F_GETFL, 0);
if (flags < 0) {
return ErrnoError() << "failed F_GETFL";
}
int rc = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
if (rc < 0) {
return ErrnoError() << "failed F_SETFL";
}
metrics_fd_.reset(fd);
return {};
}
Result<void> TrustyMetrics::WaitForEvent(int timeout_ms) {
if (!metrics_fd_.ok()) {
return Error() << "connection to Metrics TA has not been initialized yet";
}
struct pollfd pfd = {
.fd = metrics_fd_,
.events = POLLIN,
};
int rc = poll(&pfd, 1, timeout_ms);
if (rc != 1) {
return ErrnoError() << "failed poll()";
}
if (!(pfd.revents & POLLIN)) {
return ErrnoError() << "channel not ready";
}
return {};
}
Result<void> TrustyMetrics::HandleEvent() {
if (!metrics_fd_.ok()) {
return Error() << "connection to Metrics TA has not been initialized yet";
}
uint8_t msg[METRICS_MAX_MSG_SIZE];
auto rc = read(metrics_fd_, msg, sizeof(msg));
if (rc < 0) {
return ErrnoError() << "failed to read metrics message";
}
size_t msg_len = rc;
if (msg_len < sizeof(metrics_req)) {
return Error() << "message too small: " << rc;
}
auto req = reinterpret_cast<metrics_req*>(msg);
size_t offset = sizeof(metrics_req);
uint32_t status = METRICS_NO_ERROR;
switch (req->cmd) {
case METRICS_CMD_REPORT_CRASH: {
if (msg_len < offset + sizeof(metrics_report_crash_req)) {
return Error() << "message too small: " << rc;
}
auto crash_args = reinterpret_cast<metrics_report_crash_req*>(msg + offset);
offset += sizeof(metrics_report_crash_req);
if (msg_len < offset + crash_args->app_id_len) {
return Error() << "message too small: " << rc;
}
auto app_id_ptr = reinterpret_cast<char*>(msg + offset);
std::string app_id(app_id_ptr, crash_args->app_id_len);
HandleCrash(app_id);
break;
}
case METRICS_CMD_REPORT_EVENT_DROP:
HandleEventDrop();
break;
default:
status = METRICS_ERR_UNKNOWN_CMD;
break;
}
metrics_resp resp = {
.cmd = req->cmd | METRICS_CMD_RESP_BIT,
.status = status,
};
rc = write(metrics_fd_, &resp, sizeof(resp));
if (rc < 0) {
return ErrnoError() << "failed to request next metrics event";
}
if (rc != (int)sizeof(resp)) {
return Error() << "unexpected number of bytes sent event: " << rc;
}
return {};
}
} // namespace metrics
} // namespace trusty
} // namespace android

View file

@ -0,0 +1,139 @@
/*
* Copyright (C) 2021 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 <android-base/unique_fd.h>
#include <binder/IPCThreadState.h>
#include <gtest/gtest.h>
#include <poll.h>
#include <trusty/metrics/metrics.h>
#include <trusty/tipc.h>
#define TIPC_DEV "/dev/trusty-ipc-dev0"
#define CRASHER_PORT "com.android.trusty.metrics.test.crasher"
namespace android {
namespace trusty {
namespace metrics {
using android::base::unique_fd;
static void TriggerCrash() {
size_t num_retries = 3;
int fd = -1;
for (size_t i = 0; i < num_retries; i++) {
/* It's possible to time out waiting for crasher TA to restart. */
fd = tipc_connect(TIPC_DEV, CRASHER_PORT);
if (fd >= 0) {
break;
}
}
unique_fd crasher(fd);
ASSERT_GE(crasher, 0);
int msg = 0;
int rc = write(crasher, &msg, sizeof(msg));
ASSERT_EQ(rc, sizeof(msg));
}
class TrustyMetricsTest : public TrustyMetrics, public ::testing::Test {
public:
TrustyMetricsTest() : TrustyMetrics(TIPC_DEV) {}
virtual void HandleCrash(const std::string& app_id) override { crashed_app_ = app_id; }
virtual void HandleEventDrop() override { event_drop_count_++; }
virtual void SetUp() override {
auto ret = Open();
ASSERT_TRUE(ret.ok()) << ret.error();
}
void WaitForAndHandleEvent() {
auto ret = WaitForEvent(30000 /* 30 second timeout */);
ASSERT_TRUE(ret.ok()) << ret.error();
ret = HandleEvent();
ASSERT_TRUE(ret.ok()) << ret.error();
}
std::string crashed_app_;
size_t event_drop_count_;
};
TEST_F(TrustyMetricsTest, Crash) {
TriggerCrash();
WaitForAndHandleEvent();
/* Check that correct TA crashed. */
ASSERT_EQ(crashed_app_, "36f5b435-5bd3-4526-8b76-200e3a7e79f3:crasher");
}
TEST_F(TrustyMetricsTest, PollSet) {
int binder_fd;
int rc = IPCThreadState::self()->setupPolling(&binder_fd);
ASSERT_EQ(rc, 0);
ASSERT_GE(binder_fd, 0);
TriggerCrash();
struct pollfd pfds[] = {
{
.fd = binder_fd,
.events = POLLIN,
},
{
.fd = GetRawFd(),
.events = POLLIN,
},
};
rc = poll(pfds, 2, 30000 /* 30 second timeout */);
/* We expect one event on the metrics fd. */
ASSERT_EQ(rc, 1);
ASSERT_TRUE(pfds[1].revents & POLLIN);
auto ret = HandleEvent();
ASSERT_TRUE(ret.ok()) << ret.error();
/* Check that correct TA crashed. */
ASSERT_EQ(crashed_app_, "36f5b435-5bd3-4526-8b76-200e3a7e79f3:crasher");
}
TEST_F(TrustyMetricsTest, EventDrop) {
/* We know the size of the internal event queue is less than this. */
size_t num_events = 3;
ASSERT_EQ(event_drop_count_, 0);
for (auto i = 0; i < num_events; i++) {
TriggerCrash();
}
for (auto i = 0; i < num_events; i++) {
WaitForAndHandleEvent();
if (event_drop_count_ > 0) {
break;
}
}
ASSERT_EQ(event_drop_count_, 1);
}
} // namespace metrics
} // namespace trusty
} // namespace android