Merge "Refactor to IpNeighborMonitor and single-threaded semantics"

am: b501e7e25b

Change-Id: Iddf69a93400af2262e4522592f9898ee0b79314c
This commit is contained in:
Erik Kline
2017-12-08 05:58:32 +00:00
committed by android-build-merger
10 changed files with 431 additions and 439 deletions

View File

@@ -19,7 +19,7 @@ package android.net.ip;
import static android.system.OsConstants.*; import static android.system.OsConstants.*;
import android.net.NetworkUtils; import android.net.NetworkUtils;
import android.net.util.BlockingSocketReader; import android.net.util.PacketReader;
import android.net.util.ConnectivityPacketSummary; import android.net.util.ConnectivityPacketSummary;
import android.os.Handler; import android.os.Handler;
import android.system.ErrnoException; import android.system.ErrnoException;
@@ -65,7 +65,7 @@ public class ConnectivityPacketTracker {
private final String mTag; private final String mTag;
private final LocalLog mLog; private final LocalLog mLog;
private final BlockingSocketReader mPacketListener; private final PacketReader mPacketListener;
private boolean mRunning; private boolean mRunning;
private String mDisplayName; private String mDisplayName;
@@ -101,7 +101,7 @@ public class ConnectivityPacketTracker {
mDisplayName = null; mDisplayName = null;
} }
private final class PacketListener extends BlockingSocketReader { private final class PacketListener extends PacketReader {
private final int mIfIndex; private final int mIfIndex;
private final byte mHwAddr[]; private final byte mHwAddr[];

View File

@@ -815,6 +815,15 @@ public class IpClient extends StateMachine {
pw.println(Objects.toString(provisioningConfig, "N/A")); pw.println(Objects.toString(provisioningConfig, "N/A"));
pw.decreaseIndent(); pw.decreaseIndent();
final IpReachabilityMonitor iprm = mIpReachabilityMonitor;
if (iprm != null) {
pw.println();
pw.println(mTag + " current IpReachabilityMonitor state:");
pw.increaseIndent();
iprm.dump(pw);
pw.decreaseIndent();
}
pw.println(); pw.println();
pw.println(mTag + " StateMachine dump:"); pw.println(mTag + " StateMachine dump:");
pw.increaseIndent(); pw.increaseIndent();
@@ -1237,6 +1246,7 @@ public class IpClient extends StateMachine {
mIpReachabilityMonitor = new IpReachabilityMonitor( mIpReachabilityMonitor = new IpReachabilityMonitor(
mContext, mContext,
mInterfaceName, mInterfaceName,
getHandler(),
mLog, mLog,
new IpReachabilityMonitor.Callback() { new IpReachabilityMonitor.Callback() {
@Override @Override

View File

@@ -0,0 +1,236 @@
/*
* Copyright (C) 2017 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.ip;
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.StructNdMsg;
import android.net.netlink.StructNlMsgHdr;
import android.net.util.PacketReader;
import android.net.util.SharedLog;
import android.os.Handler;
import android.os.SystemClock;
import android.system.ErrnoException;
import android.system.NetlinkSocketAddress;
import android.system.Os;
import android.system.OsConstants;
import android.util.Log;
import com.android.internal.util.BitUtils;
import libcore.io.IoUtils;
import libcore.io.Libcore;
import java.io.FileDescriptor;
import java.net.InetAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.StringJoiner;
/**
* IpNeighborMonitor.
*
* Monitors the kernel rtnetlink neighbor notifications and presents to callers
* NeighborEvents describing each event. Callers can provide a consumer instance
* to both filter (e.g. by interface index and IP address) and handle the
* generated NeighborEvents.
*
* @hide
*/
public class IpNeighborMonitor extends PacketReader {
private static final String TAG = IpNeighborMonitor.class.getSimpleName();
private static final boolean DBG = false;
private static final boolean VDBG = false;
/**
* Make the kernel perform neighbor reachability detection (IPv4 ARP or IPv6 ND)
* for the given IP address on the specified interface index.
*
* @return 0 if the request was successfully passed to the kernel; otherwise return
* a non-zero error code.
*/
public static int startKernelNeighborProbe(int ifIndex, InetAddress ip) {
final String msgSnippet = "probing ip=" + ip.getHostAddress() + "%" + ifIndex;
if (DBG) { Log.d(TAG, msgSnippet); }
final byte[] msg = RtNetlinkNeighborMessage.newNewNeighborMessage(
1, ip, StructNdMsg.NUD_PROBE, ifIndex, null);
try {
NetlinkSocket.sendOneShotKernelMessage(OsConstants.NETLINK_ROUTE, msg);
} catch (ErrnoException e) {
Log.e(TAG, "Error " + msgSnippet + ": " + e);
return -e.errno;
}
return 0;
}
public static class NeighborEvent {
final long elapsedMs;
final short msgType;
final int ifindex;
final InetAddress ip;
final short nudState;
final byte[] linkLayerAddr;
public NeighborEvent(long elapsedMs, short msgType, int ifindex, InetAddress ip,
short nudState, byte[] linkLayerAddr) {
this.elapsedMs = elapsedMs;
this.msgType = msgType;
this.ifindex = ifindex;
this.ip = ip;
this.nudState = nudState;
this.linkLayerAddr = linkLayerAddr;
}
boolean isConnected() {
return (msgType != NetlinkConstants.RTM_DELNEIGH) &&
StructNdMsg.isNudStateConnected(nudState);
}
boolean isValid() {
return (msgType != NetlinkConstants.RTM_DELNEIGH) &&
StructNdMsg.isNudStateValid(nudState);
}
@Override
public String toString() {
final StringJoiner j = new StringJoiner(",", "NeighborEvent{", "}");
return j.add("@" + elapsedMs)
.add(NetlinkConstants.stringForNlMsgType(msgType))
.add("if=" + ifindex)
.add(ip.getHostAddress())
.add(StructNdMsg.stringForNudState(nudState))
.add("[" + NetlinkConstants.hexify(linkLayerAddr) + "]")
.toString();
}
}
public interface NeighborEventConsumer {
// Every neighbor event received on the netlink socket is passed in
// here. Subclasses should filter for events of interest.
public void accept(NeighborEvent event);
}
private final SharedLog mLog;
private final NeighborEventConsumer mConsumer;
public IpNeighborMonitor(Handler h, SharedLog log, NeighborEventConsumer cb) {
super(h, NetlinkSocket.DEFAULT_RECV_BUFSIZE);
mLog = log.forSubComponent(TAG);
mConsumer = (cb != null) ? cb : (event) -> { /* discard */ };
}
@Override
protected FileDescriptor createFd() {
FileDescriptor fd = null;
try {
fd = NetlinkSocket.forProto(OsConstants.NETLINK_ROUTE);
Os.bind(fd, (SocketAddress)(new NetlinkSocketAddress(0, OsConstants.RTMGRP_NEIGH)));
Os.connect(fd, (SocketAddress)(new NetlinkSocketAddress(0, 0)));
if (VDBG) {
final NetlinkSocketAddress nlAddr = (NetlinkSocketAddress) Os.getsockname(fd);
Log.d(TAG, "bound to sockaddr_nl{"
+ BitUtils.uint32(nlAddr.getPortId()) + ", "
+ nlAddr.getGroupsMask()
+ "}");
}
} catch (ErrnoException|SocketException e) {
logError("Failed to create rtnetlink socket", e);
IoUtils.closeQuietly(fd);
return null;
}
return fd;
}
@Override
protected void handlePacket(byte[] recvbuf, int length) {
final long whenMs = SystemClock.elapsedRealtime();
final ByteBuffer byteBuffer = ByteBuffer.wrap(recvbuf, 0, length);
byteBuffer.order(ByteOrder.nativeOrder());
parseNetlinkMessageBuffer(byteBuffer, whenMs);
}
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);
mLog.e("unparsable netlink msg: " + NetlinkConstants.hexify(byteBuffer));
break;
}
final int srcPortId = nlMsg.getHeader().nlmsg_pid;
if (srcPortId != 0) {
mLog.e("non-kernel source portId: " + BitUtils.uint32(srcPortId));
break;
}
if (nlMsg instanceof NetlinkErrorMessage) {
mLog.e("netlink error: " + nlMsg);
continue;
} else if (!(nlMsg instanceof RtNetlinkNeighborMessage)) {
mLog.i("non-rtnetlink neighbor msg: " + nlMsg);
continue;
}
evaluateRtNetlinkNeighborMessage((RtNetlinkNeighborMessage) nlMsg, whenMs);
}
}
private void evaluateRtNetlinkNeighborMessage(
RtNetlinkNeighborMessage neighMsg, long whenMs) {
final short msgType = neighMsg.getHeader().nlmsg_type;
final StructNdMsg ndMsg = neighMsg.getNdHeader();
if (ndMsg == null) {
mLog.e("RtNetlinkNeighborMessage without ND message header!");
return;
}
final int ifindex = ndMsg.ndm_ifindex;
final InetAddress destination = neighMsg.getDestination();
final short nudState =
(msgType == NetlinkConstants.RTM_DELNEIGH)
? StructNdMsg.NUD_NONE
: ndMsg.ndm_state;
final NeighborEvent event = new NeighborEvent(
whenMs, msgType, ifindex, destination, nudState, neighMsg.getLinkLayerAddress());
if (VDBG) {
Log.d(TAG, neighMsg.toString());
}
if (DBG) {
Log.d(TAG, event.toString());
}
mConsumer.accept(event);
}
}

View File

@@ -22,30 +22,27 @@ import android.net.LinkProperties;
import android.net.LinkProperties.ProvisioningChange; import android.net.LinkProperties.ProvisioningChange;
import android.net.ProxyInfo; import android.net.ProxyInfo;
import android.net.RouteInfo; import android.net.RouteInfo;
import android.net.ip.IpNeighborMonitor.NeighborEvent;
import android.net.metrics.IpConnectivityLog; import android.net.metrics.IpConnectivityLog;
import android.net.metrics.IpReachabilityEvent; import android.net.metrics.IpReachabilityEvent;
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.StructNdMsg; import android.net.netlink.StructNdMsg;
import android.net.netlink.StructNdaCacheInfo;
import android.net.netlink.StructNlMsgHdr;
import android.net.util.MultinetworkPolicyTracker; import android.net.util.MultinetworkPolicyTracker;
import android.net.util.SharedLog; import android.net.util.SharedLog;
import android.os.Handler;
import android.os.PowerManager; import android.os.PowerManager;
import android.os.PowerManager.WakeLock; import android.os.PowerManager.WakeLock;
import android.os.SystemClock; import android.os.SystemClock;
import android.system.ErrnoException; import android.system.ErrnoException;
import android.system.NetlinkSocketAddress;
import android.system.OsConstants; import android.system.OsConstants;
import android.util.Log; import android.util.Log;
import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.DumpUtils.Dump;
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
import java.io.PrintWriter;
import java.net.Inet6Address; import java.net.Inet6Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
@@ -134,6 +131,8 @@ import java.util.Set;
* state it may be best for the link to disconnect completely and * state it may be best for the link to disconnect completely and
* reconnect afresh. * reconnect afresh.
* *
* Accessing an instance of this class from multiple threads is NOT safe.
*
* @hide * @hide
*/ */
public class IpReachabilityMonitor { public class IpReachabilityMonitor {
@@ -169,64 +168,33 @@ public class IpReachabilityMonitor {
} }
} }
private final Object mLock = new Object();
private final String mInterfaceName; private final String mInterfaceName;
private final int mInterfaceIndex; private final int mInterfaceIndex;
private final IpNeighborMonitor mIpNeighborMonitor;
private final SharedLog mLog; private final SharedLog mLog;
private final Callback mCallback; private final Callback mCallback;
private final Dependencies mDependencies; private final Dependencies mDependencies;
private final MultinetworkPolicyTracker mMultinetworkPolicyTracker; private final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
private final NetlinkSocketObserver mNetlinkSocketObserver;
private final Thread mObserverThread;
private final IpConnectivityLog mMetricsLog = new IpConnectivityLog(); private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
@GuardedBy("mLock")
private LinkProperties mLinkProperties = new LinkProperties(); private LinkProperties mLinkProperties = new LinkProperties();
// TODO: consider a map to a private NeighborState class holding more private Map<InetAddress, NeighborEvent> mNeighborWatchList = new HashMap<>();
// information than a single NUD state entry.
@GuardedBy("mLock")
private Map<InetAddress, Short> mIpWatchList = new HashMap<>();
@GuardedBy("mLock")
private int mIpWatchListVersion;
private volatile boolean mRunning;
// Time in milliseconds of the last forced probe request. // Time in milliseconds of the last forced probe request.
private volatile long mLastProbeTimeMs; private volatile long mLastProbeTimeMs;
/** public IpReachabilityMonitor(
* Make the kernel perform neighbor reachability detection (IPv4 ARP or IPv6 ND) Context context, String ifName, Handler h, SharedLog log, Callback callback) {
* for the given IP address on the specified interface index. this(context, ifName, h, log, callback, null);
*
* @return 0 if the request was successfully passed to the kernel; otherwise return
* a non-zero error code.
*/
private static int probeNeighbor(int ifIndex, InetAddress ip) {
final String msgSnippet = "probing ip=" + ip.getHostAddress() + "%" + ifIndex;
if (DBG) { Log.d(TAG, msgSnippet); }
final byte[] msg = RtNetlinkNeighborMessage.newNewNeighborMessage(
1, ip, StructNdMsg.NUD_PROBE, ifIndex, null);
try {
NetlinkSocket.sendOneShotKernelMessage(OsConstants.NETLINK_ROUTE, msg);
} catch (ErrnoException e) {
Log.e(TAG, "Error " + msgSnippet + ": " + e);
return -e.errno;
} }
return 0; public IpReachabilityMonitor(
} Context context, String ifName, Handler h, SharedLog log, Callback callback,
public IpReachabilityMonitor(Context context, String ifName, SharedLog log, Callback callback) {
this(context, ifName, log, callback, null);
}
public IpReachabilityMonitor(Context context, String ifName, SharedLog log, Callback callback,
MultinetworkPolicyTracker tracker) { MultinetworkPolicyTracker tracker) {
this(ifName, getInterfaceIndex(ifName), log, callback, tracker, this(ifName, getInterfaceIndex(ifName), h, log, callback, tracker,
Dependencies.makeDefault(context, ifName)); Dependencies.makeDefault(context, ifName));
} }
@VisibleForTesting @VisibleForTesting
IpReachabilityMonitor(String ifName, int ifIndex, SharedLog log, Callback callback, IpReachabilityMonitor(String ifName, int ifIndex, Handler h, SharedLog log, Callback callback,
MultinetworkPolicyTracker tracker, Dependencies dependencies) { MultinetworkPolicyTracker tracker, Dependencies dependencies) {
mInterfaceName = ifName; mInterfaceName = ifName;
mLog = log.forSubComponent(TAG); mLog = log.forSubComponent(TAG);
@@ -234,47 +202,56 @@ public class IpReachabilityMonitor {
mMultinetworkPolicyTracker = tracker; mMultinetworkPolicyTracker = tracker;
mInterfaceIndex = ifIndex; mInterfaceIndex = ifIndex;
mDependencies = dependencies; mDependencies = dependencies;
mNetlinkSocketObserver = new NetlinkSocketObserver();
mObserverThread = new Thread(mNetlinkSocketObserver); mIpNeighborMonitor = new IpNeighborMonitor(h, mLog,
mObserverThread.start(); (NeighborEvent event) -> {
if (mInterfaceIndex != event.ifindex) return;
if (!mNeighborWatchList.containsKey(event.ip)) return;
final NeighborEvent prev = mNeighborWatchList.put(event.ip, event);
// TODO: Consider what to do with other states that are not within
// NeighborEvent#isValid() (i.e. NUD_NONE, NUD_INCOMPLETE).
if (event.nudState == StructNdMsg.NUD_FAILED) {
mLog.w("ALERT neighbor went from: " + prev + " to: " + event);
handleNeighborLost(event);
}
});
mIpNeighborMonitor.start();
} }
public void stop() { public void stop() {
mRunning = false; mIpNeighborMonitor.stop();
clearLinkProperties(); clearLinkProperties();
mNetlinkSocketObserver.clearNetlinkSocket();
} }
// TODO: add a public dump() method that can be called during a bug report. public void dump(PrintWriter pw) {
DumpUtils.dumpAsync(
private String describeWatchList() { mIpNeighborMonitor.getHandler(),
final String delimiter = ", "; new Dump() {
StringBuilder sb = new StringBuilder(); @Override
synchronized (mLock) { public void dump(PrintWriter pw, String prefix) {
sb.append("iface{" + mInterfaceName + "/" + mInterfaceIndex + "}, "); pw.println(describeWatchList("\n"));
sb.append("v{" + mIpWatchListVersion + "}, ");
sb.append("ntable=[");
boolean firstTime = true;
for (Map.Entry<InetAddress, Short> entry : mIpWatchList.entrySet()) {
if (firstTime) {
firstTime = false;
} else {
sb.append(delimiter);
} }
sb.append(entry.getKey().getHostAddress() + "/" + },
StructNdMsg.stringForNudState(entry.getValue())); pw, "", 1000);
}
private String describeWatchList() { return describeWatchList(" "); }
private String describeWatchList(String sep) {
final StringBuilder sb = new StringBuilder();
sb.append("iface{" + mInterfaceName + "/" + mInterfaceIndex + "}," + sep);
sb.append("ntable=[" + sep);
String delimiter = "";
for (Map.Entry<InetAddress, NeighborEvent> entry : mNeighborWatchList.entrySet()) {
sb.append(delimiter).append(entry.getKey().getHostAddress() + "/" + entry.getValue());
delimiter = "," + sep;
} }
sb.append("]"); sb.append("]");
}
return sb.toString(); return sb.toString();
} }
private boolean isWatching(InetAddress ip) {
synchronized (mLock) {
return mRunning && mIpWatchList.containsKey(ip);
}
}
private static boolean isOnLink(List<RouteInfo> routes, InetAddress ip) { private static boolean isOnLink(List<RouteInfo> routes, InetAddress ip) {
for (RouteInfo route : routes) { for (RouteInfo route : routes) {
if (!route.hasGateway() && route.matches(ip)) { if (!route.hasGateway() && route.matches(ip)) {
@@ -284,13 +261,6 @@ public class IpReachabilityMonitor {
return false; return false;
} }
private short getNeighborStateLocked(InetAddress ip) {
if (mIpWatchList.containsKey(ip)) {
return mIpWatchList.get(ip);
}
return StructNdMsg.NUD_NONE;
}
public void updateLinkProperties(LinkProperties lp) { public void updateLinkProperties(LinkProperties lp) {
if (!mInterfaceName.equals(lp.getInterfaceName())) { if (!mInterfaceName.equals(lp.getInterfaceName())) {
// TODO: figure out whether / how to cope with interface changes. // TODO: figure out whether / how to cope with interface changes.
@@ -299,51 +269,44 @@ public class IpReachabilityMonitor {
return; return;
} }
synchronized (mLock) {
mLinkProperties = new LinkProperties(lp); mLinkProperties = new LinkProperties(lp);
Map<InetAddress, Short> newIpWatchList = new HashMap<>(); Map<InetAddress, NeighborEvent> newNeighborWatchList = new HashMap<>();
final List<RouteInfo> routes = mLinkProperties.getRoutes(); final List<RouteInfo> routes = mLinkProperties.getRoutes();
for (RouteInfo route : routes) { for (RouteInfo route : routes) {
if (route.hasGateway()) { if (route.hasGateway()) {
InetAddress gw = route.getGateway(); InetAddress gw = route.getGateway();
if (isOnLink(routes, gw)) { if (isOnLink(routes, gw)) {
newIpWatchList.put(gw, getNeighborStateLocked(gw)); newNeighborWatchList.put(gw, mNeighborWatchList.getOrDefault(gw, null));
} }
} }
} }
for (InetAddress nameserver : lp.getDnsServers()) { for (InetAddress dns : lp.getDnsServers()) {
if (isOnLink(routes, nameserver)) { if (isOnLink(routes, dns)) {
newIpWatchList.put(nameserver, getNeighborStateLocked(nameserver)); newNeighborWatchList.put(dns, mNeighborWatchList.getOrDefault(dns, null));
} }
} }
mIpWatchList = newIpWatchList; mNeighborWatchList = newNeighborWatchList;
mIpWatchListVersion++;
}
if (DBG) { Log.d(TAG, "watch: " + describeWatchList()); } if (DBG) { Log.d(TAG, "watch: " + describeWatchList()); }
} }
public void clearLinkProperties() { public void clearLinkProperties() {
synchronized (mLock) {
mLinkProperties.clear(); mLinkProperties.clear();
mIpWatchList.clear(); mNeighborWatchList.clear();
mIpWatchListVersion++;
}
if (DBG) { Log.d(TAG, "clear: " + describeWatchList()); } if (DBG) { Log.d(TAG, "clear: " + describeWatchList()); }
} }
private void handleNeighborLost(String msg) { private void handleNeighborLost(NeighborEvent event) {
InetAddress ip = null; final LinkProperties whatIfLp = new LinkProperties(mLinkProperties);
final ProvisioningChange delta;
synchronized (mLock) {
LinkProperties whatIfLp = new LinkProperties(mLinkProperties);
for (Map.Entry<InetAddress, Short> entry : mIpWatchList.entrySet()) { InetAddress ip = null;
if (entry.getValue() != StructNdMsg.NUD_FAILED) { for (Map.Entry<InetAddress, NeighborEvent> entry : mNeighborWatchList.entrySet()) {
continue; // TODO: Consider using NeighborEvent#isValid() here; it's more
} // strict but may interact badly if other entries are somehow in
// NUD_INCOMPLETE (say, during network attach).
if (entry.getValue().nudState != StructNdMsg.NUD_FAILED) continue;
ip = entry.getKey(); ip = entry.getKey();
for (RouteInfo route : mLinkProperties.getRoutes()) { for (RouteInfo route : mLinkProperties.getRoutes()) {
@@ -358,11 +321,11 @@ public class IpReachabilityMonitor {
} }
} }
delta = LinkProperties.compareProvisioning(mLinkProperties, whatIfLp); final ProvisioningChange delta = LinkProperties.compareProvisioning(
} mLinkProperties, whatIfLp);
if (delta == ProvisioningChange.LOST_PROVISIONING) { if (delta == ProvisioningChange.LOST_PROVISIONING) {
final String logMsg = "FAILURE: LOST_PROVISIONING, " + msg; final String logMsg = "FAILURE: LOST_PROVISIONING, " + event;
Log.w(TAG, logMsg); Log.w(TAG, logMsg);
if (mCallback != null) { if (mCallback != null) {
// TODO: remove |ip| when the callback signature no longer has // TODO: remove |ip| when the callback signature no longer has
@@ -378,12 +341,9 @@ public class IpReachabilityMonitor {
} }
public void probeAll() { public void probeAll() {
final List<InetAddress> ipProbeList; final List<InetAddress> ipProbeList = new ArrayList<>(mNeighborWatchList.keySet());
synchronized (mLock) {
ipProbeList = new ArrayList<>(mIpWatchList.keySet());
}
if (!ipProbeList.isEmpty() && mRunning) { if (!ipProbeList.isEmpty()) {
// Keep the CPU awake long enough to allow all ARP/ND // Keep the CPU awake long enough to allow all ARP/ND
// probes a reasonable chance at success. See b/23197666. // probes a reasonable chance at success. See b/23197666.
// //
@@ -394,13 +354,10 @@ public class IpReachabilityMonitor {
} }
for (InetAddress target : ipProbeList) { for (InetAddress target : ipProbeList) {
if (!mRunning) { final int rval = IpNeighborMonitor.startKernelNeighborProbe(mInterfaceIndex, target);
break;
}
final int returnValue = probeNeighbor(mInterfaceIndex, target);
mLog.log(String.format("put neighbor %s into NUD_PROBE state (rval=%d)", mLog.log(String.format("put neighbor %s into NUD_PROBE state (rval=%d)",
target.getHostAddress(), returnValue)); target.getHostAddress(), rval));
logEvent(IpReachabilityEvent.PROBE, returnValue); logEvent(IpReachabilityEvent.PROBE, rval);
} }
mLastProbeTimeMs = SystemClock.elapsedRealtime(); mLastProbeTimeMs = SystemClock.elapsedRealtime();
} }
@@ -446,153 +403,4 @@ public class IpReachabilityMonitor {
int eventType = IpReachabilityEvent.nudFailureEventType(isFromProbe, isProvisioningLost); int eventType = IpReachabilityEvent.nudFailureEventType(isFromProbe, isProvisioningLost);
mMetricsLog.log(mInterfaceName, new IpReachabilityEvent(eventType)); mMetricsLog.log(mInterfaceName, new IpReachabilityEvent(eventType));
} }
// TODO: simplify the number of objects by making this extend Thread.
private final class NetlinkSocketObserver implements Runnable {
private NetlinkSocket mSocket;
@Override
public void run() {
if (VDBG) { Log.d(TAG, "Starting observing thread."); }
mRunning = true;
try {
setupNetlinkSocket();
} catch (ErrnoException | SocketException e) {
Log.e(TAG, "Failed to suitably initialize a netlink socket", e);
mRunning = false;
}
while (mRunning) {
final ByteBuffer byteBuffer;
try {
byteBuffer = recvKernelReply();
} catch (ErrnoException e) {
if (mRunning) { Log.w(TAG, "ErrnoException: ", e); }
break;
}
final long whenMs = SystemClock.elapsedRealtime();
if (byteBuffer == null) {
continue;
}
parseNetlinkMessageBuffer(byteBuffer, whenMs);
}
clearNetlinkSocket();
mRunning = false; // Not a no-op when ErrnoException happened.
if (VDBG) { Log.d(TAG, "Finishing observing thread."); }
}
private void clearNetlinkSocket() {
if (mSocket != null) {
mSocket.close();
}
}
// 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);
}
synchronized (mLock) {
if (mIpWatchList.containsKey(destination)) {
final short value =
(msgType == NetlinkConstants.RTM_DELNEIGH)
? StructNdMsg.NUD_NONE
: nudState;
mIpWatchList.put(destination, value);
}
}
if (nudState == StructNdMsg.NUD_FAILED) {
Log.w(TAG, "ALERT: " + eventMsg);
handleNeighborLost(eventMsg);
}
}
}
} }

View File

@@ -16,16 +16,24 @@
package android.net.netlink; package android.net.netlink;
import static android.system.OsConstants.AF_NETLINK;
import static android.system.OsConstants.EIO;
import static android.system.OsConstants.EPROTO;
import static android.system.OsConstants.ETIMEDOUT;
import static android.system.OsConstants.SO_RCVBUF;
import static android.system.OsConstants.SO_RCVTIMEO;
import static android.system.OsConstants.SO_SNDTIMEO;
import static android.system.OsConstants.SOCK_DGRAM;
import static android.system.OsConstants.SOL_SOCKET;
import android.system.ErrnoException; import android.system.ErrnoException;
import android.system.NetlinkSocketAddress; import android.system.NetlinkSocketAddress;
import android.system.Os; import android.system.Os;
import android.system.OsConstants;
import android.system.StructTimeval; import android.system.StructTimeval;
import android.util.Log; import android.util.Log;
import libcore.io.IoUtils; import libcore.io.IoUtils;
import libcore.io.Libcore; import libcore.io.Libcore;
import java.io.Closeable;
import java.io.FileDescriptor; import java.io.FileDescriptor;
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
import java.net.SocketAddress; import java.net.SocketAddress;
@@ -37,28 +45,27 @@ import java.nio.ByteOrder;
/** /**
* NetlinkSocket * NetlinkSocket
* *
* A small wrapper class to assist with AF_NETLINK socket operations. * A small static class to assist with AF_NETLINK socket operations.
* *
* @hide * @hide
*/ */
public class NetlinkSocket implements Closeable { public class NetlinkSocket {
private static final String TAG = "NetlinkSocket"; private static final String TAG = "NetlinkSocket";
private static final int SOCKET_RECV_BUFSIZE = 64 * 1024;
private static final int DEFAULT_RECV_BUFSIZE = 8 * 1024;
final private FileDescriptor mDescriptor; public static final int DEFAULT_RECV_BUFSIZE = 8 * 1024;
private NetlinkSocketAddress mAddr; public static final int SOCKET_RECV_BUFSIZE = 64 * 1024;
private long mLastRecvTimeoutMs;
private long mLastSendTimeoutMs;
public static void sendOneShotKernelMessage(int nlProto, byte[] msg) throws ErrnoException { public static void sendOneShotKernelMessage(int nlProto, byte[] msg) throws ErrnoException {
final String errPrefix = "Error in NetlinkSocket.sendOneShotKernelMessage"; final String errPrefix = "Error in NetlinkSocket.sendOneShotKernelMessage";
try (NetlinkSocket nlSocket = new NetlinkSocket(nlProto)) {
final long IO_TIMEOUT = 300L; final long IO_TIMEOUT = 300L;
nlSocket.connectToKernel();
nlSocket.sendMessage(msg, 0, msg.length, IO_TIMEOUT); FileDescriptor fd;
final ByteBuffer bytes = nlSocket.recvMessage(IO_TIMEOUT);
try {
fd = forProto(nlProto);
connectToKernel(fd);
sendMessage(fd, msg, 0, msg.length, IO_TIMEOUT);
final ByteBuffer bytes = recvMessage(fd, DEFAULT_RECV_BUFSIZE, IO_TIMEOUT);
// recvMessage() guaranteed to not return null if it did not throw. // recvMessage() guaranteed to not return null if it did not throw.
final NetlinkMessage response = NetlinkMessage.parse(bytes); final NetlinkMessage response = NetlinkMessage.parse(bytes);
if (response != null && response instanceof NetlinkErrorMessage && if (response != null && response instanceof NetlinkErrorMessage &&
@@ -81,61 +88,30 @@ public class NetlinkSocket implements Closeable {
errmsg = response.toString(); errmsg = response.toString();
} }
Log.e(TAG, errPrefix + ", errmsg=" + errmsg); Log.e(TAG, errPrefix + ", errmsg=" + errmsg);
throw new ErrnoException(errmsg, OsConstants.EPROTO); throw new ErrnoException(errmsg, EPROTO);
} }
} catch (InterruptedIOException e) { } catch (InterruptedIOException e) {
Log.e(TAG, errPrefix, e); Log.e(TAG, errPrefix, e);
throw new ErrnoException(errPrefix, OsConstants.ETIMEDOUT, e); throw new ErrnoException(errPrefix, ETIMEDOUT, e);
} catch (SocketException e) { } catch (SocketException e) {
Log.e(TAG, errPrefix, e); Log.e(TAG, errPrefix, e);
throw new ErrnoException(errPrefix, OsConstants.EIO, e); throw new ErrnoException(errPrefix, EIO, e);
}
} }
public NetlinkSocket(int nlProto) throws ErrnoException { IoUtils.closeQuietly(fd);
mDescriptor = Os.socket(
OsConstants.AF_NETLINK, OsConstants.SOCK_DGRAM, nlProto);
Os.setsockoptInt(
mDescriptor, OsConstants.SOL_SOCKET,
OsConstants.SO_RCVBUF, SOCKET_RECV_BUFSIZE);
} }
public NetlinkSocketAddress getLocalAddress() throws ErrnoException { public static FileDescriptor forProto(int nlProto) throws ErrnoException {
return (NetlinkSocketAddress) Os.getsockname(mDescriptor); final FileDescriptor fd = Os.socket(AF_NETLINK, SOCK_DGRAM, nlProto);
Os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, SOCKET_RECV_BUFSIZE);
return fd;
} }
public void bind(NetlinkSocketAddress localAddr) throws ErrnoException, SocketException { public static void connectToKernel(FileDescriptor fd) throws ErrnoException, SocketException {
Os.bind(mDescriptor, (SocketAddress)localAddr); Os.connect(fd, (SocketAddress) (new NetlinkSocketAddress(0, 0)));
} }
public void connectTo(NetlinkSocketAddress peerAddr) private static void checkTimeout(long timeoutMs) {
throws ErrnoException, SocketException {
Os.connect(mDescriptor, (SocketAddress) peerAddr);
}
public void connectToKernel() throws ErrnoException, SocketException {
connectTo(new NetlinkSocketAddress(0, 0));
}
/**
* Wait indefinitely (or until underlying socket error) for a
* netlink message of at most DEFAULT_RECV_BUFSIZE size.
*/
public ByteBuffer recvMessage()
throws ErrnoException, InterruptedIOException {
return recvMessage(DEFAULT_RECV_BUFSIZE, 0);
}
/**
* Wait up to |timeoutMs| (or until underlying socket error) for a
* netlink message of at most DEFAULT_RECV_BUFSIZE size.
*/
public ByteBuffer recvMessage(long timeoutMs) throws ErrnoException, InterruptedIOException {
return recvMessage(DEFAULT_RECV_BUFSIZE, timeoutMs);
}
private void checkTimeout(long timeoutMs) {
if (timeoutMs < 0) { if (timeoutMs < 0) {
throw new IllegalArgumentException("Negative timeouts not permitted"); throw new IllegalArgumentException("Negative timeouts not permitted");
} }
@@ -147,21 +123,14 @@ public class NetlinkSocket implements Closeable {
* *
* Multi-threaded calls with different timeouts will cause unexpected results. * Multi-threaded calls with different timeouts will cause unexpected results.
*/ */
public ByteBuffer recvMessage(int bufsize, long timeoutMs) public static ByteBuffer recvMessage(FileDescriptor fd, int bufsize, long timeoutMs)
throws ErrnoException, IllegalArgumentException, InterruptedIOException { throws ErrnoException, IllegalArgumentException, InterruptedIOException {
checkTimeout(timeoutMs); checkTimeout(timeoutMs);
synchronized (mDescriptor) { Os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, StructTimeval.fromMillis(timeoutMs));
if (mLastRecvTimeoutMs != timeoutMs) {
Os.setsockoptTimeval(mDescriptor,
OsConstants.SOL_SOCKET, OsConstants.SO_RCVTIMEO,
StructTimeval.fromMillis(timeoutMs));
mLastRecvTimeoutMs = timeoutMs;
}
}
ByteBuffer byteBuffer = ByteBuffer.allocate(bufsize); ByteBuffer byteBuffer = ByteBuffer.allocate(bufsize);
int length = Os.read(mDescriptor, byteBuffer); int length = Os.read(fd, byteBuffer);
if (length == bufsize) { if (length == bufsize) {
Log.w(TAG, "maximum read"); Log.w(TAG, "maximum read");
} }
@@ -171,40 +140,17 @@ public class NetlinkSocket implements Closeable {
return byteBuffer; return byteBuffer;
} }
/**
* Send a message to a peer to which this socket has previously connected.
*
* This blocks until completion or an error occurs.
*/
public boolean sendMessage(byte[] bytes, int offset, int count)
throws ErrnoException, InterruptedIOException {
return sendMessage(bytes, offset, count, 0);
}
/** /**
* Send a message to a peer to which this socket has previously connected, * Send a message to a peer to which this socket has previously connected,
* waiting at most |timeoutMs| milliseconds for the send to complete. * waiting at most |timeoutMs| milliseconds for the send to complete.
* *
* Multi-threaded calls with different timeouts will cause unexpected results. * Multi-threaded calls with different timeouts will cause unexpected results.
*/ */
public boolean sendMessage(byte[] bytes, int offset, int count, long timeoutMs) public static int sendMessage(
FileDescriptor fd, byte[] bytes, int offset, int count, long timeoutMs)
throws ErrnoException, IllegalArgumentException, InterruptedIOException { throws ErrnoException, IllegalArgumentException, InterruptedIOException {
checkTimeout(timeoutMs); checkTimeout(timeoutMs);
Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(timeoutMs));
synchronized (mDescriptor) { return Os.write(fd, bytes, offset, count);
if (mLastSendTimeoutMs != timeoutMs) {
Os.setsockoptTimeval(mDescriptor,
OsConstants.SOL_SOCKET, OsConstants.SO_SNDTIMEO,
StructTimeval.fromMillis(timeoutMs));
mLastSendTimeoutMs = timeoutMs;
}
}
return (count == Os.write(mDescriptor, bytes, offset, count));
}
@Override
public void close() {
IoUtils.closeQuietly(mDescriptor);
} }
} }

View File

@@ -63,6 +63,11 @@ public class StructNdMsg {
return ((nudState & (NUD_PERMANENT|NUD_NOARP|NUD_REACHABLE)) != 0); return ((nudState & (NUD_PERMANENT|NUD_NOARP|NUD_REACHABLE)) != 0);
} }
public static boolean isNudStateValid(short nudState) {
return (isNudStateConnected(nudState) ||
((nudState & (NUD_PROBE|NUD_STALE|NUD_DELAY)) != 0));
}
// Neighbor Cache Entry Flags // Neighbor Cache Entry Flags
public static byte NTF_USE = (byte) 0x01; public static byte NTF_USE = (byte) 0x01;
public static byte NTF_SELF = (byte) 0x02; public static byte NTF_SELF = (byte) 0x02;
@@ -143,7 +148,7 @@ public class StructNdMsg {
} }
public boolean nudValid() { public boolean nudValid() {
return (nudConnected() || ((ndm_state & (NUD_PROBE|NUD_STALE|NUD_DELAY)) != 0)); return isNudStateValid(ndm_state);
} }
@Override @Override

View File

@@ -67,7 +67,7 @@ import java.io.IOException;
* *
* @hide * @hide
*/ */
public abstract class BlockingSocketReader { public abstract class PacketReader {
private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR; private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
private static final int UNREGISTER_THIS_FD = 0; private static final int UNREGISTER_THIS_FD = 0;
@@ -83,11 +83,11 @@ public abstract class BlockingSocketReader {
IoUtils.closeQuietly(fd); IoUtils.closeQuietly(fd);
} }
protected BlockingSocketReader(Handler h) { protected PacketReader(Handler h) {
this(h, DEFAULT_RECV_BUF_SIZE); this(h, DEFAULT_RECV_BUF_SIZE);
} }
protected BlockingSocketReader(Handler h, int recvbufsize) { protected PacketReader(Handler h, int recvbufsize) {
mHandler = h; mHandler = h;
mQueue = mHandler.getLooper().getQueue(); mQueue = mHandler.getLooper().getQueue();
mPacket = new byte[Math.max(recvbufsize, DEFAULT_RECV_BUF_SIZE)]; mPacket = new byte[Math.max(recvbufsize, DEFAULT_RECV_BUF_SIZE)];
@@ -115,6 +115,8 @@ public abstract class BlockingSocketReader {
} }
} }
public Handler getHandler() { return mHandler; }
public final int recvBufSize() { return mPacket.length; } public final int recvBufSize() { return mPacket.length; }
public final long numPacketsReceived() { return mPacketsReceived; } public final long numPacketsReceived() { return mPacketsReceived; }

View File

@@ -18,10 +18,12 @@ package android.net.ip;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.when;
import android.net.util.SharedLog; import android.net.util.SharedLog;
import android.os.Handler;
import android.os.Looper;
import android.support.test.filters.SmallTest; import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4; import android.support.test.runner.AndroidJUnit4;
@@ -42,14 +44,18 @@ public class IpReachabilityMonitorTest {
@Mock IpReachabilityMonitor.Callback mCallback; @Mock IpReachabilityMonitor.Callback mCallback;
@Mock IpReachabilityMonitor.Dependencies mDependencies; @Mock IpReachabilityMonitor.Dependencies mDependencies;
@Mock SharedLog mLog; @Mock SharedLog mLog;
Handler mHandler;
@Before @Before
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
when(mLog.forSubComponent(anyString())).thenReturn(mLog);
mHandler = new Handler(Looper.getMainLooper());
} }
IpReachabilityMonitor makeMonitor() { IpReachabilityMonitor makeMonitor() {
return new IpReachabilityMonitor("fake0", 1, mLog, mCallback, null, mDependencies); return new IpReachabilityMonitor(
"fake0", 1, mHandler, mLog, mCallback, null, mDependencies);
} }
@Test @Test

View File

@@ -16,6 +16,8 @@
package android.net.netlink; package android.net.netlink;
import static android.net.netlink.NetlinkSocket.DEFAULT_RECV_BUFSIZE;
import static android.system.OsConstants.NETLINK_ROUTE;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@@ -28,10 +30,12 @@ import android.support.test.runner.AndroidJUnit4;
import android.support.test.filters.SmallTest; import android.support.test.filters.SmallTest;
import android.system.ErrnoException; import android.system.ErrnoException;
import android.system.NetlinkSocketAddress; import android.system.NetlinkSocketAddress;
import android.system.OsConstants; import android.system.Os;
import android.util.Log; import android.util.Log;
import libcore.io.IoUtils;
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
import java.io.FileDescriptor;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
@@ -46,29 +50,28 @@ public class NetlinkSocketTest {
@Test @Test
public void testBasicWorkingGetNeighborsQuery() throws Exception { public void testBasicWorkingGetNeighborsQuery() throws Exception {
NetlinkSocket s = new NetlinkSocket(OsConstants.NETLINK_ROUTE); final FileDescriptor fd = NetlinkSocket.forProto(NETLINK_ROUTE);
assertNotNull(s); assertNotNull(fd);
s.connectToKernel(); NetlinkSocket.connectToKernel(fd);
NetlinkSocketAddress localAddr = s.getLocalAddress(); final NetlinkSocketAddress localAddr = (NetlinkSocketAddress) Os.getsockname(fd);
assertNotNull(localAddr); assertNotNull(localAddr);
assertEquals(0, localAddr.getGroupsMask()); assertEquals(0, localAddr.getGroupsMask());
assertTrue(0 != localAddr.getPortId()); assertTrue(0 != localAddr.getPortId());
final int TEST_SEQNO = 5; final int TEST_SEQNO = 5;
final byte[] request = RtNetlinkNeighborMessage.newGetNeighborsRequest(TEST_SEQNO); final byte[] req = RtNetlinkNeighborMessage.newGetNeighborsRequest(TEST_SEQNO);
assertNotNull(request); assertNotNull(req);
final long TIMEOUT = 500; final long TIMEOUT = 500;
assertTrue(s.sendMessage(request, 0, request.length, TIMEOUT)); assertEquals(req.length, NetlinkSocket.sendMessage(fd, req, 0, req.length, TIMEOUT));
int neighMessageCount = 0; int neighMessageCount = 0;
int doneMessageCount = 0; int doneMessageCount = 0;
while (doneMessageCount == 0) { while (doneMessageCount == 0) {
ByteBuffer response = null; ByteBuffer response = NetlinkSocket.recvMessage(fd, DEFAULT_RECV_BUFSIZE, TIMEOUT);
response = s.recvMessage(TIMEOUT);
assertNotNull(response); assertNotNull(response);
assertTrue(StructNlMsgHdr.STRUCT_SIZE <= response.limit()); assertTrue(StructNlMsgHdr.STRUCT_SIZE <= response.limit());
assertEquals(0, response.position()); assertEquals(0, response.position());
@@ -100,30 +103,6 @@ public class NetlinkSocketTest {
// TODO: make sure this test passes sanely in airplane mode. // TODO: make sure this test passes sanely in airplane mode.
assertTrue(neighMessageCount > 0); assertTrue(neighMessageCount > 0);
s.close(); IoUtils.closeQuietly(fd);
}
@Test
public void testRepeatedCloseCallsAreQuiet() throws Exception {
// Create a working NetlinkSocket.
NetlinkSocket s = new NetlinkSocket(OsConstants.NETLINK_ROUTE);
assertNotNull(s);
s.connectToKernel();
NetlinkSocketAddress localAddr = s.getLocalAddress();
assertNotNull(localAddr);
assertEquals(0, localAddr.getGroupsMask());
assertTrue(0 != localAddr.getPortId());
// Close once.
s.close();
// Test that it is closed.
boolean expectedErrorSeen = false;
try {
localAddr = s.getLocalAddress();
} catch (ErrnoException e) {
expectedErrorSeen = true;
}
assertTrue(expectedErrorSeen);
// Close once more.
s.close();
} }
} }

View File

@@ -16,7 +16,7 @@
package android.net.util; package android.net.util;
import static android.net.util.BlockingSocketReader.DEFAULT_RECV_BUF_SIZE; import static android.net.util.PacketReader.DEFAULT_RECV_BUF_SIZE;
import static android.system.OsConstants.*; import static android.system.OsConstants.*;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
@@ -53,13 +53,13 @@ import org.junit.Test;
import libcore.io.IoBridge; import libcore.io.IoBridge;
/** /**
* Tests for BlockingSocketReader. * Tests for PacketReader.
* *
* @hide * @hide
*/ */
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
@SmallTest @SmallTest
public class BlockingSocketReaderTest { public class PacketReaderTest {
static final InetAddress LOOPBACK6 = Inet6Address.getLoopbackAddress(); static final InetAddress LOOPBACK6 = Inet6Address.getLoopbackAddress();
static final StructTimeval TIMEO = StructTimeval.fromMillis(500); static final StructTimeval TIMEO = StructTimeval.fromMillis(500);
@@ -69,9 +69,9 @@ public class BlockingSocketReaderTest {
protected byte[] mLastRecvBuf; protected byte[] mLastRecvBuf;
protected boolean mStopped; protected boolean mStopped;
protected HandlerThread mHandlerThread; protected HandlerThread mHandlerThread;
protected BlockingSocketReader mReceiver; protected PacketReader mReceiver;
class UdpLoopbackReader extends BlockingSocketReader { class UdpLoopbackReader extends PacketReader {
public UdpLoopbackReader(Handler h) { public UdpLoopbackReader(Handler h) {
super(h); super(h);
} }
@@ -121,7 +121,7 @@ public class BlockingSocketReaderTest {
mLastRecvBuf = null; mLastRecvBuf = null;
mStopped = false; mStopped = false;
mHandlerThread = new HandlerThread(BlockingSocketReaderTest.class.getSimpleName()); mHandlerThread = new HandlerThread(PacketReaderTest.class.getSimpleName());
mHandlerThread.start(); mHandlerThread.start();
} }
@@ -188,8 +188,8 @@ public class BlockingSocketReaderTest {
mReceiver = null; mReceiver = null;
} }
class NullBlockingSocketReader extends BlockingSocketReader { class NullPacketReader extends PacketReader {
public NullBlockingSocketReader(Handler h, int recvbufsize) { public NullPacketReader(Handler h, int recvbufsize) {
super(h, recvbufsize); super(h, recvbufsize);
} }
@@ -202,7 +202,7 @@ public class BlockingSocketReaderTest {
final Handler h = mHandlerThread.getThreadHandler(); final Handler h = mHandlerThread.getThreadHandler();
for (int i : new int[]{-1, 0, 1, DEFAULT_RECV_BUF_SIZE-1}) { for (int i : new int[]{-1, 0, 1, DEFAULT_RECV_BUF_SIZE-1}) {
final BlockingSocketReader b = new NullBlockingSocketReader(h, i); final PacketReader b = new NullPacketReader(h, i);
assertEquals(DEFAULT_RECV_BUF_SIZE, b.recvBufSize()); assertEquals(DEFAULT_RECV_BUF_SIZE, b.recvBufSize());
} }
} }