Merge "Rename IDhcpLeaseCallbacks to IDhcpEventCallbacks for more generic."
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index 9db6a35..1dac5b7 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -34,6 +34,7 @@
 import android.net.LinkProperties;
 import android.net.MacAddress;
 import android.net.RouteInfo;
+import android.net.TetherOffloadRuleParcel;
 import android.net.TetheredClient;
 import android.net.TetheringManager;
 import android.net.TetheringRequestParcel;
@@ -280,6 +281,19 @@
             return new Ipv6ForwardingRule(newUpstreamIfindex, downstreamIfindex, address, srcMac,
                     dstMac);
         }
+
+        // Don't manipulate TetherOffloadRuleParcel directly because implementing onNewUpstream()
+        // would be error-prone due to generated stable AIDL classes not having a copy constructor.
+        public TetherOffloadRuleParcel toTetherOffloadRuleParcel() {
+            final TetherOffloadRuleParcel parcel = new TetherOffloadRuleParcel();
+            parcel.inputInterfaceIndex = upstreamIfindex;
+            parcel.outputInterfaceIndex = downstreamIfindex;
+            parcel.destination = address.getAddress();
+            parcel.prefixLength = 128;
+            parcel.srcL2Address = srcMac.toByteArray();
+            parcel.dstL2Address = dstMac.toByteArray();
+            return parcel;
+        }
     }
     private final LinkedHashMap<Inet6Address, Ipv6ForwardingRule> mIpv6ForwardingRules =
             new LinkedHashMap<>();
