am cf304fb3: Merge "Initial IpReachabilityMonitor implementation." into mnc-dev
* commit 'cf304fb35c2086601178858e307d3dda36dbbff7': Initial IpReachabilityMonitor implementation.
This commit is contained in:
354
core/java/android/net/IpReachabilityMonitor.java
Normal file
354
core/java/android/net/IpReachabilityMonitor.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user