/* * 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 #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 new_connection) override { // Need to keep |new_connection| alive. client_connection_ = std::move(new_connection); OnNewIncomingConnection(client_connection_.get()); } private: std::unique_ptr 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(&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(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(&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(&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( 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( 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( 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(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