1 /*
2 * Copyright (C) 2017 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 "src/tracing/ipc/service/producer_ipc_service.h"
18
19 #include <inttypes.h>
20
21 #include "perfetto/base/logging.h"
22 #include "perfetto/base/task_runner.h"
23 #include "perfetto/ipc/host.h"
24 #include "perfetto/tracing/core/commit_data_request.h"
25 #include "perfetto/tracing/core/data_source_config.h"
26 #include "perfetto/tracing/core/data_source_descriptor.h"
27 #include "perfetto/tracing/core/tracing_service.h"
28 #include "src/tracing/ipc/posix_shared_memory.h"
29
30 // The remote Producer(s) are not trusted. All the methods from the ProducerPort
31 // IPC layer (e.g. RegisterDataSource()) must assume that the remote Producer is
32 // compromised.
33
34 namespace perfetto {
35
ProducerIPCService(TracingService * core_service)36 ProducerIPCService::ProducerIPCService(TracingService* core_service)
37 : core_service_(core_service), weak_ptr_factory_(this) {}
38
39 ProducerIPCService::~ProducerIPCService() = default;
40
41 ProducerIPCService::RemoteProducer*
GetProducerForCurrentRequest()42 ProducerIPCService::GetProducerForCurrentRequest() {
43 const ipc::ClientID ipc_client_id = ipc::Service::client_info().client_id();
44 PERFETTO_CHECK(ipc_client_id);
45 auto it = producers_.find(ipc_client_id);
46 if (it == producers_.end())
47 return nullptr;
48 return it->second.get();
49 }
50
51 // Called by the remote Producer through the IPC channel soon after connecting.
InitializeConnection(const protos::InitializeConnectionRequest & req,DeferredInitializeConnectionResponse response)52 void ProducerIPCService::InitializeConnection(
53 const protos::InitializeConnectionRequest& req,
54 DeferredInitializeConnectionResponse response) {
55 const auto& client_info = ipc::Service::client_info();
56 const ipc::ClientID ipc_client_id = client_info.client_id();
57 PERFETTO_CHECK(ipc_client_id);
58
59 if (producers_.count(ipc_client_id) > 0) {
60 PERFETTO_DLOG(
61 "The remote Producer is trying to re-initialize the connection");
62 return response.Reject();
63 }
64
65 // Create a new entry.
66 std::unique_ptr<RemoteProducer> producer(new RemoteProducer());
67
68 TracingService::ProducerSMBScrapingMode smb_scraping_mode =
69 TracingService::ProducerSMBScrapingMode::kDefault;
70 switch (req.smb_scraping_mode()) {
71 case protos::InitializeConnectionRequest::SMB_SCRAPING_UNSPECIFIED:
72 break;
73 case protos::InitializeConnectionRequest::SMB_SCRAPING_DISABLED:
74 smb_scraping_mode = TracingService::ProducerSMBScrapingMode::kDisabled;
75 break;
76 case protos::InitializeConnectionRequest::SMB_SCRAPING_ENABLED:
77 smb_scraping_mode = TracingService::ProducerSMBScrapingMode::kEnabled;
78 break;
79 }
80
81 // ConnectProducer will call OnConnect() on the next task.
82 producer->service_endpoint = core_service_->ConnectProducer(
83 producer.get(), client_info.uid(), req.producer_name(),
84 req.shared_memory_size_hint_bytes(), /*in_process=*/false,
85 smb_scraping_mode);
86
87 // Could happen if the service has too many producers connected.
88 if (!producer->service_endpoint)
89 response.Reject();
90
91 producers_.emplace(ipc_client_id, std::move(producer));
92 // Because of the std::move() |producer| is invalid after this point.
93
94 auto async_res =
95 ipc::AsyncResult<protos::InitializeConnectionResponse>::Create();
96 response.Resolve(std::move(async_res));
97 }
98
99 // Called by the remote Producer through the IPC channel.
RegisterDataSource(const protos::RegisterDataSourceRequest & req,DeferredRegisterDataSourceResponse response)100 void ProducerIPCService::RegisterDataSource(
101 const protos::RegisterDataSourceRequest& req,
102 DeferredRegisterDataSourceResponse response) {
103 RemoteProducer* producer = GetProducerForCurrentRequest();
104 if (!producer) {
105 PERFETTO_DLOG(
106 "Producer invoked RegisterDataSource() before InitializeConnection()");
107 if (response.IsBound())
108 response.Reject();
109 return;
110 }
111
112 DataSourceDescriptor dsd;
113 dsd.FromProto(req.data_source_descriptor());
114 GetProducerForCurrentRequest()->service_endpoint->RegisterDataSource(dsd);
115
116 // RegisterDataSource doesn't expect any meaningful response.
117 if (response.IsBound()) {
118 response.Resolve(
119 ipc::AsyncResult<protos::RegisterDataSourceResponse>::Create());
120 }
121 }
122
123 // Called by the IPC layer.
OnClientDisconnected()124 void ProducerIPCService::OnClientDisconnected() {
125 ipc::ClientID client_id = ipc::Service::client_info().client_id();
126 PERFETTO_DLOG("Client %" PRIu64 " disconnected", client_id);
127 producers_.erase(client_id);
128 }
129
130 // TODO(fmayer): test what happens if we receive the following tasks, in order:
131 // RegisterDataSource, UnregisterDataSource, OnDataSourceRegistered.
132 // which essentially means that the client posted back to back a
133 // ReqisterDataSource and UnregisterDataSource speculating on the next id.
134 // Called by the remote Service through the IPC channel.
UnregisterDataSource(const protos::UnregisterDataSourceRequest & req,DeferredUnregisterDataSourceResponse response)135 void ProducerIPCService::UnregisterDataSource(
136 const protos::UnregisterDataSourceRequest& req,
137 DeferredUnregisterDataSourceResponse response) {
138 RemoteProducer* producer = GetProducerForCurrentRequest();
139 if (!producer) {
140 PERFETTO_DLOG(
141 "Producer invoked UnregisterDataSource() before "
142 "InitializeConnection()");
143 if (response.IsBound())
144 response.Reject();
145 return;
146 }
147 producer->service_endpoint->UnregisterDataSource(req.data_source_name());
148
149 // UnregisterDataSource doesn't expect any meaningful response.
150 if (response.IsBound()) {
151 response.Resolve(
152 ipc::AsyncResult<protos::UnregisterDataSourceResponse>::Create());
153 }
154 }
155
RegisterTraceWriter(const protos::RegisterTraceWriterRequest & req,DeferredRegisterTraceWriterResponse response)156 void ProducerIPCService::RegisterTraceWriter(
157 const protos::RegisterTraceWriterRequest& req,
158 DeferredRegisterTraceWriterResponse response) {
159 RemoteProducer* producer = GetProducerForCurrentRequest();
160 if (!producer) {
161 PERFETTO_DLOG(
162 "Producer invoked RegisterTraceWriter() before "
163 "InitializeConnection()");
164 if (response.IsBound())
165 response.Reject();
166 return;
167 }
168 producer->service_endpoint->RegisterTraceWriter(req.trace_writer_id(),
169 req.target_buffer());
170
171 // RegisterTraceWriter doesn't expect any meaningful response.
172 if (response.IsBound()) {
173 response.Resolve(
174 ipc::AsyncResult<protos::RegisterTraceWriterResponse>::Create());
175 }
176 }
177
UnregisterTraceWriter(const protos::UnregisterTraceWriterRequest & req,DeferredUnregisterTraceWriterResponse response)178 void ProducerIPCService::UnregisterTraceWriter(
179 const protos::UnregisterTraceWriterRequest& req,
180 DeferredUnregisterTraceWriterResponse response) {
181 RemoteProducer* producer = GetProducerForCurrentRequest();
182 if (!producer) {
183 PERFETTO_DLOG(
184 "Producer invoked UnregisterTraceWriter() before "
185 "InitializeConnection()");
186 if (response.IsBound())
187 response.Reject();
188 return;
189 }
190 producer->service_endpoint->UnregisterTraceWriter(req.trace_writer_id());
191
192 // UnregisterTraceWriter doesn't expect any meaningful response.
193 if (response.IsBound()) {
194 response.Resolve(
195 ipc::AsyncResult<protos::UnregisterTraceWriterResponse>::Create());
196 }
197 }
198
CommitData(const protos::CommitDataRequest & proto_req,DeferredCommitDataResponse resp)199 void ProducerIPCService::CommitData(const protos::CommitDataRequest& proto_req,
200 DeferredCommitDataResponse resp) {
201 RemoteProducer* producer = GetProducerForCurrentRequest();
202 if (!producer) {
203 PERFETTO_DLOG(
204 "Producer invoked CommitData() before InitializeConnection()");
205 if (resp.IsBound())
206 resp.Reject();
207 return;
208 }
209 CommitDataRequest req;
210 req.FromProto(proto_req);
211
212 // We don't want to send a response if the client didn't attach a callback to
213 // the original request. Doing so would generate unnecessary wakeups and
214 // context switches.
215 std::function<void()> callback;
216 if (resp.IsBound()) {
217 // Capturing |resp| by reference here speculates on the fact that
218 // CommitData() in tracing_service_impl.cc invokes the passed callback
219 // inline, without posting it. If that assumption changes this code needs to
220 // wrap the response in a shared_ptr (C+11 lambdas don't support move) and
221 // use a weak ptr in the caller.
222 callback = [&resp] {
223 resp.Resolve(ipc::AsyncResult<protos::CommitDataResponse>::Create());
224 };
225 }
226 producer->service_endpoint->CommitData(req, callback);
227 }
228
NotifyDataSourceStarted(const protos::NotifyDataSourceStartedRequest & request,DeferredNotifyDataSourceStartedResponse response)229 void ProducerIPCService::NotifyDataSourceStarted(
230 const protos::NotifyDataSourceStartedRequest& request,
231 DeferredNotifyDataSourceStartedResponse response) {
232 RemoteProducer* producer = GetProducerForCurrentRequest();
233 if (!producer) {
234 PERFETTO_DLOG(
235 "Producer invoked NotifyDataSourceStarted() before "
236 "InitializeConnection()");
237 if (response.IsBound())
238 response.Reject();
239 return;
240 }
241 producer->service_endpoint->NotifyDataSourceStarted(request.data_source_id());
242
243 // NotifyDataSourceStopped shouldn't expect any meaningful response, avoid
244 // a useless IPC in that case.
245 if (response.IsBound()) {
246 response.Resolve(
247 ipc::AsyncResult<protos::NotifyDataSourceStartedResponse>::Create());
248 }
249 }
250
NotifyDataSourceStopped(const protos::NotifyDataSourceStoppedRequest & request,DeferredNotifyDataSourceStoppedResponse response)251 void ProducerIPCService::NotifyDataSourceStopped(
252 const protos::NotifyDataSourceStoppedRequest& request,
253 DeferredNotifyDataSourceStoppedResponse response) {
254 RemoteProducer* producer = GetProducerForCurrentRequest();
255 if (!producer) {
256 PERFETTO_DLOG(
257 "Producer invoked NotifyDataSourceStopped() before "
258 "InitializeConnection()");
259 if (response.IsBound())
260 response.Reject();
261 return;
262 }
263 producer->service_endpoint->NotifyDataSourceStopped(request.data_source_id());
264
265 // NotifyDataSourceStopped shouldn't expect any meaningful response, avoid
266 // a useless IPC in that case.
267 if (response.IsBound()) {
268 response.Resolve(
269 ipc::AsyncResult<protos::NotifyDataSourceStoppedResponse>::Create());
270 }
271 }
272
ActivateTriggers(const protos::ActivateTriggersRequest & proto_req,DeferredActivateTriggersResponse resp)273 void ProducerIPCService::ActivateTriggers(
274 const protos::ActivateTriggersRequest& proto_req,
275 DeferredActivateTriggersResponse resp) {
276 RemoteProducer* producer = GetProducerForCurrentRequest();
277 if (!producer) {
278 PERFETTO_DLOG(
279 "Producer invoked ActivateTriggers() before InitializeConnection()");
280 if (resp.IsBound())
281 resp.Reject();
282 return;
283 }
284 std::vector<std::string> triggers;
285 for (const auto& name : proto_req.trigger_names()) {
286 triggers.push_back(name);
287 }
288 producer->service_endpoint->ActivateTriggers(triggers);
289 // ActivateTriggers shouldn't expect any meaningful response, avoid
290 // a useless IPC in that case.
291 if (resp.IsBound()) {
292 resp.Resolve(ipc::AsyncResult<protos::ActivateTriggersResponse>::Create());
293 }
294 }
295
GetAsyncCommand(const protos::GetAsyncCommandRequest &,DeferredGetAsyncCommandResponse response)296 void ProducerIPCService::GetAsyncCommand(
297 const protos::GetAsyncCommandRequest&,
298 DeferredGetAsyncCommandResponse response) {
299 RemoteProducer* producer = GetProducerForCurrentRequest();
300 if (!producer) {
301 PERFETTO_DLOG(
302 "Producer invoked GetAsyncCommand() before "
303 "InitializeConnection()");
304 return response.Reject();
305 }
306 // Keep the back channel open, without ever resolving the ipc::Deferred fully,
307 // to send async commands to the RemoteProducer (e.g., starting/stopping a
308 // data source).
309 producer->async_producer_commands = std::move(response);
310 }
311
312 ////////////////////////////////////////////////////////////////////////////////
313 // RemoteProducer methods
314 ////////////////////////////////////////////////////////////////////////////////
315
316 ProducerIPCService::RemoteProducer::RemoteProducer() = default;
317 ProducerIPCService::RemoteProducer::~RemoteProducer() = default;
318
319 // Invoked by the |core_service_| business logic after the ConnectProducer()
320 // call. There is nothing to do here, we really expected the ConnectProducer()
321 // to just work in the local case.
OnConnect()322 void ProducerIPCService::RemoteProducer::OnConnect() {}
323
324 // Invoked by the |core_service_| business logic after we destroy the
325 // |service_endpoint| (in the RemoteProducer dtor).
OnDisconnect()326 void ProducerIPCService::RemoteProducer::OnDisconnect() {}
327
328 // Invoked by the |core_service_| business logic when it wants to create a new
329 // data source.
SetupDataSource(DataSourceInstanceID dsid,const DataSourceConfig & cfg)330 void ProducerIPCService::RemoteProducer::SetupDataSource(
331 DataSourceInstanceID dsid,
332 const DataSourceConfig& cfg) {
333 if (!async_producer_commands.IsBound()) {
334 PERFETTO_DLOG(
335 "The Service tried to create a new data source but the remote Producer "
336 "has not yet initialized the connection");
337 return;
338 }
339 auto cmd = ipc::AsyncResult<protos::GetAsyncCommandResponse>::Create();
340 cmd.set_has_more(true);
341 cmd->mutable_setup_data_source()->set_new_instance_id(dsid);
342 cfg.ToProto(cmd->mutable_setup_data_source()->mutable_config());
343 async_producer_commands.Resolve(std::move(cmd));
344 }
345
346 // Invoked by the |core_service_| business logic when it wants to start a new
347 // data source.
StartDataSource(DataSourceInstanceID dsid,const DataSourceConfig & cfg)348 void ProducerIPCService::RemoteProducer::StartDataSource(
349 DataSourceInstanceID dsid,
350 const DataSourceConfig& cfg) {
351 if (!async_producer_commands.IsBound()) {
352 PERFETTO_DLOG(
353 "The Service tried to start a new data source but the remote Producer "
354 "has not yet initialized the connection");
355 return;
356 }
357 auto cmd = ipc::AsyncResult<protos::GetAsyncCommandResponse>::Create();
358 cmd.set_has_more(true);
359 cmd->mutable_start_data_source()->set_new_instance_id(dsid);
360 cfg.ToProto(cmd->mutable_start_data_source()->mutable_config());
361 async_producer_commands.Resolve(std::move(cmd));
362 }
363
StopDataSource(DataSourceInstanceID dsid)364 void ProducerIPCService::RemoteProducer::StopDataSource(
365 DataSourceInstanceID dsid) {
366 if (!async_producer_commands.IsBound()) {
367 PERFETTO_DLOG(
368 "The Service tried to stop a data source but the remote Producer "
369 "has not yet initialized the connection");
370 return;
371 }
372 auto cmd = ipc::AsyncResult<protos::GetAsyncCommandResponse>::Create();
373 cmd.set_has_more(true);
374 cmd->mutable_stop_data_source()->set_instance_id(dsid);
375 async_producer_commands.Resolve(std::move(cmd));
376 }
377
OnTracingSetup()378 void ProducerIPCService::RemoteProducer::OnTracingSetup() {
379 if (!async_producer_commands.IsBound()) {
380 PERFETTO_DLOG(
381 "The Service tried to allocate the shared memory but the remote "
382 "Producer has not yet initialized the connection");
383 return;
384 }
385 PERFETTO_CHECK(service_endpoint->shared_memory());
386 const int shm_fd =
387 static_cast<PosixSharedMemory*>(service_endpoint->shared_memory())->fd();
388 auto cmd = ipc::AsyncResult<protos::GetAsyncCommandResponse>::Create();
389 cmd.set_has_more(true);
390 cmd.set_fd(shm_fd);
391 cmd->mutable_setup_tracing()->set_shared_buffer_page_size_kb(
392 static_cast<uint32_t>(service_endpoint->shared_buffer_page_size_kb()));
393 async_producer_commands.Resolve(std::move(cmd));
394 }
395
Flush(FlushRequestID flush_request_id,const DataSourceInstanceID * data_source_ids,size_t num_data_sources)396 void ProducerIPCService::RemoteProducer::Flush(
397 FlushRequestID flush_request_id,
398 const DataSourceInstanceID* data_source_ids,
399 size_t num_data_sources) {
400 if (!async_producer_commands.IsBound()) {
401 PERFETTO_DLOG(
402 "The Service tried to request a flush but the remote Producer has not "
403 "yet initialized the connection");
404 return;
405 }
406 auto cmd = ipc::AsyncResult<protos::GetAsyncCommandResponse>::Create();
407 cmd.set_has_more(true);
408 for (size_t i = 0; i < num_data_sources; i++)
409 cmd->mutable_flush()->add_data_source_ids(data_source_ids[i]);
410 cmd->mutable_flush()->set_request_id(flush_request_id);
411 async_producer_commands.Resolve(std::move(cmd));
412 }
413
ClearIncrementalState(const DataSourceInstanceID * data_source_ids,size_t num_data_sources)414 void ProducerIPCService::RemoteProducer::ClearIncrementalState(
415 const DataSourceInstanceID* data_source_ids,
416 size_t num_data_sources) {
417 if (!async_producer_commands.IsBound()) {
418 PERFETTO_DLOG(
419 "The Service tried to request an incremental state invalidation, but "
420 "the remote Producer has not yet initialized the connection");
421 return;
422 }
423 auto cmd = ipc::AsyncResult<protos::GetAsyncCommandResponse>::Create();
424 cmd.set_has_more(true);
425 for (size_t i = 0; i < num_data_sources; i++)
426 cmd->mutable_clear_incremental_state()->add_data_source_ids(
427 data_source_ids[i]);
428 async_producer_commands.Resolve(std::move(cmd));
429 }
430
431 } // namespace perfetto
432