@@ -829,9 +843,7 @@
 
     private void addIpv6ForwardingRule(Ipv6ForwardingRule rule) {
         try {
-            mNetd.tetherRuleAddDownstreamIpv6(mInterfaceParams.index, rule.upstreamIfindex,
-                    rule.address.getAddress(),  mInterfaceParams.macAddr.toByteArray(),
-                    rule.dstMac.toByteArray());
+            mNetd.tetherOffloadRuleAdd(rule.toTetherOffloadRuleParcel());
             mIpv6ForwardingRules.put(rule.address, rule);
         } catch (RemoteException | ServiceSpecificException e) {
             mLog.e("Could not add IPv6 downstream rule: ", e);
@@ -840,7 +852,7 @@
 
     private void removeIpv6ForwardingRule(Ipv6ForwardingRule rule, boolean removeFromMap) {
         try {
-            mNetd.tetherRuleRemoveDownstreamIpv6(rule.upstreamIfindex, rule.address.getAddress());
+            mNetd.tetherOffloadRuleRemove(rule.toTetherOffloadRuleParcel());
             if (removeFromMap) {
                 mIpv6ForwardingRules.remove(rule.address);
             }
diff --git a/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java b/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
index bd60594..639cf65 100644
--- a/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
+++ b/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
@@ -20,6 +20,7 @@
 import static android.net.TetheringConstants.EXTRA_PROVISION_CALLBACK;
 import static android.net.TetheringConstants.EXTRA_RUN_PROVISION;
 import static android.net.TetheringManager.TETHERING_BLUETOOTH;
+import static android.net.TetheringManager.TETHERING_ETHERNET;
 import static android.net.TetheringManager.TETHERING_INVALID;
 import static android.net.TetheringManager.TETHERING_USB;
 import static android.net.TetheringManager.TETHERING_WIFI;
@@ -537,6 +538,7 @@
     private static boolean isValidDownstreamType(int type) {
         switch (type) {
             case TETHERING_BLUETOOTH:
+            case TETHERING_ETHERNET:
             case TETHERING_USB:
             case TETHERING_WIFI:
                 return true;
@@ -650,6 +652,11 @@
 
     private void handleRequestLatestTetheringEntitlementValue(int downstream,
             ResultReceiver receiver, boolean showEntitlementUi) {
+        if (!isValidDownstreamType(downstream)) {
+            receiver.send(TETHER_ERROR_ENTITLEMENT_UNKNOWN, null);
+            return;
+        }
+
         final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration();
         if (!isTetherProvisioningRequired(config)) {
             receiver.send(TETHER_ERROR_NO_ERROR, null);
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 492ce3d..dbd68ef 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
 import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.net.TetheringManager.TETHERING_ETHERNET;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -57,6 +58,7 @@
 
 import java.io.FileDescriptor;
 import java.net.Inet4Address;
+import java.net.InterfaceAddress;
 import java.net.NetworkInterface;
 import java.net.SocketException;
 import java.nio.ByteBuffer;
@@ -66,6 +68,7 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 @RunWith(AndroidJUnit4.class)
 @MediumTest
@@ -110,7 +113,7 @@
     }
 
     private void cleanUp() throws Exception {
-        mTm.stopTethering(TetheringManager.TETHERING_ETHERNET);
+        mTm.stopTethering(TETHERING_ETHERNET);
         if (mTetheringEventCallback != null) {
             mTetheringEventCallback.awaitInterfaceUntethered();
             mTetheringEventCallback.unregister();
@@ -177,6 +180,49 @@
     }
 
     @Test
+    public void testStaticIpv4() throws Exception {
+        assumeFalse(mEm.isAvailable());
+
+        mEm.setIncludeTestInterfaces(true);
+
+        mTestIface = createTestInterface();
+
+        final String iface = mTetheredInterfaceRequester.getInterface();
+        assertEquals("TetheredInterfaceCallback for unexpected interface",
+                mTestIface.getInterfaceName(), iface);
+
+        assertInvalidStaticIpv4Request(iface, null, null);
+        assertInvalidStaticIpv4Request(iface, "2001:db8::1/64", "2001:db8:2::/64");
+        assertInvalidStaticIpv4Request(iface, "192.0.2.2/28", "2001:db8:2::/28");
+        assertInvalidStaticIpv4Request(iface, "2001:db8:2::/28", "192.0.2.2/28");
+        assertInvalidStaticIpv4Request(iface, "192.0.2.2/28", null);
+        assertInvalidStaticIpv4Request(iface, null, "192.0.2.2/28");
+        assertInvalidStaticIpv4Request(iface, "192.0.2.3/27", "192.0.2.2/28");
+
+        final String localAddr = "192.0.2.3/28";
+        final String clientAddr = "192.0.2.2/28";
+        mTetheringEventCallback = enableEthernetTethering(iface,
+                requestWithStaticIpv4(localAddr, clientAddr));
+
+        mTetheringEventCallback.awaitInterfaceTethered();
+        assertInterfaceHasIpAddress(iface, clientAddr);
+
+        byte[] client1 = MacAddress.fromString("1:2:3:4:5:6").toByteArray();
+        byte[] client2 = MacAddress.fromString("a:b:c:d:e:f").toByteArray();
+
+        FileDescriptor fd = mTestIface.getFileDescriptor().getFileDescriptor();
+        mTapPacketReader = makePacketReader(fd, getMTU(mTestIface));
+        DhcpResults dhcpResults = runDhcp(fd, client1);
+        assertEquals(new LinkAddress(clientAddr), dhcpResults.ipAddress);
+
+        try {
+            runDhcp(fd, client2);
+            fail("Only one client should get an IP address");
+        } catch (TimeoutException expected) { }
+
+    }
+
+    @Test
     public void testPhysicalEthernet() throws Exception {
         assumeTrue(mEm.isAvailable());
 
@@ -271,7 +317,8 @@
         }
     }
 
-    private MyTetheringEventCallback enableEthernetTethering(String iface) throws Exception {
+    private MyTetheringEventCallback enableEthernetTethering(String iface,
+            TetheringRequest request) throws Exception {
         MyTetheringEventCallback callback = new MyTetheringEventCallback(mTm, iface);
         mTm.registerTetheringEventCallback(mHandler::post, callback);
 
@@ -282,34 +329,37 @@
             }
         };
         Log.d(TAG, "Starting Ethernet tethering");
-        mTm.startTethering(
-                new TetheringRequest.Builder(TetheringManager.TETHERING_ETHERNET).build(),
-                mHandler::post /* executor */,  startTetheringCallback);
+        mTm.startTethering(request, mHandler::post /* executor */,  startTetheringCallback);
         callback.awaitInterfaceTethered();
         return callback;
     }
 
+    private MyTetheringEventCallback enableEthernetTethering(String iface) throws Exception {
+        return enableEthernetTethering(iface,
+                new TetheringRequest.Builder(TETHERING_ETHERNET).build());
+    }
+
     private int getMTU(TestNetworkInterface iface) throws SocketException {
         NetworkInterface nif = NetworkInterface.getByName(iface.getInterfaceName());
         assertNotNull("Can't get NetworkInterface object for " + iface.getInterfaceName(), nif);
         return nif.getMTU();
     }
 
+    private TapPacketReader makePacketReader(FileDescriptor fd, int mtu) {
+        final TapPacketReader reader = new TapPacketReader(mHandler, fd, mtu);
+        mHandler.post(() -> reader.start());
+        HandlerUtilsKt.waitForIdle(mHandler, TIMEOUT_MS);
+        return reader;
+    }
+
     private void checkVirtualEthernet(TestNetworkInterface iface, int mtu) throws Exception {
         FileDescriptor fd = iface.getFileDescriptor().getFileDescriptor();
-        mTapPacketReader = new TapPacketReader(mHandler, fd, mtu);
-        mHandler.post(() -> mTapPacketReader.start());
-        HandlerUtilsKt.waitForIdle(mHandler, TIMEOUT_MS);
-
+        mTapPacketReader = makePacketReader(fd, mtu);
         mTetheringEventCallback = enableEthernetTethering(iface.getInterfaceName());
         checkTetheredClientCallbacks(fd);
     }
 
-    private void checkTetheredClientCallbacks(FileDescriptor fd) throws Exception {
-        // Create a fake client.
-        byte[] clientMacAddr = new byte[6];
-        new Random().nextBytes(clientMacAddr);
-
+    private DhcpResults runDhcp(FileDescriptor fd, byte[] clientMacAddr) throws Exception {
         // We have to retransmit DHCP requests because IpServer declares itself to be ready before
         // its DhcpServer is actually started. TODO: fix this race and remove this loop.
         DhcpPacket offerPacket = null;
@@ -319,13 +369,25 @@
             offerPacket = getNextDhcpPacket();
             if (offerPacket instanceof DhcpOfferPacket) break;
         }
-        assertTrue("No DHCPOFFER received on interface within timeout",
-                offerPacket instanceof DhcpOfferPacket);
+        if (!(offerPacket instanceof DhcpOfferPacket)) {
+            throw new TimeoutException("No DHCPOFFER received on interface within timeout");
+        }
 
         sendDhcpRequest(fd, offerPacket, clientMacAddr);
         DhcpPacket ackPacket = getNextDhcpPacket();
-        assertTrue("No DHCPACK received on interface within timeout",
-                ackPacket instanceof DhcpAckPacket);
+        if (!(ackPacket instanceof DhcpAckPacket)) {
+            throw new TimeoutException("No DHCPACK received on interface within timeout");
+        }
+
+        return ackPacket.toDhcpResults();
+    }
+
+    private void checkTetheredClientCallbacks(FileDescriptor fd) throws Exception {
+        // Create a fake client.
+        byte[] clientMacAddr = new byte[6];
+        new Random().nextBytes(clientMacAddr);
+
+        DhcpResults dhcpResults = runDhcp(fd, clientMacAddr);
 
         final Collection<TetheredClient> clients = mTetheringEventCallback.awaitClientConnected();
         assertEquals(1, clients.size());
@@ -333,7 +395,7 @@
 
         // Check the MAC address.
         assertEquals(MacAddress.fromBytes(clientMacAddr), client.getMacAddress());
-        assertEquals(TetheringManager.TETHERING_ETHERNET, client.getTetheringType());
+        assertEquals(TETHERING_ETHERNET, client.getTetheringType());
 
         // Check the hostname.
         assertEquals(1, client.getAddresses().size());
@@ -341,7 +403,6 @@
         assertEquals(DHCP_HOSTNAME, info.getHostname());
 
         // Check the address is the one that was handed out in the DHCP ACK.
-        DhcpResults dhcpResults = offerPacket.toDhcpResults();
         assertLinkAddressMatches(dhcpResults.ipAddress, info.getAddress());
 
         // Check that the lifetime is correct +/- 10s.
@@ -441,6 +502,34 @@
         assertEquals("LinkAddress scope does not match", l1.getScope(), l2.getScope());
     }
 
+    private TetheringRequest requestWithStaticIpv4(String local, String client) {
+        LinkAddress localAddr = local == null ? null : new LinkAddress(local);
+        LinkAddress clientAddr = client == null ? null : new LinkAddress(client);
+        return new TetheringRequest.Builder(TETHERING_ETHERNET)
+                .setStaticIpv4Addresses(localAddr, clientAddr).build();
+    }
+
+    private void assertInvalidStaticIpv4Request(String iface, String local, String client)
+            throws Exception {
+        try {
+            enableEthernetTethering(iface, requestWithStaticIpv4(local, client));
+            fail("Unexpectedly accepted invalid IPv4 configuration: " + local + ", " + client);
+        } catch (IllegalArgumentException | NullPointerException expected) { }
+    }
+
+    private void assertInterfaceHasIpAddress(String iface, String expected) throws Exception {
+        LinkAddress expectedAddr = new LinkAddress(expected);
+        NetworkInterface nif = NetworkInterface.getByName(iface);
+        for (InterfaceAddress ia : nif.getInterfaceAddresses()) {
+            final LinkAddress addr = new LinkAddress(ia.getAddress(), ia.getNetworkPrefixLength());
+            if (expectedAddr.equals(addr)) {
+                return;
+            }
+        }
+        fail("Expected " + iface + " to have IP address " + expected + ", found "
+                + nif.getInterfaceAddresses());
+    }
+
     private TestNetworkInterface createTestInterface() throws Exception {
         TestNetworkManager tnm = mContext.getSystemService(TestNetworkManager.class);
         TestNetworkInterface iface = tnm.createTapInterface();
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 3106e0e..fdfdae8 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -43,7 +43,6 @@
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doAnswer;
@@ -66,6 +65,7 @@
 import android.net.LinkProperties;
 import android.net.MacAddress;
 import android.net.RouteInfo;
+import android.net.TetherOffloadRuleParcel;
 import android.net.dhcp.DhcpServingParamsParcel;
 import android.net.dhcp.IDhcpServer;
 import android.net.dhcp.IDhcpServerCallbacks;
@@ -85,6 +85,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
 import org.mockito.Captor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
@@ -92,6 +93,7 @@
 
 import java.net.Inet4Address;
 import java.net.InetAddress;
+import java.util.Arrays;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -514,6 +516,65 @@
         mLooper.dispatchAll();
     }
 
+    /**
+     * Custom ArgumentMatcher for TetherOffloadRuleParcel. This is needed because generated stable
+     * AIDL classes don't have equals(), so we cannot just use eq(). A custom assert, such as:
+     *
+     * private void checkFooCalled(StableParcelable p, ...) {
+     *     ArgumentCaptor<FooParam> captor = ArgumentCaptor.forClass(FooParam.class);
+     *     verify(mMock).foo(captor.capture());
+     *     Foo foo = captor.getValue();
+     *     assertFooMatchesExpectations(foo);
+     * }
+     *
+     * almost works, but not quite. This is because if the code under test calls foo() twice, the
+     * first call to checkFooCalled() matches both the calls, putting both calls into the captor,
+     * and then fails with TooManyActualInvocations. It also makes it harder to use other mockito
+     * features such as never(), inOrder(), etc.
+     *
+     * This approach isn't great because if the match fails, the error message is unhelpful
+     * (actual: "android.net.TetherOffloadRuleParcel@8c827b0" or some such), but at least it does
+     * work.
+     *
+     * See ConnectivityServiceTest#assertRoutesAdded for an alternative approach which solves the
+     * TooManyActualInvocations problem described above by forcing the caller of the custom assert
+     * method to specify all expected invocations in one call. This is useful when the stable
+     * parcelable class being asserted on has a corresponding Java object (eg., RouteInfo and
+     * RouteInfoParcelable), and the caller can just pass in a list of them. It not useful here
+     * because there is no such object.
+     */
+    private static class TetherOffloadRuleParcelMatcher implements
+            ArgumentMatcher<TetherOffloadRuleParcel> {
+        public final int upstreamIfindex;
+        public final InetAddress dst;
+        public final MacAddress dstMac;
+
+        TetherOffloadRuleParcelMatcher(int upstreamIfindex, InetAddress dst, MacAddress dstMac) {
+            this.upstreamIfindex = upstreamIfindex;
+            this.dst = dst;
+            this.dstMac = dstMac;
+        }
+
+        public boolean matches(TetherOffloadRuleParcel parcel) {
+            return upstreamIfindex == parcel.inputInterfaceIndex
+                    && (TEST_IFACE_PARAMS.index == parcel.outputInterfaceIndex)
+                    && Arrays.equals(dst.getAddress(), parcel.destination)
+                    && (128 == parcel.prefixLength)
+                    && Arrays.equals(TEST_IFACE_PARAMS.macAddr.toByteArray(), parcel.srcL2Address)
+                    && Arrays.equals(dstMac.toByteArray(), parcel.dstL2Address);
+        }
+
+        public String toString() {
+            return String.format("TetherOffloadRuleParcelMatcher(%d, %s, %s",
+                    upstreamIfindex, dst.getHostAddress(), dstMac);
+        }
+    }
+
+    private TetherOffloadRuleParcel matches(
+            int upstreamIfindex, InetAddress dst, MacAddress dstMac) {
+        return argThat(new TetherOffloadRuleParcelMatcher(upstreamIfindex, dst, dstMac));
+    }
+
     @Test
     public void addRemoveipv6ForwardingRules() throws Exception {
         initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */);
@@ -537,13 +598,11 @@
 
         // Events on this interface are received and sent to netd.
         recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA);
-        verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX),
-                eq(neighA.getAddress()), eq(myMac.toByteArray()), eq(macA.toByteArray()));
+        verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighA, macA));
         reset(mNetd);
 
         recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
-        verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX),
-                eq(neighB.getAddress()), eq(myMac.toByteArray()), eq(macB.toByteArray()));
+        verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighB, macB));
         reset(mNetd);
 
         // Link-local and multicast neighbors are ignored.
