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