Loading core/java/android/net/IpReachabilityMonitor.java 0 → 100644 +354 −0 Original line number Diff line number Diff line /* * 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); } } } } Loading
core/java/android/net/IpReachabilityMonitor.java 0 → 100644 +354 −0 Original line number Diff line number Diff line /* * 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); } } } }