@@ -554,12 +613,12 @@
 
         // A neighbor that is no longer valid causes the rule to be removed.
         recvNewNeigh(myIfindex, neighA, NUD_FAILED, macA);
-        verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX), eq(neighA.getAddress()));
+        verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighA, macA));
         reset(mNetd);
 
         // A neighbor that is deleted causes the rule to be removed.
         recvDelNeigh(myIfindex, neighB, NUD_STALE, macB);
-        verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX), eq(neighB.getAddress()));
+        verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighB, macB));
         reset(mNetd);
 
         // Upstream changes result in deleting and re-adding the rules.
@@ -571,22 +630,16 @@
         LinkProperties lp = new LinkProperties();
         lp.setInterfaceName(UPSTREAM_IFACE2);
         dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp);
-        inOrder.verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX2),
-                eq(neighA.getAddress()), eq(myMac.toByteArray()), eq(macA.toByteArray()));
-        inOrder.verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX),
-                    eq(neighA.getAddress()));
-        inOrder.verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX2),
-                eq(neighB.getAddress()), eq(myMac.toByteArray()), eq(macB.toByteArray()));
-        inOrder.verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX),
-                eq(neighB.getAddress()));
+        inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX2, neighA, macA));
+        inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighA, macA));
+        inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX2, neighB, macB));
+        inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighB, macB));
         reset(mNetd);
 
         // When the upstream is lost, rules are removed.
         dispatchTetherConnectionChanged(null, null);
