Loading src/android/net/dhcp/DhcpServer.java +183 −104 Original line number Original line Diff line number Diff line Loading @@ -20,6 +20,9 @@ import static android.net.dhcp.DhcpPacket.DHCP_CLIENT; import static android.net.dhcp.DhcpPacket.DHCP_HOST_NAME; import static android.net.dhcp.DhcpPacket.DHCP_HOST_NAME; import static android.net.dhcp.DhcpPacket.DHCP_SERVER; import static android.net.dhcp.DhcpPacket.DHCP_SERVER; import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP; import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP; import static android.net.dhcp.IDhcpServer.STATUS_INVALID_ARGUMENT; import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR; import static android.net.shared.Inet4AddressUtils.getBroadcastAddress; import static android.net.shared.Inet4AddressUtils.getBroadcastAddress; import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address; import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address; import static android.net.util.NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION; import static android.net.util.NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION; Loading Loading @@ -48,8 +51,6 @@ import android.net.util.NetworkStackUtils; import android.net.util.SharedLog; import android.net.util.SharedLog; import android.net.util.SocketUtils; import android.net.util.SocketUtils; import android.os.Handler; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.Message; import android.os.RemoteException; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemClock; Loading @@ -63,6 +64,8 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting; import com.android.internal.util.HexDump; import com.android.internal.util.HexDump; import com.android.internal.util.State; import com.android.internal.util.StateMachine; import java.io.FileDescriptor; import java.io.FileDescriptor; import java.io.IOException; import java.io.IOException; Loading @@ -78,12 +81,12 @@ import java.util.ArrayList; * authoritative for all leases on the subnet, which means that DHCP requests for unknown leases of * authoritative for all leases on the subnet, which means that DHCP requests for unknown leases of * unknown hosts receive a reply instead of being ignored. * unknown hosts receive a reply instead of being ignored. * * * <p>The server is single-threaded (including send/receive operations): all internal operations are * <p>The server relies on StateMachine's handler (including send/receive operations): all internal * done on the provided {@link Looper}. Public methods are thread-safe and will schedule operations * operations are done in StateMachine's looper. Public methods are thread-safe and will schedule * on the looper asynchronously. * operations on that looper asynchronously. * @hide * @hide */ */ public class DhcpServer extends IDhcpServer.Stub { public class DhcpServer extends StateMachine { private static final String REPO_TAG = "Repository"; private static final String REPO_TAG = "Repository"; // Lease time to transmit to client instead of a negative time in case a lease expired before // Lease time to transmit to client instead of a negative time in case a lease expired before Loading @@ -93,12 +96,11 @@ public class DhcpServer extends IDhcpServer.Stub { private static final int CMD_START_DHCP_SERVER = 1; private static final int CMD_START_DHCP_SERVER = 1; private static final int CMD_STOP_DHCP_SERVER = 2; private static final int CMD_STOP_DHCP_SERVER = 2; private static final int CMD_UPDATE_PARAMS = 3; private static final int CMD_UPDATE_PARAMS = 3; private static final int CMD_SUSPEND_DHCP_SERVER = 4; @NonNull @NonNull private final Context mContext; private final Context mContext; @NonNull @NonNull private final HandlerThread mHandlerThread; @NonNull private final String mIfName; private final String mIfName; @NonNull @NonNull private final DhcpLeaseRepository mLeaseRepo; private final DhcpLeaseRepository mLeaseRepo; Loading @@ -108,19 +110,22 @@ public class DhcpServer extends IDhcpServer.Stub { private final Dependencies mDeps; private final Dependencies mDeps; @NonNull @NonNull private final Clock mClock; private final Clock mClock; @NonNull private DhcpServingParams mServingParams; @Nullable private volatile ServerHandler mHandler; private final boolean mDhcpRapidCommitEnabled; // Accessed only on the handler thread @Nullable @Nullable private DhcpPacketListener mPacketListener; private DhcpPacketListener mPacketListener; @Nullable @Nullable private FileDescriptor mSocket; private FileDescriptor mSocket; @NonNull @Nullable private DhcpServingParams mServingParams; private IDhcpEventCallbacks mEventCallbacks; private final boolean mDhcpRapidCommitEnabled; // States. private final StoppedState mStoppedState = new StoppedState(); private final StartedState mStartedState = new StartedState(); private final RunningState mRunningState = new RunningState(); /** /** * Clock to be used by DhcpServer to track time for lease expiration. * Clock to be used by DhcpServer to track time for lease expiration. Loading Loading @@ -164,7 +169,7 @@ public class DhcpServer extends IDhcpServer.Stub { /** /** * Create a packet listener that will send packets to be processed. * Create a packet listener that will send packets to be processed. */ */ DhcpPacketListener makePacketListener(); DhcpPacketListener makePacketListener(@NonNull Handler handler); /** /** * Create a clock that the server will use to track time. * Create a clock that the server will use to track time. Loading @@ -178,12 +183,6 @@ public class DhcpServer extends IDhcpServer.Stub { void addArpEntry(@NonNull Inet4Address ipv4Addr, @NonNull MacAddress ethAddr, void addArpEntry(@NonNull Inet4Address ipv4Addr, @NonNull MacAddress ethAddr, @NonNull String ifname, @NonNull FileDescriptor fd) throws IOException; @NonNull String ifname, @NonNull FileDescriptor fd) throws IOException; /** * Verify that the caller is allowed to call public methods on DhcpServer. * @throws SecurityException The caller is not allowed to call public methods on DhcpServer. */ void checkCaller() throws SecurityException; /** /** * Check whether or not one specific experimental feature for connectivity namespace is * Check whether or not one specific experimental feature for connectivity namespace is * enabled. * enabled. Loading @@ -210,8 +209,8 @@ public class DhcpServer extends IDhcpServer.Stub { } } @Override @Override public DhcpPacketListener makePacketListener() { public DhcpPacketListener makePacketListener(@NonNull Handler handler) { return new PacketListener(); return new PacketListener(handler); } } @Override @Override Loading @@ -225,11 +224,6 @@ public class DhcpServer extends IDhcpServer.Stub { NetworkStackUtils.addArpEntry(ipv4Addr, ethAddr, ifname, fd); NetworkStackUtils.addArpEntry(ipv4Addr, ethAddr, ifname, fd); } } @Override public void checkCaller() { enforceNetworkStackCallingPermission(); } @Override @Override public boolean isFeatureEnabled(@NonNull Context context, @NonNull String name) { public boolean isFeatureEnabled(@NonNull Context context, @NonNull String name) { return NetworkStackUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name); return NetworkStackUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name); Loading @@ -244,19 +238,18 @@ public class DhcpServer extends IDhcpServer.Stub { public DhcpServer(@NonNull Context context, @NonNull String ifName, public DhcpServer(@NonNull Context context, @NonNull String ifName, @NonNull DhcpServingParams params, @NonNull SharedLog log) { @NonNull DhcpServingParams params, @NonNull SharedLog log) { this(context, new HandlerThread(DhcpServer.class.getSimpleName() + "." + ifName), this(context, ifName, params, log, null); ifName, params, log, null); } } @VisibleForTesting @VisibleForTesting DhcpServer(@NonNull Context context, @NonNull HandlerThread handlerThread, DhcpServer(@NonNull Context context, @NonNull String ifName, @NonNull DhcpServingParams params, @NonNull String ifName, @NonNull DhcpServingParams params, @NonNull SharedLog log, @NonNull SharedLog log, @Nullable Dependencies deps) { @Nullable Dependencies deps) { super(DhcpServer.class.getSimpleName() + "." + ifName); if (deps == null) { if (deps == null) { deps = new DependenciesImpl(); deps = new DependenciesImpl(); } } mContext = context; mContext = context; mHandlerThread = handlerThread; mIfName = ifName; mIfName = ifName; mServingParams = params; mServingParams = params; mLog = log; mLog = log; Loading @@ -264,6 +257,61 @@ public class DhcpServer extends IDhcpServer.Stub { mClock = deps.makeClock(); mClock = deps.makeClock(); mLeaseRepo = deps.makeLeaseRepository(mServingParams, mLog, mClock); mLeaseRepo = deps.makeLeaseRepository(mServingParams, mLog, mClock); mDhcpRapidCommitEnabled = deps.isFeatureEnabled(context, DHCP_RAPID_COMMIT_VERSION); mDhcpRapidCommitEnabled = deps.isFeatureEnabled(context, DHCP_RAPID_COMMIT_VERSION); // CHECKSTYLE:OFF IndentationCheck addState(mStoppedState); addState(mStartedState); addState(mRunningState, mStartedState); // CHECKSTYLE:ON IndentationCheck setInitialState(mStoppedState); super.start(); } /** * Make a IDhcpServer connector to communicate with this DhcpServer. */ public IDhcpServer makeConnector() { return new DhcpServerConnector(); } private class DhcpServerConnector extends IDhcpServer.Stub { @Override public void start(@Nullable INetworkStackStatusCallback cb) { enforceNetworkStackCallingPermission(); DhcpServer.this.start(cb); } @Override public void startWithCallbacks(@Nullable INetworkStackStatusCallback statusCb, @Nullable IDhcpEventCallbacks eventCb) { enforceNetworkStackCallingPermission(); DhcpServer.this.start(statusCb, eventCb); } @Override public void updateParams(@Nullable DhcpServingParamsParcel params, @Nullable INetworkStackStatusCallback cb) { enforceNetworkStackCallingPermission(); DhcpServer.this.updateParams(params, cb); } @Override public void stop(@Nullable INetworkStackStatusCallback cb) { enforceNetworkStackCallingPermission(); DhcpServer.this.stop(cb); } @Override public int getInterfaceVersion() { return this.VERSION; } @Override public String getInterfaceHash() { return this.HASH; } } } /** /** Loading @@ -272,9 +320,8 @@ public class DhcpServer extends IDhcpServer.Stub { * <p>It is not legal to call this method more than once; in particular the server cannot be * <p>It is not legal to call this method more than once; in particular the server cannot be * restarted after being stopped. * restarted after being stopped. */ */ @Override void start(@Nullable INetworkStackStatusCallback cb) { public void start(@Nullable INetworkStackStatusCallback cb) { start(cb, null); startWithCallbacks(cb, null); } } /** /** Loading @@ -283,12 +330,8 @@ public class DhcpServer extends IDhcpServer.Stub { * <p>It is not legal to call this method more than once; in particular the server cannot be * <p>It is not legal to call this method more than once; in particular the server cannot be * restarted after being stopped. * restarted after being stopped. */ */ @Override void start(@Nullable INetworkStackStatusCallback statusCb, public void startWithCallbacks(@Nullable INetworkStackStatusCallback statusCb, @Nullable IDhcpEventCallbacks eventCb) { @Nullable IDhcpEventCallbacks eventCb) { mDeps.checkCaller(); mHandlerThread.start(); mHandler = new ServerHandler(mHandlerThread.getLooper()); sendMessage(CMD_START_DHCP_SERVER, new Pair<>(statusCb, eventCb)); sendMessage(CMD_START_DHCP_SERVER, new Pair<>(statusCb, eventCb)); } } Loading @@ -296,19 +339,15 @@ public class DhcpServer extends IDhcpServer.Stub { * Update serving parameters. All subsequently received requests will be handled with the new * Update serving parameters. All subsequently received requests will be handled with the new * parameters, and current leases that are incompatible with the new parameters are dropped. * parameters, and current leases that are incompatible with the new parameters are dropped. */ */ @Override void updateParams(@Nullable DhcpServingParamsParcel params, public void updateParams(@Nullable DhcpServingParamsParcel params, @Nullable INetworkStackStatusCallback cb) { @Nullable INetworkStackStatusCallback cb) throws RemoteException { mDeps.checkCaller(); final DhcpServingParams parsedParams; final DhcpServingParams parsedParams; try { try { // throws InvalidParameterException with null params // throws InvalidParameterException with null params parsedParams = DhcpServingParams.fromParcelableObject(params); parsedParams = DhcpServingParams.fromParcelableObject(params); } catch (DhcpServingParams.InvalidParameterException e) { } catch (DhcpServingParams.InvalidParameterException e) { mLog.e("Invalid parameters sent to DhcpServer", e); mLog.e("Invalid parameters sent to DhcpServer", e); if (cb != null) { maybeNotifyStatus(cb, STATUS_INVALID_ARGUMENT); cb.onStatusAvailable(STATUS_INVALID_ARGUMENT); } return; return; } } sendMessage(CMD_UPDATE_PARAMS, new Pair<>(parsedParams, cb)); sendMessage(CMD_UPDATE_PARAMS, new Pair<>(parsedParams, cb)); Loading @@ -320,28 +359,74 @@ public class DhcpServer extends IDhcpServer.Stub { * <p>As the server is stopped asynchronously, some packets may still be processed shortly after * <p>As the server is stopped asynchronously, some packets may still be processed shortly after * calling this method. * calling this method. */ */ @Override void stop(@Nullable INetworkStackStatusCallback cb) { public void stop(@Nullable INetworkStackStatusCallback cb) { mDeps.checkCaller(); sendMessage(CMD_STOP_DHCP_SERVER, cb); sendMessage(CMD_STOP_DHCP_SERVER, cb); } } private void sendMessage(int what, @Nullable Object obj) { private void maybeNotifyStatus(@Nullable INetworkStackStatusCallback cb, int statusCode) { if (mHandler == null) { if (cb == null) return; mLog.e("Attempting to send a command to stopped DhcpServer: " + what); try { cb.onStatusAvailable(statusCode); } catch (RemoteException e) { mLog.e("Could not send status back to caller", e); } } class StoppedState extends State { private INetworkStackStatusCallback mOnStopCallback; @Override public void enter() { maybeNotifyStatus(mOnStopCallback, STATUS_SUCCESS); mOnStopCallback = null; } @Override public boolean processMessage(Message msg) { switch (msg.what) { case CMD_START_DHCP_SERVER: final Pair<INetworkStackStatusCallback, IDhcpEventCallbacks> obj = (Pair<INetworkStackStatusCallback, IDhcpEventCallbacks>) msg.obj; mStartedState.mOnStartCallback = obj.first; mEventCallbacks = obj.second; transitionTo(mRunningState); return HANDLED; default: return NOT_HANDLED; } } } class StartedState extends State { private INetworkStackStatusCallback mOnStartCallback; @Override public void enter() { if (mPacketListener != null) { mLog.e("Starting DHCP server more than once is not supported."); maybeNotifyStatus(mOnStartCallback, STATUS_UNKNOWN_ERROR); mOnStartCallback = null; return; return; } } mHandler.sendMessage(mHandler.obtainMessage(what, obj)); mPacketListener = mDeps.makePacketListener(getHandler()); if (!mPacketListener.start()) { mLog.e("Fail to start DHCP Packet Listener, rollback to StoppedState"); deferMessage(obtainMessage(CMD_STOP_DHCP_SERVER, null)); maybeNotifyStatus(mOnStartCallback, STATUS_UNKNOWN_ERROR); mOnStartCallback = null; return; } } private class ServerHandler extends Handler { if (mEventCallbacks != null) { ServerHandler(@NonNull Looper looper) { mLeaseRepo.addLeaseCallbacks(mEventCallbacks); super(looper); } maybeNotifyStatus(mOnStartCallback, STATUS_SUCCESS); mOnStartCallback = null; } } @Override @Override public void handleMessage(@NonNull Message msg) { public boolean processMessage(Message msg) { final INetworkStackStatusCallback cb; switch (msg.what) { switch (msg.what) { case CMD_UPDATE_PARAMS: case CMD_UPDATE_PARAMS: final Pair<DhcpServingParams, INetworkStackStatusCallback> pair = final Pair<DhcpServingParams, INetworkStackStatusCallback> pair = Loading @@ -349,40 +434,45 @@ public class DhcpServer extends IDhcpServer.Stub { final DhcpServingParams params = pair.first; final DhcpServingParams params = pair.first; mServingParams = params; mServingParams = params; mLeaseRepo.updateParams( mLeaseRepo.updateParams( DhcpServingParams.makeIpPrefix(mServingParams.serverAddr), DhcpServingParams.makeIpPrefix(params.serverAddr), params.excludedAddrs, params.excludedAddrs, params.dhcpLeaseTimeSecs, params.dhcpLeaseTimeSecs * 1000, params.singleClientAddr); params.singleClientAddr); maybeNotifyStatus(pair.second, STATUS_SUCCESS); return HANDLED; cb = pair.second; break; case CMD_START_DHCP_SERVER: case CMD_START_DHCP_SERVER: final Pair<INetworkStackStatusCallback, IDhcpEventCallbacks> obj = mLog.e("ALERT: START received in StartedState. Please fix caller."); (Pair<INetworkStackStatusCallback, IDhcpEventCallbacks>) msg.obj; return HANDLED; cb = obj.first; if (obj.second != null) { mLeaseRepo.addLeaseCallbacks(obj.second); } mPacketListener = mDeps.makePacketListener(); mPacketListener.start(); break; case CMD_STOP_DHCP_SERVER: case CMD_STOP_DHCP_SERVER: if (mPacketListener != null) { mStoppedState.mOnStopCallback = (INetworkStackStatusCallback) msg.obj; mPacketListener.stop(); transitionTo(mStoppedState); mPacketListener = null; return HANDLED; } mHandlerThread.quitSafely(); cb = (INetworkStackStatusCallback) msg.obj; break; default: default: return; return NOT_HANDLED; } } if (cb != null) { try { cb.onStatusAvailable(STATUS_SUCCESS); } catch (RemoteException e) { mLog.e("Could not send status back to caller", e); } } @Override public void exit() { mPacketListener.stop(); mLog.logf("DHCP Packet Listener stopped"); } } class RunningState extends State { @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. return HANDLED; default: // Fall through to StartedState. return NOT_HANDLED; } } } } } } Loading Loading @@ -651,8 +741,8 @@ public class DhcpServer extends IDhcpServer.Stub { } } private class PacketListener extends DhcpPacketListener { private class PacketListener extends DhcpPacketListener { PacketListener() { PacketListener(Handler handler) { super(mHandler); super(handler); } } @Override @Override Loading Loading @@ -686,21 +776,10 @@ public class DhcpServer extends IDhcpServer.Stub { return mSocket; return mSocket; } catch (IOException | ErrnoException e) { } catch (IOException | ErrnoException e) { mLog.e("Error creating UDP socket", e); mLog.e("Error creating UDP socket", e); DhcpServer.this.stop(null); return null; return null; } finally { } finally { TrafficStats.setThreadStatsTag(oldTag); TrafficStats.setThreadStatsTag(oldTag); } } } } } } @Override public int getInterfaceVersion() { return this.VERSION; } @Override public String getInterfaceHash() { return this.HASH; } } } src/com/android/server/NetworkStackService.java +1 −1 Original line number Original line Diff line number Diff line Loading @@ -356,7 +356,7 @@ public class NetworkStackService extends Service { cb.onDhcpServerCreated(STATUS_UNKNOWN_ERROR, null); cb.onDhcpServerCreated(STATUS_UNKNOWN_ERROR, null); return; return; } } cb.onDhcpServerCreated(STATUS_SUCCESS, server); cb.onDhcpServerCreated(STATUS_SUCCESS, server.makeConnector()); } } @Override @Override Loading tests/unit/src/android/net/dhcp/DhcpServerTest.java +10 −19 Original line number Original line Diff line number Diff line Loading @@ -36,7 +36,6 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.never; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.Mockito.when; Loading @@ -52,13 +51,12 @@ import android.net.dhcp.DhcpLeaseRepository.OutOfAddressesException; import android.net.dhcp.DhcpServer.Clock; import android.net.dhcp.DhcpServer.Clock; import android.net.dhcp.DhcpServer.Dependencies; import android.net.dhcp.DhcpServer.Dependencies; import android.net.util.SharedLog; import android.net.util.SharedLog; import android.os.HandlerThread; import android.testing.AndroidTestingRunner; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest; import com.android.testutils.HandlerUtilsKt; import org.junit.After; import org.junit.After; import org.junit.Before; import org.junit.Before; import org.junit.Test; import org.junit.Test; Loading @@ -76,7 +74,6 @@ import java.util.Set; @RunWith(AndroidTestingRunner.class) @RunWith(AndroidTestingRunner.class) @SmallTest @SmallTest @RunWithLooper public class DhcpServerTest { public class DhcpServerTest { private static final String TEST_IFACE = "testiface"; private static final String TEST_IFACE = "testiface"; Loading Loading @@ -107,6 +104,7 @@ public class DhcpServerTest { private static final DhcpLease TEST_LEASE_WITH_HOSTNAME = new DhcpLease(null, TEST_CLIENT_MAC, private static final DhcpLease TEST_LEASE_WITH_HOSTNAME = new DhcpLease(null, TEST_CLIENT_MAC, TEST_CLIENT_ADDR, TEST_PREFIX_LENGTH, TEST_LEASE_EXPTIME_SECS * 1000L + TEST_CLOCK_TIME, TEST_CLIENT_ADDR, TEST_PREFIX_LENGTH, TEST_LEASE_EXPTIME_SECS * 1000L + TEST_CLOCK_TIME, TEST_HOSTNAME); TEST_HOSTNAME); private static final int TEST_TIMEOUT_MS = 10000; @NonNull @Mock @NonNull @Mock private Context mContext; private Context mContext; Loading @@ -126,10 +124,6 @@ public class DhcpServerTest { @NonNull @Captor @NonNull @Captor private ArgumentCaptor<Inet4Address> mResponseDstAddrCaptor; private ArgumentCaptor<Inet4Address> mResponseDstAddrCaptor; @NonNull private HandlerThread mHandlerThread; @NonNull private TestableLooper mLooper; @NonNull @NonNull private DhcpServer mServer; private DhcpServer mServer; Loading Loading @@ -167,7 +161,7 @@ public class DhcpServerTest { private void startServer() throws Exception { private void startServer() throws Exception { mServer.start(mAssertSuccessCallback); mServer.start(mAssertSuccessCallback); mLooper.processAllMessages(); HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS); } } @Before @Before Loading @@ -176,16 +170,14 @@ public class DhcpServerTest { when(mDeps.makeLeaseRepository(any(), any(), any())).thenReturn(mRepository); when(mDeps.makeLeaseRepository(any(), any(), any())).thenReturn(mRepository); when(mDeps.makeClock()).thenReturn(mClock); when(mDeps.makeClock()).thenReturn(mClock); when(mDeps.makePacketListener()).thenReturn(mPacketListener); when(mDeps.makePacketListener(any())).thenReturn(mPacketListener); when(mDeps.isFeatureEnabled(eq(mContext), eq(DHCP_RAPID_COMMIT_VERSION))).thenReturn(true); when(mDeps.isFeatureEnabled(eq(mContext), eq(DHCP_RAPID_COMMIT_VERSION))).thenReturn(true); doNothing().when(mDeps) doNothing().when(mDeps) .sendPacket(any(), mSentPacketCaptor.capture(), mResponseDstAddrCaptor.capture()); .sendPacket(any(), mSentPacketCaptor.capture(), mResponseDstAddrCaptor.capture()); when(mClock.elapsedRealtime()).thenReturn(TEST_CLOCK_TIME); when(mClock.elapsedRealtime()).thenReturn(TEST_CLOCK_TIME); when(mPacketListener.start()).thenReturn(true); mLooper = TestableLooper.get(this); mServer = new DhcpServer(mContext, TEST_IFACE, makeServingParams(), mHandlerThread = spy(new HandlerThread("TestDhcpServer")); when(mHandlerThread.getLooper()).thenReturn(mLooper.getLooper()); mServer = new DhcpServer(mContext, mHandlerThread, TEST_IFACE, makeServingParams(), new SharedLog(DhcpServerTest.class.getSimpleName()), mDeps); new SharedLog(DhcpServerTest.class.getSimpleName()), mDeps); } } Loading @@ -193,9 +185,8 @@ public class DhcpServerTest { public void tearDown() throws Exception { public void tearDown() throws Exception { verify(mRepository, never()).addLeaseCallbacks(eq(null)); verify(mRepository, never()).addLeaseCallbacks(eq(null)); mServer.stop(mAssertSuccessCallback); mServer.stop(mAssertSuccessCallback); mLooper.processMessages(1); HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS); verify(mPacketListener, times(1)).stop(); verify(mPacketListener, times(1)).stop(); verify(mHandlerThread, times(1)).quitSafely(); } } @Test @Test Loading @@ -207,8 +198,8 @@ public class DhcpServerTest { @Test @Test public void testStartWithCallbacks() throws Exception { public void testStartWithCallbacks() throws Exception { mServer.startWithCallbacks(mAssertSuccessCallback, mEventCallbacks); mServer.start(mAssertSuccessCallback, mEventCallbacks); mLooper.processAllMessages(); HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS); verify(mRepository).addLeaseCallbacks(eq(mEventCallbacks)); verify(mRepository).addLeaseCallbacks(eq(mEventCallbacks)); } } Loading Loading
src/android/net/dhcp/DhcpServer.java +183 −104 Original line number Original line Diff line number Diff line Loading @@ -20,6 +20,9 @@ import static android.net.dhcp.DhcpPacket.DHCP_CLIENT; import static android.net.dhcp.DhcpPacket.DHCP_HOST_NAME; import static android.net.dhcp.DhcpPacket.DHCP_HOST_NAME; import static android.net.dhcp.DhcpPacket.DHCP_SERVER; import static android.net.dhcp.DhcpPacket.DHCP_SERVER; import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP; import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP; import static android.net.dhcp.IDhcpServer.STATUS_INVALID_ARGUMENT; import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR; import static android.net.shared.Inet4AddressUtils.getBroadcastAddress; import static android.net.shared.Inet4AddressUtils.getBroadcastAddress; import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address; import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address; import static android.net.util.NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION; import static android.net.util.NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION; Loading Loading @@ -48,8 +51,6 @@ import android.net.util.NetworkStackUtils; import android.net.util.SharedLog; import android.net.util.SharedLog; import android.net.util.SocketUtils; import android.net.util.SocketUtils; import android.os.Handler; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.Message; import android.os.RemoteException; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemClock; Loading @@ -63,6 +64,8 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting; import com.android.internal.util.HexDump; import com.android.internal.util.HexDump; import com.android.internal.util.State; import com.android.internal.util.StateMachine; import java.io.FileDescriptor; import java.io.FileDescriptor; import java.io.IOException; import java.io.IOException; Loading @@ -78,12 +81,12 @@ import java.util.ArrayList; * authoritative for all leases on the subnet, which means that DHCP requests for unknown leases of * authoritative for all leases on the subnet, which means that DHCP requests for unknown leases of * unknown hosts receive a reply instead of being ignored. * unknown hosts receive a reply instead of being ignored. * * * <p>The server is single-threaded (including send/receive operations): all internal operations are * <p>The server relies on StateMachine's handler (including send/receive operations): all internal * done on the provided {@link Looper}. Public methods are thread-safe and will schedule operations * operations are done in StateMachine's looper. Public methods are thread-safe and will schedule * on the looper asynchronously. * operations on that looper asynchronously. * @hide * @hide */ */ public class DhcpServer extends IDhcpServer.Stub { public class DhcpServer extends StateMachine { private static final String REPO_TAG = "Repository"; private static final String REPO_TAG = "Repository"; // Lease time to transmit to client instead of a negative time in case a lease expired before // Lease time to transmit to client instead of a negative time in case a lease expired before Loading @@ -93,12 +96,11 @@ public class DhcpServer extends IDhcpServer.Stub { private static final int CMD_START_DHCP_SERVER = 1; private static final int CMD_START_DHCP_SERVER = 1; private static final int CMD_STOP_DHCP_SERVER = 2; private static final int CMD_STOP_DHCP_SERVER = 2; private static final int CMD_UPDATE_PARAMS = 3; private static final int CMD_UPDATE_PARAMS = 3; private static final int CMD_SUSPEND_DHCP_SERVER = 4; @NonNull @NonNull private final Context mContext; private final Context mContext; @NonNull @NonNull private final HandlerThread mHandlerThread; @NonNull private final String mIfName; private final String mIfName; @NonNull @NonNull private final DhcpLeaseRepository mLeaseRepo; private final DhcpLeaseRepository mLeaseRepo; Loading @@ -108,19 +110,22 @@ public class DhcpServer extends IDhcpServer.Stub { private final Dependencies mDeps; private final Dependencies mDeps; @NonNull @NonNull private final Clock mClock; private final Clock mClock; @NonNull private DhcpServingParams mServingParams; @Nullable private volatile ServerHandler mHandler; private final boolean mDhcpRapidCommitEnabled; // Accessed only on the handler thread @Nullable @Nullable private DhcpPacketListener mPacketListener; private DhcpPacketListener mPacketListener; @Nullable @Nullable private FileDescriptor mSocket; private FileDescriptor mSocket; @NonNull @Nullable private DhcpServingParams mServingParams; private IDhcpEventCallbacks mEventCallbacks; private final boolean mDhcpRapidCommitEnabled; // States. private final StoppedState mStoppedState = new StoppedState(); private final StartedState mStartedState = new StartedState(); private final RunningState mRunningState = new RunningState(); /** /** * Clock to be used by DhcpServer to track time for lease expiration. * Clock to be used by DhcpServer to track time for lease expiration. Loading Loading @@ -164,7 +169,7 @@ public class DhcpServer extends IDhcpServer.Stub { /** /** * Create a packet listener that will send packets to be processed. * Create a packet listener that will send packets to be processed. */ */ DhcpPacketListener makePacketListener(); DhcpPacketListener makePacketListener(@NonNull Handler handler); /** /** * Create a clock that the server will use to track time. * Create a clock that the server will use to track time. Loading @@ -178,12 +183,6 @@ public class DhcpServer extends IDhcpServer.Stub { void addArpEntry(@NonNull Inet4Address ipv4Addr, @NonNull MacAddress ethAddr, void addArpEntry(@NonNull Inet4Address ipv4Addr, @NonNull MacAddress ethAddr, @NonNull String ifname, @NonNull FileDescriptor fd) throws IOException; @NonNull String ifname, @NonNull FileDescriptor fd) throws IOException; /** * Verify that the caller is allowed to call public methods on DhcpServer. * @throws SecurityException The caller is not allowed to call public methods on DhcpServer. */ void checkCaller() throws SecurityException; /** /** * Check whether or not one specific experimental feature for connectivity namespace is * Check whether or not one specific experimental feature for connectivity namespace is * enabled. * enabled. Loading @@ -210,8 +209,8 @@ public class DhcpServer extends IDhcpServer.Stub { } } @Override @Override public DhcpPacketListener makePacketListener() { public DhcpPacketListener makePacketListener(@NonNull Handler handler) { return new PacketListener(); return new PacketListener(handler); } } @Override @Override Loading @@ -225,11 +224,6 @@ public class DhcpServer extends IDhcpServer.Stub { NetworkStackUtils.addArpEntry(ipv4Addr, ethAddr, ifname, fd); NetworkStackUtils.addArpEntry(ipv4Addr, ethAddr, ifname, fd); } } @Override public void checkCaller() { enforceNetworkStackCallingPermission(); } @Override @Override public boolean isFeatureEnabled(@NonNull Context context, @NonNull String name) { public boolean isFeatureEnabled(@NonNull Context context, @NonNull String name) { return NetworkStackUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name); return NetworkStackUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name); Loading @@ -244,19 +238,18 @@ public class DhcpServer extends IDhcpServer.Stub { public DhcpServer(@NonNull Context context, @NonNull String ifName, public DhcpServer(@NonNull Context context, @NonNull String ifName, @NonNull DhcpServingParams params, @NonNull SharedLog log) { @NonNull DhcpServingParams params, @NonNull SharedLog log) { this(context, new HandlerThread(DhcpServer.class.getSimpleName() + "." + ifName), this(context, ifName, params, log, null); ifName, params, log, null); } } @VisibleForTesting @VisibleForTesting DhcpServer(@NonNull Context context, @NonNull HandlerThread handlerThread, DhcpServer(@NonNull Context context, @NonNull String ifName, @NonNull DhcpServingParams params, @NonNull String ifName, @NonNull DhcpServingParams params, @NonNull SharedLog log, @NonNull SharedLog log, @Nullable Dependencies deps) { @Nullable Dependencies deps) { super(DhcpServer.class.getSimpleName() + "." + ifName); if (deps == null) { if (deps == null) { deps = new DependenciesImpl(); deps = new DependenciesImpl(); } } mContext = context; mContext = context; mHandlerThread = handlerThread; mIfName = ifName; mIfName = ifName; mServingParams = params; mServingParams = params; mLog = log; mLog = log; Loading @@ -264,6 +257,61 @@ public class DhcpServer extends IDhcpServer.Stub { mClock = deps.makeClock(); mClock = deps.makeClock(); mLeaseRepo = deps.makeLeaseRepository(mServingParams, mLog, mClock); mLeaseRepo = deps.makeLeaseRepository(mServingParams, mLog, mClock); mDhcpRapidCommitEnabled = deps.isFeatureEnabled(context, DHCP_RAPID_COMMIT_VERSION); mDhcpRapidCommitEnabled = deps.isFeatureEnabled(context, DHCP_RAPID_COMMIT_VERSION); // CHECKSTYLE:OFF IndentationCheck addState(mStoppedState); addState(mStartedState); addState(mRunningState, mStartedState); // CHECKSTYLE:ON IndentationCheck setInitialState(mStoppedState); super.start(); } /** * Make a IDhcpServer connector to communicate with this DhcpServer. */ public IDhcpServer makeConnector() { return new DhcpServerConnector(); } private class DhcpServerConnector extends IDhcpServer.Stub { @Override public void start(@Nullable INetworkStackStatusCallback cb) { enforceNetworkStackCallingPermission(); DhcpServer.this.start(cb); } @Override public void startWithCallbacks(@Nullable INetworkStackStatusCallback statusCb, @Nullable IDhcpEventCallbacks eventCb) { enforceNetworkStackCallingPermission(); DhcpServer.this.start(statusCb, eventCb); } @Override public void updateParams(@Nullable DhcpServingParamsParcel params, @Nullable INetworkStackStatusCallback cb) { enforceNetworkStackCallingPermission(); DhcpServer.this.updateParams(params, cb); } @Override public void stop(@Nullable INetworkStackStatusCallback cb) { enforceNetworkStackCallingPermission(); DhcpServer.this.stop(cb); } @Override public int getInterfaceVersion() { return this.VERSION; } @Override public String getInterfaceHash() { return this.HASH; } } } /** /** Loading @@ -272,9 +320,8 @@ public class DhcpServer extends IDhcpServer.Stub { * <p>It is not legal to call this method more than once; in particular the server cannot be * <p>It is not legal to call this method more than once; in particular the server cannot be * restarted after being stopped. * restarted after being stopped. */ */ @Override void start(@Nullable INetworkStackStatusCallback cb) { public void start(@Nullable INetworkStackStatusCallback cb) { start(cb, null); startWithCallbacks(cb, null); } } /** /** Loading @@ -283,12 +330,8 @@ public class DhcpServer extends IDhcpServer.Stub { * <p>It is not legal to call this method more than once; in particular the server cannot be * <p>It is not legal to call this method more than once; in particular the server cannot be * restarted after being stopped. * restarted after being stopped. */ */ @Override void start(@Nullable INetworkStackStatusCallback statusCb, public void startWithCallbacks(@Nullable INetworkStackStatusCallback statusCb, @Nullable IDhcpEventCallbacks eventCb) { @Nullable IDhcpEventCallbacks eventCb) { mDeps.checkCaller(); mHandlerThread.start(); mHandler = new ServerHandler(mHandlerThread.getLooper()); sendMessage(CMD_START_DHCP_SERVER, new Pair<>(statusCb, eventCb)); sendMessage(CMD_START_DHCP_SERVER, new Pair<>(statusCb, eventCb)); } } Loading @@ -296,19 +339,15 @@ public class DhcpServer extends IDhcpServer.Stub { * Update serving parameters. All subsequently received requests will be handled with the new * Update serving parameters. All subsequently received requests will be handled with the new * parameters, and current leases that are incompatible with the new parameters are dropped. * parameters, and current leases that are incompatible with the new parameters are dropped. */ */ @Override void updateParams(@Nullable DhcpServingParamsParcel params, public void updateParams(@Nullable DhcpServingParamsParcel params, @Nullable INetworkStackStatusCallback cb) { @Nullable INetworkStackStatusCallback cb) throws RemoteException { mDeps.checkCaller(); final DhcpServingParams parsedParams; final DhcpServingParams parsedParams; try { try { // throws InvalidParameterException with null params // throws InvalidParameterException with null params parsedParams = DhcpServingParams.fromParcelableObject(params); parsedParams = DhcpServingParams.fromParcelableObject(params); } catch (DhcpServingParams.InvalidParameterException e) { } catch (DhcpServingParams.InvalidParameterException e) { mLog.e("Invalid parameters sent to DhcpServer", e); mLog.e("Invalid parameters sent to DhcpServer", e); if (cb != null) { maybeNotifyStatus(cb, STATUS_INVALID_ARGUMENT); cb.onStatusAvailable(STATUS_INVALID_ARGUMENT); } return; return; } } sendMessage(CMD_UPDATE_PARAMS, new Pair<>(parsedParams, cb)); sendMessage(CMD_UPDATE_PARAMS, new Pair<>(parsedParams, cb)); Loading @@ -320,28 +359,74 @@ public class DhcpServer extends IDhcpServer.Stub { * <p>As the server is stopped asynchronously, some packets may still be processed shortly after * <p>As the server is stopped asynchronously, some packets may still be processed shortly after * calling this method. * calling this method. */ */ @Override void stop(@Nullable INetworkStackStatusCallback cb) { public void stop(@Nullable INetworkStackStatusCallback cb) { mDeps.checkCaller(); sendMessage(CMD_STOP_DHCP_SERVER, cb); sendMessage(CMD_STOP_DHCP_SERVER, cb); } } private void sendMessage(int what, @Nullable Object obj) { private void maybeNotifyStatus(@Nullable INetworkStackStatusCallback cb, int statusCode) { if (mHandler == null) { if (cb == null) return; mLog.e("Attempting to send a command to stopped DhcpServer: " + what); try { cb.onStatusAvailable(statusCode); } catch (RemoteException e) { mLog.e("Could not send status back to caller", e); } } class StoppedState extends State { private INetworkStackStatusCallback mOnStopCallback; @Override public void enter() { maybeNotifyStatus(mOnStopCallback, STATUS_SUCCESS); mOnStopCallback = null; } @Override public boolean processMessage(Message msg) { switch (msg.what) { case CMD_START_DHCP_SERVER: final Pair<INetworkStackStatusCallback, IDhcpEventCallbacks> obj = (Pair<INetworkStackStatusCallback, IDhcpEventCallbacks>) msg.obj; mStartedState.mOnStartCallback = obj.first; mEventCallbacks = obj.second; transitionTo(mRunningState); return HANDLED; default: return NOT_HANDLED; } } } class StartedState extends State { private INetworkStackStatusCallback mOnStartCallback; @Override public void enter() { if (mPacketListener != null) { mLog.e("Starting DHCP server more than once is not supported."); maybeNotifyStatus(mOnStartCallback, STATUS_UNKNOWN_ERROR); mOnStartCallback = null; return; return; } } mHandler.sendMessage(mHandler.obtainMessage(what, obj)); mPacketListener = mDeps.makePacketListener(getHandler()); if (!mPacketListener.start()) { mLog.e("Fail to start DHCP Packet Listener, rollback to StoppedState"); deferMessage(obtainMessage(CMD_STOP_DHCP_SERVER, null)); maybeNotifyStatus(mOnStartCallback, STATUS_UNKNOWN_ERROR); mOnStartCallback = null; return; } } private class ServerHandler extends Handler { if (mEventCallbacks != null) { ServerHandler(@NonNull Looper looper) { mLeaseRepo.addLeaseCallbacks(mEventCallbacks); super(looper); } maybeNotifyStatus(mOnStartCallback, STATUS_SUCCESS); mOnStartCallback = null; } } @Override @Override public void handleMessage(@NonNull Message msg) { public boolean processMessage(Message msg) { final INetworkStackStatusCallback cb; switch (msg.what) { switch (msg.what) { case CMD_UPDATE_PARAMS: case CMD_UPDATE_PARAMS: final Pair<DhcpServingParams, INetworkStackStatusCallback> pair = final Pair<DhcpServingParams, INetworkStackStatusCallback> pair = Loading @@ -349,40 +434,45 @@ public class DhcpServer extends IDhcpServer.Stub { final DhcpServingParams params = pair.first; final DhcpServingParams params = pair.first; mServingParams = params; mServingParams = params; mLeaseRepo.updateParams( mLeaseRepo.updateParams( DhcpServingParams.makeIpPrefix(mServingParams.serverAddr), DhcpServingParams.makeIpPrefix(params.serverAddr), params.excludedAddrs, params.excludedAddrs, params.dhcpLeaseTimeSecs, params.dhcpLeaseTimeSecs * 1000, params.singleClientAddr); params.singleClientAddr); maybeNotifyStatus(pair.second, STATUS_SUCCESS); return HANDLED; cb = pair.second; break; case CMD_START_DHCP_SERVER: case CMD_START_DHCP_SERVER: final Pair<INetworkStackStatusCallback, IDhcpEventCallbacks> obj = mLog.e("ALERT: START received in StartedState. Please fix caller."); (Pair<INetworkStackStatusCallback, IDhcpEventCallbacks>) msg.obj; return HANDLED; cb = obj.first; if (obj.second != null) { mLeaseRepo.addLeaseCallbacks(obj.second); } mPacketListener = mDeps.makePacketListener(); mPacketListener.start(); break; case CMD_STOP_DHCP_SERVER: case CMD_STOP_DHCP_SERVER: if (mPacketListener != null) { mStoppedState.mOnStopCallback = (INetworkStackStatusCallback) msg.obj; mPacketListener.stop(); transitionTo(mStoppedState); mPacketListener = null; return HANDLED; } mHandlerThread.quitSafely(); cb = (INetworkStackStatusCallback) msg.obj; break; default: default: return; return NOT_HANDLED; } } if (cb != null) { try { cb.onStatusAvailable(STATUS_SUCCESS); } catch (RemoteException e) { mLog.e("Could not send status back to caller", e); } } @Override public void exit() { mPacketListener.stop(); mLog.logf("DHCP Packet Listener stopped"); } } class RunningState extends State { @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. return HANDLED; default: // Fall through to StartedState. return NOT_HANDLED; } } } } } } Loading Loading @@ -651,8 +741,8 @@ public class DhcpServer extends IDhcpServer.Stub { } } private class PacketListener extends DhcpPacketListener { private class PacketListener extends DhcpPacketListener { PacketListener() { PacketListener(Handler handler) { super(mHandler); super(handler); } } @Override @Override Loading Loading @@ -686,21 +776,10 @@ public class DhcpServer extends IDhcpServer.Stub { return mSocket; return mSocket; } catch (IOException | ErrnoException e) { } catch (IOException | ErrnoException e) { mLog.e("Error creating UDP socket", e); mLog.e("Error creating UDP socket", e); DhcpServer.this.stop(null); return null; return null; } finally { } finally { TrafficStats.setThreadStatsTag(oldTag); TrafficStats.setThreadStatsTag(oldTag); } } } } } } @Override public int getInterfaceVersion() { return this.VERSION; } @Override public String getInterfaceHash() { return this.HASH; } } }
src/com/android/server/NetworkStackService.java +1 −1 Original line number Original line Diff line number Diff line Loading @@ -356,7 +356,7 @@ public class NetworkStackService extends Service { cb.onDhcpServerCreated(STATUS_UNKNOWN_ERROR, null); cb.onDhcpServerCreated(STATUS_UNKNOWN_ERROR, null); return; return; } } cb.onDhcpServerCreated(STATUS_SUCCESS, server); cb.onDhcpServerCreated(STATUS_SUCCESS, server.makeConnector()); } } @Override @Override Loading
tests/unit/src/android/net/dhcp/DhcpServerTest.java +10 −19 Original line number Original line Diff line number Diff line Loading @@ -36,7 +36,6 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.never; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.Mockito.when; Loading @@ -52,13 +51,12 @@ import android.net.dhcp.DhcpLeaseRepository.OutOfAddressesException; import android.net.dhcp.DhcpServer.Clock; import android.net.dhcp.DhcpServer.Clock; import android.net.dhcp.DhcpServer.Dependencies; import android.net.dhcp.DhcpServer.Dependencies; import android.net.util.SharedLog; import android.net.util.SharedLog; import android.os.HandlerThread; import android.testing.AndroidTestingRunner; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest; import com.android.testutils.HandlerUtilsKt; import org.junit.After; import org.junit.After; import org.junit.Before; import org.junit.Before; import org.junit.Test; import org.junit.Test; Loading @@ -76,7 +74,6 @@ import java.util.Set; @RunWith(AndroidTestingRunner.class) @RunWith(AndroidTestingRunner.class) @SmallTest @SmallTest @RunWithLooper public class DhcpServerTest { public class DhcpServerTest { private static final String TEST_IFACE = "testiface"; private static final String TEST_IFACE = "testiface"; Loading Loading @@ -107,6 +104,7 @@ public class DhcpServerTest { private static final DhcpLease TEST_LEASE_WITH_HOSTNAME = new DhcpLease(null, TEST_CLIENT_MAC, private static final DhcpLease TEST_LEASE_WITH_HOSTNAME = new DhcpLease(null, TEST_CLIENT_MAC, TEST_CLIENT_ADDR, TEST_PREFIX_LENGTH, TEST_LEASE_EXPTIME_SECS * 1000L + TEST_CLOCK_TIME, TEST_CLIENT_ADDR, TEST_PREFIX_LENGTH, TEST_LEASE_EXPTIME_SECS * 1000L + TEST_CLOCK_TIME, TEST_HOSTNAME); TEST_HOSTNAME); private static final int TEST_TIMEOUT_MS = 10000; @NonNull @Mock @NonNull @Mock private Context mContext; private Context mContext; Loading @@ -126,10 +124,6 @@ public class DhcpServerTest { @NonNull @Captor @NonNull @Captor private ArgumentCaptor<Inet4Address> mResponseDstAddrCaptor; private ArgumentCaptor<Inet4Address> mResponseDstAddrCaptor; @NonNull private HandlerThread mHandlerThread; @NonNull private TestableLooper mLooper; @NonNull @NonNull private DhcpServer mServer; private DhcpServer mServer; Loading Loading @@ -167,7 +161,7 @@ public class DhcpServerTest { private void startServer() throws Exception { private void startServer() throws Exception { mServer.start(mAssertSuccessCallback); mServer.start(mAssertSuccessCallback); mLooper.processAllMessages(); HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS); } } @Before @Before Loading @@ -176,16 +170,14 @@ public class DhcpServerTest { when(mDeps.makeLeaseRepository(any(), any(), any())).thenReturn(mRepository); when(mDeps.makeLeaseRepository(any(), any(), any())).thenReturn(mRepository); when(mDeps.makeClock()).thenReturn(mClock); when(mDeps.makeClock()).thenReturn(mClock); when(mDeps.makePacketListener()).thenReturn(mPacketListener); when(mDeps.makePacketListener(any())).thenReturn(mPacketListener); when(mDeps.isFeatureEnabled(eq(mContext), eq(DHCP_RAPID_COMMIT_VERSION))).thenReturn(true); when(mDeps.isFeatureEnabled(eq(mContext), eq(DHCP_RAPID_COMMIT_VERSION))).thenReturn(true); doNothing().when(mDeps) doNothing().when(mDeps) .sendPacket(any(), mSentPacketCaptor.capture(), mResponseDstAddrCaptor.capture()); .sendPacket(any(), mSentPacketCaptor.capture(), mResponseDstAddrCaptor.capture()); when(mClock.elapsedRealtime()).thenReturn(TEST_CLOCK_TIME); when(mClock.elapsedRealtime()).thenReturn(TEST_CLOCK_TIME); when(mPacketListener.start()).thenReturn(true); mLooper = TestableLooper.get(this); mServer = new DhcpServer(mContext, TEST_IFACE, makeServingParams(), mHandlerThread = spy(new HandlerThread("TestDhcpServer")); when(mHandlerThread.getLooper()).thenReturn(mLooper.getLooper()); mServer = new DhcpServer(mContext, mHandlerThread, TEST_IFACE, makeServingParams(), new SharedLog(DhcpServerTest.class.getSimpleName()), mDeps); new SharedLog(DhcpServerTest.class.getSimpleName()), mDeps); } } Loading @@ -193,9 +185,8 @@ public class DhcpServerTest { public void tearDown() throws Exception { public void tearDown() throws Exception { verify(mRepository, never()).addLeaseCallbacks(eq(null)); verify(mRepository, never()).addLeaseCallbacks(eq(null)); mServer.stop(mAssertSuccessCallback); mServer.stop(mAssertSuccessCallback); mLooper.processMessages(1); HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS); verify(mPacketListener, times(1)).stop(); verify(mPacketListener, times(1)).stop(); verify(mHandlerThread, times(1)).quitSafely(); } } @Test @Test Loading @@ -207,8 +198,8 @@ public class DhcpServerTest { @Test @Test public void testStartWithCallbacks() throws Exception { public void testStartWithCallbacks() throws Exception { mServer.startWithCallbacks(mAssertSuccessCallback, mEventCallbacks); mServer.start(mAssertSuccessCallback, mEventCallbacks); mLooper.processAllMessages(); HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS); verify(mRepository).addLeaseCallbacks(eq(mEventCallbacks)); verify(mRepository).addLeaseCallbacks(eq(mEventCallbacks)); } } Loading