blob: 7d5e9e3ba1747ad29b71ecc0fd6b5627a4cee635 [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 VAN0e3d09232018-12-04 12:13:09 +090054import android.testing.AndroidTestingRunner;
55import android.testing.TestableLooper;
56import android.testing.TestableLooper.RunWithLooper;
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +090057
Brett Chabot84151d92019-02-27 15:37:59 -080058import androidx.test.filters.SmallTest;
59
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +090060import org.junit.After;
61import org.junit.Before;
62import org.junit.Test;
63import org.junit.runner.RunWith;
64import org.mockito.ArgumentCaptor;
65import org.mockito.Captor;
66import org.mockito.Mock;
67import org.mockito.MockitoAnnotations;
68
69import java.net.Inet4Address;
70import java.nio.ByteBuffer;
71import java.util.Arrays;
72import java.util.HashSet;
73import java.util.Set;
74
Remi NGUYEN VAN0e3d09232018-12-04 12:13:09 +090075@RunWith(AndroidTestingRunner.class)
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +090076@SmallTest
Remi NGUYEN VAN0e3d09232018-12-04 12:13:09 +090077@RunWithLooper
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +090078public class DhcpServerTest {
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +090079 private static final String TEST_IFACE = "testiface";
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +090080
81 private static final Inet4Address TEST_SERVER_ADDR = parseAddr("192.168.0.2");
82 private static final LinkAddress TEST_SERVER_LINKADDR = new LinkAddress(TEST_SERVER_ADDR, 20);
83 private static final Set<Inet4Address> TEST_DEFAULT_ROUTERS = new HashSet<>(
84 Arrays.asList(parseAddr("192.168.0.123"), parseAddr("192.168.0.124")));
85 private static final Set<Inet4Address> TEST_DNS_SERVERS = new HashSet<>(
86 Arrays.asList(parseAddr("192.168.0.126"), parseAddr("192.168.0.127")));
87 private static final Set<Inet4Address> TEST_EXCLUDED_ADDRS = new HashSet<>(
88 Arrays.asList(parseAddr("192.168.0.200"), parseAddr("192.168.0.201")));
89 private static final long TEST_LEASE_TIME_SECS = 3600L;
90 private static final int TEST_MTU = 1500;
Remi NGUYEN VANf90a92b2018-09-21 12:57:53 +090091 private static final String TEST_HOSTNAME = "testhostname";
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +090092
93 private static final int TEST_TRANSACTION_ID = 123;
94 private static final byte[] TEST_CLIENT_MAC_BYTES = new byte [] { 1, 2, 3, 4, 5, 6 };
95 private static final MacAddress TEST_CLIENT_MAC = MacAddress.fromBytes(TEST_CLIENT_MAC_BYTES);
96 private static final Inet4Address TEST_CLIENT_ADDR = parseAddr("192.168.0.42");
97
98 private static final long TEST_CLOCK_TIME = 1234L;
99 private static final int TEST_LEASE_EXPTIME_SECS = 3600;
100 private static final DhcpLease TEST_LEASE = new DhcpLease(null, TEST_CLIENT_MAC,
Remi NGUYEN VANf90a92b2018-09-21 12:57:53 +0900101 TEST_CLIENT_ADDR, TEST_LEASE_EXPTIME_SECS * 1000L + TEST_CLOCK_TIME,
102 null /* hostname */);
103 private static final DhcpLease TEST_LEASE_WITH_HOSTNAME = new DhcpLease(null, TEST_CLIENT_MAC,
104 TEST_CLIENT_ADDR, TEST_LEASE_EXPTIME_SECS * 1000L + TEST_CLOCK_TIME, TEST_HOSTNAME);
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900105
106 @NonNull @Mock
107 private Dependencies mDeps;
108 @NonNull @Mock
109 private DhcpLeaseRepository mRepository;
110 @NonNull @Mock
111 private Clock mClock;
112 @NonNull @Mock
113 private DhcpPacketListener mPacketListener;
114
115 @NonNull @Captor
116 private ArgumentCaptor<ByteBuffer> mSentPacketCaptor;
117 @NonNull @Captor
118 private ArgumentCaptor<Inet4Address> mResponseDstAddrCaptor;
119
120 @NonNull
Remi NGUYEN VAN0e3d09232018-12-04 12:13:09 +0900121 private HandlerThread mHandlerThread;
122 @NonNull
123 private TestableLooper mLooper;
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900124 @NonNull
125 private DhcpServer mServer;
126
127 @Nullable
128 private String mPrevShareClassloaderProp;
129
Remi NGUYEN VAN0e3d09232018-12-04 12:13:09 +0900130 private final INetworkStackStatusCallback mAssertSuccessCallback =
131 new INetworkStackStatusCallback.Stub() {
132 @Override
133 public void onStatusAvailable(int statusCode) {
134 assertEquals(STATUS_SUCCESS, statusCode);
135 }
136 };
137
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900138 @Before
139 public void setUp() throws Exception {
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900140 MockitoAnnotations.initMocks(this);
141
142 when(mDeps.makeLeaseRepository(any(), any(), any())).thenReturn(mRepository);
143 when(mDeps.makeClock()).thenReturn(mClock);
144 when(mDeps.makePacketListener()).thenReturn(mPacketListener);
145 doNothing().when(mDeps)
146 .sendPacket(any(), mSentPacketCaptor.capture(), mResponseDstAddrCaptor.capture());
147 when(mClock.elapsedRealtime()).thenReturn(TEST_CLOCK_TIME);
148
149 final DhcpServingParams servingParams = new DhcpServingParams.Builder()
150 .setDefaultRouters(TEST_DEFAULT_ROUTERS)
151 .setDhcpLeaseTimeSecs(TEST_LEASE_TIME_SECS)
152 .setDnsServers(TEST_DNS_SERVERS)
153 .setServerAddr(TEST_SERVER_LINKADDR)
154 .setLinkMtu(TEST_MTU)
155 .setExcludedAddrs(TEST_EXCLUDED_ADDRS)
156 .build();
157
Remi NGUYEN VAN0e3d09232018-12-04 12:13:09 +0900158 mLooper = TestableLooper.get(this);
159 mHandlerThread = spy(new HandlerThread("TestDhcpServer"));
160 when(mHandlerThread.getLooper()).thenReturn(mLooper.getLooper());
161 mServer = new DhcpServer(mHandlerThread, TEST_IFACE, servingParams,
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900162 new SharedLog(DhcpServerTest.class.getSimpleName()), mDeps);
163
Remi NGUYEN VAN0e3d09232018-12-04 12:13:09 +0900164 mServer.start(mAssertSuccessCallback);
165 mLooper.processAllMessages();
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900166 }
167
168 @After
Remi NGUYEN VAN0e3d09232018-12-04 12:13:09 +0900169 public void tearDown() throws Exception {
170 mServer.stop(mAssertSuccessCallback);
171 mLooper.processMessages(1);
172 verify(mPacketListener, times(1)).stop();
173 verify(mHandlerThread, times(1)).quitSafely();
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900174 }
175
176 @Test
177 public void testStart() throws Exception {
178 verify(mPacketListener, times(1)).start();
179 }
180
181 @Test
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900182 public void testDiscover() throws Exception {
183 // TODO: refactor packet construction to eliminate unnecessary/confusing/duplicate fields
184 when(mRepository.getOffer(isNull() /* clientId */, eq(TEST_CLIENT_MAC),
185 eq(INADDR_ANY) /* relayAddr */, isNull() /* reqAddr */, isNull() /* hostname */))
186 .thenReturn(TEST_LEASE);
187
188 final DhcpDiscoverPacket discover = new DhcpDiscoverPacket(TEST_TRANSACTION_ID,
189 (short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES,
190 false /* broadcast */, INADDR_ANY /* srcIp */);
Remi NGUYEN VANb0762eb2018-08-28 11:06:54 +0900191 mServer.processPacket(discover, DHCP_CLIENT);
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900192
193 assertResponseSentTo(TEST_CLIENT_ADDR);
194 final DhcpOfferPacket packet = assertOffer(getPacket());
195 assertMatchesTestLease(packet);
196 }
197
198 @Test
199 public void testDiscover_OutOfAddresses() throws Exception {
200 when(mRepository.getOffer(isNull() /* clientId */, eq(TEST_CLIENT_MAC),
201 eq(INADDR_ANY) /* relayAddr */, isNull() /* reqAddr */, isNull() /* hostname */))
202 .thenThrow(new OutOfAddressesException("Test exception"));
203
204 final DhcpDiscoverPacket discover = new DhcpDiscoverPacket(TEST_TRANSACTION_ID,
205 (short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES,
206 false /* broadcast */, INADDR_ANY /* srcIp */);
Remi NGUYEN VANb0762eb2018-08-28 11:06:54 +0900207 mServer.processPacket(discover, DHCP_CLIENT);
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900208
209 assertResponseSentTo(INADDR_BROADCAST);
210 final DhcpNakPacket packet = assertNak(getPacket());
211 assertMatchesClient(packet);
212 }
213
Remi NGUYEN VANb0762eb2018-08-28 11:06:54 +0900214 private DhcpRequestPacket makeRequestSelectingPacket() {
215 final DhcpRequestPacket request = new DhcpRequestPacket(TEST_TRANSACTION_ID,
216 (short) 0 /* secs */, INADDR_ANY /* clientIp */, INADDR_ANY /* relayIp */,
217 TEST_CLIENT_MAC_BYTES, false /* broadcast */);
218 request.mServerIdentifier = TEST_SERVER_ADDR;
219 request.mRequestedIp = TEST_CLIENT_ADDR;
220 return request;
221 }
222
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900223 @Test
224 public void testRequest_Selecting_Ack() throws Exception {
225 when(mRepository.requestLease(isNull() /* clientId */, eq(TEST_CLIENT_MAC),
Remi NGUYEN VANe1a1dcc2018-08-31 12:14:24 +0900226 eq(INADDR_ANY) /* clientAddr */, eq(INADDR_ANY) /* relayAddr */,
Remi NGUYEN VANf90a92b2018-09-21 12:57:53 +0900227 eq(TEST_CLIENT_ADDR) /* reqAddr */, eq(true) /* sidSet */, eq(TEST_HOSTNAME)))
228 .thenReturn(TEST_LEASE_WITH_HOSTNAME);
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900229
Remi NGUYEN VANb0762eb2018-08-28 11:06:54 +0900230 final DhcpRequestPacket request = makeRequestSelectingPacket();
Remi NGUYEN VANf90a92b2018-09-21 12:57:53 +0900231 request.mHostName = TEST_HOSTNAME;
232 request.mRequestedParams = new byte[] { DHCP_HOST_NAME };
Remi NGUYEN VANb0762eb2018-08-28 11:06:54 +0900233 mServer.processPacket(request, DHCP_CLIENT);
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900234
235 assertResponseSentTo(TEST_CLIENT_ADDR);
236 final DhcpAckPacket packet = assertAck(getPacket());
Remi NGUYEN VANf90a92b2018-09-21 12:57:53 +0900237 assertMatchesTestLease(packet, TEST_HOSTNAME);
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900238 }
239
240 @Test
241 public void testRequest_Selecting_Nak() throws Exception {
242 when(mRepository.requestLease(isNull(), eq(TEST_CLIENT_MAC),
Remi NGUYEN VANe1a1dcc2018-08-31 12:14:24 +0900243 eq(INADDR_ANY) /* clientAddr */, eq(INADDR_ANY) /* relayAddr */,
244 eq(TEST_CLIENT_ADDR) /* reqAddr */, eq(true) /* sidSet */, isNull() /* hostname */))
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900245 .thenThrow(new InvalidAddressException("Test error"));
246
Remi NGUYEN VANb0762eb2018-08-28 11:06:54 +0900247 final DhcpRequestPacket request = makeRequestSelectingPacket();
248 mServer.processPacket(request, DHCP_CLIENT);
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900249
250 assertResponseSentTo(INADDR_BROADCAST);
251 final DhcpNakPacket packet = assertNak(getPacket());
252 assertMatchesClient(packet);
253 }
254
255 @Test
Remi NGUYEN VANb0762eb2018-08-28 11:06:54 +0900256 public void testRequest_Selecting_WrongClientPort() throws Exception {
257 final DhcpRequestPacket request = makeRequestSelectingPacket();
258 mServer.processPacket(request, 50000);
259
Remi NGUYEN VANe1a1dcc2018-08-31 12:14:24 +0900260 verify(mRepository, never())
261 .requestLease(any(), any(), any(), any(), any(), anyBoolean(), any());
Remi NGUYEN VANb0762eb2018-08-28 11:06:54 +0900262 verify(mDeps, never()).sendPacket(any(), any(), any());
263 }
264
265 @Test
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900266 public void testRelease() throws Exception {
267 final DhcpReleasePacket release = new DhcpReleasePacket(TEST_TRANSACTION_ID,
268 TEST_SERVER_ADDR, TEST_CLIENT_ADDR,
269 INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES);
Remi NGUYEN VANb0762eb2018-08-28 11:06:54 +0900270 mServer.processPacket(release, DHCP_CLIENT);
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900271
272 verify(mRepository, times(1))
273 .releaseLease(isNull(), eq(TEST_CLIENT_MAC), eq(TEST_CLIENT_ADDR));
274 }
275
276 /* TODO: add more tests once packet construction is refactored, including:
277 * - usage of giaddr
278 * - usage of broadcast bit
279 * - other request states (init-reboot/renewing/rebinding)
280 */
281
Remi NGUYEN VANf90a92b2018-09-21 12:57:53 +0900282 private void assertMatchesTestLease(@NonNull DhcpPacket packet, @Nullable String hostname) {
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900283 assertMatchesClient(packet);
284 assertFalse(packet.hasExplicitClientId());
285 assertEquals(TEST_SERVER_ADDR, packet.mServerIdentifier);
286 assertEquals(TEST_CLIENT_ADDR, packet.mYourIp);
287 assertNotNull(packet.mLeaseTime);
288 assertEquals(TEST_LEASE_EXPTIME_SECS, (int) packet.mLeaseTime);
Remi NGUYEN VANf90a92b2018-09-21 12:57:53 +0900289 assertEquals(hostname, packet.mHostName);
290 }
291
292 private void assertMatchesTestLease(@NonNull DhcpPacket packet) {
293 assertMatchesTestLease(packet, null);
Remi NGUYEN VANa13007a2018-08-13 15:54:27 +0900294 }
295
296 private void assertMatchesClient(@NonNull DhcpPacket packet) {
297 assertEquals(TEST_TRANSACTION_ID, packet.mTransId);
298 assertEquals(TEST_CLIENT_MAC, MacAddress.fromBytes(packet.mClientMac));
299 }
300
301 private void assertResponseSentTo(@NonNull Inet4Address addr) {
302 assertEquals(addr, mResponseDstAddrCaptor.getValue());
303 }
304
305 private static DhcpNakPacket assertNak(@Nullable DhcpPacket packet) {
306 assertTrue(packet instanceof DhcpNakPacket);
307 return (DhcpNakPacket) packet;
308 }
309
310 private static DhcpAckPacket assertAck(@Nullable DhcpPacket packet) {
311 assertTrue(packet instanceof DhcpAckPacket);
312 return (DhcpAckPacket) packet;
313 }
314
315 private static DhcpOfferPacket assertOffer(@Nullable DhcpPacket packet) {
316 assertTrue(packet instanceof DhcpOfferPacket);
317 return (DhcpOfferPacket) packet;
318 }
319
320 private DhcpPacket getPacket() throws Exception {
321 verify(mDeps, times(1)).sendPacket(any(), any(), any());
322 return DhcpPacket.decodeFullPacket(mSentPacketCaptor.getValue(), ENCAP_BOOTP);
323 }
324
325 private static Inet4Address parseAddr(@Nullable String inet4Addr) {
326 return (Inet4Address) parseNumericAddress(inet4Addr);
327 }
328}