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