Merge "Refactor to IpNeighborMonitor and single-threaded semantics"
diff --git a/services/net/java/android/net/ip/ConnectivityPacketTracker.java b/services/net/java/android/net/ip/ConnectivityPacketTracker.java
index 1925c39..6cf4fa9a 100644
--- a/services/net/java/android/net/ip/ConnectivityPacketTracker.java
+++ b/services/net/java/android/net/ip/ConnectivityPacketTracker.java
@@ -19,7 +19,7 @@
 import static android.system.OsConstants.*;
 
 import android.net.NetworkUtils;
-import android.net.util.BlockingSocketReader;
+import android.net.util.PacketReader;
 import android.net.util.ConnectivityPacketSummary;
 import android.os.Handler;
 import android.system.ErrnoException;
@@ -65,7 +65,7 @@
 
     private final String mTag;
     private final LocalLog mLog;
-    private final BlockingSocketReader mPacketListener;
+    private final PacketReader mPacketListener;
     private boolean mRunning;
     private String mDisplayName;
 
@@ -101,7 +101,7 @@
         mDisplayName = null;
     }
 
-    private final class PacketListener extends BlockingSocketReader {
+    private final class PacketListener extends PacketReader {
         private final int mIfIndex;
         private final byte mHwAddr[];
 
diff --git a/services/net/java/android/net/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java
index 5c58cdb..b305b33 100644
--- a/services/net/java/android/net/ip/IpClient.java
+++ b/services/net/java/android/net/ip/IpClient.java
@@ -815,6 +815,15 @@
         pw.println(Objects.toString(provisioningConfig, "N/A"));
         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(mTag + " StateMachine dump:");
         pw.increaseIndent();
@@ -1237,6 +1246,7 @@
             mIpReachabilityMonitor = new IpReachabilityMonitor(
                     mContext,
                     mInterfaceName,
+                    getHandler(),
                     mLog,
                     new IpReachabilityMonitor.Callback() {
                         @Override
diff --git a/services/net/java/android/net/ip/IpNeighborMonitor.java b/services/net/java/android/net/ip/IpNeighborMonitor.java
new file mode 100644
index 0000000..6807334
--- /dev/null
+++ b/services/net/java/android/net/ip/IpNeighborMonitor.java
@@ -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);
+    }
+}
diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/services/net/java/android/net/ip/IpReachabilityMonitor.java
index 714b35a..b31ffbb 100644
--- a/services/net/java/android/net/ip/IpReachabilityMonitor.java
+++ b/services/net/java/android/net/ip/IpReachabilityMonitor.java
@@ -22,30 +22,27 @@
 import android.net.LinkProperties.ProvisioningChange;
 import android.net.ProxyInfo;
 import android.net.RouteInfo;
+import android.net.ip.IpNeighborMonitor.NeighborEvent;
 import android.net.metrics.IpConnectivityLog;
 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.StructNdaCacheInfo;
-import android.net.netlink.StructNlMsgHdr;
 import android.net.util.MultinetworkPolicyTracker;
 import android.net.util.SharedLog;
+import android.os.Handler;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
 import android.os.SystemClock;
 import android.system.ErrnoException;
-import android.system.NetlinkSocketAddress;
 import android.system.OsConstants;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
 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.PrintWriter;
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
@@ -134,6 +131,8 @@
  *          state it may be best for the link to disconnect completely and
  *          reconnect afresh.
  *
+ * Accessing an instance of this class from multiple threads is NOT safe.
+ *
  * @hide
  */
 public class IpReachabilityMonitor {
@@ -169,64 +168,33 @@
         }
     }
 
-    private final Object mLock = new Object();
     private final String mInterfaceName;
     private final int mInterfaceIndex;
+    private final IpNeighborMonitor mIpNeighborMonitor;
     private final SharedLog mLog;
     private final Callback mCallback;
     private final Dependencies mDependencies;
     private final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
