1 /*
2  * Copyright (C) 2017 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 
17 package com.android.server;
18 
19 import static android.system.OsConstants.AF_INET;
20 import static android.system.OsConstants.EADDRINUSE;
21 import static android.system.OsConstants.IPPROTO_UDP;
22 import static android.system.OsConstants.SOCK_DGRAM;
23 
24 import static org.junit.Assert.assertEquals;
25 import static org.junit.Assert.assertNotEquals;
26 import static org.junit.Assert.assertNotNull;
27 import static org.junit.Assert.assertTrue;
28 import static org.junit.Assert.fail;
29 import static org.mockito.Matchers.anyInt;
30 import static org.mockito.Matchers.anyString;
31 import static org.mockito.Matchers.argThat;
32 import static org.mockito.Matchers.eq;
33 import static org.mockito.Mockito.mock;
34 import static org.mockito.Mockito.verify;
35 import static org.mockito.Mockito.when;
36 
37 import android.content.Context;
38 import android.net.INetd;
39 import android.net.IpSecAlgorithm;
40 import android.net.IpSecConfig;
41 import android.net.IpSecManager;
42 import android.net.IpSecSpiResponse;
43 import android.net.IpSecUdpEncapResponse;
44 import android.os.Binder;
45 import android.os.INetworkManagementService;
46 import android.os.ParcelFileDescriptor;
47 import android.os.Process;
48 import android.system.ErrnoException;
49 import android.system.Os;
50 import android.system.StructStat;
51 
52 import androidx.test.filters.SmallTest;
53 import androidx.test.runner.AndroidJUnit4;
54 
55 import dalvik.system.SocketTagger;
56 
57 import org.junit.Before;
58 import org.junit.Test;
59 import org.junit.runner.RunWith;
60 import org.mockito.ArgumentMatcher;
61 
62 import java.io.FileDescriptor;
63 import java.net.InetAddress;
64 import java.net.ServerSocket;
65 import java.net.Socket;
66 import java.net.UnknownHostException;
67 import java.util.ArrayList;
68 import java.util.List;
69 
70 /** Unit tests for {@link IpSecService}. */
71 @SmallTest
72 @RunWith(AndroidJUnit4.class)
73 public class IpSecServiceTest {
74 
75     private static final int DROID_SPI = 0xD1201D;
76     private static final int MAX_NUM_ENCAP_SOCKETS = 100;
77     private static final int MAX_NUM_SPIS = 100;
78     private static final int TEST_UDP_ENCAP_INVALID_PORT = 100;
79     private static final int TEST_UDP_ENCAP_PORT_OUT_RANGE = 100000;
80 
81     private static final InetAddress INADDR_ANY;
82 
83     private static final byte[] AEAD_KEY = {
84         0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
85         0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
86         0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
87         0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
88         0x73, 0x61, 0x6C, 0x74
89     };
90     private static final byte[] CRYPT_KEY = {
91         0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
92         0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
93         0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
94         0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F
95     };
96     private static final byte[] AUTH_KEY = {
97         0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
98         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F,
99         0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
100         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F
101     };
102 
103     private static final IpSecAlgorithm AUTH_ALGO =
104             new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, AUTH_KEY, AUTH_KEY.length * 4);
105     private static final IpSecAlgorithm CRYPT_ALGO =
106             new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
107     private static final IpSecAlgorithm AEAD_ALGO =
108             new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
109 
110     static {
111         try {
112             INADDR_ANY = InetAddress.getByAddress(new byte[] {0, 0, 0, 0});
113         } catch (UnknownHostException e) {
114             throw new RuntimeException(e);
115         }
116     }
117 
118     Context mMockContext;
119     INetworkManagementService mMockNetworkManager;
120     INetd mMockNetd;
121     IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig;
122     IpSecService mIpSecService;
123 
124     @Before
setUp()125     public void setUp() throws Exception {
126         mMockContext = mock(Context.class);
127         mMockNetworkManager = mock(INetworkManagementService.class);
128         mMockNetd = mock(INetd.class);
129         mMockIpSecSrvConfig = mock(IpSecService.IpSecServiceConfiguration.class);
130         mIpSecService = new IpSecService(mMockContext, mMockNetworkManager, mMockIpSecSrvConfig);
131 
132         // Injecting mock netd
133         when(mMockIpSecSrvConfig.getNetdInstance()).thenReturn(mMockNetd);
134     }
135 
136     @Test
testIpSecServiceCreate()137     public void testIpSecServiceCreate() throws InterruptedException {
138         IpSecService ipSecSrv = IpSecService.create(mMockContext, mMockNetworkManager);
139         assertNotNull(ipSecSrv);
140     }
141 
142     @Test
testReleaseInvalidSecurityParameterIndex()143     public void testReleaseInvalidSecurityParameterIndex() throws Exception {
144         try {
145             mIpSecService.releaseSecurityParameterIndex(1);
146             fail("IllegalArgumentException not thrown");
147         } catch (IllegalArgumentException e) {
148         }
149     }
150 
151     /** This function finds an available port */
findUnusedPort()152     int findUnusedPort() throws Exception {
153         // Get an available port.
154         ServerSocket s = new ServerSocket(0);
155         int port = s.getLocalPort();
156         s.close();
157         return port;
158     }
159 
160     @Test
testOpenAndCloseUdpEncapsulationSocket()161     public void testOpenAndCloseUdpEncapsulationSocket() throws Exception {
162         int localport = -1;
163         IpSecUdpEncapResponse udpEncapResp = null;
164 
165         for (int i = 0; i < IpSecService.MAX_PORT_BIND_ATTEMPTS; i++) {
166             localport = findUnusedPort();
167 
168             udpEncapResp = mIpSecService.openUdpEncapsulationSocket(localport, new Binder());
169             assertNotNull(udpEncapResp);
170             if (udpEncapResp.status == IpSecManager.Status.OK) {
171                 break;
172             }
173 
174             // Else retry to reduce possibility for port-bind failures.
175         }
176 
177         assertNotNull(udpEncapResp);
178         assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
179         assertEquals(localport, udpEncapResp.port);
180 
181         mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
182         udpEncapResp.fileDescriptor.close();
183 
184         // Verify quota and RefcountedResource objects cleaned up
185         IpSecService.UserRecord userRecord =
186                 mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid());
187         assertEquals(0, userRecord.mSocketQuotaTracker.mCurrent);
188         try {
189             userRecord.mEncapSocketRecords.getRefcountedResourceOrThrow(udpEncapResp.resourceId);
190             fail("Expected IllegalArgumentException on attempt to access deleted resource");
191         } catch (IllegalArgumentException expected) {
192 
193         }
194     }
195 
196     @Test
testUdpEncapsulationSocketBinderDeath()197     public void testUdpEncapsulationSocketBinderDeath() throws Exception {
198         IpSecUdpEncapResponse udpEncapResp =
199                 mIpSecService.openUdpEncapsulationSocket(0, new Binder());
200 
201         IpSecService.UserRecord userRecord =
202                 mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid());
203         IpSecService.RefcountedResource refcountedRecord =
204                 userRecord.mEncapSocketRecords.getRefcountedResourceOrThrow(
205                         udpEncapResp.resourceId);
206 
207         refcountedRecord.binderDied();
208 
209         // Verify quota and RefcountedResource objects cleaned up
210         assertEquals(0, userRecord.mSocketQuotaTracker.mCurrent);
211         try {
212             userRecord.mEncapSocketRecords.getRefcountedResourceOrThrow(udpEncapResp.resourceId);
213             fail("Expected IllegalArgumentException on attempt to access deleted resource");
214         } catch (IllegalArgumentException expected) {
215 
216         }
217     }
218 
219     @Test
testOpenUdpEncapsulationSocketAfterClose()220     public void testOpenUdpEncapsulationSocketAfterClose() throws Exception {
221         IpSecUdpEncapResponse udpEncapResp =
222                 mIpSecService.openUdpEncapsulationSocket(0, new Binder());
223         assertNotNull(udpEncapResp);
224         assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
225         int localport = udpEncapResp.port;
226 
227         mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
228         udpEncapResp.fileDescriptor.close();
229 
230         /** Check if localport is available. */
231         FileDescriptor newSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
232         Os.bind(newSocket, INADDR_ANY, localport);
233         Os.close(newSocket);
234     }
235 
236     /**
237      * This function checks if the IpSecService holds the reserved port. If
238      * closeUdpEncapsulationSocket is not called, the socket cleanup should not be complete.
239      */
240     @Test
testUdpEncapPortNotReleased()241     public void testUdpEncapPortNotReleased() throws Exception {
242         IpSecUdpEncapResponse udpEncapResp =
243                 mIpSecService.openUdpEncapsulationSocket(0, new Binder());
244         assertNotNull(udpEncapResp);
245         assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
246         int localport = udpEncapResp.port;
247 
248         udpEncapResp.fileDescriptor.close();
249 
250         FileDescriptor newSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
251         try {
252             Os.bind(newSocket, INADDR_ANY, localport);
253             fail("ErrnoException not thrown");
254         } catch (ErrnoException e) {
255             assertEquals(EADDRINUSE, e.errno);
256         }
257         mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
258     }
259 
260     @Test
testOpenUdpEncapsulationSocketOnRandomPort()261     public void testOpenUdpEncapsulationSocketOnRandomPort() throws Exception {
262         IpSecUdpEncapResponse udpEncapResp =
263                 mIpSecService.openUdpEncapsulationSocket(0, new Binder());
264         assertNotNull(udpEncapResp);
265         assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
266         assertNotEquals(0, udpEncapResp.port);
267         mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
268         udpEncapResp.fileDescriptor.close();
269     }
270 
271     @Test
testOpenUdpEncapsulationSocketPortRange()272     public void testOpenUdpEncapsulationSocketPortRange() throws Exception {
273         try {
274             mIpSecService.openUdpEncapsulationSocket(TEST_UDP_ENCAP_INVALID_PORT, new Binder());
275             fail("IllegalArgumentException not thrown");
276         } catch (IllegalArgumentException e) {
277         }
278 
279         try {
280             mIpSecService.openUdpEncapsulationSocket(TEST_UDP_ENCAP_PORT_OUT_RANGE, new Binder());
281             fail("IllegalArgumentException not thrown");
282         } catch (IllegalArgumentException e) {
283         }
284     }
285 
286     @Test
testOpenUdpEncapsulationSocketTwice()287     public void testOpenUdpEncapsulationSocketTwice() throws Exception {
288         IpSecUdpEncapResponse udpEncapResp =
289                 mIpSecService.openUdpEncapsulationSocket(0, new Binder());
290         assertNotNull(udpEncapResp);
291         assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
292         int localport = udpEncapResp.port;
293 
294         IpSecUdpEncapResponse testUdpEncapResp =
295                 mIpSecService.openUdpEncapsulationSocket(localport, new Binder());
296         assertEquals(IpSecManager.Status.RESOURCE_UNAVAILABLE, testUdpEncapResp.status);
297 
298         mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
299         udpEncapResp.fileDescriptor.close();
300     }
301 
302     @Test
testCloseInvalidUdpEncapsulationSocket()303     public void testCloseInvalidUdpEncapsulationSocket() throws Exception {
304         try {
305             mIpSecService.closeUdpEncapsulationSocket(1);
306             fail("IllegalArgumentException not thrown");
307         } catch (IllegalArgumentException e) {
308         }
309     }
310 
311     @Test
testValidateAlgorithmsAuth()312     public void testValidateAlgorithmsAuth() {
313         // Validate that correct algorithm type succeeds
314         IpSecConfig config = new IpSecConfig();
315         config.setAuthentication(AUTH_ALGO);
316         mIpSecService.validateAlgorithms(config);
317 
318         // Validate that incorrect algorithm types fails
319         for (IpSecAlgorithm algo : new IpSecAlgorithm[] {CRYPT_ALGO, AEAD_ALGO}) {
320             try {
321                 config = new IpSecConfig();
322                 config.setAuthentication(algo);
323                 mIpSecService.validateAlgorithms(config);
324                 fail("Did not throw exception on invalid algorithm type");
325             } catch (IllegalArgumentException expected) {
326             }
327         }
328     }
329 
330     @Test
testValidateAlgorithmsCrypt()331     public void testValidateAlgorithmsCrypt() {
332         // Validate that correct algorithm type succeeds
333         IpSecConfig config = new IpSecConfig();
334         config.setEncryption(CRYPT_ALGO);
335         mIpSecService.validateAlgorithms(config);
336 
337         // Validate that incorrect algorithm types fails
338         for (IpSecAlgorithm algo : new IpSecAlgorithm[] {AUTH_ALGO, AEAD_ALGO}) {
339             try {
340                 config = new IpSecConfig();
341                 config.setEncryption(algo);
342                 mIpSecService.validateAlgorithms(config);
343                 fail("Did not throw exception on invalid algorithm type");
344             } catch (IllegalArgumentException expected) {
345             }
346         }
347     }
348 
349     @Test
testValidateAlgorithmsAead()350     public void testValidateAlgorithmsAead() {
351         // Validate that correct algorithm type succeeds
352         IpSecConfig config = new IpSecConfig();
353         config.setAuthenticatedEncryption(AEAD_ALGO);
354         mIpSecService.validateAlgorithms(config);
355 
356         // Validate that incorrect algorithm types fails
357         for (IpSecAlgorithm algo : new IpSecAlgorithm[] {AUTH_ALGO, CRYPT_ALGO}) {
358             try {
359                 config = new IpSecConfig();
360                 config.setAuthenticatedEncryption(algo);
361                 mIpSecService.validateAlgorithms(config);
362                 fail("Did not throw exception on invalid algorithm type");
363             } catch (IllegalArgumentException expected) {
364             }
365         }
366     }
367 
368     @Test
testValidateAlgorithmsAuthCrypt()369     public void testValidateAlgorithmsAuthCrypt() {
370         // Validate that correct algorithm type succeeds
371         IpSecConfig config = new IpSecConfig();
372         config.setAuthentication(AUTH_ALGO);
373         config.setEncryption(CRYPT_ALGO);
374         mIpSecService.validateAlgorithms(config);
375     }
376 
377     @Test
testValidateAlgorithmsNoAlgorithms()378     public void testValidateAlgorithmsNoAlgorithms() {
379         IpSecConfig config = new IpSecConfig();
380         try {
381             mIpSecService.validateAlgorithms(config);
382             fail("Expected exception; no algorithms specified");
383         } catch (IllegalArgumentException expected) {
384         }
385     }
386 
387     @Test
testValidateAlgorithmsAeadWithAuth()388     public void testValidateAlgorithmsAeadWithAuth() {
389         IpSecConfig config = new IpSecConfig();
390         config.setAuthenticatedEncryption(AEAD_ALGO);
391         config.setAuthentication(AUTH_ALGO);
392         try {
393             mIpSecService.validateAlgorithms(config);
394             fail("Expected exception; both AEAD and auth algorithm specified");
395         } catch (IllegalArgumentException expected) {
396         }
397     }
398 
399     @Test
testValidateAlgorithmsAeadWithCrypt()400     public void testValidateAlgorithmsAeadWithCrypt() {
401         IpSecConfig config = new IpSecConfig();
402         config.setAuthenticatedEncryption(AEAD_ALGO);
403         config.setEncryption(CRYPT_ALGO);
404         try {
405             mIpSecService.validateAlgorithms(config);
406             fail("Expected exception; both AEAD and crypt algorithm specified");
407         } catch (IllegalArgumentException expected) {
408         }
409     }
410 
411     @Test
testValidateAlgorithmsAeadWithAuthAndCrypt()412     public void testValidateAlgorithmsAeadWithAuthAndCrypt() {
413         IpSecConfig config = new IpSecConfig();
414         config.setAuthenticatedEncryption(AEAD_ALGO);
415         config.setAuthentication(AUTH_ALGO);
416         config.setEncryption(CRYPT_ALGO);
417         try {
418             mIpSecService.validateAlgorithms(config);
419             fail("Expected exception; AEAD, auth and crypt algorithm specified");
420         } catch (IllegalArgumentException expected) {
421         }
422     }
423 
424     @Test
testDeleteInvalidTransform()425     public void testDeleteInvalidTransform() throws Exception {
426         try {
427             mIpSecService.deleteTransform(1);
428             fail("IllegalArgumentException not thrown");
429         } catch (IllegalArgumentException e) {
430         }
431     }
432 
433     @Test
testRemoveTransportModeTransform()434     public void testRemoveTransportModeTransform() throws Exception {
435         Socket socket = new Socket();
436         socket.bind(null);
437         ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket);
438         mIpSecService.removeTransportModeTransforms(pfd);
439 
440         verify(mMockNetd).ipSecRemoveTransportModeTransform(pfd);
441     }
442 
443     @Test
testValidateIpAddresses()444     public void testValidateIpAddresses() throws Exception {
445         String[] invalidAddresses =
446                 new String[] {"www.google.com", "::", "2001::/64", "0.0.0.0", ""};
447         for (String address : invalidAddresses) {
448             try {
449                 IpSecSpiResponse spiResp =
450                         mIpSecService.allocateSecurityParameterIndex(
451                                 address, DROID_SPI, new Binder());
452                 fail("Invalid address was passed through IpSecService validation: " + address);
453             } catch (IllegalArgumentException e) {
454             } catch (Exception e) {
455                 fail(
456                         "Invalid InetAddress was not caught in validation: "
457                                 + address
458                                 + ", Exception: "
459                                 + e);
460             }
461         }
462     }
463 
464     /**
465      * This function checks if the number of encap UDP socket that one UID can reserve has a
466      * reasonable limit.
467      */
468     @Test
testSocketResourceTrackerLimitation()469     public void testSocketResourceTrackerLimitation() throws Exception {
470         List<IpSecUdpEncapResponse> openUdpEncapSockets = new ArrayList<IpSecUdpEncapResponse>();
471         // Reserve sockets until it fails.
472         for (int i = 0; i < MAX_NUM_ENCAP_SOCKETS; i++) {
473             IpSecUdpEncapResponse newUdpEncapSocket =
474                     mIpSecService.openUdpEncapsulationSocket(0, new Binder());
475             assertNotNull(newUdpEncapSocket);
476             if (IpSecManager.Status.OK != newUdpEncapSocket.status) {
477                 break;
478             }
479             openUdpEncapSockets.add(newUdpEncapSocket);
480         }
481         // Assert that the total sockets quota has a reasonable limit.
482         assertTrue("No UDP encap socket was open", !openUdpEncapSockets.isEmpty());
483         assertTrue(
484                 "Number of open UDP encap sockets is out of bound",
485                 openUdpEncapSockets.size() < MAX_NUM_ENCAP_SOCKETS);
486 
487         // Try to reserve one more UDP encapsulation socket, and should fail.
488         IpSecUdpEncapResponse extraUdpEncapSocket =
489                 mIpSecService.openUdpEncapsulationSocket(0, new Binder());
490         assertNotNull(extraUdpEncapSocket);
491         assertEquals(IpSecManager.Status.RESOURCE_UNAVAILABLE, extraUdpEncapSocket.status);
492 
493         // Close one of the open UDP encapsulation sockets.
494         mIpSecService.closeUdpEncapsulationSocket(openUdpEncapSockets.get(0).resourceId);
495         openUdpEncapSockets.get(0).fileDescriptor.close();
496         openUdpEncapSockets.remove(0);
497 
498         // Try to reserve one more UDP encapsulation socket, and should be successful.
499         extraUdpEncapSocket = mIpSecService.openUdpEncapsulationSocket(0, new Binder());
500         assertNotNull(extraUdpEncapSocket);
501         assertEquals(IpSecManager.Status.OK, extraUdpEncapSocket.status);
502         openUdpEncapSockets.add(extraUdpEncapSocket);
503 
504         // Close open UDP sockets.
505         for (IpSecUdpEncapResponse openSocket : openUdpEncapSockets) {
506             mIpSecService.closeUdpEncapsulationSocket(openSocket.resourceId);
507             openSocket.fileDescriptor.close();
508         }
509     }
510 
511     /**
512      * This function checks if the number of SPI that one UID can reserve has a reasonable limit.
513      * This test does not test for both address families or duplicate SPIs because resource tracking
514      * code does not depend on them.
515      */
516     @Test
testSpiResourceTrackerLimitation()517     public void testSpiResourceTrackerLimitation() throws Exception {
518         List<IpSecSpiResponse> reservedSpis = new ArrayList<IpSecSpiResponse>();
519         // Return the same SPI for all SPI allocation since IpSecService only
520         // tracks the resource ID.
521         when(mMockNetd.ipSecAllocateSpi(
522                         anyInt(),
523                         anyString(),
524                         eq(InetAddress.getLoopbackAddress().getHostAddress()),
525                         anyInt()))
526                 .thenReturn(DROID_SPI);
527         // Reserve spis until it fails.
528         for (int i = 0; i < MAX_NUM_SPIS; i++) {
529             IpSecSpiResponse newSpi =
530                     mIpSecService.allocateSecurityParameterIndex(
531                             InetAddress.getLoopbackAddress().getHostAddress(),
532                             DROID_SPI + i,
533                             new Binder());
534             assertNotNull(newSpi);
535             if (IpSecManager.Status.OK != newSpi.status) {
536                 break;
537             }
538             reservedSpis.add(newSpi);
539         }
540         // Assert that the SPI quota has a reasonable limit.
541         assertTrue(reservedSpis.size() > 0 && reservedSpis.size() < MAX_NUM_SPIS);
542 
543         // Try to reserve one more SPI, and should fail.
544         IpSecSpiResponse extraSpi =
545                 mIpSecService.allocateSecurityParameterIndex(
546                         InetAddress.getLoopbackAddress().getHostAddress(),
547                         DROID_SPI + MAX_NUM_SPIS,
548                         new Binder());
549         assertNotNull(extraSpi);
550         assertEquals(IpSecManager.Status.RESOURCE_UNAVAILABLE, extraSpi.status);
551 
552         // Release one reserved spi.
553         mIpSecService.releaseSecurityParameterIndex(reservedSpis.get(0).resourceId);
554         reservedSpis.remove(0);
555 
556         // Should successfully reserve one more spi.
557         extraSpi =
558                 mIpSecService.allocateSecurityParameterIndex(
559                         InetAddress.getLoopbackAddress().getHostAddress(),
560                         DROID_SPI + MAX_NUM_SPIS,
561                         new Binder());
562         assertNotNull(extraSpi);
563         assertEquals(IpSecManager.Status.OK, extraSpi.status);
564 
565         // Release reserved SPIs.
566         for (IpSecSpiResponse spiResp : reservedSpis) {
567             mIpSecService.releaseSecurityParameterIndex(spiResp.resourceId);
568         }
569     }
570 
571     @Test
572     public void testUidFdtagger() throws Exception {
573         SocketTagger actualSocketTagger = SocketTagger.get();
574 
575         try {
576             FileDescriptor sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
577 
578             // Has to be done after socket creation because BlockGuardOS calls tag on new sockets
579             SocketTagger mockSocketTagger = mock(SocketTagger.class);
580             SocketTagger.set(mockSocketTagger);
581 
582             mIpSecService.mUidFdTagger.tag(sockFd, Process.LAST_APPLICATION_UID);
583             verify(mockSocketTagger).tag(eq(sockFd));
584         } finally {
585             SocketTagger.set(actualSocketTagger);
586         }
587     }
588 
589     /**
590      * Checks if two file descriptors point to the same file.
591      *
592      * <p>According to stat.h documentation, the correct way to check for equivalent or duplicated
593      * file descriptors is to check their inode and device. These two entries uniquely identify any
594      * file.
595      */
596     private boolean fileDescriptorsEqual(FileDescriptor fd1, FileDescriptor fd2) {
597         try {
598             StructStat fd1Stat = Os.fstat(fd1);
599             StructStat fd2Stat = Os.fstat(fd2);
600 
601             return fd1Stat.st_ino == fd2Stat.st_ino && fd1Stat.st_dev == fd2Stat.st_dev;
602         } catch (ErrnoException e) {
603             return false;
604         }
605     }
606 
607     @Test
608     public void testOpenUdpEncapSocketTagsSocket() throws Exception {
609         IpSecService.UidFdTagger mockTagger = mock(IpSecService.UidFdTagger.class);
610         IpSecService testIpSecService = new IpSecService(
611                 mMockContext, mMockNetworkManager, mMockIpSecSrvConfig, mockTagger);
612 
613         IpSecUdpEncapResponse udpEncapResp =
614                 testIpSecService.openUdpEncapsulationSocket(0, new Binder());
615         assertNotNull(udpEncapResp);
616         assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
617 
618         FileDescriptor sockFd = udpEncapResp.fileDescriptor.getFileDescriptor();
619         ArgumentMatcher<FileDescriptor> fdMatcher =
620                 (argFd) -> {
621                     return fileDescriptorsEqual(sockFd, argFd);
622                 };
623         verify(mockTagger).tag(argThat(fdMatcher), eq(Os.getuid()));
624 
625         testIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
626         udpEncapResp.fileDescriptor.close();
627     }
628 
629     @Test
testOpenUdpEncapsulationSocketCallsSetEncapSocketOwner()630     public void testOpenUdpEncapsulationSocketCallsSetEncapSocketOwner() throws Exception {
631         IpSecUdpEncapResponse udpEncapResp =
632                 mIpSecService.openUdpEncapsulationSocket(0, new Binder());
633 
634         FileDescriptor sockFd = udpEncapResp.fileDescriptor.getFileDescriptor();
635         ArgumentMatcher<ParcelFileDescriptor> fdMatcher = (arg) -> {
636                     try {
637                         StructStat sockStat = Os.fstat(sockFd);
638                         StructStat argStat = Os.fstat(arg.getFileDescriptor());
639 
640                         return sockStat.st_ino == argStat.st_ino
641                                 && sockStat.st_dev == argStat.st_dev;
642                     } catch (ErrnoException e) {
643                         return false;
644                     }
645                 };
646 
647         verify(mMockNetd).ipSecSetEncapSocketOwner(argThat(fdMatcher), eq(Os.getuid()));
648         mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
649     }
650 
651     @Test
testReserveNetId()652     public void testReserveNetId() {
653         int start = mIpSecService.TUN_INTF_NETID_START;
654         for (int i = 0; i < mIpSecService.TUN_INTF_NETID_RANGE; i++) {
655             assertEquals(start + i, mIpSecService.reserveNetId());
656         }
657 
658         // Check that resource exhaustion triggers an exception
659         try {
660             mIpSecService.reserveNetId();
661             fail("Did not throw error for all netIds reserved");
662         } catch (IllegalStateException expected) {
663         }
664 
665         // Now release one and try again
666         int releasedNetId =
667                 mIpSecService.TUN_INTF_NETID_START + mIpSecService.TUN_INTF_NETID_RANGE / 2;
668         mIpSecService.releaseNetId(releasedNetId);
669         assertEquals(releasedNetId, mIpSecService.reserveNetId());
670     }
671 }
672