• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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 android.net.ip;
18 
19 import static android.net.util.SocketUtils.closeSocket;
20 import static android.net.util.SocketUtils.makePacketSocketAddress;
21 import static android.system.OsConstants.AF_PACKET;
22 import static android.system.OsConstants.ETH_P_ALL;
23 import static android.system.OsConstants.SOCK_NONBLOCK;
24 import static android.system.OsConstants.SOCK_RAW;
25 
26 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
27 
28 import android.net.util.ConnectivityPacketSummary;
29 import android.os.Handler;
30 import android.os.SystemClock;
31 import android.system.ErrnoException;
32 import android.system.Os;
33 import android.text.TextUtils;
34 import android.util.LocalLog;
35 import android.util.Log;
36 import android.util.LruCache;
37 
38 import androidx.annotation.NonNull;
39 import androidx.annotation.Nullable;
40 
41 import com.android.internal.annotations.VisibleForTesting;
42 import com.android.internal.util.HexDump;
43 import com.android.internal.util.TokenBucket;
44 import com.android.net.module.util.InterfaceParams;
45 import com.android.net.module.util.PacketReader;
46 import com.android.networkstack.util.NetworkStackUtils;
47 
48 import java.io.FileDescriptor;
49 import java.io.IOException;
50 import java.util.Arrays;
51 import java.util.Locale;
52 import java.util.Objects;
53 
54 
55 /**
56  * Critical connectivity packet tracking daemon.
57  *
58  * Tracks ARP, DHCPv4, and IPv6 RS/RA/NS/NA packets.
59  *
60  * This class's constructor, start() and stop() methods must only be called
61  * from the same thread on which the passed in |log| is accessed.
62  *
63  * Log lines include a hexdump of the packet, which can be decoded via:
64  *
65  *     echo -n H3XSTR1NG | sed -e 's/\([0-9A-F][0-9A-F]\)/\1 /g' -e 's/^/000000 /'
66  *                       | text2pcap - -
67  *                       | tcpdump -n -vv -e -r -
68  *
69  * @hide
70  */
71 public class ConnectivityPacketTracker {
72     /**
73      * Dependencies class for testing.
74      */
75     @VisibleForTesting(visibility = PRIVATE)
76     public static class Dependencies {
77         private final LocalLog mLog;
Dependencies(final LocalLog log)78         public Dependencies(final LocalLog log) {
79             mLog = log;
80         }
81 
82         /**
83          * Creates a raw packet socket for reading network packets.
84          *
85          * This method creates a socket and binds it to the specified network interface index,
86          * and optionally attaches a control packet filter.
87          *
88          * @param ifIndex      The index of the network interface to bind the socket to.
89          * @param attachFilter If true, attaches a control packet filter to the socket.
90          * @return The FileDescriptor of the created socket, or null if an error occurred.
91          */
92         @Nullable
createPacketReaderSocket(int ifIndex, boolean attachFilter)93         public FileDescriptor createPacketReaderSocket(int ifIndex, boolean attachFilter) {
94             FileDescriptor socket = null;
95             try {
96                 socket = Os.socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, 0);
97                 if (attachFilter) {
98                     NetworkStackUtils.attachControlPacketFilter(socket);
99                 }
100                 Os.bind(socket, makePacketSocketAddress(ETH_P_ALL, ifIndex));
101             } catch (ErrnoException | IOException e) {
102                 final String msg = "Failed to create packet tracking socket: ";
103                 Log.e(TAG, msg, e);
104                 mLog.log(msg + e);
105                 closeFd(socket);
106                 return null;
107             }
108             return socket;
109         }
110 
111 
112         /**
113          * Gets the maximum size of a captured packet.
114          *
115          * @return The maximum capture packet size.
116          */
getMaxCapturePktSize()117         public int getMaxCapturePktSize() {
118             return MAX_CAPTURE_PACKET_SIZE;
119         }
120 
closeFd(FileDescriptor fd)121         private void closeFd(FileDescriptor fd) {
122             try {
123                 closeSocket(fd);
124             } catch (IOException e) {
125                 Log.e(TAG, "failed to close socket");
126             }
127         }
128     }
129 
130     private static final String TAG = ConnectivityPacketTracker.class.getSimpleName();
131     private static final boolean DBG = false;
132     private static final String MARK_START = "--- START ---";
133     private static final String MARK_STOP = "--- STOP ---";
134     private static final String MARK_NAMED_START = "--- START (%s) ---";
135     private static final String MARK_NAMED_STOP = "--- STOP (%s) ---";
136     // Use a TokenBucket to limit CPU usage of logging packets in steady state.
137     private static final int TOKEN_FILL_RATE = 50;   // Maximum one packet every 20ms.
138     private static final int MAX_BURST_LENGTH = 100; // Maximum burst 100 packets.
139     private static final int MAX_CAPTURE_PACKET_SIZE = 100; // Maximum capture packet size
140 
141     private final String mTag;
142     private final LocalLog mLog;
143     private final PacketReader mPacketListener;
144     private final TokenBucket mTokenBucket = new TokenBucket(TOKEN_FILL_RATE, MAX_BURST_LENGTH);
145     // store packet hex string in uppercase as key, receive packet count as value
146     private final LruCache<String, Integer> mPacketCache;
147     private final Dependencies mDependencies;
148     private final boolean mAttachFilter;
149     private long mLastRateLimitLogTimeMs = 0;
150     private boolean mRunning;
151     private boolean mCapturing;
152     private String mDisplayName;
153 
ConnectivityPacketTracker( Handler h, InterfaceParams ifParams, LocalLog log, boolean attachFilter)154     public ConnectivityPacketTracker(
155             Handler h,
156             InterfaceParams ifParams,
157             LocalLog log,
158             boolean attachFilter) {
159         this(h, ifParams, log, new Dependencies(log), attachFilter);
160     }
161 
162     /**
163      * Sets the capture state.
164      *
165      * <p>This method controls whether packet capture is enabled. If capture is disabled,
166      * the internal packet map is cleared.</p>
167      *
168      * @param isCapture {@code true} to enable capture, {@code false} to disable capture
169      */
setCapture(boolean isCapture)170     public void setCapture(boolean isCapture) {
171         mCapturing = isCapture;
172         if (!isCapture) {
173             mPacketCache.evictAll();
174         }
175     }
176 
177     /**
178      * Gets the count of matched packets for a given pattern.
179      *
180      * <p>This method searches the internal packet map for packets matching the specified pattern
181      * and returns the count of such packets.</p>
182      *
183      * @param packet The hex string pattern to match against
184      * @return The count of packets matching the pattern, or 0 if no matches are found
185      */
getMatchedPacketCount(String packet)186     public int getMatchedPacketCount(String packet) {
187         // always convert to upper case since we use upper case when capturing
188         final String packetPattern = packet.toUpperCase(Locale.ROOT);
189         final Integer count = mPacketCache.get(packetPattern);
190         return (count != null) ? count : 0;
191     }
192 
start(String displayName)193     public void start(String displayName) {
194         mRunning = true;
195         mDisplayName = displayName;
196         mPacketListener.start();
197     }
198 
stop()199     public void stop() {
200         mPacketListener.stop();
201         mRunning = false;
202         mDisplayName = null;
203     }
204 
205     @VisibleForTesting(visibility = PRIVATE)
getCapturePacketTypeCount()206     public int getCapturePacketTypeCount() {
207         return mPacketCache.size();
208     }
209 
210     @VisibleForTesting(visibility = PRIVATE)
ConnectivityPacketTracker( @onNull Handler handler, @NonNull InterfaceParams ifParams, @NonNull LocalLog log, @NonNull Dependencies dependencies, boolean attachFilter )211     public ConnectivityPacketTracker(
212             @NonNull Handler handler,
213             @NonNull InterfaceParams ifParams,
214             @NonNull LocalLog log,
215             @NonNull Dependencies dependencies,
216             boolean attachFilter
217     ) {
218         mTag = TAG + "." + Objects.requireNonNull(ifParams).name;
219         mLog = log;
220         mAttachFilter = attachFilter;
221         mPacketListener = new PacketListener(handler, ifParams);
222         mDependencies = dependencies;
223         mPacketCache = new LruCache<>(mDependencies.getMaxCapturePktSize());
224     }
225 
226     private final class PacketListener extends PacketReader {
227         private final InterfaceParams mInterface;
228 
PacketListener(Handler h, InterfaceParams ifParams)229         PacketListener(Handler h, InterfaceParams ifParams) {
230             super(h, ifParams.defaultMtu);
231             mInterface = ifParams;
232         }
233 
234         @Override
createFd()235         protected FileDescriptor createFd() {
236             return mDependencies.createPacketReaderSocket(mInterface.index, mAttachFilter);
237         }
238 
239         @Override
handlePacket(byte[] recvbuf, int length)240         protected void handlePacket(byte[] recvbuf, int length) {
241             capturePacket(recvbuf, length);
242 
243             if (!mTokenBucket.get()) {
244                 // Rate limited. Log once every second so the user knows packets are missing.
245                 final long now = SystemClock.elapsedRealtime();
246                 if (now >= mLastRateLimitLogTimeMs + 1000) {
247                     addLogEntry("Warning: too many packets, rate-limiting to one every " +
248                                 TOKEN_FILL_RATE + "ms");
249                     mLastRateLimitLogTimeMs = now;
250                 }
251                 return;
252             }
253 
254             final String summary;
255             try {
256                 summary = ConnectivityPacketSummary.summarize(mInterface.macAddr, recvbuf, length);
257                 if (summary == null) return;
258             } catch (Exception e) {
259                 if (DBG) Log.d(mTag, "Error creating packet summary", e);
260                 return;
261             }
262 
263             if (DBG) Log.d(mTag, summary);
264             addLogEntry(summary + "\n[" + HexDump.toHexString(recvbuf, 0, length) + "]");
265         }
266 
267         @Override
onStart()268         protected void onStart() {
269             final String msg = TextUtils.isEmpty(mDisplayName)
270                     ? MARK_START
271                     : String.format(MARK_NAMED_START, mDisplayName);
272             mLog.log(msg);
273         }
274 
275         @Override
onStop()276         protected void onStop() {
277             String msg = TextUtils.isEmpty(mDisplayName)
278                     ? MARK_STOP
279                     : String.format(MARK_NAMED_STOP, mDisplayName);
280             if (!mRunning) msg += " (packet listener stopped unexpectedly)";
281             mLog.log(msg);
282         }
283 
284         @Override
logError(String msg, Exception e)285         protected void logError(String msg, Exception e) {
286             Log.e(mTag, msg, e);
287             addLogEntry(msg + e);
288         }
289 
addLogEntry(String entry)290         private void addLogEntry(String entry) {
291             mLog.log(entry);
292         }
293 
capturePacket(byte[] recvbuf, int length)294         private void capturePacket(byte[] recvbuf, int length) {
295             if (!mCapturing) {
296                 return;
297             }
298 
299             byte[] pkt = Arrays.copyOfRange(
300                     recvbuf, 0, Math.min(recvbuf.length, length));
301             final String pktHexString = HexDump.toHexString(pkt);
302             final Integer pktCnt = mPacketCache.get(pktHexString);
303             if (pktCnt == null) {
304                 mPacketCache.put(pktHexString, 1);
305             } else {
306                 mPacketCache.put(pktHexString, pktCnt + 1);
307             }
308         }
309     }
310 }
311