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.makePacketSocketAddress; 20 import static android.system.OsConstants.AF_PACKET; 21 import static android.system.OsConstants.ETH_P_ALL; 22 import static android.system.OsConstants.SOCK_NONBLOCK; 23 import static android.system.OsConstants.SOCK_RAW; 24 25 import android.net.util.ConnectivityPacketSummary; 26 import android.os.Handler; 27 import android.os.SystemClock; 28 import android.system.ErrnoException; 29 import android.system.Os; 30 import android.text.TextUtils; 31 import android.util.LocalLog; 32 import android.util.Log; 33 34 import com.android.internal.util.HexDump; 35 import com.android.internal.util.TokenBucket; 36 import com.android.net.module.util.InterfaceParams; 37 import com.android.net.module.util.PacketReader; 38 import com.android.networkstack.util.NetworkStackUtils; 39 40 import java.io.FileDescriptor; 41 import java.io.IOException; 42 43 44 /** 45 * Critical connectivity packet tracking daemon. 46 * 47 * Tracks ARP, DHCPv4, and IPv6 RS/RA/NS/NA packets. 48 * 49 * This class's constructor, start() and stop() methods must only be called 50 * from the same thread on which the passed in |log| is accessed. 51 * 52 * Log lines include a hexdump of the packet, which can be decoded via: 53 * 54 * echo -n H3XSTR1NG | sed -e 's/\([0-9A-F][0-9A-F]\)/\1 /g' -e 's/^/000000 /' 55 * | text2pcap - - 56 * | tcpdump -n -vv -e -r - 57 * 58 * @hide 59 */ 60 public class ConnectivityPacketTracker { 61 private static final String TAG = ConnectivityPacketTracker.class.getSimpleName(); 62 private static final boolean DBG = false; 63 private static final String MARK_START = "--- START ---"; 64 private static final String MARK_STOP = "--- STOP ---"; 65 private static final String MARK_NAMED_START = "--- START (%s) ---"; 66 private static final String MARK_NAMED_STOP = "--- STOP (%s) ---"; 67 // Use a TokenBucket to limit CPU usage of logging packets in steady state. 68 private static final int TOKEN_FILL_RATE = 50; // Maximum one packet every 20ms. 69 private static final int MAX_BURST_LENGTH = 100; // Maximum burst 100 packets. 70 71 private final String mTag; 72 private final LocalLog mLog; 73 private final PacketReader mPacketListener; 74 private final TokenBucket mTokenBucket = new TokenBucket(TOKEN_FILL_RATE, MAX_BURST_LENGTH); 75 private long mLastRateLimitLogTimeMs = 0; 76 private boolean mRunning; 77 private String mDisplayName; 78 ConnectivityPacketTracker(Handler h, InterfaceParams ifParams, LocalLog log)79 public ConnectivityPacketTracker(Handler h, InterfaceParams ifParams, LocalLog log) { 80 if (ifParams == null) throw new IllegalArgumentException("null InterfaceParams"); 81 82 mTag = TAG + "." + ifParams.name; 83 mLog = log; 84 mPacketListener = new PacketListener(h, ifParams); 85 } 86 start(String displayName)87 public void start(String displayName) { 88 mRunning = true; 89 mDisplayName = displayName; 90 mPacketListener.start(); 91 } 92 stop()93 public void stop() { 94 mPacketListener.stop(); 95 mRunning = false; 96 mDisplayName = null; 97 } 98 99 private final class PacketListener extends PacketReader { 100 private final InterfaceParams mInterface; 101 PacketListener(Handler h, InterfaceParams ifParams)102 PacketListener(Handler h, InterfaceParams ifParams) { 103 super(h, ifParams.defaultMtu); 104 mInterface = ifParams; 105 } 106 107 @Override createFd()108 protected FileDescriptor createFd() { 109 FileDescriptor s = null; 110 try { 111 s = Os.socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, 0); 112 NetworkStackUtils.attachControlPacketFilter(s); 113 Os.bind(s, makePacketSocketAddress(ETH_P_ALL, mInterface.index)); 114 } catch (ErrnoException | IOException e) { 115 logError("Failed to create packet tracking socket: ", e); 116 closeFd(s); 117 return null; 118 } 119 return s; 120 } 121 122 @Override handlePacket(byte[] recvbuf, int length)123 protected void handlePacket(byte[] recvbuf, int length) { 124 if (!mTokenBucket.get()) { 125 // Rate limited. Log once every second so the user knows packets are missing. 126 final long now = SystemClock.elapsedRealtime(); 127 if (now >= mLastRateLimitLogTimeMs + 1000) { 128 addLogEntry("Warning: too many packets, rate-limiting to one every " + 129 TOKEN_FILL_RATE + "ms"); 130 mLastRateLimitLogTimeMs = now; 131 } 132 return; 133 } 134 135 final String summary; 136 try { 137 summary = ConnectivityPacketSummary.summarize(mInterface.macAddr, recvbuf, length); 138 if (summary == null) return; 139 } catch (Exception e) { 140 if (DBG) Log.d(mTag, "Error creating packet summary", e); 141 return; 142 } 143 144 if (DBG) Log.d(mTag, summary); 145 addLogEntry(summary + "\n[" + HexDump.toHexString(recvbuf, 0, length) + "]"); 146 } 147 148 @Override onStart()149 protected void onStart() { 150 final String msg = TextUtils.isEmpty(mDisplayName) 151 ? MARK_START 152 : String.format(MARK_NAMED_START, mDisplayName); 153 mLog.log(msg); 154 } 155 156 @Override onStop()157 protected void onStop() { 158 String msg = TextUtils.isEmpty(mDisplayName) 159 ? MARK_STOP 160 : String.format(MARK_NAMED_STOP, mDisplayName); 161 if (!mRunning) msg += " (packet listener stopped unexpectedly)"; 162 mLog.log(msg); 163 } 164 165 @Override logError(String msg, Exception e)166 protected void logError(String msg, Exception e) { 167 Log.e(mTag, msg, e); 168 addLogEntry(msg + e); 169 } 170 addLogEntry(String entry)171 private void addLogEntry(String entry) { 172 mLog.log(entry); 173 } 174 } 175 } 176