blob: d4c1e2e16731836803d29e61e4fb36d2070e5ef0 [file] [log] [blame]
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +09001/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.net.dhcp;
18
Remi NGUYEN VAN0e3d09232018-12-04 12:13:09 +090019import static android.net.InetAddresses.parseNumericAddress;
Remi NGUYEN VANb0762eb2018-08-28 11:06:54 +090020import static android.net.dhcp.DhcpPacket.DHCP_CLIENT;
Remi NGUYEN VANf90a92b2018-09-21 12:57:53 +090021import static android.net.dhcp.DhcpPacket.DHCP_HOST_NAME;
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +090022import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP;
23import static android.net.dhcp.DhcpPacket.INADDR_ANY;
24import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST;
Remi NGUYEN VAN0e3d09232018-12-04 12:13:09 +090025import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +090026
27import static junit.framework.Assert.assertEquals;
28import static junit.framework.Assert.assertFalse;
29import static junit.framework.Assert.assertNotNull;
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +090030import static junit.framework.Assert.assertTrue;
31
32import static org.mockito.ArgumentMatchers.any;
Remi NGUYEN VANb0762eb2018-08-28 11:06:54 +090033import static org.mockito.ArgumentMatchers.anyBoolean;
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +090034import static org.mockito.ArgumentMatchers.eq;
35import static org.mockito.ArgumentMatchers.isNull;
36import static org.mockito.Mockito.doNothing;
Remi NGUYEN VANb0762eb2018-08-28 11:06:54 +090037import static org.mockito.Mockito.never;
Remi NGUYEN VAN0e3d09232018-12-04 12:13:09 +090038import static org.mockito.Mockito.spy;
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +090039import static org.mockito.Mockito.times;
40import static org.mockito.Mockito.verify;
41import static org.mockito.Mockito.when;
42
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +090043import android.annotation.NonNull;
44import android.annotation.Nullable;
Remi NGUYEN VAN0e3d09232018-12-04 12:13:09 +090045import android.net.INetworkStackStatusCallback;
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +090046import android.net.LinkAddress;
47import android.net.MacAddress;
48import android.net.dhcp.DhcpLeaseRepository.InvalidAddressException;
49import android.net.dhcp.DhcpLeaseRepository.OutOfAddressesException;
50import android.net.dhcp.DhcpServer.Clock;
51import android.net.dhcp.DhcpServer.Dependencies;
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +090052import android.net.util.SharedLog;
Remi NGUYEN VAN0e3d09232018-12-04 12:13:09 +090053import android.os.HandlerThread;
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +090054import android.support.test.filters.SmallTest;
Remi NGUYEN VAN0e3d09232018-12-04 12:13:09 +090055import android.testing.AndroidTestingRunner;
56import android.testing.TestableLooper;
57import android.testing.TestableLooper.RunWithLooper;
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +090058
59import org.junit.After;
60import org.junit.Before;
61import org.junit.Test;
62import org.junit.runner.RunWith;
63import org.mockito.ArgumentCaptor;
64import org.mockito.Captor;
65import org.mockito.Mock;
66import org.mockito.MockitoAnnotations;
67
68import java.net.Inet4Address;
69import java.nio.ByteBuffer;
70import java.util.Arrays;
71import java.util.HashSet;
72import java.util.Set;
73
Remi NGUYEN VAN0e3d09232018-12-04 12:13:09 +090074@RunWith(AndroidTestingRunner.class)
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +090075@SmallTest
Remi NGUYEN VAN0e3d09232018-12-04 12:13:09 +090076@RunWithLooper
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +090077public class DhcpServerTest {
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +090078 private static final String TEST_IFACE = "testiface";
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +090079
80 private static final Inet4Address TEST_SERVER_ADDR = parseAddr("192.168.0.2");
81 private static final LinkAddress TEST_SERVER_LINKADDR = new LinkAddress(TEST_SERVER_ADDR, 20);
82 private static final Set<Inet4Address> TEST_DEFAULT_ROUTERS = new HashSet<>(
83 Arrays.asList(parseAddr("192.168.0.123"), parseAddr("192.168.0.124")));
84 private static final Set<Inet4Address> TEST_DNS_SERVERS = new HashSet<>(
85 Arrays.asList(parseAddr("192.168.0.126"), parseAddr("192.168.0.127")));
86 private static final Set<Inet4Address> TEST_EXCLUDED_ADDRS = new HashSet<>(
87 Arrays.asList(parseAddr("192.168.0.200"), parseAddr("192.168.0.201")));
88 private static final long TEST_LEASE_TIME_SECS = 3600L;
89 private static final int TEST_MTU = 1500;
Remi NGUYEN VANf90a92b2018-09-21 12:57:53 +090090 private static final String TEST_HOSTNAME = "testhostname";
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +090091
92 private static final int TEST_TRANSACTION_ID = 123;
93 private static final byte[] TEST_CLIENT_MAC_BYTES = new byte [] { 1, 2, 3, 4, 5, 6 };
94 private static final MacAddress TEST_CLIENT_MAC = MacAddress.fromBytes(TEST_CLIENT_MAC_BYTES);
95 private static final Inet4Address TEST_CLIENT_ADDR = parseAddr("192.168.0.42");
96
97 private static final long TEST_CLOCK_TIME = 1234L;
98 private static final int TEST_LEASE_EXPTIME_SECS = 3600;
99 private static final DhcpLease TEST_LEASE = new DhcpLease(null, TEST_CLIENT_MAC,
Remi NGUYEN VANf90a92b2018-09-21 12:57:53 +0900100 TEST_CLIENT_ADDR, TEST_LEASE_EXPTIME_SECS * 1000L + TEST_CLOCK_TIME,
101 null /* hostname */);
102 private static final DhcpLease TEST_LEASE_WITH_HOSTNAME = new DhcpLease(null, TEST_CLIENT_MAC,
103 TEST_CLIENT_ADDR, TEST_LEASE_EXPTIME_SECS * 1000L + TEST_CLOCK_TIME, TEST_HOSTNAME);
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900104
105 @NonNull @Mock
106 private Dependencies mDeps;
107 @NonNull @Mock
108 private DhcpLeaseRepository mRepository;
109 @NonNull @Mock
110 private Clock mClock;
111 @NonNull @Mock
112 private DhcpPacketListener mPacketListener;
113
114 @NonNull @Captor
115 private ArgumentCaptor<ByteBuffer> mSentPacketCaptor;
116 @NonNull @Captor
117 private ArgumentCaptor<Inet4Address> mResponseDstAddrCaptor;
118
119 @NonNull
Remi NGUYEN VAN0e3d09232018-12-04 12:13:09 +0900120 private HandlerThread mHandlerThread;
121 @NonNull
122 private TestableLooper mLooper;
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900123 @NonNull
124 private DhcpServer mServer;
125
126 @Nullable
127 private String mPrevShareClassloaderProp;
128
Remi NGUYEN VAN0e3d09232018-12-04 12:13:09 +0900129 private final INetworkStackStatusCallback mAssertSuccessCallback =
130 new INetworkStackStatusCallback.Stub() {
131 @Override
132 public void onStatusAvailable(int statusCode) {
133 assertEquals(STATUS_SUCCESS, statusCode);
134 }
135 };
136
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900137 @Before
138 public void setUp() throws Exception {
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900139 MockitoAnnotations.initMocks(this);
140
141 when(mDeps.makeLeaseRepository(any(), any(), any())).thenReturn(mRepository);
142 when(mDeps.makeClock()).thenReturn(mClock);
143 when(mDeps.makePacketListener()).thenReturn(mPacketListener);
144 doNothing().when(mDeps)
145 .sendPacket(any(), mSentPacketCaptor.capture(), mResponseDstAddrCaptor.capture());
146 when(mClock.elapsedRealtime()).thenReturn(TEST_CLOCK_TIME);
147
148 final DhcpServingParams servingParams = new DhcpServingParams.Builder()
149 .setDefaultRouters(TEST_DEFAULT_ROUTERS)
150 .setDhcpLeaseTimeSecs(TEST_LEASE_TIME_SECS)
151 .setDnsServers(TEST_DNS_SERVERS)
152 .setServerAddr(TEST_SERVER_LINKADDR)
153 .setLinkMtu(TEST_MTU)
154 .setExcludedAddrs(TEST_EXCLUDED_ADDRS)
155 .build();
156
Remi NGUYEN VAN0e3d09232018-12-04 12:13:09 +0900157 mLooper = TestableLooper.get(this);
158 mHandlerThread = spy(new HandlerThread("TestDhcpServer"));
159 when(mHandlerThread.getLooper()).thenReturn(mLooper.getLooper());
160 mServer = new DhcpServer(mHandlerThread, TEST_IFACE, servingParams,
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900161 new SharedLog(DhcpServerTest.class.getSimpleName()), mDeps);
162
Remi NGUYEN VAN0e3d09232018-12-04 12:13:09 +0900163 mServer.start(mAssertSuccessCallback);
164 mLooper.processAllMessages();
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900165 }
166
167 @After
Remi NGUYEN VAN0e3d09232018-12-04 12:13:09 +0900168 public void tearDown() throws Exception {
169 mServer.stop(mAssertSuccessCallback);
170 mLooper.processMessages(1);
171 verify(mPacketListener, times(1)).stop();
172 verify(mHandlerThread, times(1)).quitSafely();
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900173 }
174
175 @Test
176 public void testStart() throws Exception {
177 verify(mPacketListener, times(1)).start();
178 }
179
180 @Test
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900181 public void testDiscover() throws Exception {
182 // TODO: refactor packet construction to eliminate unnecessary/confusing/duplicate fields
183 when(mRepository.getOffer(isNull() /* clientId */, eq(TEST_CLIENT_MAC),
184 eq(INADDR_ANY) /* relayAddr */, isNull() /* reqAddr */, isNull() /* hostname */))
185 .thenReturn(TEST_LEASE);
186
187 final DhcpDiscoverPacket discover = new DhcpDiscoverPacket(TEST_TRANSACTION_ID,
188 (short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES,
189 false /* broadcast */, INADDR_ANY /* srcIp */);
Remi NGUYEN VANb0762eb2018-08-28 11:06:54 +0900190 mServer.processPacket(discover, DHCP_CLIENT);
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900191
192 assertResponseSentTo(TEST_CLIENT_ADDR);
193 final DhcpOfferPacket packet = assertOffer(getPacket());
194 assertMatchesTestLease(packet);
195 }
196
197 @Test
198 public void testDiscover_OutOfAddresses() throws Exception {
199 when(mRepository.getOffer(isNull() /* clientId */, eq(TEST_CLIENT_MAC),
200 eq(INADDR_ANY) /* relayAddr */, isNull() /* reqAddr */, isNull() /* hostname */))
201 .thenThrow(new OutOfAddressesException("Test exception"));
202
203 final DhcpDiscoverPacket discover = new DhcpDiscoverPacket(TEST_TRANSACTION_ID,
204 (short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES,
205 false /* broadcast */, INADDR_ANY /* srcIp */);
Remi NGUYEN VANb0762eb2018-08-28 11:06:54 +0900206 mServer.processPacket(discover, DHCP_CLIENT);
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900207
208 assertResponseSentTo(INADDR_BROADCAST);
209 final DhcpNakPacket packet = assertNak(getPacket());
210 assertMatchesClient(packet);
211 }
212
Remi NGUYEN VANb0762eb2018-08-28 11:06:54 +0900213 private DhcpRequestPacket makeRequestSelectingPacket() {
214 final DhcpRequestPacket request = new DhcpRequestPacket(TEST_TRANSACTION_ID,
215 (short) 0 /* secs */, INADDR_ANY /* clientIp */, INADDR_ANY /* relayIp */,
216 TEST_CLIENT_MAC_BYTES, false /* broadcast */);
217 request.mServerIdentifier = TEST_SERVER_ADDR;
218 request.mRequestedIp = TEST_CLIENT_ADDR;
219 return request;
220 }
221
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900222 @Test
223 public void testRequest_Selecting_Ack() throws Exception {
224 when(mRepository.requestLease(isNull() /* clientId */, eq(TEST_CLIENT_MAC),
Remi NGUYEN VANe1a1dcc2018-08-31 12:14:24 +0900225 eq(INADDR_ANY) /* clientAddr */, eq(INADDR_ANY) /* relayAddr */,
Remi NGUYEN VANf90a92b2018-09-21 12:57:53 +0900226 eq(TEST_CLIENT_ADDR) /* reqAddr */, eq(true) /* sidSet */, eq(TEST_HOSTNAME)))
227 .thenReturn(TEST_LEASE_WITH_HOSTNAME);
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900228
Remi NGUYEN VANb0762eb2018-08-28 11:06:54 +0900229 final DhcpRequestPacket request = makeRequestSelectingPacket();
Remi NGUYEN VANf90a92b2018-09-21 12:57:53 +0900230 request.mHostName = TEST_HOSTNAME;
231 request.mRequestedParams = new byte[] { DHCP_HOST_NAME };
Remi NGUYEN VANb0762eb2018-08-28 11:06:54 +0900232 mServer.processPacket(request, DHCP_CLIENT);
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900233
234 assertResponseSentTo(TEST_CLIENT_ADDR);
235 final DhcpAckPacket packet = assertAck(getPacket());
Remi NGUYEN VANf90a92b2018-09-21 12:57:53 +0900236 assertMatchesTestLease(packet, TEST_HOSTNAME);
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900237 }
238
239 @Test
240 public void testRequest_Selecting_Nak() throws Exception {
241 when(mRepository.requestLease(isNull(), eq(TEST_CLIENT_MAC),
Remi NGUYEN VANe1a1dcc2018-08-31 12:14:24 +0900242 eq(INADDR_ANY) /* clientAddr */, eq(INADDR_ANY) /* relayAddr */,
243 eq(TEST_CLIENT_ADDR) /* reqAddr */, eq(true) /* sidSet */, isNull() /* hostname */))
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900244 .thenThrow(new InvalidAddressException("Test error"));
245
Remi NGUYEN VANb0762eb2018-08-28 11:06:54 +0900246 final DhcpRequestPacket request = makeRequestSelectingPacket();
247 mServer.processPacket(request, DHCP_CLIENT);
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900248
249 assertResponseSentTo(INADDR_BROADCAST);
250 final DhcpNakPacket packet = assertNak(getPacket());
251 assertMatchesClient(packet);
252 }
253
254 @Test
Remi NGUYEN VANb0762eb2018-08-28 11:06:54 +0900255 public void testRequest_Selecting_WrongClientPort() throws Exception {
256 final DhcpRequestPacket request = makeRequestSelectingPacket();
257 mServer.processPacket(request, 50000);
258
Remi NGUYEN VANe1a1dcc2018-08-31 12:14:24 +0900259 verify(mRepository, never())
260 .requestLease(any(), any(), any(), any(), any(), anyBoolean(), any());
Remi NGUYEN VANb0762eb2018-08-28 11:06:54 +0900261 verify(mDeps, never()).sendPacket(any(), any(), any());
262 }
263
264 @Test
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900265 public void testRelease() throws Exception {
266 final DhcpReleasePacket release = new DhcpReleasePacket(TEST_TRANSACTION_ID,
267 TEST_SERVER_ADDR, TEST_CLIENT_ADDR,
268 INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES);
Remi NGUYEN VANb0762eb2018-08-28 11:06:54 +0900269 mServer.processPacket(release, DHCP_CLIENT);
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900270
271 verify(mRepository, times(1))
272 .releaseLease(isNull(), eq(TEST_CLIENT_MAC), eq(TEST_CLIENT_ADDR));
273 }
274
275 /* TODO: add more tests once packet construction is refactored, including:
276 * - usage of giaddr
277 * - usage of broadcast bit
278 * - other request states (init-reboot/renewing/rebinding)
279 */
280
Remi NGUYEN VANf90a92b2018-09-21 12:57:53 +0900281 private void assertMatchesTestLease(@NonNull DhcpPacket packet, @Nullable String hostname) {
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900282 assertMatchesClient(packet);
283 assertFalse(packet.hasExplicitClientId());
284 assertEquals(TEST_SERVER_ADDR, packet.mServerIdentifier);
285 assertEquals(TEST_CLIENT_ADDR, packet.mYourIp);
286 assertNotNull(packet.mLeaseTime);
287 assertEquals(TEST_LEASE_EXPTIME_SECS, (int) packet.mLeaseTime);
Remi NGUYEN VANf90a92b2018-09-21 12:57:53 +0900288 assertEquals(hostname, packet.mHostName);
289 }
290
291 private void assertMatchesTestLease(@NonNull DhcpPacket packet) {
292 assertMatchesTestLease(packet, null);
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900293 }
294
295 private void assertMatchesClient(@NonNull DhcpPacket packet) {
296 assertEquals(TEST_TRANSACTION_ID, packet.mTransId);
297 assertEquals(TEST_CLIENT_MAC, MacAddress.fromBytes(packet.mClientMac));
298 }
299
300 private void assertResponseSentTo(@NonNull Inet4Address addr) {
301 assertEquals(addr, mResponseDstAddrCaptor.getValue());
302 }
303
304 private static DhcpNakPacket assertNak(@Nullable DhcpPacket packet) {
305 assertTrue(packet instanceof DhcpNakPacket);
306 return (DhcpNakPacket) packet;
307 }
308
309 private static DhcpAckPacket assertAck(@Nullable DhcpPacket packet) {
310 assertTrue(packet instanceof DhcpAckPacket);
311 return (DhcpAckPacket) packet;
312 }
313
314 private static DhcpOfferPacket assertOffer(@Nullable DhcpPacket packet) {
315 assertTrue(packet instanceof DhcpOfferPacket);
316 return (DhcpOfferPacket) packet;
317 }
318
319 private DhcpPacket getPacket() throws Exception {
320 verify(mDeps, times(1)).sendPacket(any(), any(), any());
321 return DhcpPacket.decodeFullPacket(mSentPacketCaptor.getValue(), ENCAP_BOOTP);
322 }
323
324 private static Inet4Address parseAddr(@Nullable String inet4Addr) {
325 return (Inet4Address) parseNumericAddress(inet4Addr);
326 }
327}