/* * Copyright (C) 2018 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.dhcp; import static android.net.InetAddresses.parseNumericAddress; import static android.net.dhcp.DhcpLease.HOSTNAME_NONE; import static android.net.dhcp.DhcpLeaseRepository.CLIENTID_UNSPEC; import static android.net.dhcp.DhcpLeaseRepository.INETADDR_UNSPEC; import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH; import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static java.lang.String.format; import static java.util.stream.Collectors.toSet; import android.annotation.NonNull; import android.annotation.Nullable; import android.net.IpPrefix; import android.net.MacAddress; import android.net.dhcp.DhcpServer.Clock; import android.net.util.SharedLog; import android.os.Binder; import android.os.RemoteException; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.net.Inet4Address; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; @RunWith(AndroidJUnit4.class) @SmallTest public class DhcpLeaseRepositoryTest { private static final Inet4Address TEST_DEF_ROUTER = parseAddr4("192.168.42.247"); private static final Inet4Address TEST_SERVER_ADDR = parseAddr4("192.168.42.241"); private static final Inet4Address TEST_CLIENT_ADDR = parseAddr4("192.168.42.2"); private static final Inet4Address TEST_RESERVED_ADDR = parseAddr4("192.168.42.243"); private static final MacAddress TEST_MAC_1 = MacAddress.fromBytes( new byte[] { 5, 4, 3, 2, 1, 0 }); private static final MacAddress TEST_MAC_2 = MacAddress.fromBytes( new byte[] { 0, 1, 2, 3, 4, 5 }); private static final MacAddress TEST_MAC_3 = MacAddress.fromBytes( new byte[] { 0, 1, 2, 3, 4, 6 }); private static final Inet4Address TEST_INETADDR_1 = parseAddr4("192.168.42.248"); private static final Inet4Address TEST_INETADDR_2 = parseAddr4("192.168.42.249"); private static final String TEST_HOSTNAME_1 = "hostname1"; private static final String TEST_HOSTNAME_2 = "hostname2"; private static final IpPrefix TEST_IP_PREFIX = new IpPrefix(TEST_SERVER_ADDR, 22); private static final long TEST_TIME = 100L; private static final int TEST_LEASE_TIME_MS = 3_600_000; private static final Set TEST_EXCL_SET = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( TEST_SERVER_ADDR, TEST_DEF_ROUTER, TEST_RESERVED_ADDR))); @NonNull private SharedLog mLog; @NonNull @Mock private Clock mClock; @NonNull private DhcpLeaseRepository mRepo; @NonNull @Mock private IDhcpEventCallbacks mCallbacks; private final Binder mCallbacksBinder = new Binder(); private static Inet4Address parseAddr4(String inet4Addr) { return (Inet4Address) parseNumericAddress(inet4Addr); } @Before public void setUp() { MockitoAnnotations.initMocks(this); initDhcpLeaseRepositoryWithOption(null); } private void initDhcpLeaseRepositoryWithOption(final Inet4Address clientAddr) { reset(mCallbacks, mClock); mLog = new SharedLog("DhcpLeaseRepositoryTest"); when(mClock.elapsedRealtime()).thenReturn(TEST_TIME); // Use a non-null Binder for linkToDeath when(mCallbacks.asBinder()).thenReturn(mCallbacksBinder); mRepo = new DhcpLeaseRepository( TEST_IP_PREFIX, TEST_EXCL_SET, TEST_LEASE_TIME_MS, clientAddr, mLog, mClock); mRepo.addLeaseCallbacks(mCallbacks); verify(mCallbacks, atLeastOnce()).asBinder(); } /** * Request a number of addresses through offer/request. Useful to test address exhaustion. * @param nAddr Number of addresses to request. */ private void requestAddresses(byte nAddr) throws Exception { final HashSet addrs = new HashSet<>(); byte[] hwAddrBytes = new byte[] { 8, 4, 3, 2, 1, 0 }; for (byte i = 0; i < nAddr; i++) { hwAddrBytes[5] = i; MacAddress newMac = MacAddress.fromBytes(hwAddrBytes); final String hostname = "host_" + i; final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, newMac, IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, hostname); assertNotNull(lease); assertEquals(newMac, lease.getHwAddr()); assertEquals(hostname, lease.getHostname()); assertTrue(format("Duplicate address allocated: %s in %s", lease.getNetAddr(), addrs), addrs.add(lease.getNetAddr())); requestLeaseSelecting(newMac, lease.getNetAddr(), hostname); } } @Test public void testAddressExhaustion() throws Exception { // Use a /28 to quickly run out of addresses mRepo.updateParams(new IpPrefix(TEST_SERVER_ADDR, 28), TEST_EXCL_SET, TEST_LEASE_TIME_MS, null /* clientAddr */); // /28 should have 16 addresses, 14 w/o the first/last, 11 w/o excluded addresses requestAddresses((byte) 11); verify(mCallbacks, times(11)).onLeasesChanged(any()); try { mRepo.getOffer(null, TEST_MAC_2, IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); fail("Should be out of addresses"); } catch (DhcpLeaseRepository.OutOfAddressesException e) { // Expected } verifyNoMoreInteractions(mCallbacks); } @Test public void testUpdateParams_LeaseCleanup() throws Exception { // Inside /28: final Inet4Address reqAddrIn28 = parseAddr4("192.168.42.242"); final Inet4Address declinedAddrIn28 = parseAddr4("192.168.42.245"); // Inside /28, but not available there (first address of the range) final Inet4Address declinedFirstAddrIn28 = parseAddr4("192.168.42.240"); final DhcpLease reqAddrIn28Lease = requestLeaseSelecting(TEST_MAC_1, reqAddrIn28); verifyLeasesChangedCallback(reqAddrIn28Lease); mRepo.markLeaseDeclined(declinedAddrIn28); mRepo.markLeaseDeclined(declinedFirstAddrIn28); // Inside /22, but outside /28: final Inet4Address reqAddrIn22 = parseAddr4("192.168.42.3"); final Inet4Address declinedAddrIn22 = parseAddr4("192.168.42.4"); final DhcpLease reqAddrIn22Lease = requestLeaseSelecting(TEST_MAC_3, reqAddrIn22); verifyLeasesChangedCallback(reqAddrIn28Lease, reqAddrIn22Lease); mRepo.markLeaseDeclined(declinedAddrIn22); // Address that will be reserved in the updateParams call below final Inet4Address reservedAddr = parseAddr4("192.168.42.244"); final DhcpLease reservedAddrLease = requestLeaseSelecting(TEST_MAC_2, reservedAddr); verifyLeasesChangedCallback(reqAddrIn28Lease, reqAddrIn22Lease, reservedAddrLease); // Update from /22 to /28 and add another reserved address Set newReserved = new HashSet<>(TEST_EXCL_SET); newReserved.add(reservedAddr); mRepo.updateParams(new IpPrefix(TEST_SERVER_ADDR, 28), newReserved, TEST_LEASE_TIME_MS, null /* clientAddr */); // Callback is called for the second time with just this lease verifyLeasesChangedCallback(2 /* times */, reqAddrIn28Lease); verifyNoMoreInteractions(mCallbacks); assertHasLease(reqAddrIn28Lease); assertDeclined(declinedAddrIn28); assertNotDeclined(declinedFirstAddrIn28); assertNoLease(reqAddrIn22Lease); assertNotDeclined(declinedAddrIn22); assertNoLease(reservedAddrLease); } @Test public void testGetOffer_StableAddress() throws Exception { for (final MacAddress macAddr : new MacAddress[] { TEST_MAC_1, TEST_MAC_2, TEST_MAC_3 }) { final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, macAddr, IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); // Same lease is offered twice final DhcpLease newLease = mRepo.getOffer(CLIENTID_UNSPEC, macAddr, IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); assertEquals(lease, newLease); } } @Test public void testUpdateParams_UsesNewPrefix() throws Exception { final IpPrefix newPrefix = new IpPrefix(parseAddr4("192.168.123.0"), 24); mRepo.updateParams(newPrefix, TEST_EXCL_SET, TEST_LEASE_TIME_MS, null /* clientAddr */); DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); assertTrue(newPrefix.contains(lease.getNetAddr())); } @Test public void testGetOffer_ExistingLease() throws Exception { requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1, TEST_HOSTNAME_1); DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); assertEquals(TEST_INETADDR_1, offer.getNetAddr()); assertEquals(TEST_HOSTNAME_1, offer.getHostname()); } @Test public void testGetOffer_ClientIdHasExistingLease() throws Exception { final byte[] clientId = new byte[] { 1, 2 }; mRepo.requestLease(clientId, TEST_MAC_1, IPV4_ADDR_ANY /* clientAddr */, IPV4_ADDR_ANY /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, false, TEST_HOSTNAME_1); // Different MAC, but same clientId DhcpLease offer = mRepo.getOffer(clientId, TEST_MAC_2, IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); assertEquals(TEST_INETADDR_1, offer.getNetAddr()); assertEquals(TEST_HOSTNAME_1, offer.getHostname()); } @Test public void testGetOffer_DifferentClientId() throws Exception { final byte[] clientId1 = new byte[] { 1, 2 }; final byte[] clientId2 = new byte[] { 3, 4 }; mRepo.requestLease(clientId1, TEST_MAC_1, IPV4_ADDR_ANY /* clientAddr */, IPV4_ADDR_ANY /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, false, TEST_HOSTNAME_1); // Same MAC, different client ID DhcpLease offer = mRepo.getOffer(clientId2, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); // Obtains a different address assertNotEquals(TEST_INETADDR_1, offer.getNetAddr()); assertEquals(HOSTNAME_NONE, offer.getHostname()); assertEquals(TEST_MAC_1, offer.getHwAddr()); } @Test public void testGetOffer_RequestedAddress() throws Exception { DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, TEST_HOSTNAME_1); assertEquals(TEST_INETADDR_1, offer.getNetAddr()); assertEquals(TEST_HOSTNAME_1, offer.getHostname()); // Leases are not committed on offer verify(mCallbacks, never()).onLeasesChanged(any()); } @Test public void testGetOffer_RequestedAddressInUse() throws Exception { requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1); DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_2, IPV4_ADDR_ANY /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, HOSTNAME_NONE); assertNotEquals(TEST_INETADDR_1, offer.getNetAddr()); } @Test public void testGetOffer_RequestedAddressReserved() throws Exception { DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */, TEST_RESERVED_ADDR /* reqAddr */, HOSTNAME_NONE); assertNotEquals(TEST_RESERVED_ADDR, offer.getNetAddr()); } @Test public void testGetOffer_RequestedAddressInvalid() throws Exception { final Inet4Address invalidAddr = parseAddr4("192.168.42.0"); DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */, invalidAddr /* reqAddr */, HOSTNAME_NONE); assertNotEquals(invalidAddr, offer.getNetAddr()); } @Test public void testGetOffer_RequestedAddressOutsideSubnet() throws Exception { final Inet4Address invalidAddr = parseAddr4("192.168.254.2"); DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */, invalidAddr /* reqAddr */, HOSTNAME_NONE); assertNotEquals(invalidAddr, offer.getNetAddr()); } @Test public void testGetOffer_StaticClientAddress() throws Exception { initDhcpLeaseRepositoryWithOption(TEST_CLIENT_ADDR); final DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, TEST_HOSTNAME_1); assertEquals(TEST_CLIENT_ADDR, offer.getNetAddr()); assertEquals(TEST_HOSTNAME_1, offer.getHostname()); } @Test public void testGetOffer_StaticClientAddressInUse() throws Exception { initDhcpLeaseRepositoryWithOption(TEST_CLIENT_ADDR); final byte[] clientId = new byte[] { 1 }; final DhcpLease lease = mRepo.requestLease(clientId, TEST_MAC_1, IPV4_ADDR_ANY /* clientAddr */, IPV4_ADDR_ANY /* relayAddr */, TEST_CLIENT_ADDR /* reqAddr */, false, TEST_HOSTNAME_1); // Static client address only support single client use case. try { mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); fail("Repository should be out of addresses and throw"); } catch (DhcpLeaseRepository.OutOfAddressesException e) { /* expected */ } } @Test(expected = DhcpLeaseRepository.InvalidSubnetException.class) public void testGetOffer_RelayInInvalidSubnet() throws Exception { mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, parseAddr4("192.168.254.2") /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); } @Test public void testRequestLease_SelectingTwice() throws Exception { final DhcpLease lease1 = requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1, TEST_HOSTNAME_1); verifyLeasesChangedCallback(lease1); // Second request from same client for a different address final DhcpLease lease2 = requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_2, TEST_HOSTNAME_2); verifyLeasesChangedCallback(lease2); assertEquals(TEST_INETADDR_1, lease1.getNetAddr()); assertEquals(TEST_HOSTNAME_1, lease1.getHostname()); assertEquals(TEST_INETADDR_2, lease2.getNetAddr()); assertEquals(TEST_HOSTNAME_2, lease2.getHostname()); // First address freed when client requested a different one: another client can request it final DhcpLease lease3 = requestLeaseSelecting(TEST_MAC_2, TEST_INETADDR_1, HOSTNAME_NONE); assertEquals(TEST_INETADDR_1, lease3.getNetAddr()); verifyLeasesChangedCallback(lease2, lease3); verifyNoMoreInteractions(mCallbacks); } @Test(expected = DhcpLeaseRepository.InvalidAddressException.class) public void testRequestLease_SelectingInvalid() throws Exception { requestLeaseSelecting(TEST_MAC_1, parseAddr4("192.168.254.5")); } @Test(expected = DhcpLeaseRepository.InvalidAddressException.class) public void testRequestLease_SelectingInUse() throws Exception { requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1); requestLeaseSelecting(TEST_MAC_2, TEST_INETADDR_1); } @Test(expected = DhcpLeaseRepository.InvalidAddressException.class) public void testRequestLease_SelectingReserved() throws Exception { requestLeaseSelecting(TEST_MAC_1, TEST_RESERVED_ADDR); } @Test(expected = DhcpLeaseRepository.InvalidSubnetException.class) public void testRequestLease_SelectingRelayInInvalidSubnet() throws Exception { mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* clientAddr */, parseAddr4("192.168.128.1") /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, true /* sidSet */, HOSTNAME_NONE); } @Test public void testRequestLease_InitReboot() throws Exception { // Request address once final DhcpLease oldLease = requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1); verifyLeasesChangedCallback(oldLease); final long newTime = TEST_TIME + 100; when(mClock.elapsedRealtime()).thenReturn(newTime); // init-reboot (sidSet == false): verify configuration final DhcpLease lease = requestLeaseInitReboot(TEST_MAC_1, TEST_INETADDR_1); assertEquals(TEST_INETADDR_1, lease.getNetAddr()); assertEquals(newTime + TEST_LEASE_TIME_MS, lease.getExpTime()); verifyLeasesChangedCallback(lease); verifyNoMoreInteractions(mCallbacks); } @Test(expected = DhcpLeaseRepository.InvalidAddressException.class) public void testRequestLease_InitRebootWrongAddr() throws Exception { // Request address once requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1); // init-reboot with different requested address requestLeaseInitReboot(TEST_MAC_1, TEST_INETADDR_2); } @Test public void testRequestLease_InitRebootUnknownAddr() throws Exception { // init-reboot with unknown requested address final DhcpLease lease = requestLeaseInitReboot(TEST_MAC_1, TEST_INETADDR_2); // RFC2131 says we should not reply to accommodate other servers, but since we are // authoritative we allow creating the lease to avoid issues with lost lease DB (same as // dnsmasq behavior) assertEquals(TEST_INETADDR_2, lease.getNetAddr()); } @Test(expected = DhcpLeaseRepository.InvalidAddressException.class) public void testRequestLease_InitRebootWrongSubnet() throws Exception { requestLeaseInitReboot(TEST_MAC_1, parseAddr4("192.168.254.2")); } @Test public void testRequestLease_Renewing() throws Exception { final DhcpLease oldLease = requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1); verifyLeasesChangedCallback(oldLease); final long newTime = TEST_TIME + 100; when(mClock.elapsedRealtime()).thenReturn(newTime); final DhcpLease lease = requestLeaseRenewing(TEST_MAC_1, TEST_INETADDR_1); assertEquals(TEST_INETADDR_1, lease.getNetAddr()); assertEquals(newTime + TEST_LEASE_TIME_MS, lease.getExpTime()); verifyLeasesChangedCallback(lease); verifyNoMoreInteractions(mCallbacks); } @Test public void testRequestLease_RenewingUnknownAddr() throws Exception { final long newTime = TEST_TIME + 100; when(mClock.elapsedRealtime()).thenReturn(newTime); final DhcpLease lease = requestLeaseRenewing(TEST_MAC_1, TEST_INETADDR_1); // Allows renewing an unknown address if available assertEquals(TEST_INETADDR_1, lease.getNetAddr()); assertEquals(newTime + TEST_LEASE_TIME_MS, lease.getExpTime()); } @Test public void testRequestLease_RenewingAddrInUse() throws Exception { final DhcpLease originalLease = requestLeaseSelecting(TEST_MAC_2, TEST_INETADDR_1); verifyLeasesChangedCallback(originalLease); try { requestLeaseRenewing(TEST_MAC_1, TEST_INETADDR_1); fail("Renewing with a different address should fail"); } catch (DhcpLeaseRepository.InvalidAddressException e) { // fall through } verifyNoMoreInteractions(mCallbacks); } @Test(expected = DhcpLeaseRepository.InvalidAddressException.class) public void testRequestLease_RenewingInvalidAddr() throws Exception { requestLeaseRenewing(TEST_MAC_1, parseAddr4("192.168.254.2")); } @Test public void testReleaseLease() throws Exception { final DhcpLease lease1 = requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1); assertHasLease(lease1); assertTrue(mRepo.releaseLease(CLIENTID_UNSPEC, TEST_MAC_1, TEST_INETADDR_1)); assertNoLease(lease1); final DhcpLease lease2 = requestLeaseSelecting(TEST_MAC_2, TEST_INETADDR_1); assertEquals(TEST_INETADDR_1, lease2.getNetAddr()); } @Test public void testReleaseLease_UnknownLease() { assertFalse(mRepo.releaseLease(CLIENTID_UNSPEC, TEST_MAC_1, TEST_INETADDR_1)); } @Test public void testReleaseLease_StableOffer() throws Exception { for (MacAddress mac : new MacAddress[] { TEST_MAC_1, TEST_MAC_2, TEST_MAC_3 }) { final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, mac, IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); requestLeaseSelecting(mac, lease.getNetAddr()); mRepo.releaseLease(CLIENTID_UNSPEC, mac, lease.getNetAddr()); // Same lease is offered after it was released final DhcpLease newLease = mRepo.getOffer(CLIENTID_UNSPEC, mac, IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); assertEquals(lease.getNetAddr(), newLease.getNetAddr()); } } @Test public void testMarkLeaseDeclined() throws Exception { final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); mRepo.markLeaseDeclined(lease.getNetAddr()); // Same lease is not offered again final DhcpLease newLease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); assertNotEquals(lease.getNetAddr(), newLease.getNetAddr()); } @Test public void testMarkLeaseDeclined_UsedIfOutOfAddresses() throws Exception { // Use a /28 to quickly run out of addresses mRepo.updateParams(new IpPrefix(TEST_SERVER_ADDR, 28), TEST_EXCL_SET, TEST_LEASE_TIME_MS, null /* clientAddr */); mRepo.markLeaseDeclined(TEST_INETADDR_1); mRepo.markLeaseDeclined(TEST_INETADDR_2); // /28 should have 16 addresses, 14 w/o the first/last, 11 w/o excluded addresses requestAddresses((byte) 9); // Last 2 addresses: addresses marked declined should be used final DhcpLease firstLease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, TEST_HOSTNAME_1); requestLeaseSelecting(TEST_MAC_1, firstLease.getNetAddr()); final DhcpLease secondLease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_2, IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, TEST_HOSTNAME_2); requestLeaseSelecting(TEST_MAC_2, secondLease.getNetAddr()); // Now out of addresses try { mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_3, IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); fail("Repository should be out of addresses and throw"); } catch (DhcpLeaseRepository.OutOfAddressesException e) { /* expected */ } assertEquals(TEST_INETADDR_1, firstLease.getNetAddr()); assertEquals(TEST_HOSTNAME_1, firstLease.getHostname()); assertEquals(TEST_INETADDR_2, secondLease.getNetAddr()); assertEquals(TEST_HOSTNAME_2, secondLease.getHostname()); } private DhcpLease requestLease(@NonNull MacAddress macAddr, @NonNull Inet4Address clientAddr, @Nullable Inet4Address reqAddr, @Nullable String hostname, boolean sidSet) throws DhcpLeaseRepository.DhcpLeaseException { return mRepo.requestLease(CLIENTID_UNSPEC, macAddr, clientAddr, IPV4_ADDR_ANY /* relayAddr */, reqAddr, sidSet, hostname); } /** * Request a lease simulating a client in the SELECTING state. */ private DhcpLease requestLeaseSelecting(@NonNull MacAddress macAddr, @NonNull Inet4Address reqAddr, @Nullable String hostname) throws DhcpLeaseRepository.DhcpLeaseException { return requestLease(macAddr, IPV4_ADDR_ANY /* clientAddr */, reqAddr, hostname, true /* sidSet */); } /** * Request a lease simulating a client in the SELECTING state. */ private DhcpLease requestLeaseSelecting(@NonNull MacAddress macAddr, @NonNull Inet4Address reqAddr) throws DhcpLeaseRepository.DhcpLeaseException { return requestLeaseSelecting(macAddr, reqAddr, HOSTNAME_NONE); } /** * Request a lease simulating a client in the INIT-REBOOT state. */ private DhcpLease requestLeaseInitReboot(@NonNull MacAddress macAddr, @NonNull Inet4Address reqAddr) throws DhcpLeaseRepository.DhcpLeaseException { return requestLease(macAddr, IPV4_ADDR_ANY /* clientAddr */, reqAddr, HOSTNAME_NONE, false /* sidSet */); } /** * Request a lease simulating a client in the RENEWING state. */ private DhcpLease requestLeaseRenewing(@NonNull MacAddress macAddr, @NonNull Inet4Address clientAddr) throws DhcpLeaseRepository.DhcpLeaseException { // Renewing: clientAddr filled in, no reqAddr return requestLease(macAddr, clientAddr, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE, true /* sidSet */); } private void assertNoLease(DhcpLease lease) { assertFalse("Leases contain " + lease, mRepo.getCommittedLeases().contains(lease)); } private void assertHasLease(DhcpLease lease) { assertTrue("Leases do not contain " + lease, mRepo.getCommittedLeases().contains(lease)); } private void assertNotDeclined(Inet4Address addr) { assertFalse("Address is declined: " + addr, mRepo.getDeclinedAddresses().contains(addr)); } private void assertDeclined(Inet4Address addr) { assertTrue("Address is not declined: " + addr, mRepo.getDeclinedAddresses().contains(addr)); } private void verifyLeasesChangedCallback(int times, DhcpLease... leases) { final Set expected = new HashSet<>(Arrays.asList(leases)); try { verify(mCallbacks, times(times)).onLeasesChanged(argThat(l -> l.stream().map(DhcpLeaseRepositoryTest::fromParcelable).collect(toSet()) .equals(expected))); } catch (RemoteException e) { fail("Can't happen: " + e); } } private void verifyLeasesChangedCallback(DhcpLease... leases) { verifyLeasesChangedCallback(1 /* times */, leases); } private static DhcpLease fromParcelable(DhcpLeaseParcelable p) { return new DhcpLease( p.clientId, p.hwAddr == null ? null : MacAddress.fromBytes(p.hwAddr), intToInet4AddressHTH(p.netAddr), p.prefixLength, p.expTime, p.hostname); } }