Snap for 8857176 from ed0093892f8fb732dcd1c088f0ec5dcf01cfa37e to mainline-go-ipsec-release

Change-Id: I5cd61a58cf3ca43a17f3f06336551bb0e4a60dc1
diff --git a/Android.bp b/Android.bp
index 4b3d558..385daed 100644
--- a/Android.bp
+++ b/Android.bp
@@ -284,6 +284,7 @@
         "statsprotos",
         "captiveportal-lib",
         "net-utils-device-common",
+        "net-utils-device-common-ip",
         "net-utils-device-common-netlink",
     ],
     plugins: ["java_api_finder"],
diff --git a/common/moduleutils/Android.bp b/common/moduleutils/Android.bp
index 07e322d..91a14bf 100644
--- a/common/moduleutils/Android.bp
+++ b/common/moduleutils/Android.bp
@@ -45,7 +45,9 @@
 
 filegroup {
     name: "networkstack-module-utils-srcs",
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/android/net/shared/*.java",
+    ],
     visibility: [
         "//packages/modules/NetworkStack",
     ]
@@ -55,10 +57,6 @@
 filegroup {
     name: "tethering-module-utils-srcs",
     srcs: [
-        "src/android/net/ip/ConntrackMonitor.java",
-        "src/android/net/ip/InterfaceController.java",
-        "src/android/net/ip/IpNeighborMonitor.java",
-        "src/android/net/ip/NetlinkMonitor.java",
         "src/android/net/shared/NetdUtils.java",
     ],
     visibility: [
diff --git a/common/moduleutils/src/android/net/ip/ConntrackMonitor.java b/common/moduleutils/src/android/net/ip/ConntrackMonitor.java
deleted file mode 100644
index dac4a0f..0000000
--- a/common/moduleutils/src/android/net/ip/ConntrackMonitor.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * 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.ip;
-
-import static com.android.net.module.util.netlink.ConntrackMessage.DYING_MASK;
-import static com.android.net.module.util.netlink.ConntrackMessage.ESTABLISHED_MASK;
-
-import android.os.Handler;
-import android.system.OsConstants;
-
-import androidx.annotation.NonNull;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.net.module.util.SharedLog;
-import com.android.net.module.util.netlink.ConntrackMessage;
-import com.android.net.module.util.netlink.NetlinkConstants;
-import com.android.net.module.util.netlink.NetlinkMessage;
-
-import java.util.Objects;
-
-
-/**
- * ConntrackMonitor.
- *
- * Monitors the netfilter conntrack notifications and presents to callers
- * ConntrackEvents describing each event.
- *
- * @hide
- */
-public class ConntrackMonitor extends NetlinkMonitor {
-    private static final String TAG = ConntrackMonitor.class.getSimpleName();
-    private static final boolean DBG = false;
-    private static final boolean VDBG = false;
-
-    // Reference kernel/uapi/linux/netfilter/nfnetlink_compat.h
-    public static final int NF_NETLINK_CONNTRACK_NEW = 1;
-    public static final int NF_NETLINK_CONNTRACK_UPDATE = 2;
-    public static final int NF_NETLINK_CONNTRACK_DESTROY = 4;
-
-    // The socket receive buffer size in bytes. If too many conntrack messages are sent too
-    // quickly, the conntrack messages can overflow the socket receive buffer. This can happen
-    // if too many connections are disconnected by losing network and so on. Use a large-enough
-    // buffer to avoid the error ENOBUFS while listening to the conntrack messages.
-    private static final int SOCKET_RECV_BUFSIZE = 6 * 1024 * 1024;
-
-    /**
-     * A class for describing parsed netfilter conntrack events.
-     */
-    public static class ConntrackEvent {
-        /**
-         * Conntrack event type.
-         */
-        public final short msgType;
-        /**
-         * Original direction conntrack tuple.
-         */
-        public final ConntrackMessage.Tuple tupleOrig;
-        /**
-         * Reply direction conntrack tuple.
-         */
-        public final ConntrackMessage.Tuple tupleReply;
-        /**
-         * Connection status. A bitmask of ip_conntrack_status enum flags.
-         */
-        public final int status;
-        /**
-         * Conntrack timeout.
-         */
-        public final int timeoutSec;
-
-        public ConntrackEvent(ConntrackMessage msg) {
-            this.msgType = msg.getHeader().nlmsg_type;
-            this.tupleOrig = msg.tupleOrig;
-            this.tupleReply = msg.tupleReply;
-            this.status = msg.status;
-            this.timeoutSec = msg.timeoutSec;
-        }
-
-        @VisibleForTesting
-        public ConntrackEvent(short msgType, ConntrackMessage.Tuple tupleOrig,
-                ConntrackMessage.Tuple tupleReply, int status, int timeoutSec) {
-            this.msgType = msgType;
-            this.tupleOrig = tupleOrig;
-            this.tupleReply = tupleReply;
-            this.status = status;
-            this.timeoutSec = timeoutSec;
-        }
-
-        @Override
-        @VisibleForTesting
-        public boolean equals(Object o) {
-            if (!(o instanceof ConntrackEvent)) return false;
-            ConntrackEvent that = (ConntrackEvent) o;
-            return this.msgType == that.msgType
-                    && Objects.equals(this.tupleOrig, that.tupleOrig)
-                    && Objects.equals(this.tupleReply, that.tupleReply)
-                    && this.status == that.status
-                    && this.timeoutSec == that.timeoutSec;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(msgType, tupleOrig, tupleReply, status, timeoutSec);
-        }
-
-        @Override
-        public String toString() {
-            return "ConntrackEvent{"
-                    + "msg_type{"
-                    + NetlinkConstants.stringForNlMsgType(msgType, OsConstants.NETLINK_NETFILTER)
-                    + "}, "
-                    + "tuple_orig{" + tupleOrig + "}, "
-                    + "tuple_reply{" + tupleReply + "}, "
-                    + "status{"
-                    + status + "(" + ConntrackMessage.stringForIpConntrackStatus(status) + ")"
-                    + "}, "
-                    + "timeout_sec{" + Integer.toUnsignedLong(timeoutSec) + "}"
-                    + "}";
-        }
-
-        /**
-         * Check the established NAT session conntrack message.
-         *
-         * @param msg the conntrack message to check.
-         * @return true if an established NAT message, false if not.
-         */
-        public static boolean isEstablishedNatSession(@NonNull ConntrackMessage msg) {
-            if (msg.getMessageType() != NetlinkConstants.IPCTNL_MSG_CT_NEW) return false;
-            if (msg.tupleOrig == null) return false;
-            if (msg.tupleReply == null) return false;
-            if (msg.timeoutSec == 0) return false;
-            if ((msg.status & ESTABLISHED_MASK) != ESTABLISHED_MASK) return false;
-
-            return true;
-        }
-
-        /**
-         * Check the dying NAT session conntrack message.
-         * Note that IPCTNL_MSG_CT_DELETE event has no CTA_TIMEOUT attribute.
-         *
-         * @param msg the conntrack message to check.
-         * @return true if a dying NAT message, false if not.
-         */
-        public static boolean isDyingNatSession(@NonNull ConntrackMessage msg) {
-            if (msg.getMessageType() != NetlinkConstants.IPCTNL_MSG_CT_DELETE) return false;
-            if (msg.tupleOrig == null) return false;
-            if (msg.tupleReply == null) return false;
-            if (msg.timeoutSec != 0) return false;
-            if ((msg.status & DYING_MASK) != DYING_MASK) return false;
-
-            return true;
-        }
-    }
-
-    /**
-     * A callback to caller for conntrack event.
-     */
-    public interface ConntrackEventConsumer {
-        /**
-         * Every conntrack event received on the netlink socket is passed in
-         * here.
-         */
-        void accept(@NonNull ConntrackEvent event);
-    }
-
-    private final ConntrackEventConsumer mConsumer;
-
-    public ConntrackMonitor(@NonNull Handler h, @NonNull SharedLog log,
-            @NonNull ConntrackEventConsumer cb) {
-        super(h, log, TAG, OsConstants.NETLINK_NETFILTER, NF_NETLINK_CONNTRACK_NEW
-                | NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY, SOCKET_RECV_BUFSIZE);
-        mConsumer = cb;
-    }
-
-    @Override
-    public void processNetlinkMessage(NetlinkMessage nlMsg, final long whenMs) {
-        if (!(nlMsg instanceof ConntrackMessage)) {
-            mLog.e("non-conntrack msg: " + nlMsg);
-            return;
-        }
-
-        final ConntrackMessage conntrackMsg = (ConntrackMessage) nlMsg;
-        if (!(ConntrackEvent.isEstablishedNatSession(conntrackMsg)
-                || ConntrackEvent.isDyingNatSession(conntrackMsg))) {
-            return;
-        }
-
-        mConsumer.accept(new ConntrackEvent(conntrackMsg));
-    }
-}
diff --git a/common/moduleutils/src/android/net/ip/InterfaceController.java b/common/moduleutils/src/android/net/ip/InterfaceController.java
deleted file mode 100644
index 9338792..0000000
--- a/common/moduleutils/src/android/net/ip/InterfaceController.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright (C) 2019 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 android.net.INetd.IF_STATE_DOWN;
-import static android.net.INetd.IF_STATE_UP;
-
-import android.net.INetd;
-import android.net.InterfaceConfigurationParcel;
-import android.net.LinkAddress;
-import android.os.RemoteException;
-import android.os.ServiceSpecificException;
-import android.system.OsConstants;
-
-import com.android.net.module.util.SharedLog;
-
-import java.net.Inet4Address;
-import java.net.InetAddress;
-
-
-/**
- * Encapsulates the multiple IP configuration operations performed on an interface.
- *
- * TODO: refactor/eliminate the redundant ways to set and clear addresses.
- *
- * @hide
- */
-public class InterfaceController {
-    private final static boolean DBG = false;
-
-    private final String mIfName;
-    private final INetd mNetd;
-    private final SharedLog mLog;
-
-    public InterfaceController(String ifname, INetd netd, SharedLog log) {
-        mIfName = ifname;
-        mNetd = netd;
-        mLog = log;
-    }
-
-    /**
-     * Set the IPv4 address and also optionally bring the interface up or down.
-     */
-    public boolean setInterfaceConfiguration(final LinkAddress ipv4Addr,
-            final Boolean setIfaceUp) {
-        if (!(ipv4Addr.getAddress() instanceof Inet4Address)) {
-            throw new IllegalArgumentException("Invalid or mismatched Inet4Address");
-        }
-        // Note: currently netd only support INetd#IF_STATE_UP and #IF_STATE_DOWN.
-        // Other flags would be ignored.
-
-        final InterfaceConfigurationParcel ifConfig = new InterfaceConfigurationParcel();
-        ifConfig.ifName = mIfName;
-        ifConfig.ipv4Addr = ipv4Addr.getAddress().getHostAddress();
-        ifConfig.prefixLength = ipv4Addr.getPrefixLength();
-        // Netd ignores hwaddr in interfaceSetCfg.
-        ifConfig.hwAddr = "";
-        if (setIfaceUp == null) {
-            // Empty array means no change.
-            ifConfig.flags = new String[0];
-        } else {
-            // Netd ignores any flag that's not IF_STATE_UP or IF_STATE_DOWN in interfaceSetCfg.
-            ifConfig.flags = setIfaceUp.booleanValue()
-                    ? new String[] {IF_STATE_UP} : new String[] {IF_STATE_DOWN};
-        }
-        try {
-            mNetd.interfaceSetCfg(ifConfig);
-        } catch (RemoteException | ServiceSpecificException e) {
-            logError("Setting IPv4 address to %s/%d failed: %s",
-                    ifConfig.ipv4Addr, ifConfig.prefixLength, e);
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Set the IPv4 address of the interface.
-     */
-    public boolean setIPv4Address(final LinkAddress address) {
-        return setInterfaceConfiguration(address, null);
-    }
-
-    /**
-     * Clear the IPv4Address of the interface.
-     */
-    public boolean clearIPv4Address() {
-        return setIPv4Address(new LinkAddress("0.0.0.0/0"));
-    }
-
-    private boolean setEnableIPv6(boolean enabled) {
-        try {
-            mNetd.interfaceSetEnableIPv6(mIfName, enabled);
-        } catch (RemoteException | ServiceSpecificException e) {
-            logError("%s IPv6 failed: %s", (enabled ? "enabling" : "disabling"), e);
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Enable IPv6 on the interface.
-     */
-    public boolean enableIPv6() {
-        return setEnableIPv6(true);
-    }
-
-    /**
-     * Disable IPv6 on the interface.
-     */
-    public boolean disableIPv6() {
-        return setEnableIPv6(false);
-    }
-
-    /**
-     * Enable or disable IPv6 privacy extensions on the interface.
-     * @param enabled Whether the extensions should be enabled.
-     */
-    public boolean setIPv6PrivacyExtensions(boolean enabled) {
-        try {
-            mNetd.interfaceSetIPv6PrivacyExtensions(mIfName, enabled);
-        } catch (RemoteException | ServiceSpecificException e) {
-            logError("error %s IPv6 privacy extensions: %s",
-                    (enabled ? "enabling" : "disabling"), e);
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Set IPv6 address generation mode on the interface.
-     *
-     * <p>IPv6 should be disabled before changing the mode.
-     */
-    public boolean setIPv6AddrGenModeIfSupported(int mode) {
-        try {
-            mNetd.setIPv6AddrGenMode(mIfName, mode);
-        } catch (RemoteException e) {
-            logError("Unable to set IPv6 addrgen mode: %s", e);
-            return false;
-        } catch (ServiceSpecificException e) {
-            if (e.errorCode != OsConstants.EOPNOTSUPP) {
-                logError("Unable to set IPv6 addrgen mode: %s", e);
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Add an address to the interface.
-     */
-    public boolean addAddress(LinkAddress addr) {
-        return addAddress(addr.getAddress(), addr.getPrefixLength());
-    }
-
-    /**
-     * Add an address to the interface.
-     */
-    public boolean addAddress(InetAddress ip, int prefixLen) {
-        try {
-            mNetd.interfaceAddAddress(mIfName, ip.getHostAddress(), prefixLen);
-        } catch (ServiceSpecificException | RemoteException e) {
-            logError("failed to add %s/%d: %s", ip, prefixLen, e);
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Remove an address from the interface.
-     */
-    public boolean removeAddress(InetAddress ip, int prefixLen) {
-        try {
-            mNetd.interfaceDelAddress(mIfName, ip.getHostAddress(), prefixLen);
-        } catch (ServiceSpecificException | RemoteException e) {
-            logError("failed to remove %s/%d: %s", ip, prefixLen, e);
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Remove all addresses from the interface.
-     */
-    public boolean clearAllAddresses() {
-        try {
-            mNetd.interfaceClearAddrs(mIfName);
-        } catch (Exception e) {
-            logError("Failed to clear addresses: %s", e);
-            return false;
-        }
-        return true;
-    }
-
-    private void logError(String fmt, Object... args) {
-        mLog.e(String.format(fmt, args));
-    }
-}
diff --git a/common/moduleutils/src/android/net/ip/IpNeighborMonitor.java b/common/moduleutils/src/android/net/ip/IpNeighborMonitor.java
deleted file mode 100644
index 507841b..0000000
--- a/common/moduleutils/src/android/net/ip/IpNeighborMonitor.java
+++ /dev/null
@@ -1,179 +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 android.system.OsConstants.NETLINK_ROUTE;
-
-import static com.android.net.module.util.netlink.NetlinkConstants.RTM_DELNEIGH;
-import static com.android.net.module.util.netlink.NetlinkConstants.hexify;
-import static com.android.net.module.util.netlink.NetlinkConstants.stringForNlMsgType;
-
-import android.net.MacAddress;
-import android.os.Handler;
-import android.system.ErrnoException;
-import android.system.OsConstants;
-import android.util.Log;
-
-import com.android.net.module.util.SharedLog;
-import com.android.net.module.util.netlink.NetlinkMessage;
-import com.android.net.module.util.netlink.NetlinkSocket;
-import com.android.net.module.util.netlink.RtNetlinkNeighborMessage;
-import com.android.net.module.util.netlink.StructNdMsg;
-
-import java.net.InetAddress;
-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 NetlinkMonitor {
-    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(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 MacAddress macAddr;
-
-        public NeighborEvent(long elapsedMs, short msgType, int ifindex, InetAddress ip,
-                short nudState, MacAddress macAddr) {
-            this.elapsedMs = elapsedMs;
-            this.msgType = msgType;
-            this.ifindex = ifindex;
-            this.ip = ip;
-            this.nudState = nudState;
-            this.macAddr = macAddr;
-        }
-
-        boolean isConnected() {
-            return (msgType != RTM_DELNEIGH) && StructNdMsg.isNudStateConnected(nudState);
-        }
-
-        boolean isValid() {
-            return (msgType != RTM_DELNEIGH) && StructNdMsg.isNudStateValid(nudState);
-        }
-
-        @Override
-        public String toString() {
-            final StringJoiner j = new StringJoiner(",", "NeighborEvent{", "}");
-            return j.add("@" + elapsedMs)
-                    .add(stringForNlMsgType(msgType, NETLINK_ROUTE))
-                    .add("if=" + ifindex)
-                    .add(ip.getHostAddress())
-                    .add(StructNdMsg.stringForNudState(nudState))
-                    .add("[" + macAddr + "]")
-                    .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 NeighborEventConsumer mConsumer;
-
-    public IpNeighborMonitor(Handler h, SharedLog log, NeighborEventConsumer cb) {
-        super(h, log, TAG, NETLINK_ROUTE, OsConstants.RTMGRP_NEIGH);
-        mConsumer = (cb != null) ? cb : (event) -> { /* discard */ };
-    }
-
-    @Override
-    public void processNetlinkMessage(NetlinkMessage nlMsg, final long whenMs) {
-        if (!(nlMsg instanceof RtNetlinkNeighborMessage)) {
-            mLog.e("non-rtnetlink neighbor msg: " + nlMsg);
-            return;
-        }
-
-        final RtNetlinkNeighborMessage neighMsg = (RtNetlinkNeighborMessage) nlMsg;
-        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 == RTM_DELNEIGH)
-                ? StructNdMsg.NUD_NONE
-                : ndMsg.ndm_state;
-
-        final NeighborEvent event = new NeighborEvent(
-                whenMs, msgType, ifindex, destination, nudState,
-                getMacAddress(neighMsg.getLinkLayerAddress()));
-
-        if (VDBG) {
-            Log.d(TAG, neighMsg.toString());
-        }
-        if (DBG) {
-            Log.d(TAG, event.toString());
-        }
-
-        mConsumer.accept(event);
-    }
-
-    private static MacAddress getMacAddress(byte[] linkLayerAddress) {
-        if (linkLayerAddress != null) {
-            try {
-                return MacAddress.fromBytes(linkLayerAddress);
-            } catch (IllegalArgumentException e) {
-                Log.e(TAG, "Failed to parse link-layer address: " + hexify(linkLayerAddress));
-            }
-        }
-
-        return null;
-    }
-}
diff --git a/common/moduleutils/src/android/net/ip/NetlinkMonitor.java b/common/moduleutils/src/android/net/ip/NetlinkMonitor.java
deleted file mode 100644
index 8266f76..0000000
--- a/common/moduleutils/src/android/net/ip/NetlinkMonitor.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * 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.ip;
-
-import static android.net.util.SocketUtils.makeNetlinkSocketAddress;
-import static android.system.OsConstants.AF_NETLINK;
-import static android.system.OsConstants.ENOBUFS;
-import static android.system.OsConstants.SOCK_DGRAM;
-import static android.system.OsConstants.SOCK_NONBLOCK;
-import static android.system.OsConstants.SOL_SOCKET;
-import static android.system.OsConstants.SO_RCVBUF;
-
-import static com.android.net.module.util.netlink.NetlinkConstants.hexify;
-
-import android.annotation.NonNull;
-import android.net.util.SocketUtils;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.util.Log;
-
-import com.android.net.module.util.PacketReader;
-import com.android.net.module.util.SharedLog;
-import com.android.net.module.util.netlink.NetlinkErrorMessage;
-import com.android.net.module.util.netlink.NetlinkMessage;
-import com.android.net.module.util.netlink.NetlinkSocket;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.net.SocketAddress;
-import java.net.SocketException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-/**
- * A simple base class to listen for netlink broadcasts.
- *
- * Opens a netlink socket of the given family and binds to the specified groups. Polls the socket
- * from the event loop of the passed-in {@link Handler}, and calls the subclass-defined
- * {@link #processNetlinkMessage} method on the handler thread for each netlink message that
- * arrives. Currently ignores all netlink errors.
- */
-public class NetlinkMonitor extends PacketReader {
-    protected final SharedLog mLog;
-    protected final String mTag;
-    private final int mFamily;
-    private final int mBindGroups;
-    private final int mSockRcvbufSize;
-
-    private static final boolean DBG = false;
-
-    // Default socket receive buffer size. This means the specific buffer size is not set.
-    private static final int DEFAULT_SOCKET_RECV_BUFSIZE = -1;
-
-    /**
-     * Constructs a new {@code NetlinkMonitor} instance.
-     *
-     * @param h The Handler on which to poll for messages and on which to call
-     *          {@link #processNetlinkMessage}.
-     * @param log A SharedLog to log to.
-     * @param tag The log tag to use for log messages.
-     * @param family the Netlink socket family to, e.g., {@code NETLINK_ROUTE}.
-     * @param bindGroups the netlink groups to bind to.
-     * @param sockRcvbufSize the specific socket receive buffer size in bytes. -1 means that don't
-     *        set the specific socket receive buffer size in #createFd and use the default value in
-     *        /proc/sys/net/core/rmem_default file. See SO_RCVBUF in man-pages/socket.
-     */
-    public NetlinkMonitor(@NonNull Handler h, @NonNull SharedLog log, @NonNull String tag,
-            int family, int bindGroups, int sockRcvbufSize) {
-        super(h, NetlinkSocket.DEFAULT_RECV_BUFSIZE);
-        mLog = log.forSubComponent(tag);
-        mTag = tag;
-        mFamily = family;
-        mBindGroups = bindGroups;
-        mSockRcvbufSize = sockRcvbufSize;
-    }
-
-    public NetlinkMonitor(@NonNull Handler h, @NonNull SharedLog log, @NonNull String tag,
-            int family, int bindGroups) {
-        this(h, log, tag, family, bindGroups, DEFAULT_SOCKET_RECV_BUFSIZE);
-    }
-
-    @Override
-    protected FileDescriptor createFd() {
-        FileDescriptor fd = null;
-
-        try {
-            fd = Os.socket(AF_NETLINK, SOCK_DGRAM | SOCK_NONBLOCK, mFamily);
-            if (mSockRcvbufSize != DEFAULT_SOCKET_RECV_BUFSIZE) {
-                try {
-                    Os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, mSockRcvbufSize);
-                } catch (ErrnoException e) {
-                    Log.wtf(mTag, "Failed to set SO_RCVBUF to " + mSockRcvbufSize, e);
-                }
-            }
-            Os.bind(fd, makeNetlinkSocketAddress(0, mBindGroups));
-            NetlinkSocket.connectToKernel(fd);
-
-            if (DBG) {
-                final SocketAddress nlAddr = Os.getsockname(fd);
-                Log.d(mTag, "bound to sockaddr_nl{" + nlAddr.toString() + "}");
-            }
-        } catch (ErrnoException | SocketException e) {
-            logError("Failed to create rtnetlink socket", e);
-            closeSocketQuietly(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());
-
-        while (byteBuffer.remaining() > 0) {
-            try {
-                final int position = byteBuffer.position();
-                final NetlinkMessage nlMsg = NetlinkMessage.parse(byteBuffer, mFamily);
-                if (nlMsg == null || nlMsg.getHeader() == null) {
-                    byteBuffer.position(position);
-                    mLog.e("unparsable netlink msg: " + hexify(byteBuffer));
-                    break;
-                }
-
-                if (nlMsg instanceof NetlinkErrorMessage) {
-                    mLog.e("netlink error: " + nlMsg);
-                    continue;
-                }
-
-                processNetlinkMessage(nlMsg, whenMs);
-            } catch (Exception e) {
-                mLog.e("Error handling netlink message", e);
-            }
-        }
-    }
-
-    @Override
-    protected void logError(String msg, Exception e) {
-        mLog.e(msg, e);
-    }
-
-    // Ignoring ENOBUFS may miss any important netlink messages, there are some messages which
-    // cannot be recovered by dumping current state once missed since kernel doesn't keep state
-    // for it. In addition, dumping current state will not result in any RTM_DELxxx messages, so
-    // reconstructing current state from a dump will be difficult. However, for those netlink
-    // messages don't cause any state changes, e.g. RTM_NEWLINK with current link state, maybe
-    // it's okay to ignore them, because these netlink messages won't cause any changes on the
-    // LinkProperties. Given the above trade-offs, try to ignore ENOBUFS and that's similar to
-    // what netd does today.
-    //
-    // TODO: log metrics when ENOBUFS occurs, or even force a disconnect, it will help see how
-    // often this error occurs on fields with the associated socket receive buffer size.
-    @Override
-    protected boolean handleReadError(ErrnoException e) {
-        logError("readPacket error: ", e);
-        if (e.errno == ENOBUFS) {
-            Log.wtf(mTag, "Errno: ENOBUFS");
-            return false;
-        }
-        return true;
-    }
-
-    // TODO: move NetworkStackUtils to frameworks/libs/net for NetworkStackUtils#closeSocketQuietly.
-    private void closeSocketQuietly(FileDescriptor fd) {
-        try {
-            SocketUtils.closeSocket(fd);
-        } catch (IOException ignored) {
-        }
-    }
-
-    /**
-     * Processes one netlink message. Must be overridden by subclasses.
-     * @param nlMsg the message to process.
-     * @param whenMs the timestamp, as measured by {@link SystemClock#elapsedRealtime}, when the
-     *               message was received.
-     */
-    protected void processNetlinkMessage(NetlinkMessage nlMsg, long whenMs) { }
-}
diff --git a/src/android/net/dhcp/DhcpClient.java b/src/android/net/dhcp/DhcpClient.java
index c88b653..b41484c 100644
--- a/src/android/net/dhcp/DhcpClient.java
+++ b/src/android/net/dhcp/DhcpClient.java
@@ -35,6 +35,7 @@
 import static android.net.util.NetworkStackUtils.DHCP_IPV6_ONLY_PREFERRED_VERSION;
 import static android.net.util.NetworkStackUtils.DHCP_IP_CONFLICT_DETECT_VERSION;
 import static android.net.util.NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION;
+import static android.net.util.NetworkStackUtils.DHCP_SLOW_RETRANSMISSION_VERSION;
 import static android.net.util.NetworkStackUtils.closeSocketQuietly;
 import static android.net.util.SocketUtils.makePacketSocketAddress;
 import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
@@ -349,6 +350,7 @@
     private long mTransactionStartMillis;
     private DhcpResults mDhcpLease;
     private long mDhcpLeaseExpiry;
+    private long mT2;
     private DhcpResults mOffer;
     private Configuration mConfiguration;
     private Inet4Address mLastAssignedIpv4Address;
@@ -580,6 +582,15 @@
                 true /* defaultEnabled */);
     }
 
+    /**
+     * Check whether to adopt slow DHCPREQUEST retransmission approach in Renewing/Rebinding state
+     * suggested in RFC2131 section 4.4.5.
+     */
+    public boolean isSlowRetransmissionEnabled() {
+        return mDependencies.isFeatureEnabled(mContext, DHCP_SLOW_RETRANSMISSION_VERSION,
+                false /* defaultEnabled */);
+    }
+
     private void recordMetricEnabledFeatures() {
         if (isDhcpLeaseCacheEnabled()) mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_INITREBOOT);
         if (isDhcpRapidCommitEnabled()) mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_RAPIDCOMMIT);
@@ -806,6 +817,7 @@
         final long remainingDelay = mDhcpLeaseExpiry - now;
         final long renewDelay = remainingDelay / 2;
         final long rebindDelay = remainingDelay * 7 / 8;
+        mT2 = now + rebindDelay;
         mRenewAlarm.schedule(now + renewDelay);
         mRebindAlarm.schedule(now + rebindDelay);
         mExpiryAlarm.schedule(now + remainingDelay);
@@ -880,6 +892,7 @@
     private void clearDhcpState() {
         mDhcpLease = null;
         mDhcpLeaseExpiry = 0;
+        mT2 = 0;
         mOffer = null;
     }
 
@@ -1199,7 +1212,7 @@
             return baseTimer + jitter;
         }
 
-        protected void scheduleKick() {
+        protected void scheduleFastKick() {
             long now = SystemClock.elapsedRealtime();
             long timeout = jitterTimer(mTimer);
             long alarmTime = now + timeout;
@@ -1209,6 +1222,12 @@
                 mTimer = MAX_TIMEOUT_MS;
             }
         }
+
+        protected void scheduleKick() {
+            // Always adopt the fast kick schedule by default unless this method is overrided
+            // by subclasses.
+            scheduleFastKick();
+        }
     }
 
     class ObtainingConfigurationState extends LoggingState {
@@ -1823,6 +1842,21 @@
         // in renew/rebind state or just restart reconfiguration from StoppedState.
         protected abstract boolean shouldRestartOnNak();
 
+        // Schedule alarm for the next DHCPREQUEST tranmission. Per RFC2131 if the client
+        // receives no response to its DHCPREQUEST message, the client should wait one-half
+        // of the remaining time until T2 in RENEWING state, and one-half of the remaining
+        // lease time in REBINDING state, down to a minimum of 60 seconds before transmitting
+        // DHCPREQUEST.
+        private static final long MIN_DELAY_BEFORE_NEXT_REQUEST = 60_000L;
+        protected void scheduleSlowKick(final long target) {
+            final long now = SystemClock.elapsedRealtime();
+            long remainingDelay = (target - now) / 2;
+            if (remainingDelay < MIN_DELAY_BEFORE_NEXT_REQUEST) {
+                remainingDelay = MIN_DELAY_BEFORE_NEXT_REQUEST;
+            }
+            mKickAlarm.schedule(now + remainingDelay);
+        }
+
         protected boolean sendPacket() {
             return sendRequestPacket(
                     (Inet4Address) mDhcpLease.ipAddress.getAddress(),  // ciaddr
@@ -1895,13 +1929,18 @@
         protected boolean shouldRestartOnNak() {
             return false;
         }
+
+        @Override
+        protected void scheduleKick() {
+            if (isSlowRetransmissionEnabled()) {
+                scheduleSlowKick(mT2);
+            } else {
+                scheduleFastKick();
+            }
+        }
     }
 
-    class DhcpRebindingState extends DhcpReacquiringState {
-        public DhcpRebindingState() {
-            mLeaseMsg = "Rebound";
-        }
-
+    class DhcpRebindingBaseState extends DhcpReacquiringState {
         @Override
         public void enter() {
             super.enter();
@@ -1926,7 +1965,29 @@
         }
     }
 
-    class DhcpRefreshingAddressState extends DhcpRebindingState {
+    class DhcpRebindingState extends DhcpRebindingBaseState {
+        DhcpRebindingState() {
+            mLeaseMsg = "Rebound";
+        }
+
+        @Override
+        protected void scheduleKick() {
+            if (isSlowRetransmissionEnabled()) {
+                scheduleSlowKick(mDhcpLeaseExpiry);
+            } else {
+                scheduleFastKick();
+            }
+        }
+    }
+
+    // The slow retransmission approach complied with RFC2131 should only be applied
+    // for Renewing and Rebinding state. For this state it's expected to refresh IPv4
+    // link address after roam as soon as possible, obviously it should not adopt the
+    // slow retransmission algorithm. Create a base DhcpRebindingBaseState state and
+    // have both of DhcpRebindingState and DhcpRefreshingAddressState extend from it,
+    // then override the scheduleKick method in DhcpRebindingState to comply with slow
+    // schedule and keep DhcpRefreshingAddressState as-is to use the fast schedule.
+    class DhcpRefreshingAddressState extends DhcpRebindingBaseState {
         DhcpRefreshingAddressState() {
             mLeaseMsg = "Refreshing address";
         }
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java
index c1b27e6..ea45851 100644
--- a/src/android/net/ip/IpClient.java
+++ b/src/android/net/ip/IpClient.java
@@ -107,6 +107,7 @@
 import com.android.net.module.util.DeviceConfigUtils;
 import com.android.net.module.util.InterfaceParams;
 import com.android.net.module.util.SharedLog;
+import com.android.net.module.util.ip.InterfaceController;
 import com.android.networkstack.R;
 import com.android.networkstack.apishim.NetworkInformationShimImpl;
 import com.android.networkstack.apishim.SocketUtilsShimImpl;
diff --git a/src/android/net/ip/IpClientLinkObserver.java b/src/android/net/ip/IpClientLinkObserver.java
index 94f028d..56c5293 100644
--- a/src/android/net/ip/IpClientLinkObserver.java
+++ b/src/android/net/ip/IpClientLinkObserver.java
@@ -47,6 +47,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.net.module.util.InterfaceParams;
 import com.android.net.module.util.SharedLog;
+import com.android.net.module.util.ip.NetlinkMonitor;
 import com.android.net.module.util.netlink.NduseroptMessage;
 import com.android.net.module.util.netlink.NetlinkConstants;
 import com.android.net.module.util.netlink.NetlinkMessage;
diff --git a/src/android/net/ip/IpReachabilityMonitor.java b/src/android/net/ip/IpReachabilityMonitor.java
index 5a30b54..82eb074 100644
--- a/src/android/net/ip/IpReachabilityMonitor.java
+++ b/src/android/net/ip/IpReachabilityMonitor.java
@@ -28,8 +28,6 @@
 import android.net.INetd;
 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.networkstack.aidl.ip.ReachabilityLossReason;
@@ -54,6 +52,9 @@
 import com.android.net.module.util.DeviceConfigUtils;
 import com.android.net.module.util.InterfaceParams;
 import com.android.net.module.util.SharedLog;
+import com.android.net.module.util.ip.IpNeighborMonitor;
+import com.android.net.module.util.ip.IpNeighborMonitor.NeighborEvent;
+import com.android.net.module.util.ip.IpNeighborMonitor.NeighborEventConsumer;
 import com.android.net.module.util.netlink.StructNdMsg;
 import com.android.networkstack.R;
 import com.android.networkstack.metrics.IpReachabilityMonitorMetrics;
diff --git a/src/android/net/util/NetworkStackUtils.java b/src/android/net/util/NetworkStackUtils.java
index 6dc2a5b..a6ea343 100755
--- a/src/android/net/util/NetworkStackUtils.java
+++ b/src/android/net/util/NetworkStackUtils.java
@@ -211,6 +211,13 @@
             "dhcp_ipv6_only_preferred_version";
 
     /**
+     * Minimum module version at which to enable slow DHCP retransmission approach in renew/rebind
+     * state suggested in RFC2131 section 4.4.5.
+     */
+    public static final String DHCP_SLOW_RETRANSMISSION_VERSION =
+            "dhcp_slow_retransmission_version";
+
+    /**
      * Minimum module version at which to enable dismissal CaptivePortalLogin app in validated
      * network feature. CaptivePortalLogin app will also use validation facilities in
      * {@link NetworkMonitor} to perform portal validation if feature is enabled.
diff --git a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
index b36e4bf..ff99bc8 100644
--- a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
+++ b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
@@ -17,6 +17,12 @@
 package android.net.ip;
 
 import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
 import static android.net.dhcp.DhcpClient.EXPIRED_LEASE;
 import static android.net.dhcp.DhcpPacket.DHCP_BOOTREQUEST;
 import static android.net.dhcp.DhcpPacket.DHCP_CLIENT;
@@ -113,10 +119,14 @@
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.MacAddress;
+import android.net.NetworkAgentConfig;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
 import android.net.NetworkStackIpMemoryStore;
 import android.net.RouteInfo;
 import android.net.TestNetworkInterface;
 import android.net.TestNetworkManager;
+import android.net.TestNetworkSpecifier;
 import android.net.Uri;
 import android.net.dhcp.DhcpClient;
 import android.net.dhcp.DhcpDeclinePacket;
@@ -124,7 +134,6 @@
 import android.net.dhcp.DhcpPacket;
 import android.net.dhcp.DhcpPacket.ParseException;
 import android.net.dhcp.DhcpRequestPacket;
-import android.net.ip.IpNeighborMonitor.NeighborEventConsumer;
 import android.net.ipmemorystore.NetworkAttributes;
 import android.net.ipmemorystore.OnNetworkAttributesRetrievedListener;
 import android.net.ipmemorystore.Status;
@@ -160,6 +169,8 @@
 import com.android.net.module.util.InterfaceParams;
 import com.android.net.module.util.Ipv6Utils;
 import com.android.net.module.util.SharedLog;
+import com.android.net.module.util.ip.IpNeighborMonitor;
+import com.android.net.module.util.ip.IpNeighborMonitor.NeighborEventConsumer;
 import com.android.net.module.util.netlink.StructNdOptPref64;
 import com.android.net.module.util.structs.LlaOption;
 import com.android.net.module.util.structs.PrefixInformationOption;
@@ -183,6 +194,8 @@
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 import com.android.testutils.HandlerUtils;
 import com.android.testutils.TapPacketReader;
+import com.android.testutils.TestableNetworkAgent;
+import com.android.testutils.TestableNetworkCallback;
 
 import org.junit.After;
 import org.junit.Before;
@@ -317,6 +330,8 @@
     private TapPacketReader mPacketReader;
     private FileDescriptor mTapFd;
     private byte[] mClientMac;
+    private TestableNetworkAgent mNetworkAgent;
+    private HandlerThread mNetworkAgentThread;
 
     private boolean mIsSignatureRequiredTest;
 
@@ -647,6 +662,12 @@
     @After
     public void tearDown() throws Exception {
         if (testSkipped()) return;
+        if (mNetworkAgent != null) {
+            mNetworkAgent.unregister();
+        }
+        if (mNetworkAgentThread != null) {
+            mNetworkAgentThread.quitSafely();
+        }
         teardownTapInterface();
         mIIpClient.shutdown();
         awaitIpClientShutdown();
@@ -896,11 +917,10 @@
             false /* broadcast */, message);
     }
 
-    private void sendArpReply(final byte[] clientMac) throws IOException {
-        final ByteBuffer packet = ArpPacket.buildArpPacket(clientMac /* dst */,
-                ROUTER_MAC_BYTES /* srcMac */, INADDR_ANY.getAddress() /* target IP */,
-                clientMac /* target HW address */, CLIENT_ADDR.getAddress() /* sender IP */,
-                (short) ARP_REPLY);
+    private void sendArpReply(final byte[] dstMac, final byte[] srcMac, final Inet4Address targetIp,
+            final Inet4Address senderIp) throws IOException {
+        final ByteBuffer packet = ArpPacket.buildArpPacket(dstMac, srcMac, targetIp.getAddress(),
+                dstMac /* target HW address */, senderIp.getAddress(), (short) ARP_REPLY);
         mPacketReader.sendResponse(packet);
     }
 
@@ -1114,15 +1134,18 @@
     // 2. if duplicated IPv4 address detection is enabled, verify TIMEOUT will affect ARP packets
     //    capture running in other test cases.
     // 3. if IPv6 is enabled, e.g. withoutIPv6() isn't called when starting provisioning.
-    private void verifyIPv4OnlyProvisioningSuccess(final Collection<InetAddress> addresses)
-            throws Exception {
+    private LinkProperties verifyIPv4OnlyProvisioningSuccess(
+            final Collection<InetAddress> addresses) throws Exception {
         final ArgumentCaptor<LinkProperties> captor = ArgumentCaptor.forClass(LinkProperties.class);
         verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningSuccess(captor.capture());
-        LinkProperties lp = captor.getValue();
+        final LinkProperties lp = captor.getValue();
         assertNotNull(lp);
         assertNotEquals(0, lp.getDnsServers().size());
         assertEquals(addresses.size(), lp.getAddresses().size());
         assertTrue(lp.getAddresses().containsAll(addresses));
+        assertTrue(hasRouteTo(lp, IPV4_TEST_SUBNET_PREFIX)); // IPv4 directly-connected route
+        assertTrue(hasRouteTo(lp, IPV4_ANY_ADDRESS_PREFIX)); // IPv4 default route
+        return lp;
     }
 
     private void doRestoreInitialMtuTest(final boolean shouldChangeMtu,
@@ -1283,6 +1306,15 @@
         assertEquals(packet.senderIp, CLIENT_ADDR);
     }
 
+    private void assertArpRequest(final ArpPacket packet, final Inet4Address targetIp) {
+        assertEquals(packet.opCode, ARP_REQUEST);
+        assertEquals(packet.senderIp, CLIENT_ADDR);
+        assertEquals(packet.targetIp, targetIp);
+        assertTrue(Arrays.equals(packet.targetHwAddress.toByteArray(),
+                MacAddress.fromString("00:00:00:00:00:00").toByteArray()));
+        assertTrue(Arrays.equals(packet.senderHwAddress.toByteArray(), mClientMac));
+    }
+
     private void assertGratuitousARP(final ArpPacket packet) {
         assertEquals(packet.opCode, ARP_REPLY);
         assertEquals(packet.senderIp, CLIENT_ADDR);
@@ -1308,7 +1340,8 @@
             assertArpProbe(arpProbe);
 
             if (shouldResponseArpReply) {
-                sendArpReply(mClientMac);
+                sendArpReply(mClientMac /* dstMac */, ROUTER_MAC_BYTES /* srcMac */,
+                        INADDR_ANY /* target IP */, CLIENT_ADDR /* sender IP */);
             } else {
                 sendArpProbe();
             }
@@ -1519,6 +1552,117 @@
         assertEquals(SERVER_ADDR, captor.getValue().getDhcpServerAddress());
     }
 
+    private void createTestNetworkAgentAndRegister(final LinkProperties lp) throws Exception {
+        final Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
+        final TestNetworkSpecifier testNetworkSpecifier = new TestNetworkSpecifier(mIfaceName);
+        final TestableNetworkCallback cb = new TestableNetworkCallback();
+
+        // Requesting a network make sure the NetworkAgent is alive during the whole life cycle of
+        // requested network.
+        cm.requestNetwork(new NetworkRequest.Builder()
+                .removeCapability(NET_CAPABILITY_TRUSTED)
+                .removeCapability(NET_CAPABILITY_INTERNET)
+                .addTransportType(TRANSPORT_TEST)
+                .setNetworkSpecifier(testNetworkSpecifier)
+                .build(), cb);
+        mNetworkAgent = new TestableNetworkAgent(context, mNetworkAgentThread.getLooper(),
+                new NetworkCapabilities.Builder()
+                        .removeCapability(NET_CAPABILITY_TRUSTED)
+                        .removeCapability(NET_CAPABILITY_INTERNET)
+                        .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+                        .addCapability(NET_CAPABILITY_NOT_ROAMING)
+                        .addCapability(NET_CAPABILITY_NOT_VPN)
+                        .addTransportType(TRANSPORT_TEST)
+                        .setNetworkSpecifier(testNetworkSpecifier)
+                        .build(),
+                lp,
+                new NetworkAgentConfig.Builder().build());
+        mNetworkAgent.register();
+        mNetworkAgent.markConnected();
+        cb.expectAvailableThenValidatedCallbacks(mNetworkAgent.getNetwork(), TEST_TIMEOUT_MS);
+    }
+
+    private void assertReceivedDhcpRequestPacketCount() throws Exception {
+        final List<DhcpPacket> packetList = new ArrayList<>();
+        DhcpPacket packet;
+        while ((packet = getNextDhcpPacket(PACKET_TIMEOUT_MS)) != null) {
+            assertDhcpRequestForReacquire(packet);
+            packetList.add(packet);
+        }
+        assertEquals(1, packetList.size());
+    }
+
+    private LinkProperties prepareDhcpReacquireTest() throws Exception {
+        mNetworkAgentThread =
+                new HandlerThread(IpClientIntegrationTestCommon.class.getSimpleName());
+        mNetworkAgentThread.start();
+
+        final long currentTime = System.currentTimeMillis();
+        setFeatureEnabled(NetworkStackUtils.DHCP_SLOW_RETRANSMISSION_VERSION, true);
+        performDhcpHandshake(true /* isSuccessLease */,
+                TEST_LEASE_DURATION_S, true /* isDhcpLeaseCacheEnabled */,
+                false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU,
+                false /* isDhcpIpConflictDetectEnabled */);
+        final LinkProperties lp =
+                verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
+        assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
+        return lp;
+    }
+
+    private OnAlarmListener runDhcpRenewTest(final Handler handler, final LinkProperties lp,
+            final InOrder inOrder) throws Exception {
+        // Create a NetworkAgent and register it to ConnectivityService with IPv4 LinkProperties,
+        // then ConnectivityService will call netd API to configure the IPv4 route on the kernel,
+        // otherwise, unicast DHCPREQUEST cannot be sent out due to no route to host(EHOSTUNREACH).
+        runAsShell(MANAGE_TEST_NETWORKS, () -> createTestNetworkAgentAndRegister(lp));
+
+        // DHCP client is in BOUND state right now, simulate the renewal via triggering renew alarm
+        // which should happen at T1. E.g. lease duration is 3600s, T1 = lease_duration * 0.5(1800s)
+        // T2 = lease_duration * 0.875(3150s).
+        final OnAlarmListener renewAlarm = expectAlarmSet(inOrder, "RENEW", 1800, handler);
+        final OnAlarmListener rebindAlarm = expectAlarmSet(inOrder, "REBIND", 3150, handler);
+
+        // Trigger renew alarm and force DHCP client enter RenewingState. Device needs to start
+        // the ARP resolution for the fake DHCP server IPv4 address before sending the unicast
+        // DHCPREQUEST out, wait for the unicast ARP request and respond to it with ARP reply,
+        // otherwise, DHCPREQUEST still cannot be sent out due to that there is no correct ARP
+        // table for the dest IPv4 address.
+        handler.post(() -> renewAlarm.onAlarm());
+        final ArpPacket request = getNextArpPacket();
+        assertArpRequest(request, SERVER_ADDR);
+        sendArpReply(request.senderHwAddress.toByteArray() /* dst */, ROUTER_MAC_BYTES /* srcMac */,
+                request.senderIp /* target IP */, SERVER_ADDR /* sender IP */);
+        HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS);
+
+        // Verify there should be only one unicast DHCPREQUESTs to be received per RFC2131.
+        assertReceivedDhcpRequestPacketCount();
+
+        return rebindAlarm;
+    }
+
+    @Test @SignatureRequiredTest(reason = "Need to mock the DHCP renew/rebind alarms")
+    public void testDhcpRenew() throws Exception {
+        final LinkProperties lp = prepareDhcpReacquireTest();
+        final InOrder inOrder = inOrder(mAlarm);
+        runDhcpRenewTest(mDependencies.mDhcpClient.getHandler(), lp, inOrder);
+    }
+
+    @Test @SignatureRequiredTest(reason = "Need to mock the DHCP renew/rebind alarms")
+    public void testDhcpRebind() throws Exception {
+        final LinkProperties lp = prepareDhcpReacquireTest();
+        final Handler handler = mDependencies.mDhcpClient.getHandler();
+        final InOrder inOrder = inOrder(mAlarm);
+        final OnAlarmListener rebindAlarm = runDhcpRenewTest(handler, lp, inOrder);
+
+        // Trigger rebind alarm and forece DHCP client enter RebindingState. DHCP client sends
+        // broadcast DHCPREQUEST to nearby servers, then check how many DHCPREQUEST packets are
+        // retransmitted within PACKET_TIMEOUT_MS(5s), there should be only one DHCPREQUEST
+        // captured per RFC2131.
+        handler.post(() -> rebindAlarm.onAlarm());
+        assertReceivedDhcpRequestPacketCount();
+    }
+
     @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testRestoreInitialInterfaceMtu() throws Exception {
         doRestoreInitialMtuTest(true /* shouldChangeMtu */, false /* shouldRemoveTestInterface */);
@@ -2543,6 +2687,13 @@
         mIIpClient.updateLayer2Information(roamingInfo);
     }
 
+    private void assertDhcpRequestForReacquire(final DhcpPacket packet) {
+        assertTrue(packet instanceof DhcpRequestPacket);
+        assertEquals(packet.mClientIp, CLIENT_ADDR);    // client IP
+        assertNull(packet.mRequestedIp);                // requested IP option
+        assertNull(packet.mServerIdentifier);           // server ID
+    }
+
     private void doDhcpRoamingTest(final boolean hasMismatchedIpAddress, final String displayName,
             final MacAddress bssid, final boolean expectRoaming,
             final boolean shouldReplyNakOnRoam) throws Exception {
@@ -2580,12 +2731,8 @@
             return;
         }
         // check DHCPREQUEST broadcast sent to renew IP address.
-        DhcpPacket packet;
-        packet = getNextDhcpPacket();
-        assertTrue(packet instanceof DhcpRequestPacket);
-        assertEquals(packet.mClientIp, CLIENT_ADDR);    // client IP
-        assertNull(packet.mRequestedIp);                // requested IP option
-        assertNull(packet.mServerIdentifier);           // server ID
+        final DhcpPacket packet = getNextDhcpPacket();
+        assertDhcpRequestForReacquire(packet);
 
         final ByteBuffer packetBuffer = shouldReplyNakOnRoam
                 ? buildDhcpNakPacket(packet, "request IP on a wrong subnet")
diff --git a/tests/unit/src/android/net/ip/ConntrackMonitorTest.java b/tests/unit/src/android/net/ip/ConntrackMonitorTest.java
deleted file mode 100644
index bb2175a..0000000
--- a/tests/unit/src/android/net/ip/ConntrackMonitorTest.java
+++ /dev/null
@@ -1,336 +0,0 @@
-/*
- * 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.ip;
-
-import static android.net.ip.ConntrackMonitor.ConntrackEvent;
-import static android.system.OsConstants.AF_UNIX;
-import static android.system.OsConstants.IPPROTO_TCP;
-import static android.system.OsConstants.SOCK_DGRAM;
-
-import static com.android.net.module.util.netlink.ConntrackMessage.Tuple;
-import static com.android.net.module.util.netlink.ConntrackMessage.TupleIpv4;
-import static com.android.net.module.util.netlink.ConntrackMessage.TupleProto;
-import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_DELETE;
-import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_NEW;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.fail;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-
-import android.net.InetAddresses;
-import android.os.ConditionVariable;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.system.ErrnoException;
-import android.system.Os;
-
-import androidx.annotation.NonNull;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.net.module.util.SharedLog;
-import com.android.net.module.util.netlink.NetlinkConstants;
-import com.android.net.module.util.netlink.NetlinkSocket;
-
-import libcore.util.HexEncoding;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.io.FileDescriptor;
-import java.io.InterruptedIOException;
-import java.net.Inet4Address;
-
-
-/**
- * Tests for ConntrackMonitor.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class ConntrackMonitorTest {
-    private static final long TIMEOUT_MS = 10_000L;
-
-    @Mock private SharedLog mLog;
-    @Mock private ConntrackMonitor.ConntrackEventConsumer mConsumer;
-
-    private final HandlerThread mHandlerThread = new HandlerThread(
-            ConntrackMonitorTest.class.getSimpleName());
-
-    // Late init since the handler thread has been started.
-    private Handler mHandler;
-    private TestConntrackMonitor mConntrackMonitor;
-
-    // A version of [ConntrackMonitor] that reads packets from the socket pair, and instead
-    // allows the test to write test packets to the socket pair via [sendMessage].
-    private class TestConntrackMonitor extends ConntrackMonitor {
-        TestConntrackMonitor(@NonNull Handler h, @NonNull SharedLog log,
-                @NonNull ConntrackEventConsumer cb) {
-            super(h, log, cb);
-
-            mReadFd = new FileDescriptor();
-            mWriteFd = new FileDescriptor();
-            try {
-                Os.socketpair(AF_UNIX, SOCK_DGRAM, 0, mWriteFd, mReadFd);
-            } catch (ErrnoException e) {
-                fail("Could not create socket pair: " + e);
-            }
-        }
-
-        @Override
-        protected FileDescriptor createFd() {
-            return mReadFd;
-        }
-
-        private void sendMessage(byte[] msg) {
-            mHandler.post(() -> {
-                try {
-                    NetlinkSocket.sendMessage(mWriteFd, msg, 0 /* offset */, msg.length,
-                                              TIMEOUT_MS);
-                } catch (ErrnoException | InterruptedIOException e) {
-                    fail("Unable to send netfilter message: " + e);
-                }
-            });
-        }
-
-        private final FileDescriptor mReadFd;
-        private final FileDescriptor mWriteFd;
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        mHandlerThread.start();
-        mHandler = new Handler(mHandlerThread.getLooper());
-
-        // ConntrackMonitor needs to be started from the handler thread.
-        final ConditionVariable initDone = new ConditionVariable();
-        mHandler.post(() -> {
-            TestConntrackMonitor m = new TestConntrackMonitor(mHandler, mLog, mConsumer);
-            m.start();
-            mConntrackMonitor = m;
-
-            initDone.open();
-        });
-        if (!initDone.block(TIMEOUT_MS)) {
-            fail("... init monitor timed-out after " + TIMEOUT_MS + "ms");
-        }
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        mHandlerThread.quitSafely();
-    }
-
-    public static final String CT_V4NEW_TCP_HEX =
-            // CHECKSTYLE:OFF IndentationCheck
-            // struct nlmsghdr
-            "8C000000" +      // length = 140
-            "0001" +          // type = NFNL_SUBSYS_CTNETLINK (1) << 8 | IPCTNL_MSG_CT_NEW (0)
-            "0006" +          // flags = NLM_F_CREATE (1 << 10) | NLM_F_EXCL (1 << 9)
-            "00000000" +      // seqno = 0
-            "00000000" +      // pid = 0
-            // struct nfgenmsg
-            "02" +            // nfgen_family = AF_INET
-            "00" +            // version = NFNETLINK_V0
-            "1234" +          // res_id = 0x1234 (big endian)
-             // struct nlattr
-            "3400" +          // nla_len = 52
-            "0180" +          // nla_type = nested CTA_TUPLE_ORIG
-                // struct nlattr
-                "1400" +      // nla_len = 20
-                "0180" +      // nla_type = nested CTA_TUPLE_IP
-                    "0800 0100 C0A8500C" +  // nla_type=CTA_IP_V4_SRC, ip=192.168.80.12
-                    "0800 0200 8C700874" +  // nla_type=CTA_IP_V4_DST, ip=140.112.8.116
-                // struct nlattr
-                "1C00" +      // nla_len = 28
-                "0280" +      // nla_type = nested CTA_TUPLE_PROTO
-                    "0500 0100 06 000000" +  // nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6)
-                    "0600 0200 F3F1 0000" +  // nla_type=CTA_PROTO_SRC_PORT, port=62449 (big endian)
-                    "0600 0300 01BB 0000" +  // nla_type=CTA_PROTO_DST_PORT, port=443 (big endian)
-            // struct nlattr
-            "3400" +          // nla_len = 52
-            "0280" +          // nla_type = nested CTA_TUPLE_REPLY
-                // struct nlattr
-                "1400" +      // nla_len = 20
-                "0180" +      // nla_type = nested CTA_TUPLE_IP
-                    "0800 0100 8C700874" +  // nla_type=CTA_IP_V4_SRC, ip=140.112.8.116
-                    "0800 0200 6451B301" +  // nla_type=CTA_IP_V4_DST, ip=100.81.179.1
-                // struct nlattr
-                "1C00" +      // nla_len = 28
-                "0280" +      // nla_type = nested CTA_TUPLE_PROTO
-                    "0500 0100 06 000000" +  // nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6)
-                    "0600 0200 01BB 0000" +  // nla_type=CTA_PROTO_SRC_PORT, port=443 (big endian)
-                    "0600 0300 F3F1 0000" +  // nla_type=CTA_PROTO_DST_PORT, port=62449 (big endian)
-            // struct nlattr
-            "0800" +          // nla_len = 8
-            "0300" +          // nla_type = CTA_STATUS
-            "0000019e" +      // nla_value = 0b110011110 (big endian)
-                              // IPS_SEEN_REPLY (1 << 1) | IPS_ASSURED (1 << 2) |
-                              // IPS_CONFIRMED (1 << 3) | IPS_SRC_NAT (1 << 4) |
-                              // IPS_SRC_NAT_DONE (1 << 7) | IPS_DST_NAT_DONE (1 << 8)
-            // struct nlattr
-            "0800" +          // nla_len = 8
-            "0700" +          // nla_type = CTA_TIMEOUT
-            "00000078";       // nla_value = 120 (big endian)
-            // CHECKSTYLE:ON IndentationCheck
-    public static final byte[] CT_V4NEW_TCP_BYTES =
-            HexEncoding.decode(CT_V4NEW_TCP_HEX.replaceAll(" ", "").toCharArray(), false);
-
-    @NonNull
-    private ConntrackEvent makeTestConntrackEvent(short msgType, int status, int timeoutSec) {
-        final Inet4Address privateIp =
-                (Inet4Address) InetAddresses.parseNumericAddress("192.168.80.12");
-        final Inet4Address remoteIp =
-                (Inet4Address) InetAddresses.parseNumericAddress("140.112.8.116");
-        final Inet4Address publicIp =
-                (Inet4Address) InetAddresses.parseNumericAddress("100.81.179.1");
-
-        return new ConntrackEvent(
-                (short) (NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8 | msgType),
-                new Tuple(new TupleIpv4(privateIp, remoteIp),
-                        new TupleProto((byte) IPPROTO_TCP, (short) 62449, (short) 443)),
-                new Tuple(new TupleIpv4(remoteIp, publicIp),
-                        new TupleProto((byte) IPPROTO_TCP, (short) 443, (short) 62449)),
-                status,
-                timeoutSec);
-    }
-
-    @Test
-    public void testConntrackEventNew() throws Exception {
-        final ConntrackEvent expectedEvent = makeTestConntrackEvent(IPCTNL_MSG_CT_NEW,
-                0x19e /* status */, 120 /* timeoutSec */);
-        mConntrackMonitor.sendMessage(CT_V4NEW_TCP_BYTES);
-        verify(mConsumer, timeout(TIMEOUT_MS)).accept(eq(expectedEvent));
-    }
-
-    @Test
-    public void testConntrackEventEquals() {
-        final ConntrackEvent event1 = makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, 1234 /* status */,
-                5678 /* timeoutSec*/);
-        final ConntrackEvent event2 = makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, 1234 /* status */,
-                5678 /* timeoutSec*/);
-        assertEquals(event1, event2);
-    }
-
-    @Test
-    public void testConntrackEventNotEquals() {
-        final ConntrackEvent e = makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, 1234 /* status */,
-                5678 /* timeoutSec*/);
-
-        final ConntrackEvent typeNotEqual = new ConntrackEvent((short) (e.msgType + 1) /* diff */,
-                e.tupleOrig, e.tupleReply, e.status, e.timeoutSec);
-        assertNotEquals(e, typeNotEqual);
-
-        final ConntrackEvent tupleOrigNotEqual = new ConntrackEvent(e.msgType,
-                null /* diff */, e.tupleReply, e.status, e.timeoutSec);
-        assertNotEquals(e, tupleOrigNotEqual);
-
-        final ConntrackEvent tupleReplyNotEqual = new ConntrackEvent(e.msgType,
-                e.tupleOrig, null /* diff */, e.status, e.timeoutSec);
-        assertNotEquals(e, tupleReplyNotEqual);
-
-        final ConntrackEvent statusNotEqual = new ConntrackEvent(e.msgType,
-                e.tupleOrig, e.tupleReply, e.status + 1 /* diff */, e.timeoutSec);
-        assertNotEquals(e, statusNotEqual);
-
-        final ConntrackEvent timeoutSecNotEqual = new ConntrackEvent(e.msgType,
-                e.tupleOrig, e.tupleReply, e.status, e.timeoutSec + 1 /* diff */);
-        assertNotEquals(e, timeoutSecNotEqual);
-    }
-
-    @Test
-    public void testToString() {
-        final ConntrackEvent event = makeTestConntrackEvent(IPCTNL_MSG_CT_NEW,
-                0x198 /* status */, 120 /* timeoutSec */);
-        final String expected = ""
-                + "ConntrackEvent{"
-                + "msg_type{IPCTNL_MSG_CT_NEW}, "
-                + "tuple_orig{Tuple{IPPROTO_TCP: 192.168.80.12:62449 -> 140.112.8.116:443}}, "
-                + "tuple_reply{Tuple{IPPROTO_TCP: 140.112.8.116:443 -> 100.81.179.1:62449}}, "
-                + "status{408(IPS_CONFIRMED|IPS_SRC_NAT|IPS_SRC_NAT_DONE|IPS_DST_NAT_DONE)}, "
-                + "timeout_sec{120}}";
-        assertEquals(expected, event.toString());
-    }
-
-    public static final String CT_V4DELETE_TCP_HEX =
-            // CHECKSTYLE:OFF IndentationCheck
-            // struct nlmsghdr
-            "84000000" +      // length = 132
-            "0201" +          // type = NFNL_SUBSYS_CTNETLINK (1) << 8 | IPCTNL_MSG_CT_DELETE (2)
-            "0000" +          // flags = 0
-            "00000000" +      // seqno = 0
-            "00000000" +      // pid = 0
-            // struct nfgenmsg
-            "02" +            // nfgen_family  = AF_INET
-            "00" +            // version = NFNETLINK_V0
-            "1234" +          // res_id = 0x1234 (big endian)
-            // struct nlattr
-            "3400" +          // nla_len = 52
-            "0180" +          // nla_type = nested CTA_TUPLE_ORIG
-                // struct nlattr
-                "1400" +      // nla_len = 20
-                "0180" +      // nla_type = nested CTA_TUPLE_IP
-                    "0800 0100 C0A8500C" +  // nla_type=CTA_IP_V4_SRC, ip=192.168.80.12
-                    "0800 0200 8C700874" +  // nla_type=CTA_IP_V4_DST, ip=140.112.8.116
-                // struct nlattr
-                "1C00" +      // nla_len = 28
-                "0280" +      // nla_type = nested CTA_TUPLE_PROTO
-                    "0500 0100 06 000000" +  // nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6)
-                    "0600 0200 F3F1 0000" +  // nla_type=CTA_PROTO_SRC_PORT, port=62449 (big endian)
-                    "0600 0300 01BB 0000" +  // nla_type=CTA_PROTO_DST_PORT, port=433 (big endian)
-            // struct nlattr
-            "3400" +          // nla_len = 52
-            "0280" +          // nla_type = nested CTA_TUPLE_REPLY
-                // struct nlattr
-                "1400" +      // nla_len = 20
-                "0180" +      // nla_type = nested CTA_TUPLE_IP
-                    "0800 0100 8C700874" +  // nla_type=CTA_IP_V4_SRC, ip=140.112.8.116
-                    "0800 0200 6451B301" +  // nla_type=CTA_IP_V4_DST, ip=100.81.179.1
-                // struct nlattr
-                "1C00" +      // nla_len = 28
-                "0280" +      // nla_type = nested CTA_TUPLE_PROTO
-                    "0500 0100 06 000000" +  // nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6)
-                    "0600 0200 01BB 0000" +  // nla_type=CTA_PROTO_SRC_PORT, port=433 (big endian)
-                    "0600 0300 F3F1 0000" +  // nla_type=CTA_PROTO_DST_PORT, port=62449 (big endian)
-            // struct nlattr
-            "0800" +          // nla_len = 8
-            "0300" +          // nla_type = CTA_STATUS
-            "0000039E";       // nla_value = 0b1110011110 (big endian)
-                              // IPS_SEEN_REPLY (1 << 1) | IPS_ASSURED (1 << 2) |
-                              // IPS_CONFIRMED (1 << 3) | IPS_SRC_NAT (1 << 4) |
-                              // IPS_SRC_NAT_DONE (1 << 7) | IPS_DST_NAT_DONE (1 << 8) |
-                              // IPS_DYING (1 << 9)
-            // CHECKSTYLE:ON IndentationCheck
-    public static final byte[] CT_V4DELETE_TCP_BYTES =
-            HexEncoding.decode(CT_V4DELETE_TCP_HEX.replaceAll(" ", "").toCharArray(), false);
-
-    @Test
-    public void testConntrackEventDelete() throws Exception {
-        final ConntrackEvent expectedEvent =
-                makeTestConntrackEvent(IPCTNL_MSG_CT_DELETE, 0x39e /* status */,
-                        0 /* timeoutSec (absent) */);
-        mConntrackMonitor.sendMessage(CT_V4DELETE_TCP_BYTES);
-        verify(mConsumer, timeout(TIMEOUT_MS)).accept(eq(expectedEvent));
-    }
-}
diff --git a/tests/unit/src/android/net/ip/InterfaceControllerTest.java b/tests/unit/src/android/net/ip/InterfaceControllerTest.java
deleted file mode 100644
index 944d6be..0000000
--- a/tests/unit/src/android/net/ip/InterfaceControllerTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2019 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.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.net.INetd;
-import android.net.InetAddresses;
-import android.net.InterfaceConfigurationParcel;
-import android.net.LinkAddress;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.net.module.util.SharedLog;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class InterfaceControllerTest {
-    private static final String TEST_IFACE = "testif";
-    private static final String TEST_IPV4_ADDR = "192.168.123.28";
-    private static final int TEST_PREFIXLENGTH = 31;
-
-    @Mock private INetd mNetd;
-    @Mock private SharedLog mLog;
-    @Captor private ArgumentCaptor<InterfaceConfigurationParcel> mConfigCaptor;
-
-    private InterfaceController mController;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        mController = new InterfaceController(TEST_IFACE, mNetd, mLog);
-
-        doNothing().when(mNetd).interfaceSetCfg(mConfigCaptor.capture());
-    }
-
-    @Test
-    public void testSetIPv4Address() throws Exception {
-        mController.setIPv4Address(
-                new LinkAddress(InetAddresses.parseNumericAddress(TEST_IPV4_ADDR),
-                        TEST_PREFIXLENGTH));
-        verify(mNetd, times(1)).interfaceSetCfg(any());
-        final InterfaceConfigurationParcel parcel = mConfigCaptor.getValue();
-        assertEquals(TEST_IFACE, parcel.ifName);
-        assertEquals(TEST_IPV4_ADDR, parcel.ipv4Addr);
-        assertEquals(TEST_PREFIXLENGTH, parcel.prefixLength);
-        assertEquals("", parcel.hwAddr);
-        assertArrayEquals(new String[0], parcel.flags);
-    }
-
-    @Test
-    public void testClearIPv4Address() throws Exception {
-        mController.clearIPv4Address();
-        verify(mNetd, times(1)).interfaceSetCfg(any());
-        final InterfaceConfigurationParcel parcel = mConfigCaptor.getValue();
-        assertEquals(TEST_IFACE, parcel.ifName);
-        assertEquals("0.0.0.0", parcel.ipv4Addr);
-        assertEquals(0, parcel.prefixLength);
-        assertEquals("", parcel.hwAddr);
-        assertArrayEquals(new String[0], parcel.flags);
-    }
-}
diff --git a/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt b/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt
index 55f9b6b..a8c5c97 100644
--- a/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt
+++ b/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt
@@ -17,7 +17,6 @@
 
 import android.annotation.SuppressLint
 import android.content.Context
-import android.net.ip.IpNeighborMonitor.NeighborEventConsumer
 import android.net.INetd
 import android.net.InetAddresses.parseNumericAddress
 import android.net.IpPrefix
@@ -53,10 +52,12 @@
 import androidx.test.runner.AndroidJUnit4
 import com.android.networkstack.metrics.IpReachabilityMonitorMetrics
 import com.android.net.module.util.InterfaceParams
+import com.android.net.module.util.SharedLog
+import com.android.net.module.util.ip.IpNeighborMonitor
+import com.android.net.module.util.ip.IpNeighborMonitor.NeighborEventConsumer
 import com.android.net.module.util.netlink.StructNdMsg.NUD_FAILED
 import com.android.net.module.util.netlink.StructNdMsg.NUD_REACHABLE
 import com.android.net.module.util.netlink.StructNdMsg.NUD_STALE
-import com.android.net.module.util.SharedLog
 import com.android.testutils.makeNewNeighMessage
 import com.android.testutils.waitForIdle
 import org.junit.After