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