• 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 #define LOG_TAG "NetworkTrace"
18 
19 #include "netdbpf/NetworkTraceHandler.h"
20 
21 #include <arpa/inet.h>
22 #include <bpf/BpfUtils.h>
23 #include <log/log.h>
24 #include <perfetto/config/android/network_trace_config.pbzero.h>
25 #include <perfetto/trace/android/network_trace.pbzero.h>
26 #include <perfetto/trace/profiling/profile_packet.pbzero.h>
27 #include <perfetto/tracing/platform.h>
28 #include <perfetto/tracing/tracing.h>
29 
30 // Note: this is initializing state for a templated Perfetto type that resides
31 // in the `perfetto` namespace. This must be defined in the global scope.
32 PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS(android::bpf::NetworkTraceHandler);
33 
34 namespace android {
35 namespace bpf {
36 using ::android::bpf::internal::NetworkTracePoller;
37 using ::perfetto::protos::pbzero::NetworkPacketBundle;
38 using ::perfetto::protos::pbzero::NetworkPacketEvent;
39 using ::perfetto::protos::pbzero::NetworkPacketTraceConfig;
40 using ::perfetto::protos::pbzero::TracePacket;
41 using ::perfetto::protos::pbzero::TrafficDirection;
42 
43 // Bundling takes groups of packets with similar contextual fields (generally,
44 // all fields except timestamp and length) and summarises them in a single trace
45 // packet. For example, rather than
46 //
47 //   {.timestampNs = 1, .uid = 1000, .tag = 123, .len = 72}
48 //   {.timestampNs = 2, .uid = 1000, .tag = 123, .len = 100}
49 //   {.timestampNs = 5, .uid = 1000, .tag = 123, .len = 456}
50 //
51 // The output will be something like
52 //   {
53 //     .timestamp = 1
54 //     .ctx = {.uid = 1000, .tag = 123}
55 //     .timestamp = [0, 1, 4], // delta encoded
56 //     .length = [72, 100, 456], // should be zipped with timestamps
57 //   }
58 //
59 // Most workloads have many packets from few contexts. Bundling greatly reduces
60 // the amount of redundant information written, thus reducing the overall trace
61 // size. Interning ids are similarly based on unique bundle contexts.
62 
63 // Based on boost::hash_combine
64 template <typename T, typename... Rest>
HashCombine(std::size_t & seed,const T & val,const Rest &...rest)65 void HashCombine(std::size_t& seed, const T& val, const Rest&... rest) {
66   seed ^= std::hash<T>()(val) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
67   (HashCombine(seed, rest), ...);
68 }
69 
70 // Details summarises the timestamp and lengths of packets in a bundle.
71 struct BundleDetails {
72   std::vector<std::pair<uint64_t, uint32_t>> time_and_len;
73   uint64_t minTs = std::numeric_limits<uint64_t>::max();
74   uint64_t maxTs = std::numeric_limits<uint64_t>::min();
75   uint32_t bytes = 0;
76 };
77 
78 #define AGG_FIELDS(x)                                              \
79   (x).ifindex, (x).uid, (x).tag, (x).sport, (x).dport, (x).egress, \
80       (x).ipProto, (x).tcpFlags
81 
operator ()(const BundleKey & a) const82 std::size_t BundleHash::operator()(const BundleKey& a) const {
83   std::size_t seed = 0;
84   HashCombine(seed, AGG_FIELDS(a));
85   return seed;
86 }
87 
operator ()(const BundleKey & a,const BundleKey & b) const88 bool BundleEq::operator()(const BundleKey& a, const BundleKey& b) const {
89   return std::tie(AGG_FIELDS(a)) == std::tie(AGG_FIELDS(b));
90 }
91 
92 // static
RegisterDataSource()93 void NetworkTraceHandler::RegisterDataSource() {
94   ALOGD("Registering Perfetto data source");
95   perfetto::DataSourceDescriptor dsd;
96   dsd.set_name("android.network_packets");
97   NetworkTraceHandler::Register(dsd);
98 }
99 
100 // static
InitPerfettoTracing()101 void NetworkTraceHandler::InitPerfettoTracing() {
102   perfetto::TracingInitArgs args = {};
103   args.backends |= perfetto::kSystemBackend;
104   // The following line disables the Perfetto system consumer. Perfetto inlines
105   // the call to `Initialize` which allows the compiler to see that the branch
106   // with the SystemConsumerTracingBackend is not used. With LTO enabled, this
107   // strips the Perfetto consumer code and reduces the size of this binary by
108   // around 270KB total. Be careful when changing this value.
109   args.enable_system_consumer = false;
110   perfetto::Tracing::Initialize(args);
111   NetworkTraceHandler::RegisterDataSource();
112 }
113 
114 // static
115 NetworkTracePoller NetworkTraceHandler::sPoller(
__anon1baa155f0102(const std::vector<PacketTrace>& packets) 116     [](const std::vector<PacketTrace>& packets) {
117       // Trace calls the provided callback for each active session. The context
118       // gets a reference to the NetworkTraceHandler instance associated with
119       // the session and delegates writing. The corresponding handler will write
120       // with the setting specified in the trace config.
121       NetworkTraceHandler::Trace([&](NetworkTraceHandler::TraceContext ctx) {
122         ctx.GetDataSourceLocked()->Write(packets, ctx);
123       });
124     });
125 
OnSetup(const SetupArgs & args)126 void NetworkTraceHandler::OnSetup(const SetupArgs& args) {
127   const std::string& raw = args.config->network_packet_trace_config_raw();
128   NetworkPacketTraceConfig::Decoder config(raw);
129 
130   mPollMs = config.poll_ms();
131   if (mPollMs < 100) {
132     ALOGI("poll_ms is missing or below the 100ms minimum. Increasing to 100ms");
133     mPollMs = 100;
134   }
135 
136   mInternLimit = config.intern_limit();
137   mAggregationThreshold = config.aggregation_threshold();
138   mDropLocalPort = config.drop_local_port();
139   mDropRemotePort = config.drop_remote_port();
140   mDropTcpFlags = config.drop_tcp_flags();
141 }
142 
OnStart(const StartArgs &)143 void NetworkTraceHandler::OnStart(const StartArgs&) {
144   if (mIsTest) return;  // Don't touch non-hermetic bpf in test.
145   mStarted = sPoller.Start(mPollMs);
146 }
147 
OnStop(const StopArgs &)148 void NetworkTraceHandler::OnStop(const StopArgs&) {
149   if (mIsTest) return;  // Don't touch non-hermetic bpf in test.
150   if (mStarted) sPoller.Stop();
151   mStarted = false;
152 
153   // Although this shouldn't be required, there seems to be some cases when we
154   // don't fill enough of a Perfetto Chunk for Perfetto to automatically commit
155   // the traced data. This manually flushes OnStop so we commit at least once.
156   NetworkTraceHandler::Trace([&](NetworkTraceHandler::TraceContext ctx) {
157     perfetto::LockedHandle<NetworkTraceHandler> handle =
158         ctx.GetDataSourceLocked();
159     // Trace is called for all active handlers, only flush our context. Since
160     // handle doesn't have a `.get()`, use `*` and `&` to get what it points to.
161     if (&(*handle) != this) return;
162     ctx.Flush();
163   });
164 }
165 
Write(const std::vector<PacketTrace> & packets,NetworkTraceHandler::TraceContext & ctx)166 void NetworkTraceHandler::Write(const std::vector<PacketTrace>& packets,
167                                 NetworkTraceHandler::TraceContext& ctx) {
168   // TODO: remove this fallback once Perfetto stable has support for bundles.
169   if (!mInternLimit && !mAggregationThreshold) {
170     for (const PacketTrace& pkt : packets) {
171       auto dst = ctx.NewTracePacket();
172       dst->set_timestamp(pkt.timestampNs);
173       auto* event = dst->set_network_packet();
174       event->set_length(pkt.length);
175       Fill(pkt, event);
176     }
177     return;
178   }
179 
180   uint64_t minTs = std::numeric_limits<uint64_t>::max();
181   std::unordered_map<BundleKey, BundleDetails, BundleHash, BundleEq> bundles;
182   for (const PacketTrace& pkt : packets) {
183     BundleKey key = pkt;
184 
185     // Dropping fields should remove them from the output and remove them from
186     // the aggregation key. In order to do the latter without changing the hash
187     // function, set the dropped fields to zero.
188     if (mDropTcpFlags) key.tcpFlags = 0;
189     if (mDropLocalPort) (key.egress ? key.sport : key.dport) = 0;
190     if (mDropRemotePort) (key.egress ? key.dport : key.sport) = 0;
191 
192     minTs = std::min(minTs, pkt.timestampNs);
193 
194     BundleDetails& bundle = bundles[key];
195     bundle.time_and_len.emplace_back(pkt.timestampNs, pkt.length);
196     bundle.minTs = std::min(bundle.minTs, pkt.timestampNs);
197     bundle.maxTs = std::max(bundle.maxTs, pkt.timestampNs);
198     bundle.bytes += pkt.length;
199   }
200 
201   NetworkTraceState* incr_state = ctx.GetIncrementalState();
202   for (const auto& kv : bundles) {
203     const BundleKey& key = kv.first;
204     const BundleDetails& details = kv.second;
205 
206     auto dst = ctx.NewTracePacket();
207     dst->set_timestamp(details.minTs);
208 
209     // Incremental state is only used when interning. Set the flag based on
210     // whether state was cleared. Leave the flag empty in non-intern configs.
211     if (mInternLimit > 0) {
212       if (incr_state->cleared) {
213         dst->set_sequence_flags(TracePacket::SEQ_INCREMENTAL_STATE_CLEARED);
214         incr_state->cleared = false;
215       } else {
216         dst->set_sequence_flags(TracePacket::SEQ_NEEDS_INCREMENTAL_STATE);
217       }
218     }
219 
220     auto* event = FillWithInterning(incr_state, key, dst.get());
221 
222     int count = details.time_and_len.size();
223     if (!mAggregationThreshold || count < mAggregationThreshold) {
224       protozero::PackedVarInt offsets;
225       protozero::PackedVarInt lengths;
226       for (const auto& kv : details.time_and_len) {
227         offsets.Append(kv.first - details.minTs);
228         lengths.Append(kv.second);
229       }
230 
231       event->set_packet_timestamps(offsets);
232       event->set_packet_lengths(lengths);
233     } else {
234       event->set_total_duration(details.maxTs - details.minTs);
235       event->set_total_length(details.bytes);
236       event->set_total_packets(count);
237     }
238   }
239 }
240 
Fill(const PacketTrace & src,NetworkPacketEvent * event)241 void NetworkTraceHandler::Fill(const PacketTrace& src,
242                                NetworkPacketEvent* event) {
243   event->set_direction(src.egress ? TrafficDirection::DIR_EGRESS
244                                   : TrafficDirection::DIR_INGRESS);
245   event->set_uid(src.uid);
246   event->set_tag(src.tag);
247 
248   if (!mDropLocalPort) {
249     event->set_local_port(ntohs(src.egress ? src.sport : src.dport));
250   }
251   if (!mDropRemotePort) {
252     event->set_remote_port(ntohs(src.egress ? src.dport : src.sport));
253   }
254   if (!mDropTcpFlags) {
255     event->set_tcp_flags(src.tcpFlags);
256   }
257 
258   event->set_ip_proto(src.ipProto);
259 
260   char ifname[IF_NAMESIZE] = {};
261   if (if_indextoname(src.ifindex, ifname) == ifname) {
262     event->set_interface(std::string(ifname));
263   } else {
264     event->set_interface("error");
265   }
266 }
267 
FillWithInterning(NetworkTraceState * state,const BundleKey & key,TracePacket * dst)268 NetworkPacketBundle* NetworkTraceHandler::FillWithInterning(
269     NetworkTraceState* state, const BundleKey& key, TracePacket* dst) {
270   uint64_t iid = 0;
271   bool found = false;
272 
273   if (state->iids.size() < mInternLimit) {
274     auto [iter, success] = state->iids.try_emplace(key, state->iids.size() + 1);
275     iid = iter->second;
276     found = true;
277 
278     if (success) {
279       // If we successfully empaced, record the newly interned data.
280       auto* packet_context = dst->set_interned_data()->add_packet_context();
281       Fill(key, packet_context->set_ctx());
282       packet_context->set_iid(iid);
283     }
284   } else {
285     auto iter = state->iids.find(key);
286     if (iter != state->iids.end()) {
287       iid = iter->second;
288       found = true;
289     }
290   }
291 
292   auto* event = dst->set_network_packet_bundle();
293   if (found) {
294     event->set_iid(iid);
295   } else {
296     Fill(key, event->set_ctx());
297   }
298 
299   return event;
300 }
301 
302 }  // namespace bpf
303 }  // namespace android
304