The current code only uses InterfaceParams#name, and InterfaceParams is defined in services/net which DhcpServer cannot depend on once moved to a separate app. Test: atest FrameworksNetTests Bug: b/112869080 Change-Id: I94c7dce33200c111666a9dddde82ac2e66a6794f
322 lines
13 KiB
Java
322 lines
13 KiB
Java
/*
|
|
* Copyright (C) 2018 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package android.net.dhcp;
|
|
|
|
import static android.net.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 junit.framework.Assert.assertEquals;
|
|
import static junit.framework.Assert.assertFalse;
|
|
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;
|
|
import static org.mockito.Mockito.never;
|
|
import static org.mockito.Mockito.times;
|
|
import static org.mockito.Mockito.verify;
|
|
import static org.mockito.Mockito.when;
|
|
|
|
import static java.net.InetAddress.parseNumericAddress;
|
|
|
|
import android.annotation.NonNull;
|
|
import android.annotation.Nullable;
|
|
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.util.SharedLog;
|
|
import android.os.test.TestLooper;
|
|
import android.support.test.filters.SmallTest;
|
|
import android.support.test.runner.AndroidJUnit4;
|
|
|
|
import org.junit.After;
|
|
import org.junit.Before;
|
|
import org.junit.Test;
|
|
import org.junit.runner.RunWith;
|
|
import org.mockito.ArgumentCaptor;
|
|
import org.mockito.Captor;
|
|
import org.mockito.Mock;
|
|
import org.mockito.MockitoAnnotations;
|
|
|
|
import java.net.Inet4Address;
|
|
import java.nio.ByteBuffer;
|
|
import java.util.Arrays;
|
|
import java.util.HashSet;
|
|
import java.util.Set;
|
|
|
|
@RunWith(AndroidJUnit4.class)
|
|
@SmallTest
|
|
public class DhcpServerTest {
|
|
private static final String PROP_DEXMAKER_SHARE_CLASSLOADER = "dexmaker.share_classloader";
|
|
private static final String TEST_IFACE = "testiface";
|
|
|
|
private static final Inet4Address TEST_SERVER_ADDR = parseAddr("192.168.0.2");
|
|
private static final LinkAddress TEST_SERVER_LINKADDR = new LinkAddress(TEST_SERVER_ADDR, 20);
|
|
private static final Set<Inet4Address> TEST_DEFAULT_ROUTERS = new HashSet<>(
|
|
Arrays.asList(parseAddr("192.168.0.123"), parseAddr("192.168.0.124")));
|
|
private static final Set<Inet4Address> TEST_DNS_SERVERS = new HashSet<>(
|
|
Arrays.asList(parseAddr("192.168.0.126"), parseAddr("192.168.0.127")));
|
|
private static final Set<Inet4Address> TEST_EXCLUDED_ADDRS = new HashSet<>(
|
|
Arrays.asList(parseAddr("192.168.0.200"), parseAddr("192.168.0.201")));
|
|
private static final long TEST_LEASE_TIME_SECS = 3600L;
|
|
private static final int TEST_MTU = 1500;
|
|
private static final String TEST_HOSTNAME = "testhostname";
|
|
|
|
private static final int TEST_TRANSACTION_ID = 123;
|
|
private static final byte[] TEST_CLIENT_MAC_BYTES = new byte [] { 1, 2, 3, 4, 5, 6 };
|
|
private static final MacAddress TEST_CLIENT_MAC = MacAddress.fromBytes(TEST_CLIENT_MAC_BYTES);
|
|
private static final Inet4Address TEST_CLIENT_ADDR = parseAddr("192.168.0.42");
|
|
|
|
private static final long TEST_CLOCK_TIME = 1234L;
|
|
private static final int TEST_LEASE_EXPTIME_SECS = 3600;
|
|
private static final DhcpLease TEST_LEASE = new DhcpLease(null, TEST_CLIENT_MAC,
|
|
TEST_CLIENT_ADDR, TEST_LEASE_EXPTIME_SECS * 1000L + TEST_CLOCK_TIME,
|
|
null /* hostname */);
|
|
private static final DhcpLease TEST_LEASE_WITH_HOSTNAME = new DhcpLease(null, TEST_CLIENT_MAC,
|
|
TEST_CLIENT_ADDR, TEST_LEASE_EXPTIME_SECS * 1000L + TEST_CLOCK_TIME, TEST_HOSTNAME);
|
|
|
|
@NonNull @Mock
|
|
private Dependencies mDeps;
|
|
@NonNull @Mock
|
|
private DhcpLeaseRepository mRepository;
|
|
@NonNull @Mock
|
|
private Clock mClock;
|
|
@NonNull @Mock
|
|
private DhcpPacketListener mPacketListener;
|
|
|
|
@NonNull @Captor
|
|
private ArgumentCaptor<ByteBuffer> mSentPacketCaptor;
|
|
@NonNull @Captor
|
|
private ArgumentCaptor<Inet4Address> mResponseDstAddrCaptor;
|
|
|
|
@NonNull
|
|
private TestLooper mLooper;
|
|
@NonNull
|
|
private DhcpServer mServer;
|
|
|
|
@Nullable
|
|
private String mPrevShareClassloaderProp;
|
|
|
|
@Before
|
|
public void setUp() throws Exception {
|
|
// Allow mocking package-private classes
|
|
mPrevShareClassloaderProp = System.getProperty(PROP_DEXMAKER_SHARE_CLASSLOADER);
|
|
System.setProperty(PROP_DEXMAKER_SHARE_CLASSLOADER, "true");
|
|
MockitoAnnotations.initMocks(this);
|
|
|
|
when(mDeps.makeLeaseRepository(any(), any(), any())).thenReturn(mRepository);
|
|
when(mDeps.makeClock()).thenReturn(mClock);
|
|
when(mDeps.makePacketListener()).thenReturn(mPacketListener);
|
|
doNothing().when(mDeps)
|
|
.sendPacket(any(), mSentPacketCaptor.capture(), mResponseDstAddrCaptor.capture());
|
|
when(mClock.elapsedRealtime()).thenReturn(TEST_CLOCK_TIME);
|
|
|
|
final DhcpServingParams servingParams = new DhcpServingParams.Builder()
|
|
.setDefaultRouters(TEST_DEFAULT_ROUTERS)
|
|
.setDhcpLeaseTimeSecs(TEST_LEASE_TIME_SECS)
|
|
.setDnsServers(TEST_DNS_SERVERS)
|
|
.setServerAddr(TEST_SERVER_LINKADDR)
|
|
.setLinkMtu(TEST_MTU)
|
|
.setExcludedAddrs(TEST_EXCLUDED_ADDRS)
|
|
.build();
|
|
|
|
mLooper = new TestLooper();
|
|
mServer = new DhcpServer(mLooper.getLooper(), TEST_IFACE, servingParams,
|
|
new SharedLog(DhcpServerTest.class.getSimpleName()), mDeps);
|
|
|
|
mServer.start();
|
|
mLooper.dispatchAll();
|
|
}
|
|
|
|
@After
|
|
public void tearDown() {
|
|
// Calling stop() several times is not an issue
|
|
mServer.stop();
|
|
System.setProperty(PROP_DEXMAKER_SHARE_CLASSLOADER,
|
|
(mPrevShareClassloaderProp == null ? "" : mPrevShareClassloaderProp));
|
|
}
|
|
|
|
@Test
|
|
public void testStart() throws Exception {
|
|
verify(mPacketListener, times(1)).start();
|
|
}
|
|
|
|
@Test
|
|
public void testStop() throws Exception {
|
|
mServer.stop();
|
|
mLooper.dispatchAll();
|
|
verify(mPacketListener, times(1)).stop();
|
|
}
|
|
|
|
@Test
|
|
public void testDiscover() throws Exception {
|
|
// TODO: refactor packet construction to eliminate unnecessary/confusing/duplicate fields
|
|
when(mRepository.getOffer(isNull() /* clientId */, eq(TEST_CLIENT_MAC),
|
|
eq(INADDR_ANY) /* relayAddr */, isNull() /* reqAddr */, isNull() /* hostname */))
|
|
.thenReturn(TEST_LEASE);
|
|
|
|
final DhcpDiscoverPacket discover = new DhcpDiscoverPacket(TEST_TRANSACTION_ID,
|
|
(short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES,
|
|
false /* broadcast */, INADDR_ANY /* srcIp */);
|
|
mServer.processPacket(discover, DHCP_CLIENT);
|
|
|
|
assertResponseSentTo(TEST_CLIENT_ADDR);
|
|
final DhcpOfferPacket packet = assertOffer(getPacket());
|
|
assertMatchesTestLease(packet);
|
|
}
|
|
|
|
@Test
|
|
public void testDiscover_OutOfAddresses() throws Exception {
|
|
when(mRepository.getOffer(isNull() /* clientId */, eq(TEST_CLIENT_MAC),
|
|
eq(INADDR_ANY) /* relayAddr */, isNull() /* reqAddr */, isNull() /* hostname */))
|
|
.thenThrow(new OutOfAddressesException("Test exception"));
|
|
|
|
final DhcpDiscoverPacket discover = new DhcpDiscoverPacket(TEST_TRANSACTION_ID,
|
|
(short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES,
|
|
false /* broadcast */, INADDR_ANY /* srcIp */);
|
|
mServer.processPacket(discover, DHCP_CLIENT);
|
|
|
|
assertResponseSentTo(INADDR_BROADCAST);
|
|
final DhcpNakPacket packet = assertNak(getPacket());
|
|
assertMatchesClient(packet);
|
|
}
|
|
|
|
private DhcpRequestPacket makeRequestSelectingPacket() {
|
|
final DhcpRequestPacket request = new DhcpRequestPacket(TEST_TRANSACTION_ID,
|
|
(short) 0 /* secs */, INADDR_ANY /* clientIp */, INADDR_ANY /* relayIp */,
|
|
TEST_CLIENT_MAC_BYTES, false /* broadcast */);
|
|
request.mServerIdentifier = TEST_SERVER_ADDR;
|
|
request.mRequestedIp = TEST_CLIENT_ADDR;
|
|
return request;
|
|
}
|
|
|
|
@Test
|
|
public void testRequest_Selecting_Ack() throws Exception {
|
|
when(mRepository.requestLease(isNull() /* clientId */, eq(TEST_CLIENT_MAC),
|
|
eq(INADDR_ANY) /* clientAddr */, eq(INADDR_ANY) /* relayAddr */,
|
|
eq(TEST_CLIENT_ADDR) /* reqAddr */, eq(true) /* sidSet */, eq(TEST_HOSTNAME)))
|
|
.thenReturn(TEST_LEASE_WITH_HOSTNAME);
|
|
|
|
final DhcpRequestPacket request = makeRequestSelectingPacket();
|
|
request.mHostName = TEST_HOSTNAME;
|
|
request.mRequestedParams = new byte[] { DHCP_HOST_NAME };
|
|
mServer.processPacket(request, DHCP_CLIENT);
|
|
|
|
assertResponseSentTo(TEST_CLIENT_ADDR);
|
|
final DhcpAckPacket packet = assertAck(getPacket());
|
|
assertMatchesTestLease(packet, TEST_HOSTNAME);
|
|
}
|
|
|
|
@Test
|
|
public void testRequest_Selecting_Nak() throws Exception {
|
|
when(mRepository.requestLease(isNull(), eq(TEST_CLIENT_MAC),
|
|
eq(INADDR_ANY) /* clientAddr */, eq(INADDR_ANY) /* relayAddr */,
|
|
eq(TEST_CLIENT_ADDR) /* reqAddr */, eq(true) /* sidSet */, isNull() /* hostname */))
|
|
.thenThrow(new InvalidAddressException("Test error"));
|
|
|
|
final DhcpRequestPacket request = makeRequestSelectingPacket();
|
|
mServer.processPacket(request, DHCP_CLIENT);
|
|
|
|
assertResponseSentTo(INADDR_BROADCAST);
|
|
final DhcpNakPacket packet = assertNak(getPacket());
|
|
assertMatchesClient(packet);
|
|
}
|
|
|
|
@Test
|
|
public void testRequest_Selecting_WrongClientPort() throws Exception {
|
|
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 {
|
|
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);
|
|
|
|
verify(mRepository, times(1))
|
|
.releaseLease(isNull(), eq(TEST_CLIENT_MAC), eq(TEST_CLIENT_ADDR));
|
|
}
|
|
|
|
/* 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) {
|
|
assertMatchesClient(packet);
|
|
assertFalse(packet.hasExplicitClientId());
|
|
assertEquals(TEST_SERVER_ADDR, packet.mServerIdentifier);
|
|
assertEquals(TEST_CLIENT_ADDR, packet.mYourIp);
|
|
assertNotNull(packet.mLeaseTime);
|
|
assertEquals(TEST_LEASE_EXPTIME_SECS, (int) packet.mLeaseTime);
|
|
assertEquals(hostname, packet.mHostName);
|
|
}
|
|
|
|
private void assertMatchesTestLease(@NonNull DhcpPacket packet) {
|
|
assertMatchesTestLease(packet, null);
|
|
}
|
|
|
|
private void assertMatchesClient(@NonNull DhcpPacket packet) {
|
|
assertEquals(TEST_TRANSACTION_ID, packet.mTransId);
|
|
assertEquals(TEST_CLIENT_MAC, MacAddress.fromBytes(packet.mClientMac));
|
|
}
|
|
|
|
private void assertResponseSentTo(@NonNull Inet4Address addr) {
|
|
assertEquals(addr, mResponseDstAddrCaptor.getValue());
|
|
}
|
|
|
|
private static DhcpNakPacket assertNak(@Nullable DhcpPacket packet) {
|
|
assertTrue(packet instanceof DhcpNakPacket);
|
|
return (DhcpNakPacket) packet;
|
|
}
|
|
|
|
private static DhcpAckPacket assertAck(@Nullable DhcpPacket packet) {
|
|
assertTrue(packet instanceof DhcpAckPacket);
|
|
return (DhcpAckPacket) packet;
|
|
}
|
|
|
|
private static DhcpOfferPacket assertOffer(@Nullable DhcpPacket packet) {
|
|
assertTrue(packet instanceof DhcpOfferPacket);
|
|
return (DhcpOfferPacket) packet;
|
|
}
|
|
|
|
private DhcpPacket getPacket() throws Exception {
|
|
verify(mDeps, times(1)).sendPacket(any(), any(), any());
|
|
return DhcpPacket.decodeFullPacket(mSentPacketCaptor.getValue(), ENCAP_BOOTP);
|
|
}
|
|
|
|
private static Inet4Address parseAddr(@Nullable String inet4Addr) {
|
|
return (Inet4Address) parseNumericAddress(inet4Addr);
|
|
}
|
|
}
|