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