Loading src/android/net/dhcp/DhcpLeaseRepository.java +20 −2 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import android.util.ArrayMap; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import java.net.Inet4Address; import java.util.ArrayList; Loading Loading @@ -158,7 +159,7 @@ class DhcpLeaseRepository { /** * From a map keyed by {@link Inet4Address}, remove entries where the key is invalid (as * specified by {@link #isValidAddress(Inet4Address)}), or is a reserved address. * @return true iff at least one entry was removed. * @return true if and only if at least one entry was removed. */ private <T> boolean cleanMap(Map<Inet4Address, T> map) { final Iterator<Entry<Inet4Address, T>> it = map.entrySet().iterator(); Loading Loading @@ -397,7 +398,8 @@ class DhcpLeaseRepository { mEventCallbacks.finishBroadcast(); } public void markLeaseDeclined(@NonNull Inet4Address addr) { @VisibleForTesting void markLeaseDeclined(@NonNull Inet4Address addr) { if (mDeclinedAddrs.containsKey(addr) || !isValidAddress(addr)) { mLog.logf("Not marking %s as declined: already declined or not assignable", inet4AddrToString(addr)); Loading @@ -409,6 +411,22 @@ class DhcpLeaseRepository { maybeUpdateEarliestExpiration(expTime); } /** * Mark a committed lease matching the passed in clientId and hardware address parameters to be * declined, and delete it from the repository. * * @param clientId Client identifier option if specified, or {@link #CLIENTID_UNSPEC} * @param hwAddr client's mac address * @param Addr IPv4 address to be declined * @return true if a lease matching parameters was removed from committed repository. */ public boolean markAndReleaseDeclinedLease(@Nullable byte[] clientId, @NonNull MacAddress hwAddr, @NonNull Inet4Address addr) { if (!releaseLease(clientId, hwAddr, addr)) return false; markLeaseDeclined(addr); return true; } /** * Get the list of currently valid committed leases in the repository. */ Loading src/android/net/dhcp/DhcpServer.java +158 −83 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ import static java.lang.Integer.toUnsignedLong; import android.content.Context; import android.net.INetworkStackStatusCallback; import android.net.IpPrefix; import android.net.MacAddress; import android.net.TrafficStats; import android.net.util.NetworkStackUtils; Loading Loading @@ -96,7 +97,8 @@ public class DhcpServer extends StateMachine { private static final int CMD_START_DHCP_SERVER = 1; private static final int CMD_STOP_DHCP_SERVER = 2; private static final int CMD_UPDATE_PARAMS = 3; private static final int CMD_SUSPEND_DHCP_SERVER = 4; @VisibleForTesting protected static final int CMD_RECEIVE_PACKET = 4; @NonNull private final Context mContext; Loading Loading @@ -126,6 +128,8 @@ public class DhcpServer extends StateMachine { private final StoppedState mStoppedState = new StoppedState(); private final StartedState mStartedState = new StartedState(); private final RunningState mRunningState = new RunningState(); private final WaitBeforeRetrievalState mWaitBeforeRetrievalState = new WaitBeforeRetrievalState(); /** * Clock to be used by DhcpServer to track time for lease expiration. Loading Loading @@ -262,6 +266,7 @@ public class DhcpServer extends StateMachine { addState(mStoppedState); addState(mStartedState); addState(mRunningState, mStartedState); addState(mWaitBeforeRetrievalState, mStartedState); // CHECKSTYLE:ON IndentationCheck setInitialState(mStoppedState); Loading Loading @@ -372,6 +377,17 @@ public class DhcpServer extends StateMachine { } } private void handleUpdateServingParams(@NonNull DhcpServingParams params, @Nullable INetworkStackStatusCallback cb) { mServingParams = params; mLeaseRepo.updateParams( DhcpServingParams.makeIpPrefix(params.serverAddr), params.excludedAddrs, params.dhcpLeaseTimeSecs * 1000, params.singleClientAddr); maybeNotifyStatus(cb, STATUS_SUCCESS); } class StoppedState extends State { private INetworkStackStatusCallback mOnStopCallback; Loading Loading @@ -422,6 +438,8 @@ public class DhcpServer extends StateMachine { mLeaseRepo.addLeaseCallbacks(mEventCallbacks); } maybeNotifyStatus(mOnStartCallback, STATUS_SUCCESS); // Clear INetworkStackStatusCallback binder token, so that it's freed // on the other side. mOnStartCallback = null; } Loading @@ -431,14 +449,7 @@ public class DhcpServer extends StateMachine { case CMD_UPDATE_PARAMS: final Pair<DhcpServingParams, INetworkStackStatusCallback> pair = (Pair<DhcpServingParams, INetworkStackStatusCallback>) msg.obj; final DhcpServingParams params = pair.first; mServingParams = params; mLeaseRepo.updateParams( DhcpServingParams.makeIpPrefix(params.serverAddr), params.excludedAddrs, params.dhcpLeaseTimeSecs * 1000, params.singleClientAddr); maybeNotifyStatus(pair.second, STATUS_SUCCESS); handleUpdateServingParams(pair.first, pair.second); return HANDLED; case CMD_START_DHCP_SERVER: Loading Loading @@ -466,26 +477,19 @@ public class DhcpServer extends StateMachine { @Override public boolean processMessage(Message msg) { switch (msg.what) { case CMD_SUSPEND_DHCP_SERVER: // TODO: transition to the state which waits for IpServer to reconfigure the // new selected prefix. case CMD_RECEIVE_PACKET: processPacket((DhcpPacket) msg.obj); return HANDLED; default: // Fall through to StartedState. return NOT_HANDLED; } } } @VisibleForTesting void processPacket(@NonNull DhcpPacket packet, int srcPort) { final String packetType = packet.getClass().getSimpleName(); if (srcPort != DHCP_CLIENT) { mLog.logf("Ignored packet of type %s sent from client port %d", packetType, srcPort); return; } private void processPacket(@NonNull DhcpPacket packet) { mLog.log("Received packet of type " + packet.getClass().getSimpleName()); mLog.log("Received packet of type " + packetType); final Inet4Address sid = packet.mServerIdentifier; if (sid != null && !sid.equals(mServingParams.serverAddr.getAddress())) { mLog.log("Packet ignored due to wrong server identifier: " + sid); Loading @@ -499,6 +503,8 @@ public class DhcpServer extends StateMachine { processRequest((DhcpRequestPacket) packet); } else if (packet instanceof DhcpReleasePacket) { processRelease((DhcpReleasePacket) packet); } else if (packet instanceof DhcpDeclinePacket) { processDecline((DhcpDeclinePacket) packet); } else { mLog.e("Unknown packet type: " + packet.getClass().getSimpleName()); } Loading @@ -519,8 +525,8 @@ public class DhcpServer extends StateMachine { final MacAddress clientMac = getMacAddr(packet); try { if (mDhcpRapidCommitEnabled && packet.mRapidCommit) { lease = mLeaseRepo.getCommittedLease(packet.getExplicitClientIdOrNull(), clientMac, packet.mRelayIp, packet.mHostName); lease = mLeaseRepo.getCommittedLease(packet.getExplicitClientIdOrNull(), clientMac, packet.mRelayIp, packet.mHostName); transmitAck(packet, lease, clientMac); } else { lease = mLeaseRepo.getOffer(packet.getExplicitClientIdOrNull(), clientMac, Loading @@ -534,7 +540,8 @@ public class DhcpServer extends StateMachine { } } private void processRequest(@NonNull DhcpRequestPacket packet) throws MalformedPacketException { private void processRequest(@NonNull DhcpRequestPacket packet) throws MalformedPacketException { // If set, packet SID matches with this server's ID as checked in processPacket(). final boolean sidSet = packet.mServerIdentifier != null; final DhcpLease lease; Loading @@ -558,10 +565,72 @@ public class DhcpServer extends StateMachine { throws MalformedPacketException { final byte[] clientId = packet.getExplicitClientIdOrNull(); final MacAddress macAddr = getMacAddr(packet); // Don't care about success (there is no ACK/NAK); logging is already done in the repository // Don't care about success (there is no ACK/NAK); logging is already done // in the repository. mLeaseRepo.releaseLease(clientId, macAddr, packet.mClientIp); } private void processDecline(@NonNull DhcpDeclinePacket packet) throws MalformedPacketException { final byte[] clientId = packet.getExplicitClientIdOrNull(); final MacAddress macAddr = getMacAddr(packet); int committedLeasesCount = mLeaseRepo.getCommittedLeases().size(); // If peer's clientID and macAddr doesn't match with any issued lease, nothing to do. if (!mLeaseRepo.markAndReleaseDeclinedLease(clientId, macAddr, packet.mRequestedIp)) { return; } // Check whether the boolean flag which requests a new prefix is enabled, and if // it's enabled, make sure the issued lease count should be only one, otherwise, // changing a different prefix will cause other exist host(s) configured with the // current prefix lose appropriate route. if (!mServingParams.changePrefixOnDecline || committedLeasesCount > 1) return; if (mEventCallbacks == null) { mLog.e("changePrefixOnDecline enabled but caller didn't pass a valid" + "IDhcpEventCallbacks callback."); return; } try { mEventCallbacks.onNewPrefixRequest( DhcpServingParams.makeIpPrefix(mServingParams.serverAddr)); transitionTo(mWaitBeforeRetrievalState); } catch (RemoteException e) { mLog.e("could not request a new prefix to caller", e); } } } class WaitBeforeRetrievalState extends State { @Override public boolean processMessage(Message msg) { switch (msg.what) { case CMD_UPDATE_PARAMS: final Pair<DhcpServingParams, INetworkStackStatusCallback> pair = (Pair<DhcpServingParams, INetworkStackStatusCallback>) msg.obj; final IpPrefix currentPrefix = DhcpServingParams.makeIpPrefix(mServingParams.serverAddr); final IpPrefix newPrefix = DhcpServingParams.makeIpPrefix(pair.first.serverAddr); handleUpdateServingParams(pair.first, pair.second); if (currentPrefix != null && !currentPrefix.equals(newPrefix)) { transitionTo(mRunningState); } return HANDLED; case CMD_RECEIVE_PACKET: deferMessage(msg); return HANDLED; default: // Fall through to StartedState. return NOT_HANDLED; } } } private Inet4Address getAckOrOfferDst(@NonNull DhcpPacket request, @NonNull DhcpLease lease, boolean broadcastFlag) { // Unless relayed or broadcast, send to client IP if already configured on the client, or to Loading Loading @@ -748,7 +817,13 @@ public class DhcpServer extends StateMachine { @Override protected void onReceive(@NonNull DhcpPacket packet, @NonNull Inet4Address srcAddr, int srcPort) { processPacket(packet, srcPort); if (srcPort != DHCP_CLIENT) { final String packetType = packet.getClass().getSimpleName(); mLog.logf("Ignored packet of type %s sent from client port %d", packetType, srcPort); return; } sendMessage(CMD_RECEIVE_PACKET, packet); } @Override Loading tests/unit/src/android/net/dhcp/DhcpServerTest.java +121 −23 Original line number Diff line number Diff line Loading @@ -17,12 +17,13 @@ package android.net.dhcp; import static android.net.InetAddresses.parseNumericAddress; import static android.net.dhcp.DhcpPacket.DHCP_CLIENT; import static android.net.dhcp.DhcpPacket.DHCP_HOST_NAME; import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP; import static android.net.dhcp.DhcpPacket.INADDR_ANY; import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST; import static android.net.dhcp.DhcpServer.CMD_RECEIVE_PACKET; import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH; import static android.net.util.NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION; import static junit.framework.Assert.assertEquals; Loading @@ -31,7 +32,6 @@ import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.doNothing; Loading @@ -44,12 +44,14 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.net.INetworkStackStatusCallback; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.MacAddress; import android.net.dhcp.DhcpLeaseRepository.InvalidAddressException; import android.net.dhcp.DhcpLeaseRepository.OutOfAddressesException; import android.net.dhcp.DhcpServer.Clock; import android.net.dhcp.DhcpServer.Dependencies; import android.net.shared.Inet4AddressUtils; import android.net.util.SharedLog; import android.testing.AndroidTestingRunner; Loading @@ -69,6 +71,8 @@ import org.mockito.MockitoAnnotations; import java.net.Inet4Address; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; Loading Loading @@ -156,6 +160,7 @@ public class DhcpServerTest { .setServerAddr(TEST_SERVER_LINKADDR) .setLinkMtu(TEST_MTU) .setExcludedAddrs(TEST_EXCLUDED_ADDRS) .setChangePrefixOnDecline(false) .build(); } Loading Loading @@ -215,7 +220,8 @@ public class DhcpServerTest { final DhcpDiscoverPacket discover = new DhcpDiscoverPacket(TEST_TRANSACTION_ID, (short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES, false /* broadcast */, INADDR_ANY /* srcIp */, false /* rapidCommit */); mServer.processPacket(discover, DHCP_CLIENT); mServer.sendMessage(CMD_RECEIVE_PACKET, discover); HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS); assertResponseSentTo(TEST_CLIENT_ADDR); final DhcpOfferPacket packet = assertOffer(getPacket()); Loading @@ -233,7 +239,8 @@ public class DhcpServerTest { final DhcpDiscoverPacket discover = new DhcpDiscoverPacket(TEST_TRANSACTION_ID, (short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES, false /* broadcast */, INADDR_ANY /* srcIp */, true /* rapidCommit */); mServer.processPacket(discover, DHCP_CLIENT); mServer.sendMessage(CMD_RECEIVE_PACKET, discover); HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS); assertResponseSentTo(TEST_CLIENT_ADDR); final DhcpAckPacket packet = assertAck(getPacket()); Loading @@ -251,7 +258,8 @@ public class DhcpServerTest { final DhcpDiscoverPacket discover = new DhcpDiscoverPacket(TEST_TRANSACTION_ID, (short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES, false /* broadcast */, INADDR_ANY /* srcIp */, false /* rapidCommit */); mServer.processPacket(discover, DHCP_CLIENT); mServer.sendMessage(CMD_RECEIVE_PACKET, discover); HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS); assertResponseSentTo(INADDR_BROADCAST); final DhcpNakPacket packet = assertNak(getPacket()); Loading Loading @@ -279,7 +287,8 @@ public class DhcpServerTest { final DhcpRequestPacket request = makeRequestSelectingPacket(); request.mHostName = TEST_HOSTNAME; request.mRequestedParams = new byte[] { DHCP_HOST_NAME }; mServer.processPacket(request, DHCP_CLIENT); mServer.sendMessage(CMD_RECEIVE_PACKET, request); HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS); assertResponseSentTo(TEST_CLIENT_ADDR); final DhcpAckPacket packet = assertAck(getPacket()); Loading @@ -296,25 +305,14 @@ public class DhcpServerTest { .thenThrow(new InvalidAddressException("Test error")); final DhcpRequestPacket request = makeRequestSelectingPacket(); mServer.processPacket(request, DHCP_CLIENT); mServer.sendMessage(CMD_RECEIVE_PACKET, request); HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS); assertResponseSentTo(INADDR_BROADCAST); final DhcpNakPacket packet = assertNak(getPacket()); assertMatchesClient(packet); } @Test public void testRequest_Selecting_WrongClientPort() throws Exception { startServer(); final DhcpRequestPacket request = makeRequestSelectingPacket(); mServer.processPacket(request, 50000); verify(mRepository, never()) .requestLease(any(), any(), any(), any(), any(), anyBoolean(), any()); verify(mDeps, never()).sendPacket(any(), any(), any()); } @Test public void testRelease() throws Exception { startServer(); Loading @@ -322,28 +320,128 @@ public class DhcpServerTest { final DhcpReleasePacket release = new DhcpReleasePacket(TEST_TRANSACTION_ID, TEST_SERVER_ADDR, TEST_CLIENT_ADDR, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES); mServer.processPacket(release, DHCP_CLIENT); mServer.sendMessage(CMD_RECEIVE_PACKET, release); HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS); verify(mRepository, times(1)) .releaseLease(isNull(), eq(TEST_CLIENT_MAC), eq(TEST_CLIENT_ADDR)); } @Test public void testDecline_LeaseDoesNotExist() throws Exception { when(mRepository.markAndReleaseDeclinedLease(isNull(), eq(TEST_CLIENT_MAC), eq(TEST_CLIENT_ADDR))).thenReturn(false); startServer(); runOnReceivedDeclinePacket(); verify(mEventCallbacks, never()).onNewPrefixRequest(any()); } private void runOnReceivedDeclinePacket() throws Exception { when(mRepository.getCommittedLeases()).thenReturn( Arrays.asList(new DhcpLease(null, TEST_CLIENT_MAC, TEST_CLIENT_ADDR, TEST_PREFIX_LENGTH, TEST_LEASE_EXPTIME_SECS * 1000L + TEST_CLOCK_TIME, TEST_HOSTNAME))); final DhcpDeclinePacket decline = new DhcpDeclinePacket(TEST_TRANSACTION_ID, (short) 0 /* secs */, INADDR_ANY /* clientIp */, INADDR_ANY /* yourIp */, INADDR_ANY /* nextIp */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES, TEST_CLIENT_ADDR /* requestedIp */, TEST_SERVER_ADDR /* serverIdentifier */); mServer.sendMessage(CMD_RECEIVE_PACKET, decline); HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS); verify(mRepository).markAndReleaseDeclinedLease(isNull(), eq(TEST_CLIENT_MAC), eq(TEST_CLIENT_ADDR)); } private int[] toIntArray(@NonNull Collection<Inet4Address> addrs) { return addrs.stream().mapToInt(Inet4AddressUtils::inet4AddressToIntHTH).toArray(); } private void updateServingParams(Set<Inet4Address> defaultRouters, Set<Inet4Address> dnsServers, Set<Inet4Address> excludedAddrs, LinkAddress serverAddr, boolean changePrefixOnDecline) throws Exception { final DhcpServingParamsParcel params = new DhcpServingParamsParcel(); params.serverAddr = inet4AddressToIntHTH((Inet4Address) serverAddr.getAddress()); params.serverAddrPrefixLength = serverAddr.getPrefixLength(); params.defaultRouters = toIntArray(defaultRouters); params.dnsServers = toIntArray(dnsServers); params.excludedAddrs = toIntArray(excludedAddrs); params.dhcpLeaseTimeSecs = TEST_LEASE_EXPTIME_SECS; params.linkMtu = TEST_MTU; params.metered = true; params.changePrefixOnDecline = changePrefixOnDecline; mServer.updateParams(params, mAssertSuccessCallback); HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS); } @Test public void testChangePrefixOnDecline() throws Exception { when(mRepository.markAndReleaseDeclinedLease(isNull(), eq(TEST_CLIENT_MAC), eq(TEST_CLIENT_ADDR))).thenReturn(true); mServer.start(mAssertSuccessCallback, mEventCallbacks); HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS); verify(mRepository).addLeaseCallbacks(eq(mEventCallbacks)); // Enable changePrefixOnDecline updateServingParams(TEST_DEFAULT_ROUTERS, TEST_DNS_SERVERS, TEST_EXCLUDED_ADDRS, TEST_SERVER_LINKADDR, true /* changePrefixOnDecline */); runOnReceivedDeclinePacket(); final IpPrefix servingPrefix = DhcpServingParams.makeIpPrefix(TEST_SERVER_LINKADDR); verify(mEventCallbacks).onNewPrefixRequest(eq(servingPrefix)); final Inet4Address serverAddr = parseAddr("192.168.51.129"); final LinkAddress srvLinkAddr = new LinkAddress(serverAddr, 24); final Set<Inet4Address> srvAddr = new HashSet<>(Collections.singletonList(serverAddr)); final Set<Inet4Address> excludedAddrs = new HashSet<>( Arrays.asList(parseAddr("192.168.51.200"), parseAddr("192.168.51.201"))); // Simulate IpServer updates the serving params with a new prefix. updateServingParams(srvAddr, srvAddr, excludedAddrs, srvLinkAddr, true /* changePrefixOnDecline */); final Inet4Address clientAddr = parseAddr("192.168.51.42"); final DhcpLease lease = new DhcpLease(null, TEST_CLIENT_MAC, clientAddr, 24 /*prefixLen*/, TEST_LEASE_EXPTIME_SECS * 1000L + TEST_CLOCK_TIME, null /* hostname */); when(mRepository.getOffer(isNull() /* clientId */, eq(TEST_CLIENT_MAC), eq(INADDR_ANY) /* relayAddr */, isNull() /* reqAddr */, isNull() /* hostname */)) .thenReturn(lease); // Test discover packet final DhcpDiscoverPacket discover = new DhcpDiscoverPacket(TEST_TRANSACTION_ID, (short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES, false /* broadcast */, INADDR_ANY /* srcIp */, false /* rapidCommit */); mServer.sendMessage(CMD_RECEIVE_PACKET, discover); HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS); assertResponseSentTo(clientAddr); final DhcpOfferPacket packet = assertOffer(getPacket()); assertMatchesLease(packet, serverAddr, clientAddr, null); } /* TODO: add more tests once packet construction is refactored, including: * - usage of giaddr * - usage of broadcast bit * - other request states (init-reboot/renewing/rebinding) */ private void assertMatchesTestLease(@NonNull DhcpPacket packet, @Nullable String hostname) { private void assertMatchesLease(@NonNull DhcpPacket packet, @NonNull Inet4Address srvAddr, @NonNull Inet4Address clientAddr, @Nullable String hostname) { assertMatchesClient(packet); assertFalse(packet.hasExplicitClientId()); assertEquals(TEST_SERVER_ADDR, packet.mServerIdentifier); assertEquals(TEST_CLIENT_ADDR, packet.mYourIp); assertEquals(srvAddr, packet.mServerIdentifier); assertEquals(clientAddr, packet.mYourIp); assertNotNull(packet.mLeaseTime); assertEquals(TEST_LEASE_EXPTIME_SECS, (int) packet.mLeaseTime); assertEquals(hostname, packet.mHostName); } private void assertMatchesTestLease(@NonNull DhcpPacket packet, @Nullable String hostname) { assertMatchesLease(packet, TEST_SERVER_ADDR, TEST_CLIENT_ADDR, hostname); } private void assertMatchesTestLease(@NonNull DhcpPacket packet) { assertMatchesTestLease(packet, null); } Loading Loading
src/android/net/dhcp/DhcpLeaseRepository.java +20 −2 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import android.util.ArrayMap; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import java.net.Inet4Address; import java.util.ArrayList; Loading Loading @@ -158,7 +159,7 @@ class DhcpLeaseRepository { /** * From a map keyed by {@link Inet4Address}, remove entries where the key is invalid (as * specified by {@link #isValidAddress(Inet4Address)}), or is a reserved address. * @return true iff at least one entry was removed. * @return true if and only if at least one entry was removed. */ private <T> boolean cleanMap(Map<Inet4Address, T> map) { final Iterator<Entry<Inet4Address, T>> it = map.entrySet().iterator(); Loading Loading @@ -397,7 +398,8 @@ class DhcpLeaseRepository { mEventCallbacks.finishBroadcast(); } public void markLeaseDeclined(@NonNull Inet4Address addr) { @VisibleForTesting void markLeaseDeclined(@NonNull Inet4Address addr) { if (mDeclinedAddrs.containsKey(addr) || !isValidAddress(addr)) { mLog.logf("Not marking %s as declined: already declined or not assignable", inet4AddrToString(addr)); Loading @@ -409,6 +411,22 @@ class DhcpLeaseRepository { maybeUpdateEarliestExpiration(expTime); } /** * Mark a committed lease matching the passed in clientId and hardware address parameters to be * declined, and delete it from the repository. * * @param clientId Client identifier option if specified, or {@link #CLIENTID_UNSPEC} * @param hwAddr client's mac address * @param Addr IPv4 address to be declined * @return true if a lease matching parameters was removed from committed repository. */ public boolean markAndReleaseDeclinedLease(@Nullable byte[] clientId, @NonNull MacAddress hwAddr, @NonNull Inet4Address addr) { if (!releaseLease(clientId, hwAddr, addr)) return false; markLeaseDeclined(addr); return true; } /** * Get the list of currently valid committed leases in the repository. */ Loading
src/android/net/dhcp/DhcpServer.java +158 −83 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ import static java.lang.Integer.toUnsignedLong; import android.content.Context; import android.net.INetworkStackStatusCallback; import android.net.IpPrefix; import android.net.MacAddress; import android.net.TrafficStats; import android.net.util.NetworkStackUtils; Loading Loading @@ -96,7 +97,8 @@ public class DhcpServer extends StateMachine { private static final int CMD_START_DHCP_SERVER = 1; private static final int CMD_STOP_DHCP_SERVER = 2; private static final int CMD_UPDATE_PARAMS = 3; private static final int CMD_SUSPEND_DHCP_SERVER = 4; @VisibleForTesting protected static final int CMD_RECEIVE_PACKET = 4; @NonNull private final Context mContext; Loading Loading @@ -126,6 +128,8 @@ public class DhcpServer extends StateMachine { private final StoppedState mStoppedState = new StoppedState(); private final StartedState mStartedState = new StartedState(); private final RunningState mRunningState = new RunningState(); private final WaitBeforeRetrievalState mWaitBeforeRetrievalState = new WaitBeforeRetrievalState(); /** * Clock to be used by DhcpServer to track time for lease expiration. Loading Loading @@ -262,6 +266,7 @@ public class DhcpServer extends StateMachine { addState(mStoppedState); addState(mStartedState); addState(mRunningState, mStartedState); addState(mWaitBeforeRetrievalState, mStartedState); // CHECKSTYLE:ON IndentationCheck setInitialState(mStoppedState); Loading Loading @@ -372,6 +377,17 @@ public class DhcpServer extends StateMachine { } } private void handleUpdateServingParams(@NonNull DhcpServingParams params, @Nullable INetworkStackStatusCallback cb) { mServingParams = params; mLeaseRepo.updateParams( DhcpServingParams.makeIpPrefix(params.serverAddr), params.excludedAddrs, params.dhcpLeaseTimeSecs * 1000, params.singleClientAddr); maybeNotifyStatus(cb, STATUS_SUCCESS); } class StoppedState extends State { private INetworkStackStatusCallback mOnStopCallback; Loading Loading @@ -422,6 +438,8 @@ public class DhcpServer extends StateMachine { mLeaseRepo.addLeaseCallbacks(mEventCallbacks); } maybeNotifyStatus(mOnStartCallback, STATUS_SUCCESS); // Clear INetworkStackStatusCallback binder token, so that it's freed // on the other side. mOnStartCallback = null; } Loading @@ -431,14 +449,7 @@ public class DhcpServer extends StateMachine { case CMD_UPDATE_PARAMS: final Pair<DhcpServingParams, INetworkStackStatusCallback> pair = (Pair<DhcpServingParams, INetworkStackStatusCallback>) msg.obj; final DhcpServingParams params = pair.first; mServingParams = params; mLeaseRepo.updateParams( DhcpServingParams.makeIpPrefix(params.serverAddr), params.excludedAddrs, params.dhcpLeaseTimeSecs * 1000, params.singleClientAddr); maybeNotifyStatus(pair.second, STATUS_SUCCESS); handleUpdateServingParams(pair.first, pair.second); return HANDLED; case CMD_START_DHCP_SERVER: Loading Loading @@ -466,26 +477,19 @@ public class DhcpServer extends StateMachine { @Override public boolean processMessage(Message msg) { switch (msg.what) { case CMD_SUSPEND_DHCP_SERVER: // TODO: transition to the state which waits for IpServer to reconfigure the // new selected prefix. case CMD_RECEIVE_PACKET: processPacket((DhcpPacket) msg.obj); return HANDLED; default: // Fall through to StartedState. return NOT_HANDLED; } } } @VisibleForTesting void processPacket(@NonNull DhcpPacket packet, int srcPort) { final String packetType = packet.getClass().getSimpleName(); if (srcPort != DHCP_CLIENT) { mLog.logf("Ignored packet of type %s sent from client port %d", packetType, srcPort); return; } private void processPacket(@NonNull DhcpPacket packet) { mLog.log("Received packet of type " + packet.getClass().getSimpleName()); mLog.log("Received packet of type " + packetType); final Inet4Address sid = packet.mServerIdentifier; if (sid != null && !sid.equals(mServingParams.serverAddr.getAddress())) { mLog.log("Packet ignored due to wrong server identifier: " + sid); Loading @@ -499,6 +503,8 @@ public class DhcpServer extends StateMachine { processRequest((DhcpRequestPacket) packet); } else if (packet instanceof DhcpReleasePacket) { processRelease((DhcpReleasePacket) packet); } else if (packet instanceof DhcpDeclinePacket) { processDecline((DhcpDeclinePacket) packet); } else { mLog.e("Unknown packet type: " + packet.getClass().getSimpleName()); } Loading @@ -519,8 +525,8 @@ public class DhcpServer extends StateMachine { final MacAddress clientMac = getMacAddr(packet); try { if (mDhcpRapidCommitEnabled && packet.mRapidCommit) { lease = mLeaseRepo.getCommittedLease(packet.getExplicitClientIdOrNull(), clientMac, packet.mRelayIp, packet.mHostName); lease = mLeaseRepo.getCommittedLease(packet.getExplicitClientIdOrNull(), clientMac, packet.mRelayIp, packet.mHostName); transmitAck(packet, lease, clientMac); } else { lease = mLeaseRepo.getOffer(packet.getExplicitClientIdOrNull(), clientMac, Loading @@ -534,7 +540,8 @@ public class DhcpServer extends StateMachine { } } private void processRequest(@NonNull DhcpRequestPacket packet) throws MalformedPacketException { private void processRequest(@NonNull DhcpRequestPacket packet) throws MalformedPacketException { // If set, packet SID matches with this server's ID as checked in processPacket(). final boolean sidSet = packet.mServerIdentifier != null; final DhcpLease lease; Loading @@ -558,10 +565,72 @@ public class DhcpServer extends StateMachine { throws MalformedPacketException { final byte[] clientId = packet.getExplicitClientIdOrNull(); final MacAddress macAddr = getMacAddr(packet); // Don't care about success (there is no ACK/NAK); logging is already done in the repository // Don't care about success (there is no ACK/NAK); logging is already done // in the repository. mLeaseRepo.releaseLease(clientId, macAddr, packet.mClientIp); } private void processDecline(@NonNull DhcpDeclinePacket packet) throws MalformedPacketException { final byte[] clientId = packet.getExplicitClientIdOrNull(); final MacAddress macAddr = getMacAddr(packet); int committedLeasesCount = mLeaseRepo.getCommittedLeases().size(); // If peer's clientID and macAddr doesn't match with any issued lease, nothing to do. if (!mLeaseRepo.markAndReleaseDeclinedLease(clientId, macAddr, packet.mRequestedIp)) { return; } // Check whether the boolean flag which requests a new prefix is enabled, and if // it's enabled, make sure the issued lease count should be only one, otherwise, // changing a different prefix will cause other exist host(s) configured with the // current prefix lose appropriate route. if (!mServingParams.changePrefixOnDecline || committedLeasesCount > 1) return; if (mEventCallbacks == null) { mLog.e("changePrefixOnDecline enabled but caller didn't pass a valid" + "IDhcpEventCallbacks callback."); return; } try { mEventCallbacks.onNewPrefixRequest( DhcpServingParams.makeIpPrefix(mServingParams.serverAddr)); transitionTo(mWaitBeforeRetrievalState); } catch (RemoteException e) { mLog.e("could not request a new prefix to caller", e); } } } class WaitBeforeRetrievalState extends State { @Override public boolean processMessage(Message msg) { switch (msg.what) { case CMD_UPDATE_PARAMS: final Pair<DhcpServingParams, INetworkStackStatusCallback> pair = (Pair<DhcpServingParams, INetworkStackStatusCallback>) msg.obj; final IpPrefix currentPrefix = DhcpServingParams.makeIpPrefix(mServingParams.serverAddr); final IpPrefix newPrefix = DhcpServingParams.makeIpPrefix(pair.first.serverAddr); handleUpdateServingParams(pair.first, pair.second); if (currentPrefix != null && !currentPrefix.equals(newPrefix)) { transitionTo(mRunningState); } return HANDLED; case CMD_RECEIVE_PACKET: deferMessage(msg); return HANDLED; default: // Fall through to StartedState. return NOT_HANDLED; } } } private Inet4Address getAckOrOfferDst(@NonNull DhcpPacket request, @NonNull DhcpLease lease, boolean broadcastFlag) { // Unless relayed or broadcast, send to client IP if already configured on the client, or to Loading Loading @@ -748,7 +817,13 @@ public class DhcpServer extends StateMachine { @Override protected void onReceive(@NonNull DhcpPacket packet, @NonNull Inet4Address srcAddr, int srcPort) { processPacket(packet, srcPort); if (srcPort != DHCP_CLIENT) { final String packetType = packet.getClass().getSimpleName(); mLog.logf("Ignored packet of type %s sent from client port %d", packetType, srcPort); return; } sendMessage(CMD_RECEIVE_PACKET, packet); } @Override Loading
tests/unit/src/android/net/dhcp/DhcpServerTest.java +121 −23 Original line number Diff line number Diff line Loading @@ -17,12 +17,13 @@ package android.net.dhcp; import static android.net.InetAddresses.parseNumericAddress; import static android.net.dhcp.DhcpPacket.DHCP_CLIENT; import static android.net.dhcp.DhcpPacket.DHCP_HOST_NAME; import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP; import static android.net.dhcp.DhcpPacket.INADDR_ANY; import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST; import static android.net.dhcp.DhcpServer.CMD_RECEIVE_PACKET; import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH; import static android.net.util.NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION; import static junit.framework.Assert.assertEquals; Loading @@ -31,7 +32,6 @@ import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.doNothing; Loading @@ -44,12 +44,14 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.net.INetworkStackStatusCallback; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.MacAddress; import android.net.dhcp.DhcpLeaseRepository.InvalidAddressException; import android.net.dhcp.DhcpLeaseRepository.OutOfAddressesException; import android.net.dhcp.DhcpServer.Clock; import android.net.dhcp.DhcpServer.Dependencies; import android.net.shared.Inet4AddressUtils; import android.net.util.SharedLog; import android.testing.AndroidTestingRunner; Loading @@ -69,6 +71,8 @@ import org.mockito.MockitoAnnotations; import java.net.Inet4Address; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; Loading Loading @@ -156,6 +160,7 @@ public class DhcpServerTest { .setServerAddr(TEST_SERVER_LINKADDR) .setLinkMtu(TEST_MTU) .setExcludedAddrs(TEST_EXCLUDED_ADDRS) .setChangePrefixOnDecline(false) .build(); } Loading Loading @@ -215,7 +220,8 @@ public class DhcpServerTest { final DhcpDiscoverPacket discover = new DhcpDiscoverPacket(TEST_TRANSACTION_ID, (short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES, false /* broadcast */, INADDR_ANY /* srcIp */, false /* rapidCommit */); mServer.processPacket(discover, DHCP_CLIENT); mServer.sendMessage(CMD_RECEIVE_PACKET, discover); HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS); assertResponseSentTo(TEST_CLIENT_ADDR); final DhcpOfferPacket packet = assertOffer(getPacket()); Loading @@ -233,7 +239,8 @@ public class DhcpServerTest { final DhcpDiscoverPacket discover = new DhcpDiscoverPacket(TEST_TRANSACTION_ID, (short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES, false /* broadcast */, INADDR_ANY /* srcIp */, true /* rapidCommit */); mServer.processPacket(discover, DHCP_CLIENT); mServer.sendMessage(CMD_RECEIVE_PACKET, discover); HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS); assertResponseSentTo(TEST_CLIENT_ADDR); final DhcpAckPacket packet = assertAck(getPacket()); Loading @@ -251,7 +258,8 @@ public class DhcpServerTest { final DhcpDiscoverPacket discover = new DhcpDiscoverPacket(TEST_TRANSACTION_ID, (short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES, false /* broadcast */, INADDR_ANY /* srcIp */, false /* rapidCommit */); mServer.processPacket(discover, DHCP_CLIENT); mServer.sendMessage(CMD_RECEIVE_PACKET, discover); HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS); assertResponseSentTo(INADDR_BROADCAST); final DhcpNakPacket packet = assertNak(getPacket()); Loading Loading @@ -279,7 +287,8 @@ public class DhcpServerTest { final DhcpRequestPacket request = makeRequestSelectingPacket(); request.mHostName = TEST_HOSTNAME; request.mRequestedParams = new byte[] { DHCP_HOST_NAME }; mServer.processPacket(request, DHCP_CLIENT); mServer.sendMessage(CMD_RECEIVE_PACKET, request); HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS); assertResponseSentTo(TEST_CLIENT_ADDR); final DhcpAckPacket packet = assertAck(getPacket()); Loading @@ -296,25 +305,14 @@ public class DhcpServerTest { .thenThrow(new InvalidAddressException("Test error")); final DhcpRequestPacket request = makeRequestSelectingPacket(); mServer.processPacket(request, DHCP_CLIENT); mServer.sendMessage(CMD_RECEIVE_PACKET, request); HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS); assertResponseSentTo(INADDR_BROADCAST); final DhcpNakPacket packet = assertNak(getPacket()); assertMatchesClient(packet); } @Test public void testRequest_Selecting_WrongClientPort() throws Exception { startServer(); final DhcpRequestPacket request = makeRequestSelectingPacket(); mServer.processPacket(request, 50000); verify(mRepository, never()) .requestLease(any(), any(), any(), any(), any(), anyBoolean(), any()); verify(mDeps, never()).sendPacket(any(), any(), any()); } @Test public void testRelease() throws Exception { startServer(); Loading @@ -322,28 +320,128 @@ public class DhcpServerTest { final DhcpReleasePacket release = new DhcpReleasePacket(TEST_TRANSACTION_ID, TEST_SERVER_ADDR, TEST_CLIENT_ADDR, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES); mServer.processPacket(release, DHCP_CLIENT); mServer.sendMessage(CMD_RECEIVE_PACKET, release); HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS); verify(mRepository, times(1)) .releaseLease(isNull(), eq(TEST_CLIENT_MAC), eq(TEST_CLIENT_ADDR)); } @Test public void testDecline_LeaseDoesNotExist() throws Exception { when(mRepository.markAndReleaseDeclinedLease(isNull(), eq(TEST_CLIENT_MAC), eq(TEST_CLIENT_ADDR))).thenReturn(false); startServer(); runOnReceivedDeclinePacket(); verify(mEventCallbacks, never()).onNewPrefixRequest(any()); } private void runOnReceivedDeclinePacket() throws Exception { when(mRepository.getCommittedLeases()).thenReturn( Arrays.asList(new DhcpLease(null, TEST_CLIENT_MAC, TEST_CLIENT_ADDR, TEST_PREFIX_LENGTH, TEST_LEASE_EXPTIME_SECS * 1000L + TEST_CLOCK_TIME, TEST_HOSTNAME))); final DhcpDeclinePacket decline = new DhcpDeclinePacket(TEST_TRANSACTION_ID, (short) 0 /* secs */, INADDR_ANY /* clientIp */, INADDR_ANY /* yourIp */, INADDR_ANY /* nextIp */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES, TEST_CLIENT_ADDR /* requestedIp */, TEST_SERVER_ADDR /* serverIdentifier */); mServer.sendMessage(CMD_RECEIVE_PACKET, decline); HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS); verify(mRepository).markAndReleaseDeclinedLease(isNull(), eq(TEST_CLIENT_MAC), eq(TEST_CLIENT_ADDR)); } private int[] toIntArray(@NonNull Collection<Inet4Address> addrs) { return addrs.stream().mapToInt(Inet4AddressUtils::inet4AddressToIntHTH).toArray(); } private void updateServingParams(Set<Inet4Address> defaultRouters, Set<Inet4Address> dnsServers, Set<Inet4Address> excludedAddrs, LinkAddress serverAddr, boolean changePrefixOnDecline) throws Exception { final DhcpServingParamsParcel params = new DhcpServingParamsParcel(); params.serverAddr = inet4AddressToIntHTH((Inet4Address) serverAddr.getAddress()); params.serverAddrPrefixLength = serverAddr.getPrefixLength(); params.defaultRouters = toIntArray(defaultRouters); params.dnsServers = toIntArray(dnsServers); params.excludedAddrs = toIntArray(excludedAddrs); params.dhcpLeaseTimeSecs = TEST_LEASE_EXPTIME_SECS; params.linkMtu = TEST_MTU; params.metered = true; params.changePrefixOnDecline = changePrefixOnDecline; mServer.updateParams(params, mAssertSuccessCallback); HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS); } @Test public void testChangePrefixOnDecline() throws Exception { when(mRepository.markAndReleaseDeclinedLease(isNull(), eq(TEST_CLIENT_MAC), eq(TEST_CLIENT_ADDR))).thenReturn(true); mServer.start(mAssertSuccessCallback, mEventCallbacks); HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS); verify(mRepository).addLeaseCallbacks(eq(mEventCallbacks)); // Enable changePrefixOnDecline updateServingParams(TEST_DEFAULT_ROUTERS, TEST_DNS_SERVERS, TEST_EXCLUDED_ADDRS, TEST_SERVER_LINKADDR, true /* changePrefixOnDecline */); runOnReceivedDeclinePacket(); final IpPrefix servingPrefix = DhcpServingParams.makeIpPrefix(TEST_SERVER_LINKADDR); verify(mEventCallbacks).onNewPrefixRequest(eq(servingPrefix)); final Inet4Address serverAddr = parseAddr("192.168.51.129"); final LinkAddress srvLinkAddr = new LinkAddress(serverAddr, 24); final Set<Inet4Address> srvAddr = new HashSet<>(Collections.singletonList(serverAddr)); final Set<Inet4Address> excludedAddrs = new HashSet<>( Arrays.asList(parseAddr("192.168.51.200"), parseAddr("192.168.51.201"))); // Simulate IpServer updates the serving params with a new prefix. updateServingParams(srvAddr, srvAddr, excludedAddrs, srvLinkAddr, true /* changePrefixOnDecline */); final Inet4Address clientAddr = parseAddr("192.168.51.42"); final DhcpLease lease = new DhcpLease(null, TEST_CLIENT_MAC, clientAddr, 24 /*prefixLen*/, TEST_LEASE_EXPTIME_SECS * 1000L + TEST_CLOCK_TIME, null /* hostname */); when(mRepository.getOffer(isNull() /* clientId */, eq(TEST_CLIENT_MAC), eq(INADDR_ANY) /* relayAddr */, isNull() /* reqAddr */, isNull() /* hostname */)) .thenReturn(lease); // Test discover packet final DhcpDiscoverPacket discover = new DhcpDiscoverPacket(TEST_TRANSACTION_ID, (short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES, false /* broadcast */, INADDR_ANY /* srcIp */, false /* rapidCommit */); mServer.sendMessage(CMD_RECEIVE_PACKET, discover); HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS); assertResponseSentTo(clientAddr); final DhcpOfferPacket packet = assertOffer(getPacket()); assertMatchesLease(packet, serverAddr, clientAddr, null); } /* TODO: add more tests once packet construction is refactored, including: * - usage of giaddr * - usage of broadcast bit * - other request states (init-reboot/renewing/rebinding) */ private void assertMatchesTestLease(@NonNull DhcpPacket packet, @Nullable String hostname) { private void assertMatchesLease(@NonNull DhcpPacket packet, @NonNull Inet4Address srvAddr, @NonNull Inet4Address clientAddr, @Nullable String hostname) { assertMatchesClient(packet); assertFalse(packet.hasExplicitClientId()); assertEquals(TEST_SERVER_ADDR, packet.mServerIdentifier); assertEquals(TEST_CLIENT_ADDR, packet.mYourIp); assertEquals(srvAddr, packet.mServerIdentifier); assertEquals(clientAddr, packet.mYourIp); assertNotNull(packet.mLeaseTime); assertEquals(TEST_LEASE_EXPTIME_SECS, (int) packet.mLeaseTime); assertEquals(hostname, packet.mHostName); } private void assertMatchesTestLease(@NonNull DhcpPacket packet, @Nullable String hostname) { assertMatchesLease(packet, TEST_SERVER_ADDR, TEST_CLIENT_ADDR, hostname); } private void assertMatchesTestLease(@NonNull DhcpPacket packet) { assertMatchesTestLease(packet, null); } Loading