-        verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX2),
-                eq(neighA.getAddress()));
-        verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX2),
-                eq(neighB.getAddress()));
+        verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX2, neighA, macA));
+        verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX2, neighB, macB));
         reset(mNetd);
 
         // If the upstream is IPv4-only, no rules are added.
@@ -599,31 +652,27 @@
         lp.setInterfaceName(UPSTREAM_IFACE);
         dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp);
         recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
-        verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX),
-                eq(neighB.getAddress()), eq(myMac.toByteArray()), eq(macB.toByteArray()));
-        verify(mNetd, never()).tetherRuleAddDownstreamIpv6(anyInt(), anyInt(),
-                eq(neighA.getAddress()), any(), any());
+        verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighB, macB));
+        verify(mNetd, never()).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighA, macA));
 
         // If upstream IPv6 connectivity is lost, rules are removed.
         reset(mNetd);
         dispatchTetherConnectionChanged(UPSTREAM_IFACE, null);
-        verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX), eq(neighB.getAddress()));
+        verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighB, macB));
 
         // When the interface goes down, rules are removed.
         lp.setInterfaceName(UPSTREAM_IFACE);
         dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp);
         recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA);
         recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
-        verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX),
-                eq(neighA.getAddress()), eq(myMac.toByteArray()), eq(macA.toByteArray()));
-        verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX),
-                eq(neighB.getAddress()), eq(myMac.toByteArray()), eq(macB.toByteArray()));
+        verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighA, macA));
+        verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighB, macB));
         reset(mNetd);
 
         mIpServer.stop();
         mLooper.dispatchAll();
