1/*
2 * Copyright (C) 2016 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
17package com.android.server.connectivity;
18
19import static android.net.wifi.WifiManager.IFACE_IP_MODE_LOCAL_ONLY;
20import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED;
21import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
22import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE;
23import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE;
24import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
25import static org.junit.Assert.assertEquals;
26import static org.junit.Assert.assertTrue;
27import static org.mockito.Matchers.anyBoolean;
28import static org.mockito.Matchers.anyInt;
29import static org.mockito.Matchers.anyString;
30import static org.mockito.Matchers.eq;
31import static org.mockito.Mockito.any;
32import static org.mockito.Mockito.atLeastOnce;
33import static org.mockito.Mockito.doThrow;
34import static org.mockito.Mockito.times;
35import static org.mockito.Mockito.verify;
36import static org.mockito.Mockito.verifyNoMoreInteractions;
37import static org.mockito.Mockito.when;
38
39import android.content.BroadcastReceiver;
40import android.content.Context;
41import android.content.ContextWrapper;
42import android.content.Intent;
43import android.content.IntentFilter;
44import android.content.res.Resources;
45import android.hardware.usb.UsbManager;
46import android.net.ConnectivityManager;
47import android.net.ConnectivityManager.NetworkCallback;
48import android.net.INetworkPolicyManager;
49import android.net.INetworkStatsService;
50import android.net.InterfaceConfiguration;
51import android.net.NetworkRequest;
52import android.net.wifi.WifiConfiguration;
53import android.net.wifi.WifiManager;
54import android.os.Handler;
55import android.os.INetworkManagementService;
56import android.os.PersistableBundle;
57import android.os.RemoteException;
58import android.os.test.TestLooper;
59import android.os.UserHandle;
60import android.support.test.filters.SmallTest;
61import android.support.test.runner.AndroidJUnit4;
62import android.telephony.CarrierConfigManager;
63
64import com.android.internal.util.test.BroadcastInterceptingContext;
65
66import org.junit.After;
67import org.junit.Before;
68import org.junit.Test;
69import org.junit.runner.RunWith;
70import org.mockito.Mock;
71import org.mockito.MockitoAnnotations;
72
73import java.util.ArrayList;
74import java.util.Vector;
75
76@RunWith(AndroidJUnit4.class)
77@SmallTest
78public class TetheringTest {
79    private static final String[] PROVISIONING_APP_NAME = {"some", "app"};
80
81    @Mock private Context mContext;
82    @Mock private ConnectivityManager mConnectivityManager;
83    @Mock private INetworkManagementService mNMService;
84    @Mock private INetworkStatsService mStatsService;
85    @Mock private INetworkPolicyManager mPolicyManager;
86    @Mock private MockableSystemProperties mSystemProperties;
87    @Mock private Resources mResources;
88    @Mock private UsbManager mUsbManager;
89    @Mock private WifiManager mWifiManager;
90    @Mock private CarrierConfigManager mCarrierConfigManager;
91
92    // Like so many Android system APIs, these cannot be mocked because it is marked final.
93    // We have to use the real versions.
94    private final PersistableBundle mCarrierConfig = new PersistableBundle();
95    private final TestLooper mLooper = new TestLooper();
96    private final String mTestIfname = "test_wlan0";
97
98    private Vector<Intent> mIntents;
99    private BroadcastInterceptingContext mServiceContext;
100    private BroadcastReceiver mBroadcastReceiver;
101    private Tethering mTethering;
102
103    private class MockContext extends BroadcastInterceptingContext {
104        MockContext(Context base) {
105            super(base);
106        }
107
108        @Override
109        public Resources getResources() { return mResources; }
110
111        @Override
112        public Object getSystemService(String name) {
113            if (Context.CONNECTIVITY_SERVICE.equals(name)) return mConnectivityManager;
114            if (Context.WIFI_SERVICE.equals(name)) return mWifiManager;
115            return super.getSystemService(name);
116        }
117    }
118
119    @Before
120    public void setUp() throws Exception {
121        MockitoAnnotations.initMocks(this);
122        when(mResources.getStringArray(com.android.internal.R.array.config_tether_dhcp_range))
123                .thenReturn(new String[0]);
124        when(mResources.getStringArray(com.android.internal.R.array.config_tether_usb_regexs))
125                .thenReturn(new String[0]);
126        when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_regexs))
127                .thenReturn(new String[]{ "test_wlan\\d" });
128        when(mResources.getStringArray(com.android.internal.R.array.config_tether_bluetooth_regexs))
129                .thenReturn(new String[0]);
130        when(mResources.getIntArray(com.android.internal.R.array.config_tether_upstream_types))
131                .thenReturn(new int[0]);
132        when(mNMService.listInterfaces())
133                .thenReturn(new String[]{ "test_rmnet_data0", mTestIfname });
134        when(mNMService.getInterfaceConfig(anyString()))
135                .thenReturn(new InterfaceConfiguration());
136
137        mServiceContext = new MockContext(mContext);
138        mIntents = new Vector<>();
139        mBroadcastReceiver = new BroadcastReceiver() {
140            @Override
141            public void onReceive(Context context, Intent intent) {
142                mIntents.addElement(intent);
143            }
144        };
145        mServiceContext.registerReceiver(mBroadcastReceiver,
146                new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED));
147        mTethering = new Tethering(mServiceContext, mNMService, mStatsService, mPolicyManager,
148                                   mLooper.getLooper(), mSystemProperties);
149    }
150
151    @After
152    public void tearDown() {
153        mServiceContext.unregisterReceiver(mBroadcastReceiver);
154    }
155
156    private void setupForRequiredProvisioning() {
157        // Produce some acceptable looking provision app setting if requested.
158        when(mResources.getStringArray(
159                com.android.internal.R.array.config_mobile_hotspot_provision_app))
160                .thenReturn(PROVISIONING_APP_NAME);
161        // Don't disable tethering provisioning unless requested.
162        when(mSystemProperties.getBoolean(eq(Tethering.DISABLE_PROVISIONING_SYSPROP_KEY),
163                                          anyBoolean())).thenReturn(false);
164        // Act like the CarrierConfigManager is present and ready unless told otherwise.
165        when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
166                .thenReturn(mCarrierConfigManager);
167        when(mCarrierConfigManager.getConfig()).thenReturn(mCarrierConfig);
168        mCarrierConfig.putBoolean(CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL, true);
169    }
170
171    @Test
172    public void canRequireProvisioning() {
173        setupForRequiredProvisioning();
174        assertTrue(mTethering.isTetherProvisioningRequired());
175    }
176
177    @Test
178    public void toleratesCarrierConfigManagerMissing() {
179        setupForRequiredProvisioning();
180        when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
181                .thenReturn(null);
182        // Couldn't get the CarrierConfigManager, but still had a declared provisioning app.
183        // We therefore still require provisioning.
184        assertTrue(mTethering.isTetherProvisioningRequired());
185    }
186
187    @Test
188    public void toleratesCarrierConfigMissing() {
189        setupForRequiredProvisioning();
190        when(mCarrierConfigManager.getConfig()).thenReturn(null);
191        // We still have a provisioning app configured, so still require provisioning.
192        assertTrue(mTethering.isTetherProvisioningRequired());
193    }
194
195    @Test
196    public void provisioningNotRequiredWhenAppNotFound() {
197        setupForRequiredProvisioning();
198        when(mResources.getStringArray(
199                com.android.internal.R.array.config_mobile_hotspot_provision_app))
200                .thenReturn(null);
201        assertTrue(!mTethering.isTetherProvisioningRequired());
202        when(mResources.getStringArray(
203                com.android.internal.R.array.config_mobile_hotspot_provision_app))
204                .thenReturn(new String[] {"malformedApp"});
205        assertTrue(!mTethering.isTetherProvisioningRequired());
206    }
207
208    private void sendWifiApStateChanged(int state) {
209        final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
210        intent.putExtra(EXTRA_WIFI_AP_STATE, state);
211        mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
212    }
213
214    private void sendWifiApStateChanged(int state, String ifname, int ipmode) {
215        final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
216        intent.putExtra(EXTRA_WIFI_AP_STATE, state);
217        intent.putExtra(EXTRA_WIFI_AP_INTERFACE_NAME, ifname);
218        intent.putExtra(EXTRA_WIFI_AP_MODE, ipmode);
219        mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
220    }
221
222    private void verifyInterfaceServingModeStarted() throws Exception {
223        verify(mNMService, times(1)).getInterfaceConfig(mTestIfname);
224        verify(mNMService, times(1))
225                .setInterfaceConfig(eq(mTestIfname), any(InterfaceConfiguration.class));
226        verify(mNMService, times(1)).tetherInterface(mTestIfname);
227    }
228
229    private void verifyTetheringBroadcast(String ifname, String whichExtra) {
230        // Verify that ifname is in the whichExtra array of the tether state changed broadcast.
231        final Intent bcast = mIntents.get(0);
232        assertEquals(ConnectivityManager.ACTION_TETHER_STATE_CHANGED, bcast.getAction());
233        final ArrayList<String> ifnames = bcast.getStringArrayListExtra(whichExtra);
234        assertTrue(ifnames.contains(ifname));
235        mIntents.remove(bcast);
236    }
237
238    public void failingLocalOnlyHotspotLegacyApBroadcast(
239            boolean emulateInterfaceStatusChanged) throws Exception {
240        when(mConnectivityManager.isTetheringSupported()).thenReturn(true);
241
242        // Emulate externally-visible WifiManager effects, causing the
243        // per-interface state machine to start up, and telling us that
244        // hotspot mode is to be started.
245        if (emulateInterfaceStatusChanged) {
246            mTethering.interfaceStatusChanged(mTestIfname, true);
247        }
248        sendWifiApStateChanged(WIFI_AP_STATE_ENABLED);
249        mLooper.dispatchAll();
250
251        // If, and only if, Tethering received an interface status changed
252        // then it creates a TetherInterfaceStateMachine and sends out a
253        // broadcast indicating that the interface is "available".
254        if (emulateInterfaceStatusChanged) {
255            verify(mConnectivityManager, atLeastOnce()).isTetheringSupported();
256            verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_AVAILABLE_TETHER);
257        }
258        verifyNoMoreInteractions(mConnectivityManager);
259        verifyNoMoreInteractions(mNMService);
260        verifyNoMoreInteractions(mWifiManager);
261    }
262
263    @Test
264    public void failingLocalOnlyHotspotLegacyApBroadcastWithIfaceStatusChanged() throws Exception {
265        failingLocalOnlyHotspotLegacyApBroadcast(true);
266    }
267
268    @Test
269    public void failingLocalOnlyHotspotLegacyApBroadcastSansIfaceStatusChanged() throws Exception {
270        failingLocalOnlyHotspotLegacyApBroadcast(false);
271    }
272
273    public void workingLocalOnlyHotspotEnrichedApBroadcast(
274            boolean emulateInterfaceStatusChanged) throws Exception {
275        when(mConnectivityManager.isTetheringSupported()).thenReturn(true);
276
277        // Emulate externally-visible WifiManager effects, causing the
278        // per-interface state machine to start up, and telling us that
279        // hotspot mode is to be started.
280        if (emulateInterfaceStatusChanged) {
281            mTethering.interfaceStatusChanged(mTestIfname, true);
282        }
283        sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, mTestIfname, IFACE_IP_MODE_LOCAL_ONLY);
284        mLooper.dispatchAll();
285
286        verifyInterfaceServingModeStarted();
287        verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_AVAILABLE_TETHER);
288        verify(mNMService, times(1)).setIpForwardingEnabled(true);
289        verify(mNMService, times(1)).startTethering(any(String[].class));
290        verifyNoMoreInteractions(mNMService);
291        verify(mWifiManager).updateInterfaceIpState(
292                mTestIfname, WifiManager.IFACE_IP_MODE_LOCAL_ONLY);
293        verifyNoMoreInteractions(mWifiManager);
294        verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_ACTIVE_LOCAL_ONLY);
295        // UpstreamNetworkMonitor will be started, and will register two callbacks:
296        // a "listen all" and a "track default".
297        verify(mConnectivityManager, times(1)).registerNetworkCallback(
298                any(NetworkRequest.class), any(NetworkCallback.class), any(Handler.class));
299        verify(mConnectivityManager, times(1)).registerDefaultNetworkCallback(
300                any(NetworkCallback.class), any(Handler.class));
301        // TODO: Figure out why this isn't exactly once, for sendTetherStateChangedBroadcast().
302        verify(mConnectivityManager, atLeastOnce()).isTetheringSupported();
303        verifyNoMoreInteractions(mConnectivityManager);
304
305        // Emulate externally-visible WifiManager effects, when hotspot mode
306        // is being torn down.
307        sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED);
308        mTethering.interfaceRemoved(mTestIfname);
309        mLooper.dispatchAll();
310
311        verify(mNMService, times(1)).untetherInterface(mTestIfname);
312        // TODO: Why is {g,s}etInterfaceConfig() called more than once?
313        verify(mNMService, atLeastOnce()).getInterfaceConfig(mTestIfname);
314        verify(mNMService, atLeastOnce())
315                .setInterfaceConfig(eq(mTestIfname), any(InterfaceConfiguration.class));
316        verify(mNMService, times(1)).stopTethering();
317        verify(mNMService, times(1)).setIpForwardingEnabled(false);
318        verifyNoMoreInteractions(mNMService);
319        verifyNoMoreInteractions(mWifiManager);
320        // Asking for the last error after the per-interface state machine
321        // has been reaped yields an unknown interface error.
322        assertEquals(ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE,
323                mTethering.getLastTetherError(mTestIfname));
324    }
325
326    @Test
327    public void workingLocalOnlyHotspotEnrichedApBroadcastWithIfaceChanged() throws Exception {
328        workingLocalOnlyHotspotEnrichedApBroadcast(true);
329    }
330
331    @Test
332    public void workingLocalOnlyHotspotEnrichedApBroadcastSansIfaceChanged() throws Exception {
333        workingLocalOnlyHotspotEnrichedApBroadcast(false);
334    }
335
336    // TODO: Test with and without interfaceStatusChanged().
337    @Test
338    public void failingWifiTetheringLegacyApBroadcast() throws Exception {
339        when(mConnectivityManager.isTetheringSupported()).thenReturn(true);
340        when(mWifiManager.startSoftAp(any(WifiConfiguration.class))).thenReturn(true);
341
342        // Emulate pressing the WiFi tethering button.
343        mTethering.startTethering(ConnectivityManager.TETHERING_WIFI, null, false);
344        mLooper.dispatchAll();
345        verify(mWifiManager, times(1)).startSoftAp(null);
346        verifyNoMoreInteractions(mWifiManager);
347        verifyNoMoreInteractions(mConnectivityManager);
348        verifyNoMoreInteractions(mNMService);
349
350        // Emulate externally-visible WifiManager effects, causing the
351        // per-interface state machine to start up, and telling us that
352        // tethering mode is to be started.
353        mTethering.interfaceStatusChanged(mTestIfname, true);
354        sendWifiApStateChanged(WIFI_AP_STATE_ENABLED);
355        mLooper.dispatchAll();
356
357        verify(mConnectivityManager, atLeastOnce()).isTetheringSupported();
358        verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_AVAILABLE_TETHER);
359        verifyNoMoreInteractions(mConnectivityManager);
360        verifyNoMoreInteractions(mNMService);
361        verifyNoMoreInteractions(mWifiManager);
362    }
363
364    // TODO: Test with and without interfaceStatusChanged().
365    @Test
366    public void workingWifiTetheringEnrichedApBroadcast() throws Exception {
367        when(mConnectivityManager.isTetheringSupported()).thenReturn(true);
368        when(mWifiManager.startSoftAp(any(WifiConfiguration.class))).thenReturn(true);
369
370        // Emulate pressing the WiFi tethering button.
371        mTethering.startTethering(ConnectivityManager.TETHERING_WIFI, null, false);
372        mLooper.dispatchAll();
373        verify(mWifiManager, times(1)).startSoftAp(null);
374        verifyNoMoreInteractions(mWifiManager);
375        verifyNoMoreInteractions(mConnectivityManager);
376        verifyNoMoreInteractions(mNMService);
377
378        // Emulate externally-visible WifiManager effects, causing the
379        // per-interface state machine to start up, and telling us that
380        // tethering mode is to be started.
381        mTethering.interfaceStatusChanged(mTestIfname, true);
382        sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, mTestIfname, IFACE_IP_MODE_TETHERED);
383        mLooper.dispatchAll();
384
385        verifyInterfaceServingModeStarted();
386        verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_AVAILABLE_TETHER);
387        verify(mNMService, times(1)).setIpForwardingEnabled(true);
388        verify(mNMService, times(1)).startTethering(any(String[].class));
389        verifyNoMoreInteractions(mNMService);
390        verify(mWifiManager).updateInterfaceIpState(
391                mTestIfname, WifiManager.IFACE_IP_MODE_TETHERED);
392        verifyNoMoreInteractions(mWifiManager);
393        verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_ACTIVE_TETHER);
394        // UpstreamNetworkMonitor will be started, and will register two callbacks:
395        // a "listen all" and a "track default".
396        verify(mConnectivityManager, times(1)).registerNetworkCallback(
397                any(NetworkRequest.class), any(NetworkCallback.class), any(Handler.class));
398        verify(mConnectivityManager, times(1)).registerDefaultNetworkCallback(
399                any(NetworkCallback.class), any(Handler.class));
400        // In tethering mode, in the default configuration, an explicit request
401        // for a mobile network is also made.
402        verify(mConnectivityManager, atLeastOnce()).getNetworkInfo(anyInt());
403        verify(mConnectivityManager, times(1)).requestNetwork(
404                any(NetworkRequest.class), any(NetworkCallback.class), eq(0), anyInt(),
405                any(Handler.class));
406        // TODO: Figure out why this isn't exactly once, for sendTetherStateChangedBroadcast().
407        verify(mConnectivityManager, atLeastOnce()).isTetheringSupported();
408        verifyNoMoreInteractions(mConnectivityManager);
409
410        /////
411        // We do not currently emulate any upstream being found.
412        //
413        // This is why there are no calls to verify mNMService.enableNat() or
414        // mNMService.startInterfaceForwarding().
415        /////
416
417        // Emulate pressing the WiFi tethering button.
418        mTethering.stopTethering(ConnectivityManager.TETHERING_WIFI);
419        mLooper.dispatchAll();
420        verify(mWifiManager, times(1)).stopSoftAp();
421        verifyNoMoreInteractions(mWifiManager);
422        verifyNoMoreInteractions(mConnectivityManager);
423        verifyNoMoreInteractions(mNMService);
424
425        // Emulate externally-visible WifiManager effects, when tethering mode
426        // is being torn down.
427        sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED);
428        mTethering.interfaceRemoved(mTestIfname);
429        mLooper.dispatchAll();
430
431        verify(mNMService, times(1)).untetherInterface(mTestIfname);
432        // TODO: Why is {g,s}etInterfaceConfig() called more than once?
433        verify(mNMService, atLeastOnce()).getInterfaceConfig(mTestIfname);
434        verify(mNMService, atLeastOnce())
435                .setInterfaceConfig(eq(mTestIfname), any(InterfaceConfiguration.class));
436        verify(mNMService, times(1)).stopTethering();
437        verify(mNMService, times(1)).setIpForwardingEnabled(false);
438        verifyNoMoreInteractions(mNMService);
439        verifyNoMoreInteractions(mWifiManager);
440        // Asking for the last error after the per-interface state machine
441        // has been reaped yields an unknown interface error.
442        assertEquals(ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE,
443                mTethering.getLastTetherError(mTestIfname));
444    }
445
446    // TODO: Test with and without interfaceStatusChanged().
447    @Test
448    public void failureEnablingIpForwarding() throws Exception {
449        when(mConnectivityManager.isTetheringSupported()).thenReturn(true);
450        when(mWifiManager.startSoftAp(any(WifiConfiguration.class))).thenReturn(true);
451        doThrow(new RemoteException()).when(mNMService).setIpForwardingEnabled(true);
452
453        // Emulate pressing the WiFi tethering button.
454        mTethering.startTethering(ConnectivityManager.TETHERING_WIFI, null, false);
455        mLooper.dispatchAll();
456        verify(mWifiManager, times(1)).startSoftAp(null);
457        verifyNoMoreInteractions(mWifiManager);
458        verifyNoMoreInteractions(mConnectivityManager);
459        verifyNoMoreInteractions(mNMService);
460
461        // Emulate externally-visible WifiManager effects, causing the
462        // per-interface state machine to start up, and telling us that
463        // tethering mode is to be started.
464        mTethering.interfaceStatusChanged(mTestIfname, true);
465        sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, mTestIfname, IFACE_IP_MODE_TETHERED);
466        mLooper.dispatchAll();
467
468        // We verify get/set called twice here: once for setup and once during
469        // teardown because all events happen over the course of the single
470        // dispatchAll() above.
471        verify(mNMService, times(2)).getInterfaceConfig(mTestIfname);
472        verify(mNMService, times(2))
473                .setInterfaceConfig(eq(mTestIfname), any(InterfaceConfiguration.class));
474        verify(mNMService, times(1)).tetherInterface(mTestIfname);
475        verify(mWifiManager).updateInterfaceIpState(
476                mTestIfname, WifiManager.IFACE_IP_MODE_TETHERED);
477        verify(mConnectivityManager, atLeastOnce()).isTetheringSupported();
478        verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_AVAILABLE_TETHER);
479        // This is called, but will throw.
480        verify(mNMService, times(1)).setIpForwardingEnabled(true);
481        // This never gets called because of the exception thrown above.
482        verify(mNMService, times(0)).startTethering(any(String[].class));
483        // When the master state machine transitions to an error state it tells
484        // downstream interfaces, which causes us to tell Wi-Fi about the error
485        // so it can take down AP mode.
486        verify(mNMService, times(1)).untetherInterface(mTestIfname);
487        verify(mWifiManager).updateInterfaceIpState(
488                mTestIfname, WifiManager.IFACE_IP_MODE_CONFIGURATION_ERROR);
489
490        verifyNoMoreInteractions(mWifiManager);
491        verifyNoMoreInteractions(mConnectivityManager);
492        verifyNoMoreInteractions(mNMService);
493    }
494
495    // TODO: Test that a request for hotspot mode doesn't interfere with an
496    // already operating tethering mode interface.
497}
498