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.networkstack.tethering;
18 
19 import static android.net.ConnectivityManager.TYPE_ETHERNET;
20 import static android.net.ConnectivityManager.TYPE_MOBILE;
21 import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
22 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
23 import static android.net.ConnectivityManager.TYPE_WIFI;
24 import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
25 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
26 
27 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
28 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
29 
30 import static org.junit.Assert.assertEquals;
31 import static org.junit.Assert.assertFalse;
32 import static org.junit.Assert.assertTrue;
33 import static org.mockito.Matchers.eq;
34 import static org.mockito.Mockito.when;
35 
36 import android.content.Context;
37 import android.content.res.Resources;
38 import android.net.util.SharedLog;
39 import android.provider.DeviceConfig;
40 import android.telephony.TelephonyManager;
41 
42 import androidx.test.filters.SmallTest;
43 import androidx.test.runner.AndroidJUnit4;
44 
45 import com.android.internal.util.test.BroadcastInterceptingContext;
46 
47 import org.junit.After;
48 import org.junit.Before;
49 import org.junit.Test;
50 import org.junit.runner.RunWith;
51 import org.mockito.Mock;
52 import org.mockito.MockitoSession;
53 import org.mockito.quality.Strictness;
54 
55 import java.util.Arrays;
56 import java.util.Iterator;
57 
58 @RunWith(AndroidJUnit4.class)
59 @SmallTest
60 public class TetheringConfigurationTest {
61     private final SharedLog mLog = new SharedLog("TetheringConfigurationTest");
62 
63     private static final String[] PROVISIONING_APP_NAME = {"some", "app"};
64     private static final String PROVISIONING_NO_UI_APP_NAME = "no_ui_app";
65     private static final String PROVISIONING_APP_RESPONSE = "app_response";
66     @Mock private Context mContext;
67     @Mock private TelephonyManager mTelephonyManager;
68     @Mock private Resources mResources;
69     @Mock private Resources mResourcesForSubId;
70     private Context mMockContext;
71     private boolean mHasTelephonyManager;
72     private boolean mEnableLegacyDhcpServer;
73     private MockitoSession mMockingSession;
74 
75     private class MockTetheringConfiguration extends TetheringConfiguration {
MockTetheringConfiguration(Context ctx, SharedLog log, int id)76         MockTetheringConfiguration(Context ctx, SharedLog log, int id) {
77             super(ctx, log, id);
78         }
79 
80         @Override
getResourcesForSubIdWrapper(Context ctx, int subId)81         protected Resources getResourcesForSubIdWrapper(Context ctx, int subId) {
82             return mResourcesForSubId;
83         }
84     }
85 
86     private class MockContext extends BroadcastInterceptingContext {
MockContext(Context base)87         MockContext(Context base) {
88             super(base);
89         }
90 
91         @Override
getResources()92         public Resources getResources() {
93             return mResources;
94         }
95 
96         @Override
getSystemService(String name)97         public Object getSystemService(String name) {
98             if (Context.TELEPHONY_SERVICE.equals(name)) {
99                 return mHasTelephonyManager ? mTelephonyManager : null;
100             }
101             return super.getSystemService(name);
102         }
103     }
104 
105     @Before
setUp()106     public void setUp() throws Exception {
107         // TODO: use a dependencies class instead of mock statics.
108         mMockingSession = mockitoSession()
109                 .initMocks(this)
110                 .mockStatic(DeviceConfig.class)
111                 .strictness(Strictness.WARN)
112                 .startMocking();
113         doReturn(null).when(
114                 () -> DeviceConfig.getProperty(eq(NAMESPACE_CONNECTIVITY),
115                 eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER)));
116 
117         when(mResources.getStringArray(R.array.config_tether_dhcp_range)).thenReturn(
118                 new String[0]);
119         when(mResources.getInteger(R.integer.config_tether_offload_poll_interval)).thenReturn(
120                 TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS);
121         when(mResources.getStringArray(R.array.config_tether_usb_regexs)).thenReturn(new String[0]);
122         when(mResources.getStringArray(R.array.config_tether_wifi_regexs))
123                 .thenReturn(new String[]{ "test_wlan\\d" });
124         when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs)).thenReturn(
125                 new String[0]);
126         when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(new int[0]);
127         when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app))
128                 .thenReturn(new String[0]);
129         when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
130                 false);
131         initializeBpfOffloadConfiguration(true, null /* unset */);
132 
133         mHasTelephonyManager = true;
134         mMockContext = new MockContext(mContext);
135         mEnableLegacyDhcpServer = false;
136     }
137 
138     @After
tearDown()139     public void tearDown() throws Exception {
140         mMockingSession.finishMocking();
141     }
142 
getTetheringConfiguration(int... legacyTetherUpstreamTypes)143     private TetheringConfiguration getTetheringConfiguration(int... legacyTetherUpstreamTypes) {
144         when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(
145                 legacyTetherUpstreamTypes);
146         return new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
147     }
148 
149     @Test
testNoTelephonyManagerMeansNoDun()150     public void testNoTelephonyManagerMeansNoDun() {
151         mHasTelephonyManager = false;
152         final TetheringConfiguration cfg = getTetheringConfiguration(
153                 new int[]{TYPE_MOBILE_DUN, TYPE_WIFI});
154         assertFalse(cfg.isDunRequired);
155         assertFalse(cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_DUN));
156         // Just to prove we haven't clobbered Wi-Fi:
157         assertTrue(cfg.preferredUpstreamIfaceTypes.contains(TYPE_WIFI));
158     }
159 
160     @Test
testDunFromTelephonyManagerMeansDun()161     public void testDunFromTelephonyManagerMeansDun() {
162         when(mTelephonyManager.isTetheringApnRequired()).thenReturn(true);
163 
164         final TetheringConfiguration cfgWifi = getTetheringConfiguration(TYPE_WIFI);
165         final TetheringConfiguration cfgMobileWifiHipri = getTetheringConfiguration(
166                 TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI);
167         final TetheringConfiguration cfgWifiDun = getTetheringConfiguration(
168                 TYPE_WIFI, TYPE_MOBILE_DUN);
169         final TetheringConfiguration cfgMobileWifiHipriDun = getTetheringConfiguration(
170                 TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI, TYPE_MOBILE_DUN);
171 
172         for (TetheringConfiguration cfg : Arrays.asList(cfgWifi, cfgMobileWifiHipri,
173                 cfgWifiDun, cfgMobileWifiHipriDun)) {
174             String msg = "config=" + cfg.toString();
175             assertTrue(msg, cfg.isDunRequired);
176             assertTrue(msg, cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_DUN));
177             assertFalse(msg, cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE));
178             assertFalse(msg, cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_HIPRI));
179             // Just to prove we haven't clobbered Wi-Fi:
180             assertTrue(msg, cfg.preferredUpstreamIfaceTypes.contains(TYPE_WIFI));
181         }
182     }
183 
184     @Test
testDunNotRequiredFromTelephonyManagerMeansNoDun()185     public void testDunNotRequiredFromTelephonyManagerMeansNoDun() {
186         when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
187 
188         final TetheringConfiguration cfgWifi = getTetheringConfiguration(TYPE_WIFI);
189         final TetheringConfiguration cfgMobileWifiHipri = getTetheringConfiguration(
190                 TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI);
191         final TetheringConfiguration cfgWifiDun = getTetheringConfiguration(
192                 TYPE_WIFI, TYPE_MOBILE_DUN);
193         final TetheringConfiguration cfgWifiMobile = getTetheringConfiguration(
194                 TYPE_WIFI, TYPE_MOBILE);
195         final TetheringConfiguration cfgWifiHipri = getTetheringConfiguration(
196                 TYPE_WIFI, TYPE_MOBILE_HIPRI);
197         final TetheringConfiguration cfgMobileWifiHipriDun = getTetheringConfiguration(
198                 TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI, TYPE_MOBILE_DUN);
199 
200         String msg;
201         // TYPE_MOBILE_DUN should be present in none of the combinations.
202         // TYPE_WIFI should not be affected.
203         for (TetheringConfiguration cfg : Arrays.asList(cfgWifi, cfgMobileWifiHipri, cfgWifiDun,
204                 cfgWifiMobile, cfgWifiHipri, cfgMobileWifiHipriDun)) {
205             msg = "config=" + cfg.toString();
206             assertFalse(msg, cfg.isDunRequired);
207             assertFalse(msg, cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_DUN));
208             assertTrue(msg, cfg.preferredUpstreamIfaceTypes.contains(TYPE_WIFI));
209         }
210 
211         for (TetheringConfiguration cfg : Arrays.asList(cfgWifi, cfgMobileWifiHipri, cfgWifiDun,
212                 cfgMobileWifiHipriDun)) {
213             msg = "config=" + cfg.toString();
214             assertTrue(msg, cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE));
215             assertTrue(msg, cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_HIPRI));
216         }
217         msg = "config=" + cfgWifiMobile.toString();
218         assertTrue(msg, cfgWifiMobile.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE));
219         assertFalse(msg, cfgWifiMobile.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_HIPRI));
220         msg = "config=" + cfgWifiHipri.toString();
221         assertFalse(msg, cfgWifiHipri.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE));
222         assertTrue(msg, cfgWifiHipri.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_HIPRI));
223 
224     }
225 
226     @Test
testNoDefinedUpstreamTypesAddsEthernet()227     public void testNoDefinedUpstreamTypesAddsEthernet() {
228         when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(new int[]{});
229         when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
230 
231         final TetheringConfiguration cfg = new TetheringConfiguration(
232                 mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
233         final Iterator<Integer> upstreamIterator = cfg.preferredUpstreamIfaceTypes.iterator();
234         assertTrue(upstreamIterator.hasNext());
235         assertEquals(TYPE_ETHERNET, upstreamIterator.next().intValue());
236         // The following is because the code always adds some kind of mobile
237         // upstream, be it DUN or, in this case where DUN is NOT required,
238         // make sure there is at least one of MOBILE or HIPRI. With the empty
239         // list of the configuration in this test, it will always add both
240         // MOBILE and HIPRI, in that order.
241         assertTrue(upstreamIterator.hasNext());
242         assertEquals(TYPE_MOBILE, upstreamIterator.next().intValue());
243         assertTrue(upstreamIterator.hasNext());
244         assertEquals(TYPE_MOBILE_HIPRI, upstreamIterator.next().intValue());
245         assertFalse(upstreamIterator.hasNext());
246     }
247 
248     @Test
testDefinedUpstreamTypesSansEthernetAddsEthernet()249     public void testDefinedUpstreamTypesSansEthernetAddsEthernet() {
250         when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(
251                 new int[]{TYPE_WIFI, TYPE_MOBILE_HIPRI});
252         when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
253 
254         final TetheringConfiguration cfg = new TetheringConfiguration(
255                 mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
256         final Iterator<Integer> upstreamIterator = cfg.preferredUpstreamIfaceTypes.iterator();
257         assertTrue(upstreamIterator.hasNext());
258         assertEquals(TYPE_ETHERNET, upstreamIterator.next().intValue());
259         assertTrue(upstreamIterator.hasNext());
260         assertEquals(TYPE_WIFI, upstreamIterator.next().intValue());
261         assertTrue(upstreamIterator.hasNext());
262         assertEquals(TYPE_MOBILE_HIPRI, upstreamIterator.next().intValue());
263         assertFalse(upstreamIterator.hasNext());
264     }
265 
266     @Test
testDefinedUpstreamTypesWithEthernetDoesNotAddEthernet()267     public void testDefinedUpstreamTypesWithEthernetDoesNotAddEthernet() {
268         when(mResources.getIntArray(R.array.config_tether_upstream_types))
269                 .thenReturn(new int[]{TYPE_WIFI, TYPE_ETHERNET, TYPE_MOBILE_HIPRI});
270         when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
271 
272         final TetheringConfiguration cfg = new TetheringConfiguration(
273                 mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
274         final Iterator<Integer> upstreamIterator = cfg.preferredUpstreamIfaceTypes.iterator();
275         assertTrue(upstreamIterator.hasNext());
276         assertEquals(TYPE_WIFI, upstreamIterator.next().intValue());
277         assertTrue(upstreamIterator.hasNext());
278         assertEquals(TYPE_ETHERNET, upstreamIterator.next().intValue());
279         assertTrue(upstreamIterator.hasNext());
280         assertEquals(TYPE_MOBILE_HIPRI, upstreamIterator.next().intValue());
281         assertFalse(upstreamIterator.hasNext());
282     }
283 
initializeBpfOffloadConfiguration( final boolean fromRes, final String fromDevConfig)284     private void initializeBpfOffloadConfiguration(
285             final boolean fromRes, final String fromDevConfig) {
286         when(mResources.getBoolean(R.bool.config_tether_enable_bpf_offload)).thenReturn(fromRes);
287         doReturn(fromDevConfig).when(
288                 () -> DeviceConfig.getProperty(eq(NAMESPACE_CONNECTIVITY),
289                 eq(TetheringConfiguration.OVERRIDE_TETHER_ENABLE_BPF_OFFLOAD)));
290     }
291 
292     @Test
testBpfOffloadEnabledByResource()293     public void testBpfOffloadEnabledByResource() {
294         initializeBpfOffloadConfiguration(true, null /* unset */);
295         final TetheringConfiguration enableByRes =
296                 new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
297         assertTrue(enableByRes.isBpfOffloadEnabled());
298     }
299 
300     @Test
testBpfOffloadEnabledByDeviceConfigOverride()301     public void testBpfOffloadEnabledByDeviceConfigOverride() {
302         for (boolean res : new boolean[]{true, false}) {
303             initializeBpfOffloadConfiguration(res, "true");
304             final TetheringConfiguration enableByDevConOverride =
305                     new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
306             assertTrue(enableByDevConOverride.isBpfOffloadEnabled());
307         }
308     }
309 
310     @Test
testBpfOffloadDisabledByResource()311     public void testBpfOffloadDisabledByResource() {
312         initializeBpfOffloadConfiguration(false, null /* unset */);
313         final TetheringConfiguration disableByRes =
314                 new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
315         assertFalse(disableByRes.isBpfOffloadEnabled());
316     }
317 
318     @Test
testBpfOffloadDisabledByDeviceConfigOverride()319     public void testBpfOffloadDisabledByDeviceConfigOverride() {
320         for (boolean res : new boolean[]{true, false}) {
321             initializeBpfOffloadConfiguration(res, "false");
322             final TetheringConfiguration disableByDevConOverride =
323                     new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
324             assertFalse(disableByDevConOverride.isBpfOffloadEnabled());
325         }
326     }
327 
328     @Test
testNewDhcpServerDisabled()329     public void testNewDhcpServerDisabled() {
330         when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
331                 true);
332         doReturn("false").when(
333                 () -> DeviceConfig.getProperty(eq(NAMESPACE_CONNECTIVITY),
334                 eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER)));
335 
336         final TetheringConfiguration enableByRes =
337                 new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
338         assertTrue(enableByRes.enableLegacyDhcpServer);
339 
340         when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
341                 false);
342         doReturn("true").when(
343                 () -> DeviceConfig.getProperty(eq(NAMESPACE_CONNECTIVITY),
344                 eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER)));
345 
346         final TetheringConfiguration enableByDevConfig =
347                 new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
348         assertTrue(enableByDevConfig.enableLegacyDhcpServer);
349     }
350 
351     @Test
testNewDhcpServerEnabled()352     public void testNewDhcpServerEnabled() {
353         when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
354                 false);
355         doReturn("false").when(
356                 () -> DeviceConfig.getProperty(eq(NAMESPACE_CONNECTIVITY),
357                 eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER)));
358 
359         final TetheringConfiguration cfg =
360                 new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
361 
362         assertFalse(cfg.enableLegacyDhcpServer);
363     }
364 
365     @Test
testOffloadIntervalByResource()366     public void testOffloadIntervalByResource() {
367         final TetheringConfiguration intervalByDefault =
368                 new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
369         assertEquals(TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS,
370                 intervalByDefault.getOffloadPollInterval());
371 
372         final int[] testOverrides = {0, 3000, -1};
373         for (final int override : testOverrides) {
374             when(mResources.getInteger(R.integer.config_tether_offload_poll_interval)).thenReturn(
375                     override);
376             final TetheringConfiguration overrideByRes =
377                     new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
378             assertEquals(override, overrideByRes.getOffloadPollInterval());
379         }
380     }
381 
382     @Test
testGetResourcesBySubId()383     public void testGetResourcesBySubId() {
384         setUpResourceForSubId();
385         final TetheringConfiguration cfg = new TetheringConfiguration(
386                 mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
387         assertTrue(cfg.provisioningApp.length == 0);
388         final int anyValidSubId = 1;
389         final MockTetheringConfiguration mockCfg =
390                 new MockTetheringConfiguration(mMockContext, mLog, anyValidSubId);
391         assertEquals(mockCfg.provisioningApp[0], PROVISIONING_APP_NAME[0]);
392         assertEquals(mockCfg.provisioningApp[1], PROVISIONING_APP_NAME[1]);
393         assertEquals(mockCfg.provisioningAppNoUi, PROVISIONING_NO_UI_APP_NAME);
394         assertEquals(mockCfg.provisioningResponse, PROVISIONING_APP_RESPONSE);
395     }
396 
setUpResourceForSubId()397     private void setUpResourceForSubId() {
398         when(mResourcesForSubId.getStringArray(
399                 R.array.config_tether_dhcp_range)).thenReturn(new String[0]);
400         when(mResourcesForSubId.getStringArray(
401                 R.array.config_tether_usb_regexs)).thenReturn(new String[0]);
402         when(mResourcesForSubId.getStringArray(
403                 R.array.config_tether_wifi_regexs)).thenReturn(new String[]{ "test_wlan\\d" });
404         when(mResourcesForSubId.getStringArray(
405                 R.array.config_tether_bluetooth_regexs)).thenReturn(new String[0]);
406         when(mResourcesForSubId.getIntArray(R.array.config_tether_upstream_types)).thenReturn(
407                 new int[0]);
408         when(mResourcesForSubId.getStringArray(
409                 R.array.config_mobile_hotspot_provision_app)).thenReturn(PROVISIONING_APP_NAME);
410         when(mResourcesForSubId.getString(R.string.config_mobile_hotspot_provision_app_no_ui))
411                 .thenReturn(PROVISIONING_NO_UI_APP_NAME);
412         when(mResourcesForSubId.getString(
413                 R.string.config_mobile_hotspot_provision_response)).thenReturn(
414                 PROVISIONING_APP_RESPONSE);
415     }
416 }
417