blob: b0f2003551d65703ec864b084f9e34d81d3ec796 [file] [log] [blame]
Erik Kline787d9352015-05-07 16:04:54 +09001/*
2 * Copyright (C) 2015 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
17package android.net;
18
19import android.net.LinkAddress;
20import android.net.LinkProperties;
21import android.net.ProxyInfo;
22import android.net.RouteInfo;
23import android.net.netlink.NetlinkConstants;
24import android.net.netlink.NetlinkErrorMessage;
25import android.net.netlink.NetlinkMessage;
26import android.net.netlink.NetlinkSocket;
27import android.net.netlink.RtNetlinkNeighborMessage;
28import android.net.netlink.StructNdaCacheInfo;
29import android.net.netlink.StructNdMsg;
30import android.net.netlink.StructNlMsgHdr;
31import android.os.SystemClock;
32import android.system.ErrnoException;
33import android.system.NetlinkSocketAddress;
34import android.system.OsConstants;
35import android.text.TextUtils;
36import android.util.Log;
37
38import java.io.InterruptedIOException;
39import java.net.InetAddress;
40import java.net.InetSocketAddress;
41import java.net.NetworkInterface;
42import java.net.SocketAddress;
43import java.net.SocketException;
44import java.nio.ByteBuffer;
45import java.util.Arrays;
46import java.util.HashMap;
47import java.util.HashSet;
48import java.util.List;
49import java.util.Set;
50
51
52/**
53 * IpReachabilityMonitor.
54 *
55 * Monitors on-link IP reachability and notifies callers whenever any on-link
56 * addresses of interest appear to have become unresponsive.
57 *
58 * @hide
59 */
60public class IpReachabilityMonitor {
61 private static final String TAG = "IpReachabilityMonitor";
62 private static final boolean DBG = true;
63 private static final boolean VDBG = false;
64
65 public interface Callback {
66 public void notifyLost(InetAddress ip, String logMsg);
67 }
68
69 private final Object mLock = new Object();
70 private final String mInterfaceName;
71 private final int mInterfaceIndex;
72 private final Callback mCallback;
73 private final Set<InetAddress> mIpWatchList;
74 private int mIpWatchListVersion;
75 private boolean mRunning;
Erik Klineabd31422015-05-15 18:49:17 +090076 private final NetlinkSocketObserver mNetlinkSocketObserver;
77 private final Thread mObserverThread;
Erik Kline787d9352015-05-07 16:04:54 +090078
Erik Klinecef7bc92015-05-19 14:17:11 +090079 /**
80 * Make the kernel to perform neighbor reachability detection (IPv4 ARP or IPv6 ND)
81 * for the given IP address on the specified interface index.
82 *
83 * @return true, if the request was successfully passed to the kernel; false otherwise.
84 */
85 public static boolean probeNeighbor(int ifIndex, InetAddress ip) {
86 final long IO_TIMEOUT = 300L;
87 // This currently does not cause neighbor probing if the target |ip|
88 // has been confirmed reachable within the past "delay_probe_time"
89 // seconds, i.e. within the past 5 seconds.
90 //
91 // TODO: replace with a transition directly to NUD_PROBE state once
92 // kernels are updated to do so correctly.
93 if (DBG) { Log.d(TAG, "Probing ip=" + ip.getHostAddress()); }
94
95 final byte[] msg = RtNetlinkNeighborMessage.newNewNeighborMessage(
96 1, ip, StructNdMsg.NUD_DELAY, ifIndex, null);
97 NetlinkSocket nlSocket = null;
98 boolean returnValue = false;
99
100 try {
101 nlSocket = new NetlinkSocket(OsConstants.NETLINK_ROUTE);
102 nlSocket.connectToKernel();
103 nlSocket.sendMessage(msg, 0, msg.length, IO_TIMEOUT);
104 final ByteBuffer bytes = nlSocket.recvMessage(IO_TIMEOUT);
105 final NetlinkMessage response = NetlinkMessage.parse(bytes);
106 if (response != null && response instanceof NetlinkErrorMessage &&
107 (((NetlinkErrorMessage) response).getNlMsgError() != null) &&
108 (((NetlinkErrorMessage) response).getNlMsgError().error == 0)) {
109 returnValue = true;
110 } else {
111 String errmsg;
112 if (bytes == null) {
113 errmsg = "null recvMessage";
114 } else if (response == null) {
115 bytes.position(0);
116 errmsg = "raw bytes: " + NetlinkConstants.hexify(bytes);
117 } else {
118 errmsg = response.toString();
119 }
120 Log.e(TAG, "Error probing ip=" + ip.getHostAddress() +
121 ", errmsg=" + errmsg);
122 }
123 } catch (ErrnoException | InterruptedIOException | SocketException e) {
124 Log.d(TAG, "Error probing ip=" + ip.getHostAddress(), e);
125 }
126
127 if (nlSocket != null) {
128 nlSocket.close();
129 }
130 return returnValue;
131 }
132
Erik Kline787d9352015-05-07 16:04:54 +0900133 public IpReachabilityMonitor(String ifName, Callback callback) throws IllegalArgumentException {
134 mInterfaceName = ifName;
135 int ifIndex = -1;
136 try {
137 NetworkInterface netIf = NetworkInterface.getByName(ifName);
138 mInterfaceIndex = netIf.getIndex();
139 } catch (SocketException | NullPointerException e) {
140 throw new IllegalArgumentException("invalid interface '" + ifName + "': ", e);
141 }
142 mCallback = callback;
143 mIpWatchList = new HashSet<InetAddress>();
144 mIpWatchListVersion = 0;
145 mRunning = false;
Erik Klineabd31422015-05-15 18:49:17 +0900146 mNetlinkSocketObserver = new NetlinkSocketObserver();
147 mObserverThread = new Thread(mNetlinkSocketObserver);
Erik Kline787d9352015-05-07 16:04:54 +0900148 mObserverThread.start();
149 }
150
151 public void stop() {
Erik Klineabd31422015-05-15 18:49:17 +0900152 synchronized (mLock) { mRunning = false; }
153 clearLinkProperties();
154 mNetlinkSocketObserver.clearNetlinkSocket();
Erik Kline787d9352015-05-07 16:04:54 +0900155 }
156
157 // TODO: add a public dump() method that can be called during a bug report.
158
159 private static Set<InetAddress> getOnLinkNeighbors(LinkProperties lp) {
160 Set<InetAddress> allIps = new HashSet<InetAddress>();
161
162 final List<RouteInfo> routes = lp.getRoutes();
163 for (RouteInfo route : routes) {
164 if (route.hasGateway()) {
165 allIps.add(route.getGateway());
166 }
167 }
168
169 for (InetAddress nameserver : lp.getDnsServers()) {
170 allIps.add(nameserver);
171 }
172
173 try {
174 // Don't block here for DNS lookups. If the proxy happens to be an
175 // IP literal then we add it the list, but otherwise skip it.
176 allIps.add(NetworkUtils.numericToInetAddress(lp.getHttpProxy().getHost()));
177 } catch (NullPointerException|IllegalArgumentException e) {
178 // No proxy, PAC proxy, or proxy is not a literal IP address.
179 }
180
181 Set<InetAddress> neighbors = new HashSet<InetAddress>();
182 for (InetAddress ip : allIps) {
183 // TODO: consider using the prefixes of the LinkAddresses instead
184 // of the routes--it may be more accurate.
185 for (RouteInfo route : routes) {
186 if (route.hasGateway()) {
187 continue; // Not directly connected.
188 }
189 if (route.matches(ip)) {
190 neighbors.add(ip);
191 break;
192 }
193 }
194 }
195 return neighbors;
196 }
197
198 private String describeWatchList() {
199 synchronized (mLock) {
200 return "version{" + mIpWatchListVersion + "}, " +
201 "ips=[" + TextUtils.join(",", mIpWatchList) + "]";
202 }
203 }
204
205 private boolean isWatching(InetAddress ip) {
206 synchronized (mLock) {
207 return mRunning && mIpWatchList.contains(ip);
208 }
209 }
210
Erik Kline9ce5d602015-05-16 21:10:29 +0900211 private boolean stillRunning() {
212 synchronized (mLock) {
213 return mRunning;
214 }
215 }
216
Erik Kline787d9352015-05-07 16:04:54 +0900217 public void updateLinkProperties(LinkProperties lp) {
218 if (!mInterfaceName.equals(lp.getInterfaceName())) {
219 // TODO: figure out how to cope with interface changes.
220 Log.wtf(TAG, "requested LinkProperties interface '" + lp.getInterfaceName() +
221 "' does not match: " + mInterfaceName);
222 return;
223 }
224
225 // We rely upon the caller to determine when LinkProperties have actually
226 // changed and call this at the appropriate time. Note that even though
227 // the LinkProperties may change, the set of on-link neighbors might not.
228 //
229 // Nevertheless, just clear and re-add everything.
230 final Set<InetAddress> neighbors = getOnLinkNeighbors(lp);
231 if (neighbors.isEmpty()) {
232 return;
233 }
234
235 synchronized (mLock) {
236 mIpWatchList.clear();
237 mIpWatchList.addAll(neighbors);
238 mIpWatchListVersion++;
239 }
240 if (DBG) { Log.d(TAG, "watch: " + describeWatchList()); }
241 }
242
243 public void clearLinkProperties() {
244 synchronized (mLock) {
245 mIpWatchList.clear();
246 mIpWatchListVersion++;
247 }
248 if (DBG) { Log.d(TAG, "clear: " + describeWatchList()); }
249 }
250
251 private void notifyLost(InetAddress ip, String msg) {
252 if (!isWatching(ip)) {
253 // Ignore stray notifications. This can happen when, for example,
254 // several neighbors are reported unreachable or deleted
255 // back-to-back. Because these messages are parsed serially, and
256 // this method is called for each notification, the caller above us
257 // may have already processed an earlier lost notification and
258 // cleared the watch list as it moves to handle the situation.
259 return;
260 }
261 Log.w(TAG, "ALERT: " + ip.getHostAddress() + " -- " + msg);
262 if (mCallback != null) {
263 mCallback.notifyLost(ip, msg);
264 }
265 }
266
Erik Kline9ce5d602015-05-16 21:10:29 +0900267 public void probeAll() {
268 Set<InetAddress> ipProbeList = new HashSet<InetAddress>();
269 synchronized (mLock) {
270 ipProbeList.addAll(mIpWatchList);
271 }
272 for (InetAddress target : ipProbeList) {
Erik Klinecef7bc92015-05-19 14:17:11 +0900273 if (!stillRunning()) {
274 break;
Erik Kline9ce5d602015-05-16 21:10:29 +0900275 }
Erik Klinecef7bc92015-05-19 14:17:11 +0900276 probeNeighbor(mInterfaceIndex, target);
Erik Kline9ce5d602015-05-16 21:10:29 +0900277 }
278 }
279
Erik Kline787d9352015-05-07 16:04:54 +0900280
Erik Klineabd31422015-05-15 18:49:17 +0900281 // TODO: simply the number of objects by making this extend Thread.
Erik Kline787d9352015-05-07 16:04:54 +0900282 private final class NetlinkSocketObserver implements Runnable {
283 private static final String TAG = "NetlinkSocketObserver";
284 private NetlinkSocket mSocket;
285
286 @Override
287 public void run() {
288 if (VDBG) { Log.d(TAG, "Starting observing thread."); }
289 synchronized (mLock) { mRunning = true; }
290
291 try {
292 setupNetlinkSocket();
293 } catch (ErrnoException | SocketException e) {
294 Log.e(TAG, "Failed to suitably initialize a netlink socket", e);
295 synchronized (mLock) { mRunning = false; }
296 }
297
298 ByteBuffer byteBuffer;
299 while (stillRunning()) {
300 try {
301 byteBuffer = recvKernelReply();
302 } catch (ErrnoException e) {
303 Log.w(TAG, "ErrnoException: ", e);
304 break;
305 }
306 final long whenMs = SystemClock.elapsedRealtime();
307 if (byteBuffer == null) {
308 continue;
309 }
310 parseNetlinkMessageBuffer(byteBuffer, whenMs);
311 }
312
313 clearNetlinkSocket();
314
315 synchronized (mLock) { mRunning = false; }
316 if (VDBG) { Log.d(TAG, "Finishing observing thread."); }
317 }
318
Erik Kline787d9352015-05-07 16:04:54 +0900319 private void clearNetlinkSocket() {
320 if (mSocket != null) {
321 mSocket.close();
322 }
Erik Kline787d9352015-05-07 16:04:54 +0900323 }
324
325 // TODO: Refactor the main loop to recreate the socket upon recoverable errors.
326 private void setupNetlinkSocket() throws ErrnoException, SocketException {
327 clearNetlinkSocket();
328 mSocket = new NetlinkSocket(OsConstants.NETLINK_ROUTE);
329
330 final NetlinkSocketAddress listenAddr = new NetlinkSocketAddress(
331 0, OsConstants.RTMGRP_NEIGH);
332 mSocket.bind(listenAddr);
333
334 if (VDBG) {
335 final NetlinkSocketAddress nlAddr = mSocket.getLocalAddress();
336 Log.d(TAG, "bound to sockaddr_nl{"
337 + ((long) (nlAddr.getPortId() & 0xffffffff)) + ", "
338 + nlAddr.getGroupsMask()
339 + "}");
340 }
341 }
342
343 private ByteBuffer recvKernelReply() throws ErrnoException {
344 try {
345 return mSocket.recvMessage(0);
346 } catch (InterruptedIOException e) {
347 // Interruption or other error, e.g. another thread closed our file descriptor.
348 } catch (ErrnoException e) {
349 if (e.errno != OsConstants.EAGAIN) {
350 throw e;
351 }
352 }
353 return null;
354 }
355
356 private void parseNetlinkMessageBuffer(ByteBuffer byteBuffer, long whenMs) {
357 while (byteBuffer.remaining() > 0) {
358 final int position = byteBuffer.position();
359 final NetlinkMessage nlMsg = NetlinkMessage.parse(byteBuffer);
360 if (nlMsg == null || nlMsg.getHeader() == null) {
361 byteBuffer.position(position);
362 Log.e(TAG, "unparsable netlink msg: " + NetlinkConstants.hexify(byteBuffer));
363 break;
364 }
365
366 final int srcPortId = nlMsg.getHeader().nlmsg_pid;
367 if (srcPortId != 0) {
368 Log.e(TAG, "non-kernel source portId: " + ((long) (srcPortId & 0xffffffff)));
369 break;
370 }
371
372 if (nlMsg instanceof NetlinkErrorMessage) {
373 Log.e(TAG, "netlink error: " + nlMsg);
374 continue;
375 } else if (!(nlMsg instanceof RtNetlinkNeighborMessage)) {
376 if (DBG) {
377 Log.d(TAG, "non-rtnetlink neighbor msg: " + nlMsg);
378 }
379 continue;
380 }
381
382 evaluateRtNetlinkNeighborMessage((RtNetlinkNeighborMessage) nlMsg, whenMs);
383 }
384 }
385
386 private void evaluateRtNetlinkNeighborMessage(
387 RtNetlinkNeighborMessage neighMsg, long whenMs) {
388 final StructNdMsg ndMsg = neighMsg.getNdHeader();
389 if (ndMsg == null || ndMsg.ndm_ifindex != mInterfaceIndex) {
390 return;
391 }
392
393 final InetAddress destination = neighMsg.getDestination();
394 if (!isWatching(destination)) {
395 return;
396 }
397
398 final short msgType = neighMsg.getHeader().nlmsg_type;
399 final short nudState = ndMsg.ndm_state;
400 final String eventMsg = "NeighborEvent{"
401 + "elapsedMs=" + whenMs + ", "
402 + destination.getHostAddress() + ", "
403 + "[" + NetlinkConstants.hexify(neighMsg.getLinkLayerAddress()) + "], "
404 + NetlinkConstants.stringForNlMsgType(msgType) + ", "
405 + StructNdMsg.stringForNudState(nudState)
406 + "}";
407
408 if (VDBG) {
409 Log.d(TAG, neighMsg.toString());
410 } else if (DBG) {
411 Log.d(TAG, eventMsg);
412 }
413
414 if ((msgType == NetlinkConstants.RTM_DELNEIGH) ||
415 (nudState == StructNdMsg.NUD_FAILED)) {
416 final String logMsg = "FAILURE: " + eventMsg;
417 notifyLost(destination, logMsg);
418 }
419 }
420 }
421}