Set NLM_F_ACK in our RTM_NEWNEIGH requests

With NLM_F_ACK set in RTM_NEWNEIGH requests we get some response from
the kernel, whether there was an error or not.

Additionally:

    [1] add IpReachabilityMonitor#probeNeighbor() as a public
        static method, since it actually depends very little on the
        class internals and might be of larger use.

    [2] add a unittest for parsing NetlinkErrorMessages.

Bug: 18581716
Change-Id: I5d62e7a9972c7440f0483c38c77677436d3a1a25
diff --git a/core/java/android/net/IpReachabilityMonitor.java b/core/java/android/net/IpReachabilityMonitor.java
index cb8c866..b0f2003 100644
--- a/core/java/android/net/IpReachabilityMonitor.java
+++ b/core/java/android/net/IpReachabilityMonitor.java
@@ -76,6 +76,60 @@
     private final NetlinkSocketObserver mNetlinkSocketObserver;
     private final Thread mObserverThread;
 
+    /**
+     * Make the kernel to perform neighbor reachability detection (IPv4 ARP or IPv6 ND)
+     * for the given IP address on the specified interface index.
+     *
+     * @return true, if the request was successfully passed to the kernel; false otherwise.
+     */
+    public static boolean probeNeighbor(int ifIndex, InetAddress ip) {
+        final long IO_TIMEOUT = 300L;
+        // This currently does not cause neighbor probing if the target |ip|
+        // has been confirmed reachable within the past "delay_probe_time"
+        // seconds, i.e. within the past 5 seconds.
+        //
+        // TODO: replace with a transition directly to NUD_PROBE state once
+        // kernels are updated to do so correctly.
+        if (DBG) { Log.d(TAG, "Probing ip=" + ip.getHostAddress()); }
+
+        final byte[] msg = RtNetlinkNeighborMessage.newNewNeighborMessage(
+                1, ip, StructNdMsg.NUD_DELAY, ifIndex, null);
+        NetlinkSocket nlSocket = null;
+        boolean returnValue = false;
+
+        try {
+            nlSocket = new NetlinkSocket(OsConstants.NETLINK_ROUTE);
+            nlSocket.connectToKernel();
+            nlSocket.sendMessage(msg, 0, msg.length, IO_TIMEOUT);
+            final ByteBuffer bytes = nlSocket.recvMessage(IO_TIMEOUT);
+            final NetlinkMessage response = NetlinkMessage.parse(bytes);
+            if (response != null && response instanceof NetlinkErrorMessage &&
+                    (((NetlinkErrorMessage) response).getNlMsgError() != null) &&
+                    (((NetlinkErrorMessage) response).getNlMsgError().error == 0)) {
+                returnValue = true;
+            } else {
+                String errmsg;
+                if (bytes == null) {
+                    errmsg = "null recvMessage";
+                } else if (response == null) {
+                    bytes.position(0);
+                    errmsg = "raw bytes: " + NetlinkConstants.hexify(bytes);
+                } else {
+                    errmsg = response.toString();
+                }
+                Log.e(TAG, "Error probing ip=" + ip.getHostAddress() +
+                        ", errmsg=" + errmsg);
+            }
+        } catch (ErrnoException | InterruptedIOException | SocketException e) {
+            Log.d(TAG, "Error probing ip=" + ip.getHostAddress(), e);
+        }
+
+        if (nlSocket != null) {
+            nlSocket.close();
+        }
+        return returnValue;
+    }
+
     public IpReachabilityMonitor(String ifName, Callback callback) throws IllegalArgumentException {
         mInterfaceName = ifName;
         int ifIndex = -1;
@@ -216,38 +270,10 @@
             ipProbeList.addAll(mIpWatchList);
         }
         for (InetAddress target : ipProbeList) {
-            if (!stillRunning()) { break; }
-            probeIp(target);
-        }
-    }
-
-    private void probeIp(InetAddress ip) {
-        // This currently does not cause neighbor probing if the target |ip|
-        // has been confirmed reachable within the past "delay_probe_time"
-        // seconds, i.e. within the past 5 seconds.
-        //
-        // TODO: replace with a transition directly to NUD_PROBE state once
-        // kernels are updated to do so correctly.
-        if (DBG) { Log.d(TAG, "Probing ip=" + ip.getHostAddress()); }
-
-        final byte[] msg = RtNetlinkNeighborMessage.newNewNeighborMessage(
-                1, ip, StructNdMsg.NUD_DELAY, mInterfaceIndex, null);
-        NetlinkSocket nlSocket = null;
-
-        try {
-            nlSocket = new NetlinkSocket(OsConstants.NETLINK_ROUTE);
-            nlSocket.connectToKernel();
-            nlSocket.sendMessage(msg, 0, msg.length, 300);
-            final NetlinkMessage response = NetlinkMessage.parse(nlSocket.recvMessage(300));
-            if (response != null && response instanceof NetlinkErrorMessage) {
-                Log.e(TAG, "Error probing ip=" + response.toString());
+            if (!stillRunning()) {
+                break;
             }
-        } catch (ErrnoException | InterruptedIOException | SocketException e) {
-            Log.d(TAG, "Error probing ip=" + ip.getHostAddress(), e);
-        }
-
-        if (nlSocket != null) {
-            nlSocket.close();
+            probeNeighbor(mInterfaceIndex, target);
         }
     }
 
diff --git a/core/java/android/net/netlink/NetlinkErrorMessage.java b/core/java/android/net/netlink/NetlinkErrorMessage.java
index dbc10d6..e275574 100644
--- a/core/java/android/net/netlink/NetlinkErrorMessage.java
+++ b/core/java/android/net/netlink/NetlinkErrorMessage.java
@@ -18,7 +18,6 @@
 
 import android.net.netlink.StructNlMsgHdr;
 import android.net.netlink.NetlinkMessage;
-import android.util.Log;
 
 import java.nio.ByteBuffer;
 
diff --git a/core/java/android/net/netlink/NetlinkMessage.java b/core/java/android/net/netlink/NetlinkMessage.java
index bc04a16..3bf75ca 100644
--- a/core/java/android/net/netlink/NetlinkMessage.java
+++ b/core/java/android/net/netlink/NetlinkMessage.java
@@ -60,7 +60,7 @@
         switch (nlmsghdr.nlmsg_type) {
             //case NetlinkConstants.NLMSG_NOOP:
             case NetlinkConstants.NLMSG_ERROR:
-                return (NetlinkMessage) NetlinkErrorMessage.parse(byteBuffer);
+                return (NetlinkMessage) NetlinkErrorMessage.parse(nlmsghdr, byteBuffer);
             case NetlinkConstants.NLMSG_DONE:
                 byteBuffer.position(byteBuffer.position() + payloadLength);
                 return new NetlinkMessage(nlmsghdr);
diff --git a/core/java/android/net/netlink/RtNetlinkNeighborMessage.java b/core/java/android/net/netlink/RtNetlinkNeighborMessage.java
index b5f5817..02df131 100644
--- a/core/java/android/net/netlink/RtNetlinkNeighborMessage.java
+++ b/core/java/android/net/netlink/RtNetlinkNeighborMessage.java
@@ -16,6 +16,11 @@
 
 package android.net.netlink;
 
+import static android.net.netlink.StructNlMsgHdr.NLM_F_ACK;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_REPLACE;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;
+
 import android.net.netlink.StructNdaCacheInfo;
 import android.net.netlink.StructNdMsg;
 import android.net.netlink.StructNlAttr;
@@ -123,7 +128,7 @@
         final StructNlMsgHdr nlmsghdr = new StructNlMsgHdr();
         nlmsghdr.nlmsg_len = length;
         nlmsghdr.nlmsg_type = NetlinkConstants.RTM_GETNEIGH;
-        nlmsghdr.nlmsg_flags = StructNlMsgHdr.NLM_F_REQUEST|StructNlMsgHdr.NLM_F_DUMP;
+        nlmsghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
         nlmsghdr.nlmsg_seq = seqNo;
         nlmsghdr.pack(byteBuffer);
 
@@ -141,7 +146,7 @@
             int seqNo, InetAddress ip, short nudState, int ifIndex, byte[] llAddr) {
         final StructNlMsgHdr nlmsghdr = new StructNlMsgHdr();
         nlmsghdr.nlmsg_type = NetlinkConstants.RTM_NEWNEIGH;
-        nlmsghdr.nlmsg_flags = StructNlMsgHdr.NLM_F_REQUEST | StructNlMsgHdr.NLM_F_REPLACE;
+        nlmsghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE;
         nlmsghdr.nlmsg_seq = seqNo;
 
         final RtNetlinkNeighborMessage msg = new RtNetlinkNeighborMessage(nlmsghdr);
diff --git a/core/java/android/net/netlink/StructNlMsgErr.java b/core/java/android/net/netlink/StructNlMsgErr.java
index 6b02650..f095af4 100644
--- a/core/java/android/net/netlink/StructNlMsgErr.java
+++ b/core/java/android/net/netlink/StructNlMsgErr.java
@@ -52,11 +52,6 @@
     public int error;
     public StructNlMsgHdr msg;
 
-    public StructNlMsgErr() {
-        error = 0;
-        msg = null;
-    }
-
     public void pack(ByteBuffer byteBuffer) {
         // The ByteOrder must have already been set by the caller.  In most
         // cases ByteOrder.nativeOrder() is correct, with the possible
diff --git a/core/tests/coretests/src/android/net/netlink/NetlinkErrorMessageTest.java b/core/tests/coretests/src/android/net/netlink/NetlinkErrorMessageTest.java
new file mode 100644
index 0000000..e677475
--- /dev/null
+++ b/core/tests/coretests/src/android/net/netlink/NetlinkErrorMessageTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.netlink;
+
+import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_ACK;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_REPLACE;
+
+import android.net.netlink.NetlinkConstants;
+import android.net.netlink.NetlinkErrorMessage;
+import android.net.netlink.NetlinkMessage;
+import android.net.netlink.StructNlMsgErr;
+import android.util.Log;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import junit.framework.TestCase;
+import libcore.util.HexEncoding;
+
+
+public class NetlinkErrorMessageTest extends TestCase {
+    private final String TAG = "NetlinkErrorMessageTest";
+
+    // Hexadecimal representation of packet capture.
+    public static final String NLM_ERROR_OK_HEX =
+            // struct nlmsghdr
+            "24000000" +     // length = 36
+            "0200"     +     // type = 2 (NLMSG_ERROR)
+            "0000"     +     // flags
+            "26350000" +     // seqno
+            "64100000" +     // pid = userspace process
+            // error integer
+            "00000000" +     // "errno" (0 == OK)
+            // struct nlmsghdr
+            "30000000" +     // length (48) of original request
+            "1C00"     +     // type = 28 (RTM_NEWNEIGH)
+            "0501"     +     // flags (NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE)
+            "26350000" +     // seqno
+            "00000000";      // pid = kernel
+    public static final byte[] NLM_ERROR_OK =
+            HexEncoding.decode(NLM_ERROR_OK_HEX.toCharArray(), false);
+
+    public void testParseNlmErrorOk() {
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(NLM_ERROR_OK);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer);
+        assertNotNull(msg);
+        assertTrue(msg instanceof NetlinkErrorMessage);
+        final NetlinkErrorMessage errorMsg = (NetlinkErrorMessage) msg;
+
+        final StructNlMsgHdr hdr = errorMsg.getHeader();
+        assertNotNull(hdr);
+        assertEquals(36, hdr.nlmsg_len);
+        assertEquals(NetlinkConstants.NLMSG_ERROR, hdr.nlmsg_type);
+        assertEquals(0, hdr.nlmsg_flags);
+        assertEquals(13606, hdr.nlmsg_seq);
+        assertEquals(4196, hdr.nlmsg_pid);
+
+        final StructNlMsgErr err = errorMsg.getNlMsgError();
+        assertNotNull(err);
+        assertEquals(0, err.error);
+        assertNotNull(err.msg);
+        assertEquals(48, err.msg.nlmsg_len);
+        assertEquals(NetlinkConstants.RTM_NEWNEIGH, err.msg.nlmsg_type);
+        assertEquals((NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE), err.msg.nlmsg_flags);
+        assertEquals(13606, err.msg.nlmsg_seq);
+        assertEquals(0, err.msg.nlmsg_pid);
+    }
+}
diff --git a/core/tests/coretests/src/android/net/netlink/RtNetlinkNeighborMessageTest.java b/core/tests/coretests/src/android/net/netlink/RtNetlinkNeighborMessageTest.java
index a7bebad..19ee000 100644
--- a/core/tests/coretests/src/android/net/netlink/RtNetlinkNeighborMessageTest.java
+++ b/core/tests/coretests/src/android/net/netlink/RtNetlinkNeighborMessageTest.java
@@ -222,7 +222,7 @@
                 // struct nlmsghdr
                 "30000000" +     // length = 48
                 "1c00" +         // type = 28 (RTM_NEWNEIGH)
-                "0101" +         // flags (NLM_F_REQUEST | NLM_F_REPLACE)
+                "0501" +         // flags (NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE)
                 "4b0a0000" +     // seqno
                 "00000000" +     // pid (0 == kernel)
                 // struct ndmsg