-    private final NetlinkSocketObserver mNetlinkSocketObserver;
-    private final Thread mObserverThread;
     private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
-    @GuardedBy("mLock")
     private LinkProperties mLinkProperties = new LinkProperties();
-    // TODO: consider a map to a private NeighborState class holding more
-    // 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;
+    private Map<InetAddress, NeighborEvent> mNeighborWatchList = new HashMap<>();
     // Time in milliseconds of the last forced probe request.
     private volatile long mLastProbeTimeMs;
 
-    /**
-     * 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.
-     */
-    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) {
+        this(context, ifName, h, log, callback, null);
     }
 
-    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,
+    public IpReachabilityMonitor(
+            Context context, String ifName, Handler h, SharedLog log, Callback callback,
             MultinetworkPolicyTracker tracker) {
-        this(ifName, getInterfaceIndex(ifName), log, callback, tracker,
+        this(ifName, getInterfaceIndex(ifName), h, log, callback, tracker,
                 Dependencies.makeDefault(context, ifName));
     }
 
     @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) {
         mInterfaceName = ifName;
         mLog = log.forSubComponent(TAG);
@@ -234,47 +202,56 @@
         mMultinetworkPolicyTracker = tracker;
         mInterfaceIndex = ifIndex;
         mDependencies = dependencies;
-        mNetlinkSocketObserver = new NetlinkSocketObserver();
-        mObserverThread = new Thread(mNetlinkSocketObserver);
-        mObserverThread.start();
+
+        mIpNeighborMonitor = new IpNeighborMonitor(h, mLog,
+                (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() {
-        mRunning = false;
+        mIpNeighborMonitor.stop();
         clearLinkProperties();
-        mNetlinkSocketObserver.clearNetlinkSocket();
     }
 
-    // TODO: add a public dump() method that can be called during a bug report.
+    public void dump(PrintWriter pw) {
+        DumpUtils.dumpAsync(
+                mIpNeighborMonitor.getHandler(),
+                new Dump() {
+                    @Override
+                    public void dump(PrintWriter pw, String prefix) {
+                        pw.println(describeWatchList("\n"));
+                    }
+                },
+                pw, "", 1000);
+    }
 
-    private String describeWatchList() {
-        final String delimiter = ", ";
-        StringBuilder sb = new StringBuilder();
-        synchronized (mLock) {
-            sb.append("iface{" + mInterfaceName + "/" + mInterfaceIndex + "}, ");
-            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()));
-            }
-            sb.append("]");
+    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("]");
         return sb.toString();
     }
 
-    private boolean isWatching(InetAddress ip) {
-        synchronized (mLock) {
-            return mRunning && mIpWatchList.containsKey(ip);
-        }
-    }
-
     private static boolean isOnLink(List<RouteInfo> routes, InetAddress ip) {
         for (RouteInfo route : routes) {
             if (!route.hasGateway() && route.matches(ip)) {
@@ -284,13 +261,6 @@
         return false;
     }
 
-    private short getNeighborStateLocked(InetAddress ip) {
-        if (mIpWatchList.containsKey(ip)) {
-            return mIpWatchList.get(ip);
-        }
-        return StructNdMsg.NUD_NONE;
-    }
-
     public void updateLinkProperties(LinkProperties lp) {
         if (!mInterfaceName.equals(lp.getInterfaceName())) {
             // TODO: figure out whether / how to cope with interface changes.
@@ -299,70 +269,63 @@
             return;
         }
 
-        synchronized (mLock) {
-            mLinkProperties = new LinkProperties(lp);
-            Map<InetAddress, Short> newIpWatchList = new HashMap<>();
+        mLinkProperties = new LinkProperties(lp);
+        Map<InetAddress, NeighborEvent> newNeighborWatchList = new HashMap<>();
 
-            final List<RouteInfo> routes = mLinkProperties.getRoutes();
-            for (RouteInfo route : routes) {
-                if (route.hasGateway()) {
-                    InetAddress gw = route.getGateway();
-                    if (isOnLink(routes, gw)) {
-                        newIpWatchList.put(gw, getNeighborStateLocked(gw));
-                    }
+        final List<RouteInfo> routes = mLinkProperties.getRoutes();
+        for (RouteInfo route : routes) {
+            if (route.hasGateway()) {
+                InetAddress gw = route.getGateway();
+                if (isOnLink(routes, gw)) {
+                    newNeighborWatchList.put(gw, mNeighborWatchList.getOrDefault(gw, null));
                 }
             }
-
-            for (InetAddress nameserver : lp.getDnsServers()) {
-                if (isOnLink(routes, nameserver)) {
-                    newIpWatchList.put(nameserver, getNeighborStateLocked(nameserver));
-                }
-            }
-
-            mIpWatchList = newIpWatchList;
-            mIpWatchListVersion++;
         }
+
+        for (InetAddress dns : lp.getDnsServers()) {
+            if (isOnLink(routes, dns)) {
+                newNeighborWatchList.put(dns, mNeighborWatchList.getOrDefault(dns, null));
+            }
+        }
+
+        mNeighborWatchList = newNeighborWatchList;
         if (DBG) { Log.d(TAG, "watch: " + describeWatchList()); }
     }
 
     public void clearLinkProperties() {
-        synchronized (mLock) {
-            mLinkProperties.clear();
-            mIpWatchList.clear();
-            mIpWatchListVersion++;
-        }
+        mLinkProperties.clear();
+        mNeighborWatchList.clear();
         if (DBG) { Log.d(TAG, "clear: " + describeWatchList()); }
     }
 
-    private void handleNeighborLost(String msg) {
+    private void handleNeighborLost(NeighborEvent event) {
+        final LinkProperties whatIfLp = new LinkProperties(mLinkProperties);
+
         InetAddress ip = null;
-        final ProvisioningChange delta;
-        synchronized (mLock) {
-            LinkProperties whatIfLp = new LinkProperties(mLinkProperties);
+        for (Map.Entry<InetAddress, NeighborEvent> entry : mNeighborWatchList.entrySet()) {
+            // 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;
 
-            for (Map.Entry<InetAddress, Short> entry : mIpWatchList.entrySet()) {
-                if (entry.getValue() != StructNdMsg.NUD_FAILED) {
-                    continue;
-                }
-
-                ip = entry.getKey();
-                for (RouteInfo route : mLinkProperties.getRoutes()) {
-                    if (ip.equals(route.getGateway())) {
-                        whatIfLp.removeRoute(route);
-                    }
-                }
-
-                if (avoidingBadLinks() || !(ip instanceof Inet6Address)) {
-                    // We should do this unconditionally, but alas we cannot: b/31827713.
-                    whatIfLp.removeDnsServer(ip);
+            ip = entry.getKey();
+            for (RouteInfo route : mLinkProperties.getRoutes()) {
+                if (ip.equals(route.getGateway())) {
+                    whatIfLp.removeRoute(route);
                 }
             }
 
-            delta = LinkProperties.compareProvisioning(mLinkProperties, whatIfLp);
+            if (avoidingBadLinks() || !(ip instanceof Inet6Address)) {
+                // We should do this unconditionally, but alas we cannot: b/31827713.
+                whatIfLp.removeDnsServer(ip);
+            }
         }
 
+        final ProvisioningChange delta = LinkProperties.compareProvisioning(
+                mLinkProperties, whatIfLp);
+
         if (delta == ProvisioningChange.LOST_PROVISIONING) {
-            final String logMsg = "FAILURE: LOST_PROVISIONING, " + msg;
+            final String logMsg = "FAILURE: LOST_PROVISIONING, " + event;
             Log.w(TAG, logMsg);
             if (mCallback != null) {
                 // TODO: remove |ip| when the callback signature no longer has
@@ -378,12 +341,9 @@
     }
 
     public void probeAll() {
-        final List<InetAddress> ipProbeList;
-        synchronized (mLock) {
-            ipProbeList = new ArrayList<>(mIpWatchList.keySet());
-        }
+        final List<InetAddress> ipProbeList = new ArrayList<>(mNeighborWatchList.keySet());
 
-        if (!ipProbeList.isEmpty() && mRunning) {
+        if (!ipProbeList.isEmpty()) {
             // Keep the CPU awake long enough to allow all ARP/ND
             // probes a reasonable chance at success. See b/23197666.
             //
@@ -394,13 +354,10 @@
         }
 
         for (InetAddress target : ipProbeList) {
-            if (!mRunning) {
-                break;
-            }
-            final int returnValue = probeNeighbor(mInterfaceIndex, target);
+            final int rval = IpNeighborMonitor.startKernelNeighborProbe(mInterfaceIndex, target);
             mLog.log(String.format("put neighbor %s into NUD_PROBE state (rval=%d)",
-                     target.getHostAddress(), returnValue));
-            logEvent(IpReachabilityEvent.PROBE, returnValue);
+                     target.getHostAddress(), rval));
+            logEvent(IpReachabilityEvent.PROBE, rval);
         }
         mLastProbeTimeMs = SystemClock.elapsedRealtime();
     }
@@ -446,153 +403,4 @@
         int eventType = IpReachabilityEvent.nudFailureEventType(isFromProbe, isProvisioningLost);
         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);
-            }
-        }
-    }
 }
diff --git a/services/net/java/android/net/netlink/NetlinkSocket.java b/services/net/java/android/net/netlink/NetlinkSocket.java
index f5f211d..5af3c29 100644
--- a/services/net/java/android/net/netlink/NetlinkSocket.java
+++ b/services/net/java/android/net/netlink/NetlinkSocket.java
@@ -16,16 +16,24 @@
 
 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.NetlinkSocketAddress;
 import android.system.Os;
-import android.system.OsConstants;
 import android.system.StructTimeval;
 import android.util.Log;
 import libcore.io.IoUtils;
 import libcore.io.Libcore;
 
-import java.io.Closeable;
 import java.io.FileDescriptor;
 import java.io.InterruptedIOException;
 import java.net.SocketAddress;
@@ -37,28 +45,27 @@
 /**
  * NetlinkSocket
  *
- * A small wrapper class to assist with AF_NETLINK socket operations.
+ * A small static class to assist with AF_NETLINK socket operations.
  *
  * @hide
  */
-public class NetlinkSocket implements Closeable {
+public class 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;
-    private NetlinkSocketAddress mAddr;
-    private long mLastRecvTimeoutMs;
-    private long mLastSendTimeoutMs;
+    public static final int DEFAULT_RECV_BUFSIZE = 8 * 1024;
+    public static final int SOCKET_RECV_BUFSIZE = 64 * 1024;
 
     public static void sendOneShotKernelMessage(int nlProto, byte[] msg) throws ErrnoException {
         final String errPrefix = "Error in NetlinkSocket.sendOneShotKernelMessage";
+        final long IO_TIMEOUT = 300L;
 
-        try (NetlinkSocket nlSocket = new NetlinkSocket(nlProto)) {
-            final long IO_TIMEOUT = 300L;
-            nlSocket.connectToKernel();
-            nlSocket.sendMessage(msg, 0, msg.length, IO_TIMEOUT);
-            final ByteBuffer bytes = nlSocket.recvMessage(IO_TIMEOUT);
+        FileDescriptor fd;
+
+        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.
             final NetlinkMessage response = NetlinkMessage.parse(bytes);
             if (response != null && response instanceof NetlinkErrorMessage &&
@@ -81,61 +88,30 @@
                     errmsg = response.toString();
                 }
                 Log.e(TAG, errPrefix + ", errmsg=" + errmsg);
-                throw new ErrnoException(errmsg, OsConstants.EPROTO);
+                throw new ErrnoException(errmsg, EPROTO);
             }
         } catch (InterruptedIOException e) {
             Log.e(TAG, errPrefix, e);
-            throw new ErrnoException(errPrefix, OsConstants.ETIMEDOUT, e);
+            throw new ErrnoException(errPrefix, ETIMEDOUT, e);
         } catch (SocketException e) {
             Log.e(TAG, errPrefix, e);
-            throw new ErrnoException(errPrefix, OsConstants.EIO, e);
+            throw new ErrnoException(errPrefix, EIO, e);
         }
+
+        IoUtils.closeQuietly(fd);
     }
 
-    public NetlinkSocket(int nlProto) throws ErrnoException {
-        mDescriptor = Os.socket(
-                OsConstants.AF_NETLINK, OsConstants.SOCK_DGRAM, nlProto);
-
-        Os.setsockoptInt(
-                mDescriptor, OsConstants.SOL_SOCKET,
-                OsConstants.SO_RCVBUF, SOCKET_RECV_BUFSIZE);
+    public static FileDescriptor forProto(int nlProto) throws ErrnoException {
+        final FileDescriptor fd = Os.socket(AF_NETLINK, SOCK_DGRAM, nlProto);
+        Os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, SOCKET_RECV_BUFSIZE);
+        return fd;
     }
 
-    public NetlinkSocketAddress getLocalAddress() throws ErrnoException {
-        return (NetlinkSocketAddress) Os.getsockname(mDescriptor);
+    public static void connectToKernel(FileDescriptor fd) throws ErrnoException, SocketException {
+        Os.connect(fd, (SocketAddress) (new NetlinkSocketAddress(0, 0)));
     }
 
-    public void bind(NetlinkSocketAddress localAddr) throws ErrnoException, SocketException {
-        Os.bind(mDescriptor, (SocketAddress)localAddr);
-    }
-
-    public void connectTo(NetlinkSocketAddress peerAddr)
-            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) {
+    private static void checkTimeout(long timeoutMs) {
         if (timeoutMs < 0) {
             throw new IllegalArgumentException("Negative timeouts not permitted");
         }
@@ -147,21 +123,14 @@
      *
      * 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 {
         checkTimeout(timeoutMs);
 
-        synchronized (mDescriptor) {
-            if (mLastRecvTimeoutMs != timeoutMs) {
-                Os.setsockoptTimeval(mDescriptor,
-                        OsConstants.SOL_SOCKET, OsConstants.SO_RCVTIMEO,
-                        StructTimeval.fromMillis(timeoutMs));
-                mLastRecvTimeoutMs = timeoutMs;
-            }
-        }
+        Os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, StructTimeval.fromMillis(timeoutMs));
 
         ByteBuffer byteBuffer = ByteBuffer.allocate(bufsize);
-        int length = Os.read(mDescriptor, byteBuffer);
+        int length = Os.read(fd, byteBuffer);
         if (length == bufsize) {
             Log.w(TAG, "maximum read");
         }
@@ -172,39 +141,16 @@
     }
 
     /**
-     * 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,
      * waiting at most |timeoutMs| milliseconds for the send to complete.
      *
      * 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 {
         checkTimeout(timeoutMs);
-
-        synchronized (mDescriptor) {
-            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);
+        Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(timeoutMs));
+        return Os.write(fd, bytes, offset, count);
     }
 }
diff --git a/services/net/java/android/net/netlink/StructNdMsg.java b/services/net/java/android/net/netlink/StructNdMsg.java
index b68ec0b..e34ec39 100644
--- a/services/net/java/android/net/netlink/StructNdMsg.java
+++ b/services/net/java/android/net/netlink/StructNdMsg.java
@@ -63,6 +63,11 @@
         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
     public static byte NTF_USE       = (byte) 0x01;
     public static byte NTF_SELF      = (byte) 0x02;
@@ -143,7 +148,7 @@
     }
 
     public boolean nudValid() {
-        return (nudConnected() || ((ndm_state & (NUD_PROBE|NUD_STALE|NUD_DELAY)) != 0));
+        return isNudStateValid(ndm_state);
     }
 
     @Override
diff --git a/services/net/java/android/net/util/BlockingSocketReader.java b/services/net/java/android/net/util/PacketReader.java
similarity index 97%
rename from services/net/java/android/net/util/BlockingSocketReader.java
rename to services/net/java/android/net/util/PacketReader.java
index 99bf469..10da2a5 100644
--- a/services/net/java/android/net/util/BlockingSocketReader.java
+++ b/services/net/java/android/net/util/PacketReader.java
@@ -67,7 +67,7 @@
  *
  * @hide
  */
-public abstract class BlockingSocketReader {
+public abstract class PacketReader {
     private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
     private static final int UNREGISTER_THIS_FD = 0;
 
@@ -83,11 +83,11 @@
         IoUtils.closeQuietly(fd);
     }
 
-    protected BlockingSocketReader(Handler h) {
+    protected PacketReader(Handler h) {
         this(h, DEFAULT_RECV_BUF_SIZE);
     }
 
-    protected BlockingSocketReader(Handler h, int recvbufsize) {
+    protected PacketReader(Handler h, int recvbufsize) {
         mHandler = h;
         mQueue = mHandler.getLooper().getQueue();
         mPacket = new byte[Math.max(recvbufsize, DEFAULT_RECV_BUF_SIZE)];
@@ -115,6 +115,8 @@
         }
     }
 
+    public Handler getHandler() { return mHandler; }
+
     public final int recvBufSize() { return mPacket.length; }
 
     public final long numPacketsReceived() { return mPacketsReceived; }
diff --git a/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java b/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java
index f849689..54776db 100644
--- a/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java
+++ b/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java
@@ -18,10 +18,12 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.when;
 
 import android.net.util.SharedLog;
+import android.os.Handler;
+import android.os.Looper;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
@@ -42,14 +44,18 @@
     @Mock IpReachabilityMonitor.Callback mCallback;
     @Mock IpReachabilityMonitor.Dependencies mDependencies;
     @Mock SharedLog mLog;
+    Handler mHandler;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        when(mLog.forSubComponent(anyString())).thenReturn(mLog);
+        mHandler = new Handler(Looper.getMainLooper());
     }
 
     IpReachabilityMonitor makeMonitor() {
-        return new IpReachabilityMonitor("fake0", 1, mLog, mCallback, null, mDependencies);
+        return new IpReachabilityMonitor(
+                "fake0", 1, mHandler, mLog, mCallback, null, mDependencies);
     }
 
     @Test
diff --git a/tests/net/java/android/net/netlink/NetlinkSocketTest.java b/tests/net/java/android/net/netlink/NetlinkSocketTest.java
index bd36bac8..11be40b 100644
--- a/tests/net/java/android/net/netlink/NetlinkSocketTest.java
+++ b/tests/net/java/android/net/netlink/NetlinkSocketTest.java
@@ -16,6 +16,8 @@
 
 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.assertNotNull;
 import static org.junit.Assert.assertTrue;
@@ -28,10 +30,12 @@
 import android.support.test.filters.SmallTest;
 import android.system.ErrnoException;
 import android.system.NetlinkSocketAddress;
-import android.system.OsConstants;
+import android.system.Os;
 import android.util.Log;
+import libcore.io.IoUtils;
 
 import java.io.InterruptedIOException;
+import java.io.FileDescriptor;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 
@@ -46,29 +50,28 @@
 
     @Test
     public void testBasicWorkingGetNeighborsQuery() throws Exception {
-        NetlinkSocket s = new NetlinkSocket(OsConstants.NETLINK_ROUTE);
-        assertNotNull(s);
+        final FileDescriptor fd = NetlinkSocket.forProto(NETLINK_ROUTE);
+        assertNotNull(fd);
 
-        s.connectToKernel();
+        NetlinkSocket.connectToKernel(fd);
 
-        NetlinkSocketAddress localAddr = s.getLocalAddress();
+        final NetlinkSocketAddress localAddr = (NetlinkSocketAddress) Os.getsockname(fd);
         assertNotNull(localAddr);
         assertEquals(0, localAddr.getGroupsMask());
         assertTrue(0 != localAddr.getPortId());
 
         final int TEST_SEQNO = 5;
-        final byte[] request = RtNetlinkNeighborMessage.newGetNeighborsRequest(TEST_SEQNO);
-        assertNotNull(request);
+        final byte[] req = RtNetlinkNeighborMessage.newGetNeighborsRequest(TEST_SEQNO);
+        assertNotNull(req);
 
         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 doneMessageCount = 0;
 
         while (doneMessageCount == 0) {
-            ByteBuffer response = null;
-            response = s.recvMessage(TIMEOUT);
+            ByteBuffer response = NetlinkSocket.recvMessage(fd, DEFAULT_RECV_BUFSIZE, TIMEOUT);
             assertNotNull(response);
             assertTrue(StructNlMsgHdr.STRUCT_SIZE <= response.limit());
             assertEquals(0, response.position());
@@ -100,30 +103,6 @@
         // TODO: make sure this test passes sanely in airplane mode.
         assertTrue(neighMessageCount > 0);
 
-        s.close();
-    }
-
-    @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();
+        IoUtils.closeQuietly(fd);
     }
 }
diff --git a/tests/net/java/android/net/util/BlockingSocketReaderTest.java b/tests/net/java/android/net/util/PacketReaderTest.java
similarity index 91%
rename from tests/net/java/android/net/util/BlockingSocketReaderTest.java
rename to tests/net/java/android/net/util/PacketReaderTest.java
index 29dfa4c..dced743 100644
--- a/tests/net/java/android/net/util/BlockingSocketReaderTest.java
+++ b/tests/net/java/android/net/util/PacketReaderTest.java
@@ -16,7 +16,7 @@
 
 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 org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -53,13 +53,13 @@
 import libcore.io.IoBridge;
 
 /**
- * Tests for BlockingSocketReader.
+ * Tests for PacketReader.
  *
  * @hide
  */
 @RunWith(AndroidJUnit4.class)
 @SmallTest
-public class BlockingSocketReaderTest {
+public class PacketReaderTest {
     static final InetAddress LOOPBACK6 = Inet6Address.getLoopbackAddress();
     static final StructTimeval TIMEO = StructTimeval.fromMillis(500);
 
@@ -69,9 +69,9 @@
     protected byte[] mLastRecvBuf;
     protected boolean mStopped;
     protected HandlerThread mHandlerThread;
-    protected BlockingSocketReader mReceiver;
+    protected PacketReader mReceiver;
 
-    class UdpLoopbackReader extends BlockingSocketReader {
+    class UdpLoopbackReader extends PacketReader {
         public UdpLoopbackReader(Handler h) {
             super(h);
         }
@@ -121,7 +121,7 @@
         mLastRecvBuf = null;
         mStopped = false;
 
-        mHandlerThread = new HandlerThread(BlockingSocketReaderTest.class.getSimpleName());
+        mHandlerThread = new HandlerThread(PacketReaderTest.class.getSimpleName());
         mHandlerThread.start();
     }
 
@@ -188,8 +188,8 @@
         mReceiver = null;
     }
 
-    class NullBlockingSocketReader extends BlockingSocketReader {
-        public NullBlockingSocketReader(Handler h, int recvbufsize) {
+    class NullPacketReader extends PacketReader {
+        public NullPacketReader(Handler h, int recvbufsize) {
             super(h, recvbufsize);
         }
 
@@ -202,7 +202,7 @@
         final Handler h = mHandlerThread.getThreadHandler();
 
         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());
         }
     }