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