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