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 package com.android.server.wifi;
18 
19 
20 import android.util.ArrayMap;
21 
22 import com.android.internal.annotations.VisibleForTesting;
23 import com.android.server.wifi.util.FileUtils;
24 
25 import java.io.File;
26 import java.io.FileInputStream;
27 import java.io.IOException;
28 import java.io.PrintWriter;
29 import java.nio.file.Files;
30 import java.nio.file.Paths;
31 import java.util.Map;
32 
33 /**
34  * Provides a facility for capturing kernel trace events related to Wifi control and data paths.
35  */
36 public class LastMileLogger {
LastMileLogger(WifiInjector injector)37     public LastMileLogger(WifiInjector injector) {
38         File tracefsEnablePath = new File(WIFI_EVENT_ENABLE_PATH);
39         if (tracefsEnablePath.exists()) {
40             initLastMileLogger(injector, WIFI_EVENT_BUFFER_PATH, WIFI_EVENT_ENABLE_PATH,
41                     WIFI_EVENT_RELEASE_PATH);
42         } else {
43             initLastMileLogger(injector, WIFI_EVENT_BUFFER_PATH_DEBUGFS,
44                     WIFI_EVENT_ENABLE_PATH_DEBUGFS, WIFI_EVENT_RELEASE_PATH_DEBUGFS);
45         }
46     }
47 
48     @VisibleForTesting
LastMileLogger(WifiInjector injector, String bufferPath, String enablePath, String releasePath)49     public LastMileLogger(WifiInjector injector, String bufferPath, String enablePath,
50                           String releasePath) {
51         initLastMileLogger(injector, bufferPath, enablePath, releasePath);
52     }
53 
54     /**
55      * Informs LastMileLogger that a connection event has occurred.
56      * @param event an event defined in WifiDiagnostics
57      */
reportConnectionEvent(String ifaceName, byte event)58     public void reportConnectionEvent(String ifaceName, byte event) {
59         boolean wasTracingEnabled = anyConnectionInProgress();
60 
61         mIfaceToConnectionStatus.put(ifaceName, event);
62 
63         boolean shouldTracingBeEnabled = anyConnectionInProgress();
64 
65         if (!wasTracingEnabled && shouldTracingBeEnabled) {
66             enableTracing();
67         } else if (wasTracingEnabled && !shouldTracingBeEnabled) {
68             disableTracing();
69         }
70 
71         if (event == WifiDiagnostics.CONNECTION_EVENT_FAILED
72                 || event == WifiDiagnostics.CONNECTION_EVENT_TIMEOUT) {
73             mLastMileLogForLastFailure = readTrace();
74         }
75     }
76 
anyConnectionInProgress()77     private boolean anyConnectionInProgress() {
78         for (byte status : mIfaceToConnectionStatus.values()) {
79             if (status == WifiDiagnostics.CONNECTION_EVENT_STARTED) {
80                 return true;
81             }
82         }
83         return false;
84     }
85 
86     /**
87      * Dumps the contents of the log.
88      * @param pw the PrintWriter that will receive the dump
89      */
dump(PrintWriter pw)90     public void dump(PrintWriter pw) {
91         dumpInternal(pw, "Last failed last-mile log", mLastMileLogForLastFailure);
92         dumpInternal(pw, "Latest last-mile log", readTrace());
93     }
94 
95     private static final String TAG = "LastMileLogger";
96     private static final String WIFI_EVENT_BUFFER_PATH =
97             "/sys/kernel/tracing/instances/wifi/trace";
98     private static final String WIFI_EVENT_ENABLE_PATH =
99             "/sys/kernel/tracing/instances/wifi/tracing_on";
100     private static final String WIFI_EVENT_RELEASE_PATH =
101             "/sys/kernel/tracing/instances/wifi/free_buffer";
102     private static final String WIFI_EVENT_BUFFER_PATH_DEBUGFS =
103             "/sys/kernel/debug/tracing/instances/wifi/trace";
104     private static final String WIFI_EVENT_ENABLE_PATH_DEBUGFS =
105             "/sys/kernel/debug/tracing/instances/wifi/tracing_on";
106     private static final String WIFI_EVENT_RELEASE_PATH_DEBUGFS =
107             "/sys/kernel/debug/tracing/instances/wifi/free_buffer";
108 
109     private String mEventBufferPath;
110     private String mEventEnablePath;
111     private String mEventReleasePath;
112     private WifiLog mLog;
113     private byte[] mLastMileLogForLastFailure;
114     private FileInputStream mLastMileTraceHandle;
115     /**
116      * String key: iface name
117      * byte value: Connection status, one of WifiDiagnostics.CONNECTION_EVENT_*
118      */
119     private final Map<String, Byte> mIfaceToConnectionStatus = new ArrayMap<>();
120 
initLastMileLogger(WifiInjector injector, String bufferPath, String enablePath, String releasePath)121     private void initLastMileLogger(WifiInjector injector, String bufferPath, String enablePath,
122                           String releasePath) {
123         mLog = injector.makeLog(TAG);
124         mEventBufferPath = bufferPath;
125         mEventEnablePath = enablePath;
126         mEventReleasePath = releasePath;
127     }
128 
enableTracing()129     private void enableTracing() {
130         if (!ensureFailSafeIsArmed()) {
131             mLog.wC("Failed to arm fail-safe.");
132             return;
133         }
134 
135         try {
136             FileUtils.stringToFile(mEventEnablePath, "1");
137         } catch (IOException e) {
138             mLog.warn("Failed to start event tracing: %").r(e.getMessage()).flush();
139         }
140     }
141 
disableTracing()142     private void disableTracing() {
143         try {
144             FileUtils.stringToFile(mEventEnablePath, "0");
145         } catch (IOException e) {
146             mLog.warn("Failed to stop event tracing: %").r(e.getMessage()).flush();
147         }
148     }
149 
readTrace()150     private byte[] readTrace() {
151         try {
152             return Files.readAllBytes(Paths.get(mEventBufferPath));
153         } catch (IOException e) {
154             mLog.warn("Failed to read event trace: %").r(e.getMessage()).flush();
155             return new byte[0];
156         }
157     }
158 
ensureFailSafeIsArmed()159     private boolean ensureFailSafeIsArmed() {
160         if (mLastMileTraceHandle != null) {
161             return true;
162         }
163 
164         try {
165             // This file provides fail-safe behavior for Last-Mile logging. Given that we:
166             // 1. Set the disable_on_free option in the trace_options pseudo-file
167             //    (see wifi-events.rc), and
168             // 2. Hold the WIFI_EVENT_RELEASE_PATH open,
169             //
170             // Then, when this process dies, the kernel will automatically disable any
171             // tracing in the wifi trace instance.
172             //
173             // Note that, despite Studio's suggestion that |mLastMileTraceHandle| could be demoted
174             // to a local variable, we need to stick with a field. Otherwise, the handle could be
175             // garbage collected.
176             mLastMileTraceHandle = new FileInputStream(mEventReleasePath);
177             return true;
178         } catch (IOException e) {
179             mLog.warn("Failed to open free_buffer pseudo-file: %").r(e.getMessage()).flush();
180             return false;
181         }
182     }
183 
dumpInternal(PrintWriter pw, String description, byte[] lastMileLog)184     private static void dumpInternal(PrintWriter pw, String description, byte[] lastMileLog) {
185         if (lastMileLog == null || lastMileLog.length < 1) {
186             pw.format("No last mile log for \"%s\"\n", description);
187             return;
188         }
189 
190         pw.format("-------------------------- %s ---------------------------\n", description);
191         pw.print(new String(lastMileLog));
192         pw.println("--------------------------------------------------------------------");
193     }
194 }
195