blob: e27184d6be6219cd79ea67cc44e855c3942c4a71 [file] [log] [blame]
/*
* Copyright (C) 2023 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 "src/traced_relay/relay_service.h"
#include <memory>
#include "perfetto/ext/base/unix_socket.h"
#include "protos/perfetto/ipc/wire_protocol.gen.h"
#include "src/base/test/test_task_runner.h"
#include "src/ipc/buffered_frame_deserializer.h"
#include "test/gtest_and_gmock.h"
// Disable tests on MacOS and Windows as neither abstract sockets nor pseudo
// boot ID are supported.
#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
namespace perfetto {
namespace {
using ::testing::_;
using ::testing::Invoke;
class TestEventListener : public base::UnixSocket::EventListener {
public:
MOCK_METHOD(void, OnDataAvailable, (base::UnixSocket*), (override));
MOCK_METHOD(void, OnConnect, (base::UnixSocket*, bool), (override));
MOCK_METHOD(void, OnNewIncomingConnection, (base::UnixSocket*));
void OnNewIncomingConnection(
base::UnixSocket*,
std::unique_ptr<base::UnixSocket> new_connection) override {
// Need to keep |new_connection| alive.
client_connection_ = std::move(new_connection);
OnNewIncomingConnection(client_connection_.get());
}
private:
std::unique_ptr<base::UnixSocket> client_connection_;
};
// Exercises the relay service and also validates that the relay service injects
// a SetPeerIdentity message:
//
// producer (client UnixSocket) <- @producer.sock -> relay service
// <- 127.0.0.1.* -> tcp_server (listening UnixSocet).
TEST(RelayServiceTest, SetPeerIdentity) {
base::TestTaskRunner task_runner;
auto relay_service = std::make_unique<RelayService>(&task_runner);
// Disable the extra socket connection created by RelayClient.
relay_service->SetRelayClientDisabledForTesting(true);
// Set up a server UnixSocket to find an unused TCP port.
// The TCP connection emulates the socket to the host traced.
TestEventListener tcp_listener;
auto tcp_server = base::UnixSocket::Listen(
"127.0.0.1:0", &tcp_listener, &task_runner, base::SockFamily::kInet,
base::SockType::kStream);
ASSERT_TRUE(tcp_server->is_listening());
auto tcp_sock_name = tcp_server->GetSockAddr();
auto* unix_sock_name = "@producer.sock"; // Use abstract socket for server.
// Start the relay service.
relay_service->Start(unix_sock_name, tcp_sock_name.c_str());
// Emulates the producer connection.
TestEventListener producer_listener;
auto producer = base::UnixSocket::Connect(
unix_sock_name, &producer_listener, &task_runner, base::SockFamily::kUnix,
base::SockType::kStream);
auto producer_connected = task_runner.CreateCheckpoint("producer_connected");
EXPECT_CALL(producer_listener, OnConnect(_, _))
.WillOnce(Invoke([&](base::UnixSocket* s, bool conn) {
EXPECT_TRUE(conn);
EXPECT_EQ(s, producer.get());
producer_connected();
}));
task_runner.RunUntilCheckpoint("producer_connected");
// Add some producer data.
ipc::Frame test_frame;
test_frame.add_data_for_testing("test_data");
auto test_data = ipc::BufferedFrameDeserializer::Serialize(test_frame);
producer->SendStr(test_data);
base::UnixSocket* tcp_client_connection = nullptr;
auto tcp_client_connected =
task_runner.CreateCheckpoint("tcp_client_connected");
EXPECT_CALL(tcp_listener, OnNewIncomingConnection(_))
.WillOnce(Invoke([&](base::UnixSocket* client) {
tcp_client_connection = client;
tcp_client_connected();
}));
task_runner.RunUntilCheckpoint("tcp_client_connected");
// Asserts that we can receive the SetPeerIdentity message.
auto peer_identity_recv = task_runner.CreateCheckpoint("peer_identity_recv");
ipc::BufferedFrameDeserializer deserializer;
EXPECT_CALL(tcp_listener, OnDataAvailable(_))
.WillRepeatedly(Invoke([&](base::UnixSocket* tcp_conn) {
auto buf = deserializer.BeginReceive();
auto rsize = tcp_conn->Receive(buf.data, buf.size);
EXPECT_TRUE(deserializer.EndReceive(rsize));
auto frame = deserializer.PopNextFrame();
EXPECT_TRUE(frame->has_set_peer_identity());
const auto& set_peer_identity = frame->set_peer_identity();
EXPECT_EQ(set_peer_identity.pid(), getpid());
EXPECT_EQ(set_peer_identity.uid(), static_cast<int32_t>(geteuid()));
EXPECT_TRUE(set_peer_identity.has_machine_id_hint());
frame = deserializer.PopNextFrame();
EXPECT_EQ(1u, frame->data_for_testing().size());
EXPECT_EQ(std::string("test_data"), frame->data_for_testing()[0]);
peer_identity_recv();
}));
task_runner.RunUntilCheckpoint("peer_identity_recv");
}
TEST(RelayServiceTest, MachineIDHint) {
base::TestTaskRunner task_runner;
auto relay_service = std::make_unique<RelayService>(&task_runner);
auto hint1 = relay_service->GetMachineIdHint();
auto hint2 =
relay_service->GetMachineIdHint(/*use_pseudo_boot_id_for_testing=*/true);
EXPECT_NE(hint1, hint2);
// Add a short sleep to verify that pseudo boot ID isn't affected.
std::this_thread::sleep_for(std::chrono::milliseconds(1));
relay_service = std::make_unique<RelayService>(&task_runner);
auto hint3 = relay_service->GetMachineIdHint();
auto hint4 =
relay_service->GetMachineIdHint(/*use_pseudo_boot_id_for_testing=*/true);
EXPECT_NE(hint3, hint4);
EXPECT_FALSE(hint1.empty());
#if !PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
// This test can run on Android kernel 3.x, but pseudo boot ID uses statx(2)
// that requires kernel 4.11.
EXPECT_FALSE(hint2.empty());
#endif
EXPECT_EQ(hint1, hint3);
EXPECT_EQ(hint2, hint4);
}
// Test that the RelayClient notifies its usr with the callback on
// connection errors.
TEST(RelayClientTest, OnErrorCallback) {
base::TestTaskRunner task_runner;
// Set up a server UnixSocket to find an unused TCP port.
// The TCP connection emulates the socket to the host traced.
TestEventListener tcp_listener;
auto tcp_server = base::UnixSocket::Listen(
"127.0.0.1:0", &tcp_listener, &task_runner, base::SockFamily::kInet,
base::SockType::kStream);
ASSERT_TRUE(tcp_server->is_listening());
auto tcp_sock_name = tcp_server->GetSockAddr();
auto on_relay_client_error =
task_runner.CreateCheckpoint("on_relay_client_error");
auto on_error_callback = [&]() { on_relay_client_error(); };
auto relay_client = std::make_unique<RelayClient>(
tcp_sock_name, "fake_machine_id_hint", &task_runner, on_error_callback);
base::UnixSocket* tcp_client_connection = nullptr;
auto tcp_client_connected =
task_runner.CreateCheckpoint("tcp_client_connected");
EXPECT_CALL(tcp_listener, OnNewIncomingConnection(_))
.WillOnce(Invoke([&](base::UnixSocket* client) {
tcp_client_connection = client;
tcp_client_connected();
}));
task_runner.RunUntilCheckpoint("tcp_client_connected");
// Just drain the data passed over the socket.
EXPECT_CALL(tcp_listener, OnDataAvailable(_))
.WillRepeatedly(Invoke([&](base::UnixSocket* tcp_conn) {
::testing::IgnoreResult(tcp_conn->ReceiveString());
}));
EXPECT_FALSE(relay_client->clock_synced_with_service_for_testing());
// Shutdown the connected connection. The RelayClient should notice this
// error.
tcp_client_connection->Shutdown(true);
task_runner.RunUntilCheckpoint("on_relay_client_error");
// Shutdown the server. The RelayClient should notice that the connection is
// refused.
tcp_server->Shutdown(true);
on_relay_client_error =
task_runner.CreateCheckpoint("on_relay_client_error_2");
relay_client = std::make_unique<RelayClient>(
tcp_sock_name, "fake_machine_id_hint", &task_runner,
[&]() { on_relay_client_error(); });
task_runner.RunUntilCheckpoint("on_relay_client_error_2");
}
TEST(RelayClientTest, SetPeerIdentity) {
base::TestTaskRunner task_runner;
// Set up a server UnixSocket to find an unused TCP port.
// The TCP connection emulates the socket to the host traced.
TestEventListener tcp_listener;
auto tcp_server = base::UnixSocket::Listen(
"127.0.0.1:0", &tcp_listener, &task_runner, base::SockFamily::kInet,
base::SockType::kStream);
ASSERT_TRUE(tcp_server->is_listening());
auto tcp_sock_name = tcp_server->GetSockAddr();
auto on_error_callback = [&]() { FAIL() << "Should not be called"; };
auto relay_service = std::make_unique<RelayClient>(
tcp_sock_name, "fake_machine_id_hint", &task_runner, on_error_callback);
base::UnixSocket* tcp_client_connection = nullptr;
auto tcp_client_connected =
task_runner.CreateCheckpoint("tcp_client_connected");
EXPECT_CALL(tcp_listener, OnNewIncomingConnection(_))
.WillOnce(Invoke([&](base::UnixSocket* client) {
tcp_client_connection = client;
tcp_client_connected();
}));
task_runner.RunUntilCheckpoint("tcp_client_connected");
// Asserts that we can receive the SetPeerIdentity message.
auto peer_identity_recv = task_runner.CreateCheckpoint("peer_identity_recv");
ipc::BufferedFrameDeserializer deserializer;
EXPECT_CALL(tcp_listener, OnDataAvailable(_))
.WillRepeatedly(Invoke([&](base::UnixSocket* tcp_conn) {
auto buf = deserializer.BeginReceive();
auto rsize = tcp_conn->Receive(buf.data, buf.size);
EXPECT_TRUE(deserializer.EndReceive(rsize));
auto frame = deserializer.PopNextFrame();
EXPECT_TRUE(frame->has_set_peer_identity());
const auto& set_peer_identity = frame->set_peer_identity();
EXPECT_EQ(set_peer_identity.pid(), getpid());
EXPECT_EQ(set_peer_identity.uid(), static_cast<int32_t>(geteuid()));
EXPECT_EQ(set_peer_identity.machine_id_hint(), "fake_machine_id_hint");
peer_identity_recv();
}));
task_runner.RunUntilCheckpoint("peer_identity_recv");
}
} // namespace
} // namespace perfetto
#endif