diff options
author | Erik Kline <ek@google.com> | 2015-05-13 13:11:12 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2015-05-13 13:11:13 +0000 |
commit | cf304fb35c2086601178858e307d3dda36dbbff7 (patch) | |
tree | c603228197f3af52c1df1d7d068d594c9a45b5be | |
parent | ebc456691e15fb373130988687d3c70d76b97424 (diff) | |
parent | 787d935611c8a6d1ed66e4d718f78084fe7bd3c5 (diff) | |
download | frameworks_base-cf304fb35c2086601178858e307d3dda36dbbff7.zip frameworks_base-cf304fb35c2086601178858e307d3dda36dbbff7.tar.gz frameworks_base-cf304fb35c2086601178858e307d3dda36dbbff7.tar.bz2 |
Merge "Initial IpReachabilityMonitor implementation." into mnc-dev
-rw-r--r-- | core/java/android/net/IpReachabilityMonitor.java | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/core/java/android/net/IpReachabilityMonitor.java b/core/java/android/net/IpReachabilityMonitor.java new file mode 100644 index 0000000..7e1c05b --- /dev/null +++ b/core/java/android/net/IpReachabilityMonitor.java @@ -0,0 +1,354 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.ProxyInfo; +import android.net.RouteInfo; +import android.net.netlink.NetlinkConstants; +import android.net.netlink.NetlinkErrorMessage; +import android.net.netlink.NetlinkMessage; +import android.net.netlink.NetlinkSocket; +import android.net.netlink.RtNetlinkNeighborMessage; +import android.net.netlink.StructNdaCacheInfo; +import android.net.netlink.StructNdMsg; +import android.net.netlink.StructNlMsgHdr; +import android.os.SystemClock; +import android.system.ErrnoException; +import android.system.NetlinkSocketAddress; +import android.system.OsConstants; +import android.text.TextUtils; +import android.util.Log; + +import java.io.InterruptedIOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.NetworkInterface; +import java.net.SocketAddress; +import java.net.SocketException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + + +/** + * IpReachabilityMonitor. + * + * Monitors on-link IP reachability and notifies callers whenever any on-link + * addresses of interest appear to have become unresponsive. + * + * @hide + */ +public class IpReachabilityMonitor { + private static final String TAG = "IpReachabilityMonitor"; + private static final boolean DBG = true; + private static final boolean VDBG = false; + + public interface Callback { + public void notifyLost(InetAddress ip, String logMsg); + } + + private final Object mLock = new Object(); + private final String mInterfaceName; + private final int mInterfaceIndex; + private final Callback mCallback; + private final Set<InetAddress> mIpWatchList; + private int mIpWatchListVersion; + private boolean mRunning; + final private Thread mObserverThread; + + // TODO: consider passing in a NetworkInterface object from the caller. + public IpReachabilityMonitor(String ifName, Callback callback) throws IllegalArgumentException { + mInterfaceName = ifName; + int ifIndex = -1; + try { + NetworkInterface netIf = NetworkInterface.getByName(ifName); + mInterfaceIndex = netIf.getIndex(); + } catch (SocketException | NullPointerException e) { + throw new IllegalArgumentException("invalid interface '" + ifName + "': ", e); + } + mCallback = callback; + mIpWatchList = new HashSet<InetAddress>(); + mIpWatchListVersion = 0; + mRunning = false; + mObserverThread = new Thread(new NetlinkSocketObserver()); + mObserverThread.start(); + } + + public void stop() { + synchronized (mLock) { + mRunning = false; + mIpWatchList.clear(); + } + } + + // TODO: add a public dump() method that can be called during a bug report. + + private static Set<InetAddress> getOnLinkNeighbors(LinkProperties lp) { + Set<InetAddress> allIps = new HashSet<InetAddress>(); + + final List<RouteInfo> routes = lp.getRoutes(); + for (RouteInfo route : routes) { + if (route.hasGateway()) { + allIps.add(route.getGateway()); + } + } + + for (InetAddress nameserver : lp.getDnsServers()) { + allIps.add(nameserver); + } + + try { + // Don't block here for DNS lookups. If the proxy happens to be an + // IP literal then we add it the list, but otherwise skip it. + allIps.add(NetworkUtils.numericToInetAddress(lp.getHttpProxy().getHost())); + } catch (NullPointerException|IllegalArgumentException e) { + // No proxy, PAC proxy, or proxy is not a literal IP address. + } + + Set<InetAddress> neighbors = new HashSet<InetAddress>(); + for (InetAddress ip : allIps) { + // TODO: consider using the prefixes of the LinkAddresses instead + // of the routes--it may be more accurate. + for (RouteInfo route : routes) { + if (route.hasGateway()) { + continue; // Not directly connected. + } + if (route.matches(ip)) { + neighbors.add(ip); + break; + } + } + } + return neighbors; + } + + private String describeWatchList() { + synchronized (mLock) { + return "version{" + mIpWatchListVersion + "}, " + + "ips=[" + TextUtils.join(",", mIpWatchList) + "]"; + } + } + + private boolean isWatching(InetAddress ip) { + synchronized (mLock) { + return mRunning && mIpWatchList.contains(ip); + } + } + + public void updateLinkProperties(LinkProperties lp) { + if (!mInterfaceName.equals(lp.getInterfaceName())) { + // TODO: figure out how to cope with interface changes. + Log.wtf(TAG, "requested LinkProperties interface '" + lp.getInterfaceName() + + "' does not match: " + mInterfaceName); + return; + } + + // We rely upon the caller to determine when LinkProperties have actually + // changed and call this at the appropriate time. Note that even though + // the LinkProperties may change, the set of on-link neighbors might not. + // + // Nevertheless, just clear and re-add everything. + final Set<InetAddress> neighbors = getOnLinkNeighbors(lp); + if (neighbors.isEmpty()) { + return; + } + + synchronized (mLock) { + mIpWatchList.clear(); + mIpWatchList.addAll(neighbors); + mIpWatchListVersion++; + } + if (DBG) { Log.d(TAG, "watch: " + describeWatchList()); } + } + + public void clearLinkProperties() { + synchronized (mLock) { + mIpWatchList.clear(); + mIpWatchListVersion++; + } + if (DBG) { Log.d(TAG, "clear: " + describeWatchList()); } + } + + private void notifyLost(InetAddress ip, String msg) { + if (!isWatching(ip)) { + // Ignore stray notifications. This can happen when, for example, + // several neighbors are reported unreachable or deleted + // back-to-back. Because these messages are parsed serially, and + // this method is called for each notification, the caller above us + // may have already processed an earlier lost notification and + // cleared the watch list as it moves to handle the situation. + return; + } + Log.w(TAG, "ALERT: " + ip.getHostAddress() + " -- " + msg); + if (mCallback != null) { + mCallback.notifyLost(ip, msg); + } + } + + + private final class NetlinkSocketObserver implements Runnable { + private static final String TAG = "NetlinkSocketObserver"; + private NetlinkSocket mSocket; + + @Override + public void run() { + if (VDBG) { Log.d(TAG, "Starting observing thread."); } + synchronized (mLock) { mRunning = true; } + + try { + setupNetlinkSocket(); + } catch (ErrnoException | SocketException e) { + Log.e(TAG, "Failed to suitably initialize a netlink socket", e); + synchronized (mLock) { mRunning = false; } + } + + ByteBuffer byteBuffer; + while (stillRunning()) { + try { + byteBuffer = recvKernelReply(); + } catch (ErrnoException e) { + Log.w(TAG, "ErrnoException: ", e); + break; + } + final long whenMs = SystemClock.elapsedRealtime(); + if (byteBuffer == null) { + continue; + } + parseNetlinkMessageBuffer(byteBuffer, whenMs); + } + + clearNetlinkSocket(); + + synchronized (mLock) { mRunning = false; } + if (VDBG) { Log.d(TAG, "Finishing observing thread."); } + } + + private boolean stillRunning() { + synchronized (mLock) { + return mRunning; + } + } + + private void clearNetlinkSocket() { + if (mSocket != null) { + mSocket.close(); + } + mSocket = null; + } + + // TODO: Refactor the main loop to recreate the socket upon recoverable errors. + private void setupNetlinkSocket() throws ErrnoException, SocketException { + clearNetlinkSocket(); + mSocket = new NetlinkSocket(OsConstants.NETLINK_ROUTE); + + final NetlinkSocketAddress listenAddr = new NetlinkSocketAddress( + 0, OsConstants.RTMGRP_NEIGH); + mSocket.bind(listenAddr); + + if (VDBG) { + final NetlinkSocketAddress nlAddr = mSocket.getLocalAddress(); + Log.d(TAG, "bound to sockaddr_nl{" + + ((long) (nlAddr.getPortId() & 0xffffffff)) + ", " + + nlAddr.getGroupsMask() + + "}"); + } + } + + private ByteBuffer recvKernelReply() throws ErrnoException { + try { + return mSocket.recvMessage(0); + } catch (InterruptedIOException e) { + // Interruption or other error, e.g. another thread closed our file descriptor. + } catch (ErrnoException e) { + if (e.errno != OsConstants.EAGAIN) { + throw e; + } + } + return null; + } + + private void parseNetlinkMessageBuffer(ByteBuffer byteBuffer, long whenMs) { + while (byteBuffer.remaining() > 0) { + final int position = byteBuffer.position(); + final NetlinkMessage nlMsg = NetlinkMessage.parse(byteBuffer); + if (nlMsg == null || nlMsg.getHeader() == null) { + byteBuffer.position(position); + Log.e(TAG, "unparsable netlink msg: " + NetlinkConstants.hexify(byteBuffer)); + break; + } + + final int srcPortId = nlMsg.getHeader().nlmsg_pid; + if (srcPortId != 0) { + Log.e(TAG, "non-kernel source portId: " + ((long) (srcPortId & 0xffffffff))); + break; + } + + if (nlMsg instanceof NetlinkErrorMessage) { + Log.e(TAG, "netlink error: " + nlMsg); + continue; + } else if (!(nlMsg instanceof RtNetlinkNeighborMessage)) { + if (DBG) { + Log.d(TAG, "non-rtnetlink neighbor msg: " + nlMsg); + } + continue; + } + + evaluateRtNetlinkNeighborMessage((RtNetlinkNeighborMessage) nlMsg, whenMs); + } + } + + private void evaluateRtNetlinkNeighborMessage( + RtNetlinkNeighborMessage neighMsg, long whenMs) { + final StructNdMsg ndMsg = neighMsg.getNdHeader(); + if (ndMsg == null || ndMsg.ndm_ifindex != mInterfaceIndex) { + return; + } + + final InetAddress destination = neighMsg.getDestination(); + if (!isWatching(destination)) { + return; + } + + final short msgType = neighMsg.getHeader().nlmsg_type; + final short nudState = ndMsg.ndm_state; + final String eventMsg = "NeighborEvent{" + + "elapsedMs=" + whenMs + ", " + + destination.getHostAddress() + ", " + + "[" + NetlinkConstants.hexify(neighMsg.getLinkLayerAddress()) + "], " + + NetlinkConstants.stringForNlMsgType(msgType) + ", " + + StructNdMsg.stringForNudState(nudState) + + "}"; + + if (VDBG) { + Log.d(TAG, neighMsg.toString()); + } else if (DBG) { + Log.d(TAG, eventMsg); + } + + if ((msgType == NetlinkConstants.RTM_DELNEIGH) || + (nudState == StructNdMsg.NUD_FAILED)) { + final String logMsg = "FAILURE: " + eventMsg; + notifyLost(destination, logMsg); + } + } + } +} |