-        verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX), eq(neighA.getAddress()));
-        verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX), eq(neighB.getAddress()));
+        verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighA, macA));
+        verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighB, macB));
         reset(mNetd);
     }
 
diff --git a/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
index 0a7850b..b3a30ab 100644
--- a/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
+++ b/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
@@ -17,8 +17,10 @@
 package com.android.server.connectivity.tethering;
 
 import static android.net.TetheringManager.TETHERING_BLUETOOTH;
+import static android.net.TetheringManager.TETHERING_ETHERNET;
 import static android.net.TetheringManager.TETHERING_USB;
 import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHERING_WIFI_P2P;
 import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN;
 import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
 import static android.net.TetheringManager.TETHER_ERROR_PROVISIONING_FAILED;
@@ -353,6 +355,20 @@
         callbackTimeoutHelper(mCallbacklatch);
         assertEquals(0, mEnMgr.uiProvisionCount);
         mEnMgr.reset();
+        // 8. Test get value for invalid downstream type.
+        mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
+        receiver = new ResultReceiver(null) {
+            @Override
+            protected void onReceiveResult(int resultCode, Bundle resultData) {
+                assertEquals(TETHER_ERROR_ENTITLEMENT_UNKNOWN, resultCode);
+                mCallbacklatch.countDown();
+            }
+        };
+        mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI_P2P, receiver, true);
+        mLooper.dispatchAll();
+        callbackTimeoutHelper(mCallbacklatch);
+        assertEquals(0, mEnMgr.uiProvisionCount);
+        mEnMgr.reset();
     }
 
     void callbackTimeoutHelper(final CountDownLatch latch) throws Exception {
@@ -471,6 +487,22 @@
         mLooper.dispatchAll();
         assertEquals(0, mEnMgr.uiProvisionCount);
         assertEquals(3, mEnMgr.silentProvisionCount);
+        assertFalse(mEnMgr.isCellularUpstreamPermitted());
+        mEnMgr.reset();
+        // 7. start ui provisioning, upstream is mobile, downstream is ethernet
+        mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
+        mEnMgr.startProvisioningIfNeeded(TETHERING_ETHERNET, true);
+        mLooper.dispatchAll();
+        assertEquals(1, mEnMgr.uiProvisionCount);
+        assertEquals(0, mEnMgr.silentProvisionCount);
+        assertTrue(mEnMgr.isCellularUpstreamPermitted());
+        mEnMgr.reset();
+        // 8. downstream is invalid
+        mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
+        mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI_P2P, true);
+        mLooper.dispatchAll();
+        assertEquals(0, mEnMgr.uiProvisionCount);
+        assertEquals(0, mEnMgr.silentProvisionCount);
         mEnMgr.reset();
     }