| // 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 <netinet/in.h> |
| #include <sys/socket.h> |
| |
| #include <cstddef> |
| #include <memory> |
| |
| #include "absl/status/status.h" |
| #include "absl/strings/str_format.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "gmock/gmock.h" |
| #include "google/protobuf/message_lite.h" |
| #include "gtest/gtest.h" |
| #include "secagentd/bpf/bpf_types.h" |
| #include "secagentd/bpf_skeleton_wrappers.h" |
| #include "secagentd/plugins.h" |
| #include "secagentd/proto/security_xdr_events.pb.h" |
| #include "secagentd/test/mock_batch_sender.h" |
| #include "secagentd/test/mock_bpf_skeleton.h" |
| #include "secagentd/test/mock_device_user.h" |
| #include "secagentd/test/mock_message_sender.h" |
| #include "secagentd/test/mock_policies_features_broker.h" |
| #include "secagentd/test/mock_process_cache.h" |
| #include "secagentd/test/test_utils.h" |
| |
| namespace secagentd::testing { |
| namespace pb = cros_xdr::reporting; |
| namespace { |
| struct ExpectedProcess { |
| uint64_t pid; |
| uint64_t uid; |
| std::string cmdline; |
| uint64_t rel_start_time_s; |
| }; |
| const uint64_t kDefaultPid{1452}; |
| constexpr bpf::time_ns_t kSpawnStartTime{2222}; |
| |
| const bpf::cros_process_start kDefaultProcessInfo = { |
| .task_info = {.pid = 5139, |
| .ppid = 5132, |
| .start_time = 51382, |
| .parent_start_time = 5786, |
| .uid = 382, |
| .gid = 4234}, |
| .image_info = |
| { |
| .inode = 24, |
| .mode = 123, |
| }, |
| .spawn_namespace = {.cgroup_ns = 54}}; |
| |
| const std::vector<ExpectedProcess> kDefaultProcessHierarchy{ |
| {.pid = kDefaultPid, |
| .uid = 3123, |
| .cmdline{"commandline1"}, |
| .rel_start_time_s = 144234}, |
| {.pid = 12314, |
| .uid = 14123, |
| .cmdline{"commandline2"}, |
| .rel_start_time_s = 51234}, |
| }; |
| bpf::cros_event CreateCrosFlowEvent(const bpf::cros_synthetic_network_flow& f) { |
| bpf::cros_event rv = { |
| .data.network_event{ |
| .type = bpf::cros_network_event_type::kSyntheticNetworkFlow}, |
| .type = bpf::kNetworkEvent}; |
| memmove(&rv.data.network_event.data.flow, &f, sizeof(f)); |
| return rv; |
| } |
| } // namespace |
| |
| using ::testing::_; |
| using ::testing::AnyNumber; |
| using ::testing::ByMove; |
| using ::testing::DoAll; |
| using ::testing::Eq; |
| using ::testing::Invoke; |
| using ::testing::Ref; |
| using ::testing::Return; |
| using ::testing::SaveArg; |
| using ::testing::StrictMock; |
| using ::testing::WithArg; |
| |
| constexpr char kDeviceUser[] = "deviceUser@email.com"; |
| constexpr char kSanitized[] = "943cebc444e3e19da9a2dbf9c8a473bc7cc16d9d"; |
| |
| class NetworkPluginTestFixture : public ::testing::Test { |
| protected: |
| using BatchSenderType = MockBatchSender<std::string, |
| pb::XdrNetworkEvent, |
| pb::NetworkEventAtomicVariant>; |
| |
| static constexpr uint32_t kBatchInterval = 10; |
| |
| static void SetPluginBatchSenderForTesting( |
| PluginInterface* plugin, std::unique_ptr<BatchSenderType> batch_sender) { |
| // This downcast here is very unfortunate but it avoids a lot of templating |
| // in the plugin interface and the plugin factory. The factory generally |
| // requires future cleanup to cleanly accommodate plugin specific dependency |
| // injections. |
| google::protobuf::internal::DownCast<NetworkPlugin*>(plugin) |
| ->SetBatchSenderForTesting(std::move(batch_sender)); |
| } |
| |
| void SetUp() override { |
| bpf_skeleton = std::make_unique<MockBpfSkeleton>(); |
| bpf_skeleton_ = bpf_skeleton.get(); |
| skel_factory_ = base::MakeRefCounted<MockSkeletonFactory>(); |
| message_sender_ = base::MakeRefCounted<MockMessageSender>(); |
| process_cache_ = base::MakeRefCounted<MockProcessCache>(); |
| auto batch_sender = std::make_unique<BatchSenderType>(); |
| batch_sender_ = batch_sender.get(); |
| plugin_factory_ = std::make_unique<PluginFactory>(skel_factory_); |
| device_user_ = base::MakeRefCounted<MockDeviceUser>(); |
| |
| plugin_ = plugin_factory_->Create(Types::Plugin::kNetwork, message_sender_, |
| process_cache_, policies_features_broker_, |
| device_user_, kBatchInterval); |
| EXPECT_NE(nullptr, plugin_); |
| SetPluginBatchSenderForTesting(plugin_.get(), std::move(batch_sender)); |
| |
| EXPECT_CALL(*skel_factory_, |
| Create(Types::BpfSkeleton::kNetwork, _, kBatchInterval)) |
| .WillOnce( |
| DoAll(SaveArg<1>(&cbs_), Return(ByMove(std::move(bpf_skeleton))))); |
| EXPECT_CALL(*batch_sender_, Start()); |
| EXPECT_TRUE(plugin_->Activate().ok()); |
| } |
| |
| void GenerateHierarchyFromExpectedProcesses( |
| std::vector<std::unique_ptr<pb::Process>>* hierarchy, |
| std::vector<pb::Process>* expected_hierarchy, |
| std::vector<ExpectedProcess> expected) { |
| for (const auto& p : expected) { |
| hierarchy->push_back(std::make_unique<pb::Process>()); |
| hierarchy->back()->set_canonical_pid(p.pid); |
| hierarchy->back()->set_canonical_uid(p.uid); |
| hierarchy->back()->set_commandline(p.cmdline); |
| hierarchy->back()->set_rel_start_time_s(p.rel_start_time_s); |
| expected_hierarchy->emplace_back(*hierarchy->back()); |
| } |
| } |
| |
| absl::Status fillTuple(const char local_addr[], |
| uint16_t local_port, |
| const char remote_addr[], |
| uint16_t remote_port, |
| bpf::cros_network_protocol protocol, |
| bpf::cros_synthetic_network_flow* flow) { |
| auto& tuple = flow->flow_map_key.five_tuple; |
| if (inet_pton(AF_INET, local_addr, &tuple.local_addr.addr4) == 1 && |
| inet_pton(AF_INET, remote_addr, &tuple.remote_addr.addr4) == 1) { |
| tuple.local_port = local_port; |
| tuple.remote_port = remote_port; |
| tuple.family = bpf::CROS_FAMILY_AF_INET; |
| tuple.protocol = protocol; |
| return absl::OkStatus(); |
| } else if (inet_pton(AF_INET6, local_addr, tuple.local_addr.addr6) == 1 && |
| inet_pton(AF_INET6, remote_addr, tuple.remote_addr.addr6) == 1) { |
| tuple.local_port = local_port; |
| tuple.remote_port = remote_port; |
| tuple.family = bpf::CROS_FAMILY_AF_INET6; |
| tuple.protocol = protocol; |
| return absl::OkStatus(); |
| } |
| return absl::InternalError("invalid format for ip addresses"); |
| } |
| |
| scoped_refptr<MockSkeletonFactory> skel_factory_; |
| scoped_refptr<MockMessageSender> message_sender_; |
| scoped_refptr<MockProcessCache> process_cache_; |
| scoped_refptr<MockDeviceUser> device_user_; |
| scoped_refptr<MockPoliciesFeaturesBroker> policies_features_broker_; |
| BatchSenderType* batch_sender_; |
| std::unique_ptr<PluginFactory> plugin_factory_; |
| std::unique_ptr<MockBpfSkeleton> bpf_skeleton; |
| MockBpfSkeleton* bpf_skeleton_; |
| std::unique_ptr<PluginInterface> plugin_; |
| BpfCallbacks cbs_; |
| }; |
| |
| TEST_F(NetworkPluginTestFixture, TestActivationFailureBadSkeleton) { |
| auto plugin = plugin_factory_->Create( |
| Types::Plugin::kNetwork, message_sender_, process_cache_, |
| policies_features_broker_, device_user_, kBatchInterval); |
| EXPECT_TRUE(plugin); |
| SetPluginBatchSenderForTesting(plugin.get(), |
| std::make_unique<BatchSenderType>()); |
| |
| EXPECT_CALL(*skel_factory_, |
| Create(Types::BpfSkeleton::kNetwork, _, kBatchInterval)) |
| .WillOnce(Return(ByMove(nullptr))); |
| EXPECT_FALSE(plugin->Activate().ok()); |
| } |
| |
| TEST_F(NetworkPluginTestFixture, TestGetName) { |
| ASSERT_EQ("Network", plugin_->GetName()); |
| } |
| TEST_F(NetworkPluginTestFixture, TestBPFEventIsAvailable) { |
| const bpf::cros_event socket_listen_event = { |
| .data.network_event = |
| { |
| .type = bpf::cros_network_event_type::kNetworkSocketListen, |
| .data.socket_listen = |
| { |
| /* 192.168.0.1 */ |
| .family = bpf::CROS_FAMILY_AF_INET, |
| .protocol = bpf::CROS_PROTOCOL_TCP, |
| .process_info = {.task_info{ |
| .pid = kDefaultPid, .start_time = kSpawnStartTime}}, |
| .socket_type = SOCK_STREAM, |
| .port = 1234, |
| .ipv4_addr = 0x0100A8C0, |
| }, |
| }, |
| .type = bpf::kNetworkEvent, |
| }; |
| EXPECT_CALL(*bpf_skeleton_, ConsumeEvent()).Times(1); |
| // Notify the plugin that an event is available. |
| cbs_.ring_buffer_read_ready_callback.Run(); |
| EXPECT_CALL(*message_sender_, SendMessage).Times(AnyNumber()); |
| cbs_.ring_buffer_event_callback.Run(socket_listen_event); |
| } |
| |
| TEST_F(NetworkPluginTestFixture, TestWrongBPFEvent) { |
| EXPECT_CALL(*bpf_skeleton_, ConsumeEvent()).Times(1); |
| // Notify the plugin that an event is available. |
| cbs_.ring_buffer_read_ready_callback.Run(); |
| EXPECT_CALL(*message_sender_, SendMessage).Times(0); |
| cbs_.ring_buffer_event_callback.Run( |
| bpf::cros_event{.type = bpf::kProcessEvent}); |
| } |
| |
| TEST_F(NetworkPluginTestFixture, TestSyntheticIpv4FlowEvent) { |
| bpf::cros_synthetic_network_flow flow; |
| constexpr char remote_addr[] = "168.152.10.1"; |
| constexpr char local_addr[] = "192.168.0.1"; |
| constexpr uint16_t local_port{4591}; |
| constexpr uint16_t remote_port{5231}; |
| ASSERT_EQ(fillTuple(local_addr, local_port, remote_addr, remote_port, |
| bpf::CROS_PROTOCOL_TCP, &flow), |
| absl::OkStatus()); |
| |
| auto& val = flow.flow_map_value; |
| val.direction = bpf::CROS_SOCKET_DIRECTION_OUT; |
| val.garbage_collect_me = false; |
| constexpr uint32_t rx_bytes{1456}; |
| constexpr uint32_t tx_bytes{2563}; |
| constexpr uint32_t rx_bytes2{rx_bytes + 100}; |
| constexpr uint32_t tx_bytes2{tx_bytes + 124}; |
| val.rx_bytes = rx_bytes; |
| val.tx_bytes = tx_bytes; |
| |
| flow.flow_map_value = val; |
| flow.flow_map_value.has_full_process_info = false; |
| flow.flow_map_value.process_info = kDefaultProcessInfo; |
| |
| auto flow1 = CreateCrosFlowEvent(flow); |
| std::vector<std::unique_ptr<pb::Process>> hierarchy, hierarchy2; |
| std::vector<pb::Process> expected_hierarchy; |
| |
| GenerateHierarchyFromExpectedProcesses(&hierarchy, &expected_hierarchy, |
| kDefaultProcessHierarchy); |
| GenerateHierarchyFromExpectedProcesses(&hierarchy2, &expected_hierarchy, |
| kDefaultProcessHierarchy); |
| |
| auto& process = |
| flow1.data.network_event.data.flow.flow_map_value.process_info.task_info; |
| /* Three flows will be generated but only two events are expected. |
| The second flow has the same tx/rx byte count so it should be ignored.*/ |
| EXPECT_CALL(*process_cache_, |
| GetProcessHierarchy(process.pid, process.start_time, 2)) |
| .Times(2) |
| .WillOnce(Return(ByMove(std::move(hierarchy)))) |
| .WillOnce(Return(ByMove(std::move(hierarchy2)))); |
| |
| std::unique_ptr<pb::NetworkEventAtomicVariant> actual_sent_event; |
| EXPECT_CALL(*device_user_, GetDeviceUserAsync) |
| .Times(2) |
| .WillRepeatedly(WithArg<0>(Invoke( |
| [](base::OnceCallback<void(const std::string& device_user, |
| const std::string& sanitized_uname)> cb) { |
| std::move(cb).Run(kDeviceUser, kSanitized); |
| }))); |
| |
| EXPECT_CALL(*batch_sender_, Enqueue(_)) |
| .Times(2) |
| .WillRepeatedly([&actual_sent_event]( |
| std::unique_ptr<pb::NetworkEventAtomicVariant> e) { |
| actual_sent_event = std::move(e); |
| }); |
| |
| cbs_.ring_buffer_event_callback.Run(flow1); |
| EXPECT_THAT(expected_hierarchy[0], |
| EqualsProto(actual_sent_event->network_flow().process())); |
| EXPECT_THAT(expected_hierarchy[1], |
| EqualsProto(actual_sent_event->network_flow().parent_process())); |
| EXPECT_EQ(actual_sent_event->network_flow().network_flow().local_ip(), |
| local_addr); |
| EXPECT_EQ(actual_sent_event->network_flow().network_flow().local_port(), |
| local_port); |
| EXPECT_EQ(actual_sent_event->network_flow().network_flow().remote_ip(), |
| remote_addr); |
| EXPECT_EQ(actual_sent_event->network_flow().network_flow().remote_port(), |
| remote_port); |
| EXPECT_EQ(actual_sent_event->network_flow().network_flow().protocol(), |
| pb::TCP); |
| EXPECT_EQ(actual_sent_event->network_flow().network_flow().direction(), |
| pb::NetworkFlow_Direction_OUTGOING); |
| EXPECT_EQ(actual_sent_event->network_flow().network_flow().rx_bytes(), |
| rx_bytes); |
| EXPECT_EQ(actual_sent_event->network_flow().network_flow().tx_bytes(), |
| tx_bytes); |
| EXPECT_EQ(actual_sent_event->network_flow().network_flow().community_id_v1(), |
| "1:xQuGZjr6e08tldWqhl7702m03YU="); |
| |
| // Identical event so no event should be generated. |
| cbs_.ring_buffer_event_callback.Run(flow1); |
| |
| // a bit more traffic. |
| val.rx_bytes = rx_bytes2; |
| val.tx_bytes = tx_bytes2; |
| auto flow2 = CreateCrosFlowEvent(flow); |
| cbs_.ring_buffer_event_callback.Run(flow2); |
| EXPECT_THAT(expected_hierarchy[0], |
| EqualsProto(actual_sent_event->network_flow().process())); |
| EXPECT_THAT(expected_hierarchy[1], |
| EqualsProto(actual_sent_event->network_flow().parent_process())); |
| EXPECT_EQ(actual_sent_event->network_flow().network_flow().local_ip(), |
| local_addr); |
| EXPECT_EQ(actual_sent_event->network_flow().network_flow().local_port(), |
| local_port); |
| EXPECT_EQ(actual_sent_event->network_flow().network_flow().remote_ip(), |
| remote_addr); |
| EXPECT_EQ(actual_sent_event->network_flow().network_flow().remote_port(), |
| remote_port); |
| EXPECT_EQ(actual_sent_event->network_flow().network_flow().protocol(), |
| pb::TCP); |
| EXPECT_EQ(actual_sent_event->network_flow().network_flow().direction(), |
| pb::NetworkFlow_Direction_OUTGOING); |
| EXPECT_EQ(actual_sent_event->network_flow().network_flow().rx_bytes(), |
| rx_bytes2 - rx_bytes); |
| EXPECT_EQ(actual_sent_event->network_flow().network_flow().tx_bytes(), |
| tx_bytes2 - tx_bytes); |
| EXPECT_EQ(actual_sent_event->network_flow().network_flow().community_id_v1(), |
| "1:xQuGZjr6e08tldWqhl7702m03YU="); |
| } |
| |
| TEST_F(NetworkPluginTestFixture, TestSyntheticIpv6FlowEvent) { |
| bpf::cros_synthetic_network_flow flow; |
| constexpr char remote_addr[] = "fd00::65:4cc4:d4ff:fe18:d7b9"; |
| constexpr char local_addr[] = "fd00::65:fb92:6a08:5c09:b81"; |
| constexpr uint16_t local_port{4591}; |
| constexpr uint16_t remote_port{5231}; |
| ASSERT_EQ(fillTuple(local_addr, local_port, remote_addr, remote_port, |
| bpf::CROS_PROTOCOL_TCP, &flow), |
| absl::OkStatus()); |
| |
| auto& val = flow.flow_map_value; |
| val.direction = bpf::CROS_SOCKET_DIRECTION_OUT; |
| val.garbage_collect_me = false; |
| |
| constexpr uint32_t rx_bytes{1456}; |
| constexpr uint32_t tx_bytes{2563}; |
| val.rx_bytes = rx_bytes; |
| val.tx_bytes = tx_bytes; |
| |
| flow.flow_map_value.has_full_process_info = false; |
| flow.flow_map_value.process_info = kDefaultProcessInfo; |
| |
| auto flowEvent = CreateCrosFlowEvent(flow); |
| std::vector<std::unique_ptr<pb::Process>> hierarchy; |
| std::vector<pb::Process> expected_hierarchy; |
| for (const auto& p : kDefaultProcessHierarchy) { |
| hierarchy.push_back(std::make_unique<pb::Process>()); |
| hierarchy.back()->set_canonical_pid(p.pid); |
| hierarchy.back()->set_canonical_uid(p.uid); |
| hierarchy.back()->set_commandline(p.cmdline); |
| hierarchy.back()->set_rel_start_time_s(p.rel_start_time_s); |
| expected_hierarchy.emplace_back(*hierarchy.back()); |
| } |
| auto& process = flowEvent.data.network_event.data.flow.flow_map_value |
| .process_info.task_info; |
| EXPECT_CALL(*process_cache_, |
| GetProcessHierarchy(process.pid, process.start_time, 2)) |
| .WillOnce(Return(ByMove(std::move(hierarchy)))); |
| |
| std::unique_ptr<pb::NetworkEventAtomicVariant> actual_sent_event; |
| EXPECT_CALL(*device_user_, GetDeviceUserAsync) |
| .WillOnce(WithArg<0>(Invoke( |
| [](base::OnceCallback<void(const std::string& device_user, |
| const std::string& sanitized_uname)> cb) { |
| std::move(cb).Run(kDeviceUser, kSanitized); |
| }))); |
| |
| EXPECT_CALL(*batch_sender_, Enqueue(_)) |
| .Times(1) |
| .WillOnce([&actual_sent_event]( |
| std::unique_ptr<pb::NetworkEventAtomicVariant> e) { |
| actual_sent_event = std::move(e); |
| }); |
| |
| cbs_.ring_buffer_event_callback.Run(flowEvent); |
| EXPECT_THAT(expected_hierarchy[0], |
| EqualsProto(actual_sent_event->network_flow().process())); |
| EXPECT_THAT(expected_hierarchy[1], |
| EqualsProto(actual_sent_event->network_flow().parent_process())); |
| EXPECT_EQ(actual_sent_event->network_flow().network_flow().local_ip(), |
| local_addr); |
| EXPECT_EQ(actual_sent_event->network_flow().network_flow().local_port(), |
| local_port); |
| EXPECT_EQ(actual_sent_event->network_flow().network_flow().remote_ip(), |
| remote_addr); |
| EXPECT_EQ(actual_sent_event->network_flow().network_flow().remote_port(), |
| remote_port); |
| EXPECT_EQ(actual_sent_event->network_flow().network_flow().protocol(), |
| pb::TCP); |
| EXPECT_EQ(actual_sent_event->network_flow().network_flow().direction(), |
| pb::NetworkFlow_Direction_OUTGOING); |
| EXPECT_EQ(actual_sent_event->network_flow().network_flow().rx_bytes(), |
| rx_bytes); |
| EXPECT_EQ(actual_sent_event->network_flow().network_flow().tx_bytes(), |
| tx_bytes); |
| EXPECT_EQ(actual_sent_event->network_flow().network_flow().community_id_v1(), |
| "1:Ri1ArKrJ+g/QrTLp8fPFFQd3jcw="); |
| } |
| |
| TEST_F(NetworkPluginTestFixture, TestSyntheticFlowEventWithFullProcessInfo) { |
| bpf::cros_synthetic_network_flow flow; |
| |
| constexpr char remote_addr[] = "168.152.10.1"; |
| constexpr char local_addr[] = "192.168.0.1"; |
| constexpr uint16_t local_port{4591}; |
| constexpr uint16_t remote_port{5231}; |
| ASSERT_EQ(fillTuple(local_addr, local_port, remote_addr, remote_port, |
| bpf::CROS_PROTOCOL_TCP, &flow), |
| absl::OkStatus()); |
| |
| auto& val = flow.flow_map_value; |
| val.direction = bpf::CROS_SOCKET_DIRECTION_OUT; |
| val.garbage_collect_me = false; |
| constexpr uint32_t rx_bytes{1456}; |
| constexpr uint32_t tx_bytes{2563}; |
| val.tx_bytes = tx_bytes; |
| val.rx_bytes = rx_bytes; |
| |
| flow.flow_map_value.has_full_process_info = true; |
| flow.flow_map_value.process_info = kDefaultProcessInfo; |
| |
| auto event = CreateCrosFlowEvent(flow); |
| std::vector<std::unique_ptr<pb::Process>> hierarchy; |
| hierarchy.push_back(std::make_unique<pb::Process>()); |
| hierarchy.back()->set_canonical_pid(kDefaultProcessHierarchy[1].pid); |
| hierarchy.back()->set_canonical_uid(kDefaultProcessHierarchy[1].uid); |
| hierarchy.back()->set_commandline(kDefaultProcessHierarchy[1].cmdline); |
| hierarchy.back()->set_rel_start_time_s( |
| kDefaultProcessHierarchy[1].rel_start_time_s); |
| std::vector<pb::Process> expected_hierarchy; |
| expected_hierarchy.emplace_back(*hierarchy.back()); |
| |
| auto& process = |
| event.data.network_event.data.flow.flow_map_value.process_info.task_info; |
| |
| std::list<std::string> redacted_usernames = {"username"}; |
| EXPECT_CALL(*device_user_, GetUsernamesForRedaction) |
| .WillOnce(Return(redacted_usernames)); |
| auto& process_info = flow.flow_map_value.process_info; |
| EXPECT_CALL(*process_cache_, FillProcessFromBpf(_, _, _, redacted_usernames)) |
| .WillOnce(WithArg<2>( |
| Invoke([&process_info](cros_xdr::reporting::Process* process_proto) { |
| process_proto->set_canonical_pid(process_info.task_info.pid); |
| process_proto->set_canonical_uid(process_info.task_info.uid); |
| process_proto->set_rel_start_time_s( |
| process_info.task_info.start_time); |
| process_proto->mutable_image()->set_inode( |
| process_info.image_info.inode); |
| process_proto->mutable_image()->set_mode( |
| process_info.image_info.mode); |
| process_proto->set_meta_first_appearance(true); |
| }))); |
| // Expect an attempt to use cache to retrieve parent. |
| EXPECT_CALL(*process_cache_, |
| GetProcessHierarchy(process.ppid, process.parent_start_time, 1)) |
| .WillOnce(Return(ByMove(std::move(hierarchy)))); |
| |
| EXPECT_CALL(*device_user_, GetDeviceUserAsync) |
| .WillOnce(WithArg<0>(Invoke( |
| [](base::OnceCallback<void(const std::string& device_user, |
| const std::string& sanitized_uname)> cb) { |
| std::move(cb).Run(kDeviceUser, kSanitized); |
| }))); |
| |
| pb::NetworkEventAtomicVariant actual_sent_event; |
| EXPECT_CALL(*batch_sender_, Enqueue(_)) |
| .Times(1) |
| .WillOnce([&actual_sent_event]( |
| std::unique_ptr<pb::NetworkEventAtomicVariant> e) { |
| actual_sent_event = *e; |
| }); |
| cbs_.ring_buffer_event_callback.Run(event); |
| auto& actual_process = actual_sent_event.network_flow().process(); |
| /* Expect that the process proto is filled by the info in the bpf event.*/ |
| EXPECT_THAT(actual_process.canonical_pid(), process_info.task_info.pid); |
| EXPECT_THAT(actual_process.canonical_uid(), process_info.task_info.uid); |
| EXPECT_THAT(actual_process.rel_start_time_s(), |
| process_info.task_info.start_time); |
| |
| EXPECT_THAT(actual_process.image().inode(), process_info.image_info.inode); |
| EXPECT_THAT(actual_process.image().mode(), process_info.image_info.mode); |
| EXPECT_THAT(actual_process.meta_first_appearance(), true); |
| |
| EXPECT_THAT(expected_hierarchy[0], |
| EqualsProto(actual_sent_event.network_flow().parent_process())); |
| EXPECT_EQ(actual_sent_event.network_flow().network_flow().local_ip(), |
| local_addr); |
| EXPECT_EQ(actual_sent_event.network_flow().network_flow().local_port(), |
| local_port); |
| EXPECT_EQ(actual_sent_event.network_flow().network_flow().remote_ip(), |
| remote_addr); |
| EXPECT_EQ(actual_sent_event.network_flow().network_flow().remote_port(), |
| remote_port); |
| EXPECT_EQ(actual_sent_event.network_flow().network_flow().protocol(), |
| pb::TCP); |
| EXPECT_EQ(actual_sent_event.network_flow().network_flow().direction(), |
| pb::NetworkFlow_Direction_OUTGOING); |
| EXPECT_EQ(actual_sent_event.network_flow().network_flow().rx_bytes(), |
| rx_bytes); |
| EXPECT_EQ(actual_sent_event.network_flow().network_flow().tx_bytes(), |
| tx_bytes); |
| EXPECT_EQ(actual_sent_event.network_flow().network_flow().community_id_v1(), |
| "1:xQuGZjr6e08tldWqhl7702m03YU="); |
| } |
| |
| TEST_F(NetworkPluginTestFixture, TestSSDPFiltering) { |
| // UUT should ignore SSDP broadcast traffic from patchpanel. |
| const uint32_t kPatchPanelPid{0xDEADBEEF}; |
| const uint32_t kPatchPanelPpid{0xFEED}; |
| const uint32_t kPatchPanelStartTime{123098}; |
| |
| std::vector<std::unique_ptr<pb::Process>> patch_panel_hierarchy; |
| patch_panel_hierarchy.push_back(std::make_unique<pb::Process>()); |
| patch_panel_hierarchy.back()->set_canonical_pid(kPatchPanelPid); |
| pb::FileImage patch_panel_image = pb::FileImage(); |
| patch_panel_image.set_pathname("/usr/bin/patchpaneld"); |
| *patch_panel_hierarchy.back()->mutable_image() = patch_panel_image; |
| |
| patch_panel_hierarchy.push_back(std::make_unique<pb::Process>()); |
| patch_panel_hierarchy.back()->set_canonical_pid(kPatchPanelPpid); |
| EXPECT_CALL(*process_cache_, |
| GetProcessHierarchy(kPatchPanelPid, kPatchPanelStartTime, 2)) |
| .WillOnce(Return(ByMove(std::move(patch_panel_hierarchy)))); |
| |
| EXPECT_CALL(*batch_sender_, Enqueue(_)).Times(0); |
| |
| bpf::cros_synthetic_network_flow patchpaneld_flow; |
| |
| constexpr char remote_addr[] = "10.10.10.10"; |
| constexpr char local_addr[] = "239.255.255.250"; |
| constexpr uint16_t local_port{1900}; |
| constexpr uint16_t remote_port{2500}; |
| ASSERT_EQ(fillTuple(local_addr, local_port, remote_addr, remote_port, |
| bpf::CROS_PROTOCOL_UDP, &patchpaneld_flow), |
| absl::OkStatus()); |
| auto& patchpaneld_value = patchpaneld_flow.flow_map_value; |
| constexpr uint32_t rx_bytes{24}; |
| constexpr uint32_t tx_bytes{48}; |
| patchpaneld_value.rx_bytes = rx_bytes; |
| patchpaneld_value.tx_bytes = tx_bytes; |
| |
| patchpaneld_value.direction = bpf::CROS_SOCKET_DIRECTION_OUT; |
| patchpaneld_value.process_info.task_info.pid = kPatchPanelPid; |
| patchpaneld_value.process_info.task_info.start_time = kPatchPanelStartTime; |
| patchpaneld_flow.flow_map_value.process_info.task_info.pid = kPatchPanelPid; |
| patchpaneld_flow.flow_map_value.has_full_process_info = false; |
| auto flow_event = CreateCrosFlowEvent(patchpaneld_flow); |
| cbs_.ring_buffer_event_callback.Run(flow_event); |
| } |
| |
| TEST_F(NetworkPluginTestFixture, TestAvahiScriptFiltering) { |
| // UUT should ignore spammy network traffic from avahi bash script. |
| const uint32_t kAvahiPid{0xFADE}; |
| const uint32_t kAvahiPpid{0xEDAF}; |
| const uint32_t kAvahiStartTime{9876}; |
| |
| std::vector<std::unique_ptr<pb::Process>> avahi_hierarchy; |
| avahi_hierarchy.push_back(std::make_unique<pb::Process>()); |
| avahi_hierarchy.back()->set_canonical_pid(kAvahiPid); |
| avahi_hierarchy.back()->set_commandline("avahi-daemon: running"); |
| avahi_hierarchy.push_back(std::make_unique<pb::Process>()); |
| avahi_hierarchy.back()->set_canonical_pid(kAvahiPpid); |
| EXPECT_CALL(*process_cache_, |
| GetProcessHierarchy(kAvahiPid, kAvahiStartTime, 2)) |
| .WillOnce(Return(ByMove(std::move(avahi_hierarchy)))); |
| EXPECT_CALL(*batch_sender_, Enqueue(_)).Times(0); |
| |
| bpf::cros_synthetic_network_flow avahi_flow; |
| auto& avahi_tuple = avahi_flow.flow_map_key.five_tuple; |
| auto& avahi_value = avahi_flow.flow_map_value; |
| avahi_value.rx_bytes = 24; |
| avahi_value.tx_bytes = 48; |
| inet_pton(AF_INET, "10.10.10.10", &avahi_tuple.remote_addr.addr4); |
| inet_pton(AF_INET, "239.255.255.250", &avahi_tuple.local_addr.addr4); |
| avahi_tuple.local_port = 12; |
| avahi_tuple.protocol = bpf::CROS_PROTOCOL_TCP; |
| avahi_value.process_info.task_info.pid = kAvahiPid; |
| avahi_value.process_info.task_info.start_time = kAvahiStartTime; |
| avahi_value.has_full_process_info = false; |
| auto avahi_event = CreateCrosFlowEvent(avahi_flow); |
| cbs_.ring_buffer_event_callback.Run(avahi_event); |
| } |
| |
| TEST_F(NetworkPluginTestFixture, TestNetworkPluginListenEvent) { |
| // Descending order in time starting from the youngest. |
| std::vector<std::unique_ptr<pb::Process>> hierarchy; |
| std::vector<pb::Process> expected_hierarchy; |
| for (const auto& p : kDefaultProcessHierarchy) { |
| hierarchy.push_back(std::make_unique<pb::Process>()); |
| hierarchy.back()->set_canonical_pid(p.pid); |
| hierarchy.back()->set_canonical_uid(p.uid); |
| hierarchy.back()->set_commandline(p.cmdline); |
| hierarchy.back()->set_rel_start_time_s(p.rel_start_time_s); |
| expected_hierarchy.emplace_back(*hierarchy.back()); |
| } |
| const bpf::cros_event a = { |
| .data.network_event = |
| { |
| .type = bpf::cros_network_event_type::kNetworkSocketListen, |
| .data.socket_listen = |
| { |
| /* 192.168.0.1 */ |
| .family = bpf::CROS_FAMILY_AF_INET, |
| .protocol = bpf::CROS_PROTOCOL_TCP, |
| .process_info{.task_info{.pid = kDefaultPid, |
| .start_time = kSpawnStartTime}}, |
| .socket_type = SOCK_STREAM, |
| .port = 1234, |
| .ipv4_addr = 0x0100A8C0, |
| }, |
| }, |
| .type = bpf::kNetworkEvent, |
| }; |
| const auto& socket_event = a.data.network_event.data.socket_listen; |
| EXPECT_CALL( |
| *process_cache_, |
| GetProcessHierarchy(socket_event.process_info.task_info.pid, |
| socket_event.process_info.task_info.start_time, 2)) |
| .WillOnce(Return(ByMove(std::move(hierarchy)))); |
| |
| EXPECT_CALL(*device_user_, GetDeviceUserAsync) |
| .WillOnce(WithArg<0>(Invoke( |
| [](base::OnceCallback<void(const std::string& device_user, |
| const std::string& sanitized_uname)> cb) { |
| std::move(cb).Run(kDeviceUser, kSanitized); |
| }))); |
| |
| std::unique_ptr<pb::NetworkEventAtomicVariant> actual_sent_event; |
| EXPECT_CALL(*batch_sender_, Enqueue(_)) |
| .Times(1) |
| .WillOnce([&actual_sent_event]( |
| std::unique_ptr<pb::NetworkEventAtomicVariant> e) { |
| actual_sent_event = std::move(e); |
| }); |
| |
| cbs_.ring_buffer_event_callback.Run(a); |
| EXPECT_THAT( |
| expected_hierarchy[0], |
| EqualsProto(actual_sent_event->network_socket_listen().process())); |
| EXPECT_THAT( |
| expected_hierarchy[1], |
| EqualsProto(actual_sent_event->network_socket_listen().parent_process())); |
| EXPECT_EQ(actual_sent_event->network_socket_listen().socket().bind_addr(), |
| "192.168.0.1"); |
| EXPECT_EQ(actual_sent_event->network_socket_listen().socket().bind_port(), |
| socket_event.port); |
| EXPECT_EQ(actual_sent_event->network_socket_listen().socket().protocol(), |
| pb::NetworkProtocol::TCP); |
| } |
| using IPv6TestParam = std::pair<std::array<uint8_t, 16>, std::string>; |
| class IPv6VariationsTestFixture |
| : public NetworkPluginTestFixture, |
| public ::testing::WithParamInterface<IPv6TestParam> {}; |
| |
| /* Make sure that the compressed formatting of IPv6 is correct.*/ |
| INSTANTIATE_TEST_SUITE_P( |
| TestIPv6AddressFormatting, |
| IPv6VariationsTestFixture, |
| ::testing::Values( |
| IPv6TestParam{{0xb4, 0x75, 0x34, 0x24, 0xde, 0x03, 0xa0, 0x90, 0xa0, |
| 0x86, 0xb5, 0xff, 0x3c, 0x12, 0xb4, 0x56}, |
| "b475:3424:de03:a090:a086:b5ff:3c12:b456"}, |
| /* 0: Test correct IPv6 compression of stripping leading zeroes.*/ |
| IPv6TestParam{{0xb4, 0x75, 00, 0x24, 0xde, 0x03, 0xa0, 0x90, 0xa0, 0x86, |
| 0x0, 0xff, 0x3c, 0x12, 0xb4, 0x56}, |
| "b475:24:de03:a090:a086:ff:3c12:b456"}, |
| /* 1: Test that a single group of 0's is not fully compressed. */ |
| IPv6TestParam{{0xb4, 0x75, 0x34, 0x24, 0x0, 0x0, 0xa0, 0x90, 0xa0, 0x86, |
| 0xb5, 0xff, 0x3c, 0x12, 0xb4, 0x56}, |
| "b475:3424:0:a090:a086:b5ff:3c12:b456"}, |
| /* 2: Test that multiple groups of 0s are compressed into :: */ |
| IPv6TestParam{{0xb4, 0x75, 0x34, 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, |
| 0xb5, 0xff, 0x3c, 0x12, 0xb4, 0x56}, |
| "b475:3424::b5ff:3c12:b456"}, |
| /* 3:Test that only the left most groups of 0's are compressed into ::*/ |
| IPv6TestParam{{0xb4, 0x75, 0x34, 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, |
| 0xb5, 0xff, 0x0, 0x0, 0x0, 0x0}, |
| "b475:3424::b5ff:0:0"}), |
| [](::testing::TestParamInfo<IPv6TestParam> p) -> std::string { |
| switch (p.index) { |
| case 0: |
| return "StripLeadingZeroes"; |
| case 1: |
| return "Single0GroupNotCompressed"; |
| case 2: |
| return "Multiple0GroupsCompressed"; |
| case 3: |
| return "LeftMost0GroupsCompressed"; |
| default: |
| return absl::StrFormat("MysteryTestCase%d", p.index); |
| } |
| }); |
| |
| TEST_P(IPv6VariationsTestFixture, TestSocketListenIPv6) { |
| constexpr bpf::time_ns_t kSpawnStartTime = 2222; |
| // Descending order in time starting from the youngest. |
| std::vector<std::unique_ptr<pb::Process>> hierarchy; |
| std::vector<pb::Process> expected_hierarchy; |
| for (const auto& p : kDefaultProcessHierarchy) { |
| hierarchy.push_back(std::make_unique<pb::Process>()); |
| hierarchy.back()->set_canonical_pid(p.pid); |
| hierarchy.back()->set_canonical_uid(p.uid); |
| hierarchy.back()->set_commandline(p.cmdline); |
| hierarchy.back()->set_rel_start_time_s(p.rel_start_time_s); |
| expected_hierarchy.emplace_back(*hierarchy.back()); |
| } |
| bpf::cros_event a = { |
| .data.network_event = |
| { |
| .type = bpf::cros_network_event_type::kNetworkSocketListen, |
| .data.socket_listen = |
| {/* 192.168.0.1 */ |
| .family = bpf::CROS_FAMILY_AF_INET6, |
| .protocol = bpf::CROS_PROTOCOL_TCP, |
| .process_info{.task_info{.pid = kDefaultPid, |
| .start_time = kSpawnStartTime}}, |
| .socket_type = 0, |
| .port = 1234}, |
| }, |
| .type = bpf::kNetworkEvent, |
| }; |
| auto& ipv6_field = a.data.network_event.data.socket_listen.ipv6_addr; |
| memmove(ipv6_field, GetParam().first.data(), sizeof(ipv6_field)); |
| auto expected_ipaddr = GetParam().second; |
| const auto& socket_event = a.data.network_event.data.socket_listen; |
| EXPECT_CALL( |
| *process_cache_, |
| GetProcessHierarchy(socket_event.process_info.task_info.pid, |
| socket_event.process_info.task_info.start_time, 2)) |
| .WillOnce(Return(ByMove(std::move(hierarchy)))); |
| |
| EXPECT_CALL(*device_user_, GetDeviceUserAsync) |
| .WillOnce(WithArg<0>(Invoke( |
| [](base::OnceCallback<void(const std::string& device_user, |
| const std::string& sanitized_uname)> cb) { |
| std::move(cb).Run(kDeviceUser, kSanitized); |
| }))); |
| |
| std::unique_ptr<pb::NetworkEventAtomicVariant> actual_sent_event; |
| EXPECT_CALL(*batch_sender_, Enqueue(_)) |
| .Times(1) |
| .WillOnce([&actual_sent_event]( |
| std::unique_ptr<pb::NetworkEventAtomicVariant> e) { |
| actual_sent_event = std::move(e); |
| }); |
| |
| cbs_.ring_buffer_event_callback.Run(a); |
| EXPECT_EQ(actual_sent_event->network_socket_listen().socket().bind_addr(), |
| expected_ipaddr); |
| } |
| |
| class ProtocolVariationsTestFixture |
| : public NetworkPluginTestFixture, |
| public ::testing::WithParamInterface<bpf::cros_network_protocol> {}; |
| using SocketTypeTestParam = std::pair<int, pb::SocketType>; |
| class SocketTypeVariationsTestFixture |
| : public NetworkPluginTestFixture, |
| public ::testing::WithParamInterface<SocketTypeTestParam> {}; |
| |
| /* Test all possible network protocols. */ |
| INSTANTIATE_TEST_SUITE_P( |
| TestDifferentProtocols, |
| ProtocolVariationsTestFixture, |
| ::testing::Values(bpf::cros_network_protocol::CROS_PROTOCOL_ICMP, |
| bpf::cros_network_protocol::CROS_PROTOCOL_RAW, |
| bpf::cros_network_protocol::CROS_PROTOCOL_TCP, |
| bpf::cros_network_protocol::CROS_PROTOCOL_UDP, |
| bpf::cros_network_protocol::CROS_PROTOCOL_UNKNOWN), |
| [](::testing::TestParamInfo<bpf::cros_network_protocol> p) -> std::string { |
| switch (p.param) { |
| case bpf::cros_network_protocol::CROS_PROTOCOL_ICMP: |
| case bpf::cros_network_protocol::CROS_PROTOCOL_ICMP6: |
| return "ICMP"; |
| case bpf::cros_network_protocol::CROS_PROTOCOL_RAW: |
| return "RAW"; |
| case bpf::cros_network_protocol::CROS_PROTOCOL_TCP: |
| return "TCP"; |
| case bpf::cros_network_protocol::CROS_PROTOCOL_UDP: |
| return "UDP"; |
| case bpf::cros_network_protocol::CROS_PROTOCOL_UNKNOWN: |
| return "UnknownProtocol"; |
| } |
| }); |
| |
| TEST_P(ProtocolVariationsTestFixture, TestSocketListenProtocols) { |
| constexpr bpf::time_ns_t kSpawnStartTime = 2222; |
| // Descending order in time starting from the youngest. |
| std::vector<std::unique_ptr<pb::Process>> hierarchy; |
| std::vector<pb::Process> expected_hierarchy; |
| for (const auto& p : kDefaultProcessHierarchy) { |
| hierarchy.push_back(std::make_unique<pb::Process>()); |
| hierarchy.back()->set_canonical_pid(p.pid); |
| hierarchy.back()->set_canonical_uid(p.uid); |
| hierarchy.back()->set_commandline(p.cmdline); |
| hierarchy.back()->set_rel_start_time_s(p.rel_start_time_s); |
| expected_hierarchy.emplace_back(*hierarchy.back()); |
| } |
| bpf::cros_event a = { |
| .data.network_event = |
| { |
| .type = bpf::cros_network_event_type::kNetworkSocketListen, |
| .data.socket_listen = |
| {.family = bpf::CROS_FAMILY_AF_INET, |
| .protocol = bpf::CROS_PROTOCOL_TCP, |
| .process_info{.task_info = {.pid = kDefaultPid, |
| .start_time = kSpawnStartTime}}, |
| .socket_type = SOCK_STREAM, |
| .port = 1234, |
| .ipv4_addr = 0x1020304}, |
| }, |
| .type = bpf::kNetworkEvent, |
| }; |
| a.data.network_event.data.socket_listen.protocol = GetParam(); |
| pb::NetworkProtocol expected_protocol; |
| switch (a.data.network_event.data.socket_listen.protocol) { |
| case bpf::cros_network_protocol::CROS_PROTOCOL_ICMP: |
| case bpf::cros_network_protocol::CROS_PROTOCOL_ICMP6: |
| expected_protocol = pb::NetworkProtocol::ICMP; |
| break; |
| case bpf::cros_network_protocol::CROS_PROTOCOL_RAW: |
| expected_protocol = pb::NetworkProtocol::RAW; |
| break; |
| case bpf::cros_network_protocol::CROS_PROTOCOL_TCP: |
| expected_protocol = pb::NetworkProtocol::TCP; |
| break; |
| case bpf::cros_network_protocol::CROS_PROTOCOL_UDP: |
| expected_protocol = pb::NetworkProtocol::UDP; |
| break; |
| case bpf::cros_network_protocol::CROS_PROTOCOL_UNKNOWN: |
| expected_protocol = pb::NetworkProtocol::NETWORK_PROTOCOL_UNKNOWN; |
| break; |
| } |
| const auto& socket_event = a.data.network_event.data.socket_listen; |
| EXPECT_CALL( |
| *process_cache_, |
| GetProcessHierarchy(socket_event.process_info.task_info.pid, |
| socket_event.process_info.task_info.start_time, 2)) |
| .WillOnce(Return(ByMove(std::move(hierarchy)))); |
| |
| EXPECT_CALL(*device_user_, GetDeviceUserAsync) |
| .WillOnce(WithArg<0>(Invoke( |
| [](base::OnceCallback<void(const std::string& device_user, |
| const std::string& sanitized_uname)> cb) { |
| std::move(cb).Run(kDeviceUser, kSanitized); |
| }))); |
| |
| std::unique_ptr<pb::NetworkEventAtomicVariant> actual_sent_event; |
| EXPECT_CALL(*batch_sender_, Enqueue(_)) |
| .Times(1) |
| .WillOnce([&actual_sent_event]( |
| std::unique_ptr<pb::NetworkEventAtomicVariant> e) { |
| actual_sent_event = std::move(e); |
| }); |
| |
| cbs_.ring_buffer_event_callback.Run(a); |
| EXPECT_EQ(actual_sent_event->network_socket_listen().socket().protocol(), |
| expected_protocol); |
| } |
| |
| /* Test all possible socket types. */ |
| INSTANTIATE_TEST_SUITE_P( |
| TestDifferentSocketTypes, |
| SocketTypeVariationsTestFixture, |
| ::testing::Values( |
| SocketTypeTestParam{__socket_type::SOCK_STREAM, |
| pb::SocketType::SOCK_STREAM}, |
| SocketTypeTestParam{__socket_type::SOCK_DGRAM, |
| pb::SocketType::SOCK_DGRAM}, |
| SocketTypeTestParam{__socket_type::SOCK_RAW, pb::SocketType::SOCK_RAW}, |
| SocketTypeTestParam{__socket_type::SOCK_RDM, pb::SocketType::SOCK_RDM}, |
| SocketTypeTestParam{__socket_type::SOCK_PACKET, |
| pb::SocketType::SOCK_PACKET}, |
| SocketTypeTestParam{__socket_type::SOCK_SEQPACKET, |
| pb::SocketType::SOCK_SEQPACKET}), |
| [](::testing::TestParamInfo<SocketTypeTestParam> p) -> std::string { |
| switch (p.param.first) { |
| case __socket_type::SOCK_STREAM: |
| return "STREAM"; |
| case __socket_type::SOCK_RAW: |
| return "RAW"; |
| case __socket_type::SOCK_DGRAM: |
| return "DATAGRAM"; |
| case __socket_type::SOCK_RDM: |
| return "RDM"; |
| case __socket_type::SOCK_PACKET: |
| return "PACKET"; |
| case __socket_type::SOCK_SEQPACKET: |
| return "SEQPACKET"; |
| default: |
| return "UNKNOWN"; |
| } |
| }); |
| |
| TEST_P(SocketTypeVariationsTestFixture, TestSocketListenSocketTypes) { |
| constexpr bpf::time_ns_t kSpawnStartTime = 2222; |
| // Descending order in time starting from the youngest. |
| std::vector<std::unique_ptr<pb::Process>> hierarchy; |
| std::vector<pb::Process> expected_hierarchy; |
| for (const auto& p : kDefaultProcessHierarchy) { |
| hierarchy.push_back(std::make_unique<pb::Process>()); |
| hierarchy.back()->set_canonical_pid(p.pid); |
| hierarchy.back()->set_canonical_uid(p.uid); |
| hierarchy.back()->set_commandline(p.cmdline); |
| hierarchy.back()->set_rel_start_time_s(p.rel_start_time_s); |
| expected_hierarchy.emplace_back(*hierarchy.back()); |
| } |
| bpf::cros_event a = { |
| .data.network_event = |
| { |
| .type = bpf::cros_network_event_type::kNetworkSocketListen, |
| .data.socket_listen = {.family = bpf::CROS_FAMILY_AF_INET, |
| .protocol = bpf::CROS_PROTOCOL_TCP, |
| .process_info{.task_info{ |
| .pid = kDefaultPid, |
| .start_time = kSpawnStartTime}}, |
| .socket_type = SOCK_STREAM, |
| .port = 1234, |
| .ipv4_addr = 0x1020304}, |
| }, |
| .type = bpf::kNetworkEvent, |
| }; |
| a.data.network_event.data.socket_listen.socket_type = GetParam().first; |
| auto expected_socket_type = GetParam().second; |
| const auto& socket_event = a.data.network_event.data.socket_listen; |
| EXPECT_CALL( |
| *process_cache_, |
| GetProcessHierarchy(socket_event.process_info.task_info.pid, |
| socket_event.process_info.task_info.start_time, 2)) |
| .WillOnce(Return(ByMove(std::move(hierarchy)))); |
| |
| EXPECT_CALL(*device_user_, GetDeviceUserAsync) |
| .WillOnce(WithArg<0>(Invoke( |
| [](base::OnceCallback<void(const std::string& device_user, |
| const std::string& sanitized_uname)> cb) { |
| std::move(cb).Run(kDeviceUser, kSanitized); |
| }))); |
| |
| std::unique_ptr<pb::NetworkEventAtomicVariant> actual_sent_event; |
| EXPECT_CALL(*batch_sender_, Enqueue(_)) |
| .Times(1) |
| .WillOnce([&actual_sent_event]( |
| std::unique_ptr<pb::NetworkEventAtomicVariant> e) { |
| actual_sent_event = std::move(e); |
| }); |
| |
| cbs_.ring_buffer_event_callback.Run(a); |
| EXPECT_EQ(actual_sent_event->network_socket_listen().socket().socket_type(), |
| expected_socket_type); |
| } |
| |
| struct CommunityHashTestParam { |
| std::string source_address; |
| std::string dest_address; |
| uint16_t source_port; |
| uint16_t dest_port; |
| bpf::cros_network_protocol protocol; |
| std::string expected; |
| }; |
| class CommunityHashingTestFixture |
| : public ::testing::Test, |
| public ::testing::WithParamInterface<CommunityHashTestParam> { |
| public: |
| absl::StatusOr<std::array<uint8_t, 16>> TryIPv6StringToNBOBuffer( |
| std::string_view in) { |
| struct in6_addr addr; |
| if (inet_pton(AF_INET6, in.data(), &addr) != 1) { |
| return absl::InvalidArgumentError( |
| absl::StrFormat("%s is not a valid ipv6 address.", in)); |
| } |
| std::array<uint8_t, sizeof(addr.__in6_u.__u6_addr8)> rv; |
| memmove(rv.data(), &addr.__in6_u.__u6_addr8[0], rv.size()); |
| return rv; |
| } |
| |
| absl::StatusOr<std::array<uint8_t, 4>> TryIPv4StringToNBOBuffer( |
| std::string_view in) { |
| struct in_addr addr; |
| if (inet_pton(AF_INET, in.data(), &addr) != 1) { |
| return absl::InvalidArgumentError( |
| absl::StrFormat("%s is not a valid ipv4 address.", in)); |
| } |
| std::array<uint8_t, 4> rv; |
| memmove(rv.data(), &addr.s_addr, rv.size()); |
| return rv; |
| } |
| }; |
| INSTANTIATE_TEST_SUITE_P( |
| CommunityIDHashing, |
| CommunityHashingTestFixture, |
| ::testing::Values( |
| // Same ip addr but different port. |
| CommunityHashTestParam{ |
| // idx 0. |
| .source_address = "b475:3424:de03:a090:a086:b5ff:3c12:b456", |
| .dest_address = "b475:3424:de03:a090:a086:b5ff:3c12:b456", |
| .source_port = 456, |
| .dest_port = 457, |
| .protocol = bpf::CROS_PROTOCOL_TCP, |
| .expected = "1:9nlcNcNqbWThbbrqcZ653+nS/Ig="}, |
| |
| // Same port but source address has a smaller IP address. |
| CommunityHashTestParam{ |
| // idx 1. |
| .source_address = "b475:3424:de03:a090:a086:b5ff:3c12:b453", |
| .dest_address = "b475:3424:de03:a090:a086:b5ff:3c12:b456", |
| .source_port = 457, |
| .dest_port = 457, |
| .protocol = bpf::CROS_PROTOCOL_UDP, |
| .expected = "1:0bk6xBJMSDtsXhLKWuSD1waPfOg="}, |
| // Same port but dest address has a smaller IP address. |
| CommunityHashTestParam{ |
| // idx 2. |
| .source_address = "b475:3424:de03:a090:a086:b5ff:3c12:b456", |
| .dest_address = "b475:3424:de03:a090:a086:b5ff:3c12:b453", |
| .source_port = 457, |
| .dest_port = 457, |
| .protocol = bpf::CROS_PROTOCOL_UDP, |
| .expected = "1:0bk6xBJMSDtsXhLKWuSD1waPfOg="}, |
| // Same ip addr but different port. |
| CommunityHashTestParam{// idx 3. |
| .source_address = "192.168.0.1", |
| .dest_address = "192.168.0.1", |
| .source_port = 456, |
| .dest_port = 457, |
| .protocol = bpf::CROS_PROTOCOL_TCP, |
| .expected = "1:wtrJ3294c/p34IEHKppjTVgTvmY="}, |
| // Same port but source address has a smaller IP address. |
| CommunityHashTestParam{// idx 4. |
| .source_address = "192.168.0.0", |
| .dest_address = "192.168.0.1", |
| .source_port = 457, |
| .dest_port = 457, |
| .protocol = bpf::CROS_PROTOCOL_TCP, |
| .expected = "1:fxjiNC2ogHm2gNZIiJssJkyUiGE="}, |
| // Same port but dest address has a smaller IP address. |
| CommunityHashTestParam{// idx 5. |
| .source_address = "192.168.0.1", |
| .dest_address = "192.168.0.0", |
| .source_port = 457, |
| .dest_port = 457, |
| .protocol = bpf::CROS_PROTOCOL_TCP, |
| .expected = "1:fxjiNC2ogHm2gNZIiJssJkyUiGE="}), |
| [](::testing::TestParamInfo<CommunityHashTestParam> p) -> std::string { |
| switch (p.index) { |
| case 0: |
| return "IPv6SameAddrDifferentPorts"; |
| case 1: |
| return "IPv6SourceAddressSmaller"; |
| case 2: |
| return "IPv6DestAddrSmaller"; |
| case 3: |
| return "IPv4SameAddrDifferentPorts"; |
| case 4: |
| return "IPv4SourceAddressSmaller"; |
| case 5: |
| return "IPv4DestAddrSmaller"; |
| default: |
| return absl::StrFormat("MysteryTestCase%d", p.index); |
| } |
| }); |
| |
| TEST_P(CommunityHashingTestFixture, CommunityFlowIDHash) { |
| auto i = GetParam(); |
| auto ipv4_source = TryIPv4StringToNBOBuffer(i.source_address); |
| auto ipv4_dest = TryIPv4StringToNBOBuffer(i.dest_address); |
| auto ipv6_source = TryIPv6StringToNBOBuffer(i.source_address); |
| auto ipv6_dest = TryIPv6StringToNBOBuffer(i.dest_address); |
| absl::Span<const uint8_t> source, dest; |
| if (ipv4_source.ok()) { |
| source = absl::MakeSpan(ipv4_source.value()); |
| } else if (ipv6_source.ok()) { |
| source = absl::MakeSpan(ipv6_source.value()); |
| } |
| |
| if (ipv4_dest.ok()) { |
| dest = absl::MakeSpan(ipv4_dest.value()); |
| } else if (ipv6_dest.ok()) { |
| dest = absl::MakeSpan(ipv6_dest.value()); |
| } |
| auto result = NetworkPlugin::ComputeCommunityHashv1( |
| source, dest, i.source_port, i.dest_port, i.protocol); |
| EXPECT_EQ(result, i.expected); |
| } |
| } // namespace secagentd::testing |