• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <gmock/gmock.h>
18 #include <gtest/gtest.h>
19 
20 #include <vector>
21 
22 #include "netdbpf/NetworkTraceHandler.h"
23 #include "protos/perfetto/config/android/network_trace_config.gen.h"
24 #include "protos/perfetto/trace/android/network_trace.pb.h"
25 #include "protos/perfetto/trace/trace.pb.h"
26 #include "protos/perfetto/trace/trace_packet.pb.h"
27 
28 namespace android {
29 namespace bpf {
30 using ::perfetto::protos::NetworkPacketEvent;
31 using ::perfetto::protos::NetworkPacketTraceConfig;
32 using ::perfetto::protos::Trace;
33 using ::perfetto::protos::TracePacket;
34 using ::perfetto::protos::TrafficDirection;
35 
36 class NetworkTraceHandlerTest : public testing::Test {
37  protected:
38   // Starts a tracing session with the handler under test.
StartTracing(NetworkPacketTraceConfig settings)39   std::unique_ptr<perfetto::TracingSession> StartTracing(
40       NetworkPacketTraceConfig settings) {
41     perfetto::TracingInitArgs args;
42     args.backends = perfetto::kInProcessBackend;
43     perfetto::Tracing::Initialize(args);
44 
45     perfetto::DataSourceDescriptor dsd;
46     dsd.set_name("test.network_packets");
47     NetworkTraceHandler::Register(dsd, /*isTest=*/true);
48 
49     perfetto::TraceConfig cfg;
50     cfg.add_buffers()->set_size_kb(1024);
51     auto* config = cfg.add_data_sources()->mutable_config();
52     config->set_name("test.network_packets");
53     config->set_network_packet_trace_config_raw(settings.SerializeAsString());
54 
55     auto session = perfetto::Tracing::NewTrace(perfetto::kInProcessBackend);
56     session->Setup(cfg);
57     session->StartBlocking();
58     return session;
59   }
60 
61   // Stops the trace session and reports all relevant trace packets.
StopTracing(perfetto::TracingSession * session,std::vector<TracePacket> * output)62   bool StopTracing(perfetto::TracingSession* session,
63                    std::vector<TracePacket>* output) {
64     session->StopBlocking();
65 
66     Trace trace;
67     std::vector<char> raw_trace = session->ReadTraceBlocking();
68     if (!trace.ParseFromArray(raw_trace.data(), raw_trace.size())) {
69       ADD_FAILURE() << "trace.ParseFromArray failed";
70       return false;
71     }
72 
73     // This is a real trace and includes irrelevant trace packets such as trace
74     // metadata. The following strips the results to just the packets we want.
75     for (const auto& pkt : trace.packet()) {
76       if (pkt.has_network_packet() || pkt.has_network_packet_bundle()) {
77         output->emplace_back(pkt);
78       }
79     }
80 
81     return true;
82   }
83 
84   // This runs a trace with a single call to Write.
TraceAndSortPackets(const std::vector<PacketTrace> & input,std::vector<TracePacket> * output,NetworkPacketTraceConfig config={})85   bool TraceAndSortPackets(const std::vector<PacketTrace>& input,
86                            std::vector<TracePacket>* output,
87                            NetworkPacketTraceConfig config = {}) {
88     auto session = StartTracing(config);
__anon1680891f0102(NetworkTraceHandler::TraceContext ctx) 89     NetworkTraceHandler::Trace([&](NetworkTraceHandler::TraceContext ctx) {
90       ctx.GetDataSourceLocked()->Write(input, ctx);
91       ctx.Flush();
92     });
93 
94     if (!StopTracing(session.get(), output)) {
95       return false;
96     }
97 
98     // Sort to provide deterministic ordering regardless of Perfetto internals
99     // or implementation-defined (e.g. hash map) reshuffling.
100     std::sort(output->begin(), output->end(),
__anon1680891f0202(const TracePacket& a, const TracePacket& b) 101               [](const TracePacket& a, const TracePacket& b) {
102                 return a.timestamp() < b.timestamp();
103               });
104 
105     return true;
106   }
107 };
108 
TEST_F(NetworkTraceHandlerTest,WriteBasicFields)109 TEST_F(NetworkTraceHandlerTest, WriteBasicFields) {
110   std::vector<PacketTrace> input = {
111       PacketTrace{
112           .timestampNs = 1000,
113           .length = 100,
114           .uid = 10,
115           .tag = 123,
116           .ipProto = 6,
117           .tcpFlags = 1,
118       },
119   };
120 
121   std::vector<TracePacket> events;
122   ASSERT_TRUE(TraceAndSortPackets(input, &events));
123 
124   ASSERT_EQ(events.size(), 1);
125   EXPECT_THAT(events[0].timestamp(), 1000);
126   EXPECT_THAT(events[0].network_packet().uid(), 10);
127   EXPECT_THAT(events[0].network_packet().tag(), 123);
128   EXPECT_THAT(events[0].network_packet().ip_proto(), 6);
129   EXPECT_THAT(events[0].network_packet().tcp_flags(), 1);
130   EXPECT_THAT(events[0].network_packet().length(), 100);
131   EXPECT_THAT(events[0].has_sequence_flags(), false);
132 }
133 
TEST_F(NetworkTraceHandlerTest,WriteDirectionAndPorts)134 TEST_F(NetworkTraceHandlerTest, WriteDirectionAndPorts) {
135   std::vector<PacketTrace> input = {
136       PacketTrace{
137           .timestampNs = 1,
138           .sport = htons(8080),
139           .dport = htons(443),
140           .egress = true,
141       },
142       PacketTrace{
143           .timestampNs = 2,
144           .sport = htons(443),
145           .dport = htons(8080),
146           .egress = false,
147       },
148   };
149 
150   std::vector<TracePacket> events;
151   ASSERT_TRUE(TraceAndSortPackets(input, &events));
152 
153   ASSERT_EQ(events.size(), 2);
154   EXPECT_THAT(events[0].network_packet().local_port(), 8080);
155   EXPECT_THAT(events[0].network_packet().remote_port(), 443);
156   EXPECT_THAT(events[0].network_packet().direction(),
157               TrafficDirection::DIR_EGRESS);
158   EXPECT_THAT(events[1].network_packet().local_port(), 8080);
159   EXPECT_THAT(events[1].network_packet().remote_port(), 443);
160   EXPECT_THAT(events[1].network_packet().direction(),
161               TrafficDirection::DIR_INGRESS);
162 }
163 
TEST_F(NetworkTraceHandlerTest,BasicBundling)164 TEST_F(NetworkTraceHandlerTest, BasicBundling) {
165   // TODO: remove this once bundling becomes default. Until then, set arbitrary
166   // aggregation threshold to enable bundling.
167   NetworkPacketTraceConfig config;
168   config.set_aggregation_threshold(10);
169 
170   std::vector<PacketTrace> input = {
171       PacketTrace{.uid = 123, .timestampNs = 2, .length = 200},
172       PacketTrace{.uid = 123, .timestampNs = 1, .length = 100},
173       PacketTrace{.uid = 123, .timestampNs = 4, .length = 300},
174 
175       PacketTrace{.uid = 456, .timestampNs = 2, .length = 400},
176       PacketTrace{.uid = 456, .timestampNs = 4, .length = 100},
177   };
178 
179   std::vector<TracePacket> events;
180   ASSERT_TRUE(TraceAndSortPackets(input, &events, config));
181 
182   ASSERT_EQ(events.size(), 2);
183 
184   EXPECT_THAT(events[0].timestamp(), 1);
185   EXPECT_THAT(events[0].network_packet_bundle().ctx().uid(), 123);
186   EXPECT_THAT(events[0].network_packet_bundle().packet_lengths(),
187               testing::ElementsAre(200, 100, 300));
188   EXPECT_THAT(events[0].network_packet_bundle().packet_timestamps(),
189               testing::ElementsAre(1, 0, 3));
190 
191   EXPECT_THAT(events[1].timestamp(), 2);
192   EXPECT_THAT(events[1].network_packet_bundle().ctx().uid(), 456);
193   EXPECT_THAT(events[1].network_packet_bundle().packet_lengths(),
194               testing::ElementsAre(400, 100));
195   EXPECT_THAT(events[1].network_packet_bundle().packet_timestamps(),
196               testing::ElementsAre(0, 2));
197 }
198 
TEST_F(NetworkTraceHandlerTest,AggregationThreshold)199 TEST_F(NetworkTraceHandlerTest, AggregationThreshold) {
200   // With an aggregation threshold of 3, the set of packets with uid=123 will
201   // be aggregated (3>=3) whereas packets with uid=456 get per-packet info.
202   NetworkPacketTraceConfig config;
203   config.set_aggregation_threshold(3);
204 
205   std::vector<PacketTrace> input = {
206       PacketTrace{.uid = 123, .timestampNs = 2, .length = 200},
207       PacketTrace{.uid = 123, .timestampNs = 1, .length = 100},
208       PacketTrace{.uid = 123, .timestampNs = 4, .length = 300},
209 
210       PacketTrace{.uid = 456, .timestampNs = 2, .length = 400},
211       PacketTrace{.uid = 456, .timestampNs = 4, .length = 100},
212   };
213 
214   std::vector<TracePacket> events;
215   ASSERT_TRUE(TraceAndSortPackets(input, &events, config));
216 
217   ASSERT_EQ(events.size(), 2);
218 
219   EXPECT_EQ(events[0].timestamp(), 1);
220   EXPECT_EQ(events[0].network_packet_bundle().ctx().uid(), 123);
221   EXPECT_EQ(events[0].network_packet_bundle().total_duration(), 3);
222   EXPECT_EQ(events[0].network_packet_bundle().total_packets(), 3);
223   EXPECT_EQ(events[0].network_packet_bundle().total_length(), 600);
224 
225   EXPECT_EQ(events[1].timestamp(), 2);
226   EXPECT_EQ(events[1].network_packet_bundle().ctx().uid(), 456);
227   EXPECT_THAT(events[1].network_packet_bundle().packet_lengths(),
228               testing::ElementsAre(400, 100));
229   EXPECT_THAT(events[1].network_packet_bundle().packet_timestamps(),
230               testing::ElementsAre(0, 2));
231 }
232 
TEST_F(NetworkTraceHandlerTest,DropLocalPort)233 TEST_F(NetworkTraceHandlerTest, DropLocalPort) {
234   NetworkPacketTraceConfig config;
235   config.set_drop_local_port(true);
236   config.set_aggregation_threshold(10);
237 
238   __be16 a = htons(10000);
239   __be16 b = htons(10001);
240   std::vector<PacketTrace> input = {
241       // Recall that local is `src` for egress and `dst` for ingress.
242       PacketTrace{.timestampNs = 1, .length = 2, .egress = true, .sport = a},
243       PacketTrace{.timestampNs = 2, .length = 4, .egress = false, .dport = a},
244       PacketTrace{.timestampNs = 3, .length = 6, .egress = true, .sport = b},
245       PacketTrace{.timestampNs = 4, .length = 8, .egress = false, .dport = b},
246   };
247 
248   std::vector<TracePacket> events;
249   ASSERT_TRUE(TraceAndSortPackets(input, &events, config));
250   ASSERT_EQ(events.size(), 2);
251 
252   // Despite having different local ports, drop and bundle by remaining fields.
253   EXPECT_EQ(events[0].network_packet_bundle().ctx().direction(),
254             TrafficDirection::DIR_EGRESS);
255   EXPECT_THAT(events[0].network_packet_bundle().packet_lengths(),
256               testing::ElementsAre(2, 6));
257 
258   EXPECT_EQ(events[1].network_packet_bundle().ctx().direction(),
259             TrafficDirection::DIR_INGRESS);
260   EXPECT_THAT(events[1].network_packet_bundle().packet_lengths(),
261               testing::ElementsAre(4, 8));
262 
263   // Local port shouldn't be in output.
264   EXPECT_FALSE(events[0].network_packet_bundle().ctx().has_local_port());
265   EXPECT_FALSE(events[1].network_packet_bundle().ctx().has_local_port());
266 }
267 
TEST_F(NetworkTraceHandlerTest,DropRemotePort)268 TEST_F(NetworkTraceHandlerTest, DropRemotePort) {
269   NetworkPacketTraceConfig config;
270   config.set_drop_remote_port(true);
271   config.set_aggregation_threshold(10);
272 
273   __be16 a = htons(443);
274   __be16 b = htons(80);
275   std::vector<PacketTrace> input = {
276       // Recall that remote is `dst` for egress and `src` for ingress.
277       PacketTrace{.timestampNs = 1, .length = 2, .egress = true, .dport = a},
278       PacketTrace{.timestampNs = 2, .length = 4, .egress = false, .sport = a},
279       PacketTrace{.timestampNs = 3, .length = 6, .egress = true, .dport = b},
280       PacketTrace{.timestampNs = 4, .length = 8, .egress = false, .sport = b},
281   };
282 
283   std::vector<TracePacket> events;
284   ASSERT_TRUE(TraceAndSortPackets(input, &events, config));
285   ASSERT_EQ(events.size(), 2);
286 
287   // Despite having different remote ports, drop and bundle by remaining fields.
288   EXPECT_EQ(events[0].network_packet_bundle().ctx().direction(),
289             TrafficDirection::DIR_EGRESS);
290   EXPECT_THAT(events[0].network_packet_bundle().packet_lengths(),
291               testing::ElementsAre(2, 6));
292 
293   EXPECT_EQ(events[1].network_packet_bundle().ctx().direction(),
294             TrafficDirection::DIR_INGRESS);
295   EXPECT_THAT(events[1].network_packet_bundle().packet_lengths(),
296               testing::ElementsAre(4, 8));
297 
298   // Remote port shouldn't be in output.
299   EXPECT_FALSE(events[0].network_packet_bundle().ctx().has_remote_port());
300   EXPECT_FALSE(events[1].network_packet_bundle().ctx().has_remote_port());
301 }
302 
TEST_F(NetworkTraceHandlerTest,DropTcpFlags)303 TEST_F(NetworkTraceHandlerTest, DropTcpFlags) {
304   NetworkPacketTraceConfig config;
305   config.set_drop_tcp_flags(true);
306   config.set_aggregation_threshold(10);
307 
308   std::vector<PacketTrace> input = {
309       PacketTrace{.timestampNs = 1, .uid = 123, .length = 1, .tcpFlags = 1},
310       PacketTrace{.timestampNs = 2, .uid = 123, .length = 2, .tcpFlags = 2},
311       PacketTrace{.timestampNs = 3, .uid = 456, .length = 3, .tcpFlags = 1},
312       PacketTrace{.timestampNs = 4, .uid = 456, .length = 4, .tcpFlags = 2},
313   };
314 
315   std::vector<TracePacket> events;
316   ASSERT_TRUE(TraceAndSortPackets(input, &events, config));
317 
318   ASSERT_EQ(events.size(), 2);
319 
320   // Despite having different tcp flags, drop and bundle by remaining fields.
321   EXPECT_EQ(events[0].network_packet_bundle().ctx().uid(), 123);
322   EXPECT_THAT(events[0].network_packet_bundle().packet_lengths(),
323               testing::ElementsAre(1, 2));
324 
325   EXPECT_EQ(events[1].network_packet_bundle().ctx().uid(), 456);
326   EXPECT_THAT(events[1].network_packet_bundle().packet_lengths(),
327               testing::ElementsAre(3, 4));
328 
329   // Tcp flags shouldn't be in output.
330   EXPECT_FALSE(events[0].network_packet_bundle().ctx().has_tcp_flags());
331   EXPECT_FALSE(events[1].network_packet_bundle().ctx().has_tcp_flags());
332 }
333 
TEST_F(NetworkTraceHandlerTest,Interning)334 TEST_F(NetworkTraceHandlerTest, Interning) {
335   NetworkPacketTraceConfig config;
336   config.set_intern_limit(2);
337 
338   // The test writes 4 packets coming from three sources (uids). With an intern
339   // limit of 2, the first two sources should be interned. This test splits this
340   // into individual writes since internally an unordered map is used and would
341   // otherwise non-deterministically choose what to intern (this is fine for
342   // real use, but not good for test assertions).
343   std::vector<std::vector<PacketTrace>> inputs = {
344       {PacketTrace{.timestampNs = 1, .uid = 123}},
345       {PacketTrace{.timestampNs = 2, .uid = 456}},
346       {PacketTrace{.timestampNs = 3, .uid = 789}},
347       {PacketTrace{.timestampNs = 4, .uid = 123}},
348   };
349 
350   auto session = StartTracing(config);
351 
352   NetworkTraceHandler::Trace([&](NetworkTraceHandler::TraceContext ctx) {
353     ctx.GetDataSourceLocked()->Write(inputs[0], ctx);
354     ctx.GetDataSourceLocked()->Write(inputs[1], ctx);
355     ctx.GetDataSourceLocked()->Write(inputs[2], ctx);
356     ctx.GetDataSourceLocked()->Write(inputs[3], ctx);
357     ctx.Flush();
358   });
359 
360   std::vector<TracePacket> events;
361   ASSERT_TRUE(StopTracing(session.get(), &events));
362 
363   ASSERT_EQ(events.size(), 4);
364 
365   // First time seen, emit new interned data, bundle uses iid instead of ctx.
366   EXPECT_EQ(events[0].network_packet_bundle().iid(), 1);
367   ASSERT_EQ(events[0].interned_data().packet_context().size(), 1);
368   EXPECT_EQ(events[0].interned_data().packet_context(0).iid(), 1);
369   EXPECT_EQ(events[0].interned_data().packet_context(0).ctx().uid(), 123);
370   EXPECT_EQ(events[0].sequence_flags(),
371             TracePacket::SEQ_INCREMENTAL_STATE_CLEARED);
372 
373   // First time seen, emit new interned data, bundle uses iid instead of ctx.
374   EXPECT_EQ(events[1].network_packet_bundle().iid(), 2);
375   ASSERT_EQ(events[1].interned_data().packet_context().size(), 1);
376   EXPECT_EQ(events[1].interned_data().packet_context(0).iid(), 2);
377   EXPECT_EQ(events[1].interned_data().packet_context(0).ctx().uid(), 456);
378   EXPECT_EQ(events[1].sequence_flags(),
379             TracePacket::SEQ_NEEDS_INCREMENTAL_STATE);
380 
381   // Not enough room in intern table (limit 2), inline the context.
382   EXPECT_EQ(events[2].network_packet_bundle().ctx().uid(), 789);
383   EXPECT_EQ(events[2].interned_data().packet_context().size(), 0);
384   EXPECT_EQ(events[2].sequence_flags(),
385             TracePacket::SEQ_NEEDS_INCREMENTAL_STATE);
386 
387   // Second time seen, no need to re-emit interned data, only record iid.
388   EXPECT_EQ(events[3].network_packet_bundle().iid(), 1);
389   EXPECT_EQ(events[3].interned_data().packet_context().size(), 0);
390   EXPECT_EQ(events[3].sequence_flags(),
391             TracePacket::SEQ_NEEDS_INCREMENTAL_STATE);
392 }
393 
394 }  // namespace bpf
395 }  // namespace android
396