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