Add tests for IpReachabilityMonitor

The first tests just verify that provisioning is lost if all IPv4/6 DNS
servers or gateways are lost.

Test: atest NetworkStackTests
Bug: 152819907
Merged-In: I8da6a8f4f237ce963c0a1610432d310160fd3f20
Change-Id: I8da6a8f4f237ce963c0a1610432d310160fd3f20
diff --git a/common/moduleutils/src/android/net/util/FdEventsReader.java b/common/moduleutils/src/android/net/util/FdEventsReader.java
index 5a1154f..ebd6c53 100644
--- a/common/moduleutils/src/android/net/util/FdEventsReader.java
+++ b/common/moduleutils/src/android/net/util/FdEventsReader.java
@@ -27,6 +27,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -92,6 +93,12 @@
         mBuffer = buffer;
     }
 
+    @VisibleForTesting
+    @NonNull
+    protected MessageQueue getMessageQueue() {
+        return mQueue;
+    }
+
     /** Start this FdEventsReader. */
     public boolean start() {
         if (!onCorrectThread()) {
@@ -185,7 +192,7 @@
 
         if (mFd == null) return false;
 
-        mQueue.addOnFileDescriptorEventListener(
+        getMessageQueue().addOnFileDescriptorEventListener(
                 mFd,
                 FD_EVENTS,
                 (fd, events) -> {
@@ -247,7 +254,7 @@
     private void unregisterAndDestroyFd() {
         if (mFd == null) return;
 
-        mQueue.removeOnFileDescriptorEventListener(mFd);
+        getMessageQueue().removeOnFileDescriptorEventListener(mFd);
         closeFd(mFd);
         mFd = null;
         onStop();
diff --git a/src/android/net/ip/IpReachabilityMonitor.java b/src/android/net/ip/IpReachabilityMonitor.java
index 17b1f3c..e1d4548 100644
--- a/src/android/net/ip/IpReachabilityMonitor.java
+++ b/src/android/net/ip/IpReachabilityMonitor.java
@@ -27,6 +27,7 @@
 import android.net.LinkProperties;
 import android.net.RouteInfo;
 import android.net.ip.IpNeighborMonitor.NeighborEvent;
+import android.net.ip.IpNeighborMonitor.NeighborEventConsumer;
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.IpReachabilityEvent;
 import android.net.netlink.StructNdMsg;
@@ -154,11 +155,12 @@
     }
 
     /**
-     * Encapsulates IpReachabilityMonitor depencencies on systems that hinder unit testing.
+     * Encapsulates IpReachabilityMonitor dependencies on systems that hinder unit testing.
      * TODO: consider also wrapping MultinetworkPolicyTracker in this interface.
      */
     interface Dependencies {
         void acquireWakeLock(long durationMs);
+        IpNeighborMonitor makeIpNeighborMonitor(Handler h, SharedLog log, NeighborEventConsumer cb);
 
         static Dependencies makeDefault(Context context, String iface) {
             final String lockName = TAG + "." + iface;
@@ -169,6 +171,11 @@
                 public void acquireWakeLock(long durationMs) {
                     lock.acquire(durationMs);
                 }
+
+                public IpNeighborMonitor makeIpNeighborMonitor(Handler h, SharedLog log,
+                        NeighborEventConsumer cb) {
+                    return new IpNeighborMonitor(h, log, cb);
+                }
             };
         }
     }
@@ -223,7 +230,7 @@
         }
         setNeighbourParametersForSteadyState();
 
-        mIpNeighborMonitor = new IpNeighborMonitor(h, mLog,
+        mIpNeighborMonitor = mDependencies.makeIpNeighborMonitor(h, mLog,
                 (NeighborEvent event) -> {
                     if (mInterfaceParams.index != event.ifindex) return;
                     if (!mNeighborWatchList.containsKey(event.ip)) return;
diff --git a/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.java b/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.java
deleted file mode 100644
index ba3b306..0000000
--- a/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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 static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.net.INetd;
-import android.net.util.InterfaceParams;
-import android.net.util.SharedLog;
-import android.os.Handler;
-import android.os.Looper;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Tests for IpReachabilityMonitor.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class IpReachabilityMonitorTest {
-    @Mock IpReachabilityMonitor.Callback mCallback;
-    @Mock IpReachabilityMonitor.Dependencies mDependencies;
-    @Mock SharedLog mLog;
-    @Mock Context mContext;
-    @Mock INetd mNetd;
-    Handler mHandler;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        when(mLog.forSubComponent(anyString())).thenReturn(mLog);
-        mHandler = new Handler(Looper.getMainLooper());
-    }
-
-    IpReachabilityMonitor makeMonitor() {
-        final InterfaceParams ifParams = new InterfaceParams("fake0", 1, null);
-        return new IpReachabilityMonitor(
-                mContext, ifParams, mHandler, mLog, mCallback, false, mDependencies, mNetd);
-    }
-
-    @Test
-    public void testNothing() {
-        // make sure the unit test runs in the same thread with main looper.
-        // Otherwise, throwing IllegalStateException would cause test fails.
-        mHandler.post(() -> makeMonitor());
-    }
-}
diff --git a/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt b/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt
new file mode 100644
index 0000000..ac50651
--- /dev/null
+++ b/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt
@@ -0,0 +1,247 @@
+/*
+ * 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.content.Context
+import android.net.INetd
+import android.net.InetAddresses.parseNumericAddress
+import android.net.IpPrefix
+import android.net.LinkAddress
+import android.net.LinkProperties
+import android.net.RouteInfo
+import android.net.netlink.StructNdMsg.NUD_FAILED
+import android.net.netlink.StructNdMsg.NUD_STALE
+import android.net.netlink.makeNewNeighMessage
+import android.net.util.InterfaceParams
+import android.net.util.SharedLog
+import android.os.Handler
+import android.os.HandlerThread
+import android.os.MessageQueue
+import android.os.MessageQueue.OnFileDescriptorEventListener
+import android.system.ErrnoException
+import android.system.OsConstants.EAGAIN
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.timeout
+import org.mockito.Mockito.verify
+import java.io.FileDescriptor
+import java.net.Inet4Address
+import java.net.Inet6Address
+import java.net.InetAddress
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.ConcurrentLinkedQueue
+import java.util.concurrent.TimeUnit
+import kotlin.test.assertTrue
+import kotlin.test.fail
+
+private const val TEST_TIMEOUT_MS = 10_000L
+
+private val TEST_IPV4_GATEWAY = parseNumericAddress("192.168.222.3") as Inet4Address
+private val TEST_IPV6_GATEWAY = parseNumericAddress("2001:db8::1") as Inet6Address
+
+private val TEST_IPV4_LINKADDR = LinkAddress("192.168.222.123/24")
+private val TEST_IPV6_LINKADDR = LinkAddress("2001:db8::123/64")
+
+// DNSes inside IP prefix
+private val TEST_IPV4_DNS = parseNumericAddress("192.168.222.1") as Inet4Address
+private val TEST_IPV6_DNS = parseNumericAddress("2001:db8::321") as Inet6Address
+
+private val TEST_IFACE = InterfaceParams("fake0", 21, null)
+private val TEST_LINK_PROPERTIES = LinkProperties().apply {
+    interfaceName = TEST_IFACE.name
+    addLinkAddress(TEST_IPV4_LINKADDR)
+    addLinkAddress(TEST_IPV6_LINKADDR)
+
+    // Add on link routes
+    addRoute(RouteInfo(TEST_IPV4_LINKADDR, null /* gateway */, TEST_IFACE.name))
+    addRoute(RouteInfo(TEST_IPV6_LINKADDR, null /* gateway */, TEST_IFACE.name))
+
+    // Add default routes
+    addRoute(RouteInfo(IpPrefix(parseNumericAddress("0.0.0.0"), 0), TEST_IPV4_GATEWAY))
+    addRoute(RouteInfo(IpPrefix(parseNumericAddress("::"), 0), TEST_IPV6_GATEWAY))
+
+    addDnsServer(TEST_IPV4_DNS)
+    addDnsServer(TEST_IPV6_DNS)
+}
+
+/**
+ * Tests for IpReachabilityMonitor.
+ */
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class IpReachabilityMonitorTest {
+    private val callback = mock(IpReachabilityMonitor.Callback::class.java)
+    private val dependencies = mock(IpReachabilityMonitor.Dependencies::class.java)
+    private val log = mock(SharedLog::class.java)
+    private val context = mock(Context::class.java)
+    private val netd = mock(INetd::class.java)
+    private val fd = mock(FileDescriptor::class.java)
+
+    private val handlerThread = HandlerThread(IpReachabilityMonitorTest::class.simpleName)
+    private val handler by lazy { Handler(handlerThread.looper) }
+
+    private lateinit var reachabilityMonitor: IpReachabilityMonitor
+    private lateinit var neighborMonitor: TestIpNeighborMonitor
+
+    /**
+     * A version of [IpNeighborMonitor] that overrides packet reading from a socket, and instead
+     * allows the test to enqueue test packets via [enqueuePacket].
+     */
+    private class TestIpNeighborMonitor(
+        handler: Handler,
+        log: SharedLog,
+        cb: NeighborEventConsumer,
+        private val fd: FileDescriptor
+    ) : IpNeighborMonitor(handler, log, cb) {
+
+        private val pendingPackets = ConcurrentLinkedQueue<ByteArray>()
+        val msgQueue = mock(MessageQueue::class.java)
+
+        private var eventListener: OnFileDescriptorEventListener? = null
+
+        override fun createFd() = fd
+        override fun getMessageQueue() = msgQueue
+
+        fun enqueuePacket(packet: ByteArray) {
+            val listener = eventListener ?: fail("IpNeighborMonitor was not yet started")
+            pendingPackets.add(packet)
+            handler.post {
+                listener.onFileDescriptorEvents(fd, OnFileDescriptorEventListener.EVENT_INPUT)
+            }
+        }
+
+        override fun readPacket(fd: FileDescriptor, packetBuffer: ByteArray): Int {
+            val packet = pendingPackets.poll() ?: throw ErrnoException("No pending packet", EAGAIN)
+            if (packet.size > packetBuffer.size) {
+                fail("Buffer (${packetBuffer.size}) is too small for packet (${packet.size})")
+            }
+            System.arraycopy(packet, 0, packetBuffer, 0, packet.size)
+            return packet.size
+        }
+
+        override fun onStart() {
+            super.onStart()
+
+            // Find the file descriptor listener that was registered on the instrumented queue
+            val captor = ArgumentCaptor.forClass(OnFileDescriptorEventListener::class.java)
+            verify(msgQueue).addOnFileDescriptorEventListener(
+                    eq(fd), anyInt(), captor.capture())
+            eventListener = captor.value
+        }
+    }
+
+    @Before
+    fun setUp() {
+        doReturn(log).`when`(log).forSubComponent(anyString())
+        doReturn(true).`when`(fd).valid()
+        handlerThread.start()
+
+        doAnswer { inv ->
+            val handler = inv.getArgument<Handler>(0)
+            val log = inv.getArgument<SharedLog>(1)
+            val cb = inv.getArgument<IpNeighborMonitor.NeighborEventConsumer>(2)
+            neighborMonitor = TestIpNeighborMonitor(handler, log, cb, fd)
+            neighborMonitor
+        }.`when`(dependencies).makeIpNeighborMonitor(any(), any(), any())
+
+        val monitorFuture = CompletableFuture<IpReachabilityMonitor>()
+        // IpReachabilityMonitor needs to be started from the handler thread
+        handler.post {
+            monitorFuture.complete(IpReachabilityMonitor(
+                    context,
+                    TEST_IFACE,
+                    handler,
+                    log,
+                    callback,
+                    false /* useMultinetworkPolicyTracker */,
+                    dependencies,
+                    netd))
+        }
+        reachabilityMonitor = monitorFuture.get(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS)
+        assertTrue(::neighborMonitor.isInitialized,
+                "IpReachabilityMonitor did not call makeIpNeighborMonitor")
+    }
+
+    @After
+    fun tearDown() {
+        doReturn(false).`when`(fd).valid()
+        handlerThread.quitSafely()
+    }
+
+    // TODO: fix this bug
+    @Test
+    fun testLoseProvisioning_CrashIfFirstProbeIsFailed() {
+        reachabilityMonitor.updateLinkProperties(TEST_LINK_PROPERTIES)
+
+        doAnswer {
+            // Set the fd as invalid when the event listener is removed, to avoid a crash when the
+            // reader tries to close the mock fd.
+            // This does not exactly reflect behavior on close, but this test is only demonstrating
+            // a bug that causes the close, and it will be removed when the bug fixed.
+            doReturn(false).`when`(fd).valid()
+        }.`when`(neighborMonitor.msgQueue).removeOnFileDescriptorEventListener(any())
+
+        neighborMonitor.enqueuePacket(makeNewNeighMessage(TEST_IPV4_DNS, NUD_FAILED))
+        verify(neighborMonitor.msgQueue, timeout(TEST_TIMEOUT_MS))
+                .removeOnFileDescriptorEventListener(any())
+        verify(callback, never()).notifyLost(eq(TEST_IPV4_DNS), anyString())
+    }
+
+    private fun runLoseProvisioningTest(lostNeighbor: InetAddress) {
+        reachabilityMonitor.updateLinkProperties(TEST_LINK_PROPERTIES)
+
+        neighborMonitor.enqueuePacket(makeNewNeighMessage(TEST_IPV4_GATEWAY, NUD_STALE))
+        neighborMonitor.enqueuePacket(makeNewNeighMessage(TEST_IPV6_GATEWAY, NUD_STALE))
+        neighborMonitor.enqueuePacket(makeNewNeighMessage(TEST_IPV4_DNS, NUD_STALE))
+        neighborMonitor.enqueuePacket(makeNewNeighMessage(TEST_IPV6_DNS, NUD_STALE))
+
+        neighborMonitor.enqueuePacket(makeNewNeighMessage(lostNeighbor, NUD_FAILED))
+        verify(callback, timeout(TEST_TIMEOUT_MS)).notifyLost(eq(lostNeighbor), anyString())
+    }
+
+    @Test
+    fun testLoseProvisioning_Ipv4DnsLost() {
+        runLoseProvisioningTest(TEST_IPV4_DNS)
+    }
+
+    @Test
+    fun testLoseProvisioning_Ipv6DnsLost() {
+        runLoseProvisioningTest(TEST_IPV6_DNS)
+    }
+
+    @Test
+    fun testLoseProvisioning_Ipv4GatewayLost() {
+        runLoseProvisioningTest(TEST_IPV4_GATEWAY)
+    }
+
+    @Test
+    fun testLoseProvisioning_Ipv6GatewayLost() {
+        runLoseProvisioningTest(TEST_IPV6_GATEWAY)
+    }
+}
\ No newline at end of file
diff --git a/tests/unit/src/android/net/netlink/NetlinkTestUtils.kt b/tests/unit/src/android/net/netlink/NetlinkTestUtils.kt
new file mode 100644
index 0000000..6655e96
--- /dev/null
+++ b/tests/unit/src/android/net/netlink/NetlinkTestUtils.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2020 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 android.net.netlink.NetlinkConstants.RTM_DELNEIGH
+import android.net.netlink.NetlinkConstants.RTM_NEWNEIGH
+import libcore.util.HexEncoding
+import libcore.util.HexEncoding.encodeToString
+import java.net.Inet6Address
+import java.net.InetAddress
+
+/**
+ * Make a RTM_NEWNEIGH netlink message.
+ */
+fun makeNewNeighMessage(
+    neighAddr: InetAddress,
+    nudState: Short
+) = makeNeighborMessage(
+        neighAddr = neighAddr,
+        type = RTM_NEWNEIGH,
+        nudState = nudState
+)
+
+/**
+ * Make a RTM_DELNEIGH netlink message.
+ */
+fun makeDelNeighMessage(
+    neighAddr: InetAddress,
+    nudState: Short
+) = makeNeighborMessage(
+        neighAddr = neighAddr,
+        type = RTM_DELNEIGH,
+        nudState = nudState
+)
+
+private fun makeNeighborMessage(
+    neighAddr: InetAddress,
+    type: Short,
+    nudState: Short
+) = HexEncoding.decode(
+    /* ktlint-disable indent */
+    // -- struct nlmsghdr --
+                         // length = 88 or 76:
+    (if (neighAddr is Inet6Address) "58000000" else "4c000000") +
+    type.toLEHex() +     // type
+    "0000" +             // flags
+    "00000000" +         // seqno
+    "00000000" +         // pid (0 == kernel)
+    // struct ndmsg
+                         // family (AF_INET6 or AF_INET)
+    (if (neighAddr is Inet6Address) "0a" else "02") +
+    "00" +               // pad1
+    "0000" +             // pad2
+    "15000000" +         // interface index (21 == wlan0, on test device)
+    nudState.toLEHex() + // NUD state
+    "00" +               // flags
+    "01" +               // type
+    // -- struct nlattr: NDA_DST --
+                         // length = 20 or 8:
+    (if (neighAddr is Inet6Address) "1400" else "0800") +
+    "0100" +             // type (1 == NDA_DST, for neighbor messages)
+                         // IP address:
+    encodeToString(neighAddr.address, false /* upperCase */) +
+    // -- struct nlattr: NDA_LLADDR --
+    "0a00" +             // length = 10
+    "0200" +             // type (2 == NDA_LLADDR, for neighbor messages)
+    "00005e000164" +     // MAC Address (== 00:00:5e:00:01:64)
+    "0000" +             // padding, for 4 byte alignment
+    // -- struct nlattr: NDA_PROBES --
+    "0800" +             // length = 8
+    "0400" +             // type (4 == NDA_PROBES, for neighbor messages)
+    "01000000" +         // number of probes
+    // -- struct nlattr: NDA_CACHEINFO --
+    "1400" +             // length = 20
+    "0300" +             // type (3 == NDA_CACHEINFO, for neighbor messages)
+    "05190000" +         // ndm_used, as "clock ticks ago"
+    "05190000" +         // ndm_confirmed, as "clock ticks ago"
+    "190d0000" +         // ndm_updated, as "clock ticks ago"
+    "00000000",          // ndm_refcnt
+    false /* allowSingleChar */)
+    /* ktlint-enable indent */
+
+/**
+ * Convert a [Short] to a little-endian hex string.
+ */
+private fun Short.toLEHex() = String.format("%04x", java.lang.Short.reverseBytes(this))
diff --git a/tests/unit/src/android/net/netlink/RtNetlinkNeighborMessageTest.java b/tests/unit/src/android/net/netlink/RtNetlinkNeighborMessageTest.java
index 72e6bca..34257b8 100644
--- a/tests/unit/src/android/net/netlink/RtNetlinkNeighborMessageTest.java
+++ b/tests/unit/src/android/net/netlink/RtNetlinkNeighborMessageTest.java
@@ -16,10 +16,15 @@
 
 package android.net.netlink;
 
+import static android.net.netlink.NetlinkTestUtilsKt.makeDelNeighMessage;
+import static android.net.netlink.NetlinkTestUtilsKt.makeNewNeighMessage;
+import static android.net.netlink.StructNdMsg.NUD_STALE;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import android.net.InetAddresses;
 import android.net.netlink.NetlinkConstants;
 import android.net.netlink.NetlinkMessage;
 import android.net.netlink.RtNetlinkNeighborMessage;
@@ -46,83 +51,11 @@
 public class RtNetlinkNeighborMessageTest {
     private final String TAG = "RtNetlinkNeighborMessageTest";
 
-    // Hexadecimal representation of packet capture.
-    public static final String RTM_DELNEIGH_HEX =
-            // struct nlmsghdr
-            "4c000000" +     // length = 76
-            "1d00" +         // type = 29 (RTM_DELNEIGH)
-            "0000" +         // flags
-            "00000000" +     // seqno
-            "00000000" +     // pid (0 == kernel)
-            // struct ndmsg
-            "02" +           // family
-            "00" +           // pad1
-            "0000" +         // pad2
-            "15000000" +     // interface index (21  == wlan0, on test device)
-            "0400" +         // NUD state (0x04 == NUD_STALE)
-            "00" +           // flags
-            "01" +           // type
-            // struct nlattr: NDA_DST
-            "0800" +         // length = 8
-            "0100" +         // type (1 == NDA_DST, for neighbor messages)
-            "c0a89ffe" +     // IPv4 address (== 192.168.159.254)
-            // struct nlattr: NDA_LLADDR
-            "0a00" +         // length = 10
-            "0200" +         // type (2 == NDA_LLADDR, for neighbor messages)
-            "00005e000164" + // MAC Address (== 00:00:5e:00:01:64)
-            "0000" +         // padding, for 4 byte alignment
-            // struct nlattr: NDA_PROBES
-            "0800" +         // length = 8
-            "0400" +         // type (4 == NDA_PROBES, for neighbor messages)
-            "01000000" +     // number of probes
-            // struct nlattr: NDA_CACHEINFO
-            "1400" +         // length = 20
-            "0300" +         // type (3 == NDA_CACHEINFO, for neighbor messages)
-            "05190000" +     // ndm_used, as "clock ticks ago"
-            "05190000" +     // ndm_confirmed, as "clock ticks ago"
-            "190d0000" +     // ndm_updated, as "clock ticks ago"
-            "00000000";      // ndm_refcnt
-    public static final byte[] RTM_DELNEIGH =
-            HexEncoding.decode(RTM_DELNEIGH_HEX.toCharArray(), false);
+    public static final byte[] RTM_DELNEIGH = makeDelNeighMessage(
+            InetAddresses.parseNumericAddress("192.168.159.254"), NUD_STALE);
 
-    // Hexadecimal representation of packet capture.
-    public static final String RTM_NEWNEIGH_HEX =
-            // struct nlmsghdr
-            "58000000" +     // length = 88
-            "1c00" +         // type = 28 (RTM_NEWNEIGH)
-            "0000" +         // flags
-            "00000000" +     // seqno
-            "00000000" +     // pid (0 == kernel)
-            // struct ndmsg
-            "0a" +           // family
-            "00" +           // pad1
-            "0000" +         // pad2
-            "15000000" +     // interface index (21  == wlan0, on test device)
-            "0400" +         // NUD state (0x04 == NUD_STALE)
-            "80" +           // flags
-            "01" +           // type
-            // struct nlattr: NDA_DST
-            "1400" +         // length = 20
-            "0100" +         // type (1 == NDA_DST, for neighbor messages)
-            "fe8000000000000086c9b2fffe6aed4b" + // IPv6 address (== fe80::86c9:b2ff:fe6a:ed4b)
-            // struct nlattr: NDA_LLADDR
-            "0a00" +         // length = 10
-            "0200" +         // type (2 == NDA_LLADDR, for neighbor messages)
-            "84c9b26aed4b" + // MAC Address (== 84:c9:b2:6a:ed:4b)
-            "0000" +         // padding, for 4 byte alignment
-            // struct nlattr: NDA_PROBES
-            "0800" +         // length = 8
-            "0400" +         // type (4 == NDA_PROBES, for neighbor messages)
-            "01000000" +     // number of probes
-            // struct nlattr: NDA_CACHEINFO
-            "1400" +         // length = 20
-            "0300" +         // type (3 == NDA_CACHEINFO, for neighbor messages)
-            "eb0e0000" +     // ndm_used, as "clock ticks ago"
-            "861f0000" +     // ndm_confirmed, as "clock ticks ago"
-            "00000000" +     // ndm_updated, as "clock ticks ago"
-            "05000000";      // ndm_refcnt
-    public static final byte[] RTM_NEWNEIGH =
-            HexEncoding.decode(RTM_NEWNEIGH_HEX.toCharArray(), false);
+    public static final byte[] RTM_NEWNEIGH = makeNewNeighMessage(
+            InetAddresses.parseNumericAddress("fe80::86c9:b2ff:fe6a:ed4b"), NUD_STALE);
 
     // An example of the full response from an RTM_GETNEIGH query.
     private static final String RTM_GETNEIGH_RESPONSE_HEX =
@@ -165,7 +98,7 @@
         assertNotNull(ndmsgHdr);
         assertEquals((byte) OsConstants.AF_INET, ndmsgHdr.ndm_family);
         assertEquals(21, ndmsgHdr.ndm_ifindex);
-        assertEquals(StructNdMsg.NUD_STALE, ndmsgHdr.ndm_state);
+        assertEquals(NUD_STALE, ndmsgHdr.ndm_state);
         final InetAddress destination = neighMsg.getDestination();
         assertNotNull(destination);
         assertEquals(InetAddress.parseNumericAddress("192.168.159.254"), destination);
@@ -192,7 +125,7 @@
         assertNotNull(ndmsgHdr);
         assertEquals((byte) OsConstants.AF_INET6, ndmsgHdr.ndm_family);
         assertEquals(21, ndmsgHdr.ndm_ifindex);
-        assertEquals(StructNdMsg.NUD_STALE, ndmsgHdr.ndm_state);
+        assertEquals(NUD_STALE, ndmsgHdr.ndm_state);
         final InetAddress destination = neighMsg.getDestination();
         assertNotNull(destination);
         assertEquals(InetAddress.parseNumericAddress("fe80::86c9:b2ff:fe6a:ed4b"), destination);