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.content.Context.TELEPHONY_SERVICE;
20 import static android.net.ConnectivityManager.TYPE_ETHERNET;
21 import static android.net.ConnectivityManager.TYPE_MOBILE;
22 import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
23 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
24 import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
25 
26 import android.content.Context;
27 import android.content.res.Resources;
28 import android.net.TetheringConfigurationParcel;
29 import android.net.util.SharedLog;
30 import android.provider.DeviceConfig;
31 import android.telephony.SubscriptionManager;
32 import android.telephony.TelephonyManager;
33 import android.text.TextUtils;
34 
35 import com.android.internal.annotations.VisibleForTesting;
36 
37 import java.io.PrintWriter;
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 import java.util.Collection;
41 import java.util.StringJoiner;
42 
43 
44 /**
45  * A utility class to encapsulate the various tethering configuration elements.
46  *
47  * This configuration data includes elements describing upstream properties
48  * (preferred and required types of upstream connectivity as well as default
49  * DNS servers to use if none are available) and downstream properties (such
50  * as regular expressions use to match suitable downstream interfaces and the
51  * DHCPv4 ranges to use).
52  *
53  * @hide
54  */
55 public class TetheringConfiguration {
56     private static final String TAG = TetheringConfiguration.class.getSimpleName();
57 
58     private static final String[] EMPTY_STRING_ARRAY = new String[0];
59 
60     // Default ranges used for the legacy DHCP server.
61     // USB is  192.168.42.1 and 255.255.255.0
62     // Wifi is 192.168.43.1 and 255.255.255.0
63     // BT is limited to max default of 5 connections. 192.168.44.1 to 192.168.48.1
64     // with 255.255.255.0
65     // P2P is 192.168.49.1 and 255.255.255.0
66     private static final String[] LEGACY_DHCP_DEFAULT_RANGE = {
67         "192.168.42.2", "192.168.42.254", "192.168.43.2", "192.168.43.254",
68         "192.168.44.2", "192.168.44.254", "192.168.45.2", "192.168.45.254",
69         "192.168.46.2", "192.168.46.254", "192.168.47.2", "192.168.47.254",
70         "192.168.48.2", "192.168.48.254", "192.168.49.2", "192.168.49.254",
71     };
72 
73     private static final String[] DEFAULT_IPV4_DNS = {"8.8.4.4", "8.8.8.8"};
74 
75     /**
76      * Override enabling BPF offload configuration for tethering.
77      */
78     public static final String OVERRIDE_TETHER_ENABLE_BPF_OFFLOAD =
79             "override_tether_enable_bpf_offload";
80 
81     /**
82      * Use the old dnsmasq DHCP server for tethering instead of the framework implementation.
83      */
84     public static final String TETHER_ENABLE_LEGACY_DHCP_SERVER =
85             "tether_enable_legacy_dhcp_server";
86 
87     /**
88      * Default value that used to periodic polls tether offload stats from tethering offload HAL
89      * to make the data warnings work.
90      */
91     public static final int DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS = 5000;
92 
93     public final String[] tetherableUsbRegexs;
94     public final String[] tetherableWifiRegexs;
95     public final String[] tetherableWigigRegexs;
96     public final String[] tetherableWifiP2pRegexs;
97     public final String[] tetherableBluetoothRegexs;
98     public final String[] tetherableNcmRegexs;
99     public final boolean isDunRequired;
100     public final boolean chooseUpstreamAutomatically;
101     public final Collection<Integer> preferredUpstreamIfaceTypes;
102     public final String[] legacyDhcpRanges;
103     public final String[] defaultIPv4DNS;
104     public final boolean enableLegacyDhcpServer;
105 
106     public final String[] provisioningApp;
107     public final String provisioningAppNoUi;
108     public final int provisioningCheckPeriod;
109     public final String provisioningResponse;
110 
111     public final int activeDataSubId;
112 
113     private final int mOffloadPollInterval;
114     // TODO: Add to TetheringConfigurationParcel if required.
115     private final boolean mEnableBpfOffload;
116 
TetheringConfiguration(Context ctx, SharedLog log, int id)117     public TetheringConfiguration(Context ctx, SharedLog log, int id) {
118         final SharedLog configLog = log.forSubComponent("config");
119 
120         activeDataSubId = id;
121         Resources res = getResources(ctx, activeDataSubId);
122 
123         tetherableUsbRegexs = getResourceStringArray(res, R.array.config_tether_usb_regexs);
124         tetherableNcmRegexs = getResourceStringArray(res, R.array.config_tether_ncm_regexs);
125         // TODO: Evaluate deleting this altogether now that Wi-Fi always passes
126         // us an interface name. Careful consideration needs to be given to
127         // implications for Settings and for provisioning checks.
128         tetherableWifiRegexs = getResourceStringArray(res, R.array.config_tether_wifi_regexs);
129         tetherableWigigRegexs = getResourceStringArray(res, R.array.config_tether_wigig_regexs);
130         tetherableWifiP2pRegexs = getResourceStringArray(
131                 res, R.array.config_tether_wifi_p2p_regexs);
132         tetherableBluetoothRegexs = getResourceStringArray(
133                 res, R.array.config_tether_bluetooth_regexs);
134 
135         isDunRequired = checkDunRequired(ctx);
136 
137         chooseUpstreamAutomatically = getResourceBoolean(
138                 res, R.bool.config_tether_upstream_automatic, false /** defaultValue */);
139         preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(res, isDunRequired);
140 
141         legacyDhcpRanges = getLegacyDhcpRanges(res);
142         defaultIPv4DNS = copy(DEFAULT_IPV4_DNS);
143         mEnableBpfOffload = getEnableBpfOffload(res);
144         enableLegacyDhcpServer = getEnableLegacyDhcpServer(res);
145 
146         provisioningApp = getResourceStringArray(res, R.array.config_mobile_hotspot_provision_app);
147         provisioningAppNoUi = getResourceString(res,
148                 R.string.config_mobile_hotspot_provision_app_no_ui);
149         provisioningCheckPeriod = getResourceInteger(res,
150                 R.integer.config_mobile_hotspot_provision_check_period,
151                 0 /* No periodic re-check */);
152         provisioningResponse = getResourceString(res,
153                 R.string.config_mobile_hotspot_provision_response);
154 
155         mOffloadPollInterval = getResourceInteger(res,
156                 R.integer.config_tether_offload_poll_interval,
157                 DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS);
158 
159         configLog.log(toString());
160     }
161 
162     /** Check whether input interface belong to usb.*/
isUsb(String iface)163     public boolean isUsb(String iface) {
164         return matchesDownstreamRegexs(iface, tetherableUsbRegexs);
165     }
166 
167     /** Check whether input interface belong to wifi.*/
isWifi(String iface)168     public boolean isWifi(String iface) {
169         return matchesDownstreamRegexs(iface, tetherableWifiRegexs);
170     }
171 
172     /** Check whether input interface belong to wigig.*/
isWigig(String iface)173     public boolean isWigig(String iface) {
174         return matchesDownstreamRegexs(iface, tetherableWigigRegexs);
175     }
176 
177     /** Check whether this interface is Wifi P2P interface. */
isWifiP2p(String iface)178     public boolean isWifiP2p(String iface) {
179         return matchesDownstreamRegexs(iface, tetherableWifiP2pRegexs);
180     }
181 
182     /** Check whether using legacy mode for wifi P2P. */
isWifiP2pLegacyTetheringMode()183     public boolean isWifiP2pLegacyTetheringMode() {
184         return (tetherableWifiP2pRegexs == null || tetherableWifiP2pRegexs.length == 0);
185     }
186 
187     /** Check whether input interface belong to bluetooth.*/
isBluetooth(String iface)188     public boolean isBluetooth(String iface) {
189         return matchesDownstreamRegexs(iface, tetherableBluetoothRegexs);
190     }
191 
192     /** Check if interface is ncm */
isNcm(String iface)193     public boolean isNcm(String iface) {
194         return matchesDownstreamRegexs(iface, tetherableNcmRegexs);
195     }
196 
197     /** Check whether no ui entitlement application is available.*/
hasMobileHotspotProvisionApp()198     public boolean hasMobileHotspotProvisionApp() {
199         return !TextUtils.isEmpty(provisioningAppNoUi);
200     }
201 
202     /** Does the dumping.*/
dump(PrintWriter pw)203     public void dump(PrintWriter pw) {
204         pw.print("activeDataSubId: ");
205         pw.println(activeDataSubId);
206 
207         dumpStringArray(pw, "tetherableUsbRegexs", tetherableUsbRegexs);
208         dumpStringArray(pw, "tetherableWifiRegexs", tetherableWifiRegexs);
209         dumpStringArray(pw, "tetherableWifiP2pRegexs", tetherableWifiP2pRegexs);
210         dumpStringArray(pw, "tetherableBluetoothRegexs", tetherableBluetoothRegexs);
211         dumpStringArray(pw, "tetherableNcmRegexs", tetherableNcmRegexs);
212 
213         pw.print("isDunRequired: ");
214         pw.println(isDunRequired);
215 
216         pw.print("chooseUpstreamAutomatically: ");
217         pw.println(chooseUpstreamAutomatically);
218         pw.print("legacyPreredUpstreamIfaceTypes: ");
219         pw.println(Arrays.toString(toIntArray(preferredUpstreamIfaceTypes)));
220 
221         dumpStringArray(pw, "legacyDhcpRanges", legacyDhcpRanges);
222         dumpStringArray(pw, "defaultIPv4DNS", defaultIPv4DNS);
223 
224         pw.print("offloadPollInterval: ");
225         pw.println(mOffloadPollInterval);
226 
227         dumpStringArray(pw, "provisioningApp", provisioningApp);
228         pw.print("provisioningAppNoUi: ");
229         pw.println(provisioningAppNoUi);
230 
231         pw.print("enableBpfOffload: ");
232         pw.println(mEnableBpfOffload);
233 
234         pw.print("enableLegacyDhcpServer: ");
235         pw.println(enableLegacyDhcpServer);
236     }
237 
238     /** Returns the string representation of this object.*/
toString()239     public String toString() {
240         final StringJoiner sj = new StringJoiner(" ");
241         sj.add(String.format("activeDataSubId:%d", activeDataSubId));
242         sj.add(String.format("tetherableUsbRegexs:%s", makeString(tetherableUsbRegexs)));
243         sj.add(String.format("tetherableWifiRegexs:%s", makeString(tetherableWifiRegexs)));
244         sj.add(String.format("tetherableWifiP2pRegexs:%s", makeString(tetherableWifiP2pRegexs)));
245         sj.add(String.format("tetherableBluetoothRegexs:%s",
246                 makeString(tetherableBluetoothRegexs)));
247         sj.add(String.format("isDunRequired:%s", isDunRequired));
248         sj.add(String.format("chooseUpstreamAutomatically:%s", chooseUpstreamAutomatically));
249         sj.add(String.format("offloadPollInterval:%d", mOffloadPollInterval));
250         sj.add(String.format("preferredUpstreamIfaceTypes:%s",
251                 toIntArray(preferredUpstreamIfaceTypes)));
252         sj.add(String.format("provisioningApp:%s", makeString(provisioningApp)));
253         sj.add(String.format("provisioningAppNoUi:%s", provisioningAppNoUi));
254         sj.add(String.format("enableBpfOffload:%s", mEnableBpfOffload));
255         sj.add(String.format("enableLegacyDhcpServer:%s", enableLegacyDhcpServer));
256         return String.format("TetheringConfiguration{%s}", sj.toString());
257     }
258 
dumpStringArray(PrintWriter pw, String label, String[] values)259     private static void dumpStringArray(PrintWriter pw, String label, String[] values) {
260         pw.print(label);
261         pw.print(": ");
262 
263         if (values != null) {
264             final StringJoiner sj = new StringJoiner(", ", "[", "]");
265             for (String value : values) sj.add(value);
266             pw.print(sj.toString());
267         } else {
268             pw.print("null");
269         }
270 
271         pw.println();
272     }
273 
makeString(String[] strings)274     private static String makeString(String[] strings) {
275         if (strings == null) return "null";
276         final StringJoiner sj = new StringJoiner(",", "[", "]");
277         for (String s : strings) sj.add(s);
278         return sj.toString();
279     }
280 
281     /** Check whether dun is required. */
checkDunRequired(Context ctx)282     public static boolean checkDunRequired(Context ctx) {
283         final TelephonyManager tm = (TelephonyManager) ctx.getSystemService(TELEPHONY_SERVICE);
284         // TelephonyManager would uses the active data subscription, which should be the one used
285         // by tethering.
286         return (tm != null) ? tm.isTetheringApnRequired() : false;
287     }
288 
getOffloadPollInterval()289     public int getOffloadPollInterval() {
290         return mOffloadPollInterval;
291     }
292 
isBpfOffloadEnabled()293     public boolean isBpfOffloadEnabled() {
294         return mEnableBpfOffload;
295     }
296 
getUpstreamIfaceTypes(Resources res, boolean dunRequired)297     private static Collection<Integer> getUpstreamIfaceTypes(Resources res, boolean dunRequired) {
298         final int[] ifaceTypes = res.getIntArray(R.array.config_tether_upstream_types);
299         final ArrayList<Integer> upstreamIfaceTypes = new ArrayList<>(ifaceTypes.length);
300         for (int i : ifaceTypes) {
301             switch (i) {
302                 case TYPE_MOBILE:
303                 case TYPE_MOBILE_HIPRI:
304                     if (dunRequired) continue;
305                     break;
306                 case TYPE_MOBILE_DUN:
307                     if (!dunRequired) continue;
308                     break;
309             }
310             upstreamIfaceTypes.add(i);
311         }
312 
313         // Fix up upstream interface types for DUN or mobile. NOTE: independent
314         // of the value of |dunRequired|, cell data of one form or another is
315         // *always* an upstream, regardless of the upstream interface types
316         // specified by configuration resources.
317         if (dunRequired) {
318             appendIfNotPresent(upstreamIfaceTypes, TYPE_MOBILE_DUN);
319         } else {
320             // Do not modify if a cellular interface type is already present in the
321             // upstream interface types. Add TYPE_MOBILE and TYPE_MOBILE_HIPRI if no
322             // cellular interface types are found in the upstream interface types.
323             // This preserves backwards compatibility and prevents the DUN and default
324             // mobile types incorrectly appearing together, which could happen on
325             // previous releases in the common case where checkDunRequired returned
326             // DUN_UNSPECIFIED.
327             if (!containsOneOf(upstreamIfaceTypes, TYPE_MOBILE, TYPE_MOBILE_HIPRI)) {
328                 upstreamIfaceTypes.add(TYPE_MOBILE);
329                 upstreamIfaceTypes.add(TYPE_MOBILE_HIPRI);
330             }
331         }
332 
333         // Always make sure our good friend Ethernet is present.
334         // TODO: consider unilaterally forcing this at the front.
335         prependIfNotPresent(upstreamIfaceTypes, TYPE_ETHERNET);
336 
337         return upstreamIfaceTypes;
338     }
339 
matchesDownstreamRegexs(String iface, String[] regexs)340     private static boolean matchesDownstreamRegexs(String iface, String[] regexs) {
341         for (String regex : regexs) {
342             if (iface.matches(regex)) return true;
343         }
344         return false;
345     }
346 
getLegacyDhcpRanges(Resources res)347     private static String[] getLegacyDhcpRanges(Resources res) {
348         final String[] fromResource = getResourceStringArray(res, R.array.config_tether_dhcp_range);
349         if ((fromResource.length > 0) && (fromResource.length % 2 == 0)) {
350             return fromResource;
351         }
352         return copy(LEGACY_DHCP_DEFAULT_RANGE);
353     }
354 
getResourceString(Resources res, final int resId)355     private static String getResourceString(Resources res, final int resId) {
356         try {
357             return res.getString(resId);
358         } catch (Resources.NotFoundException e) {
359             return "";
360         }
361     }
362 
getResourceBoolean(Resources res, int resId, boolean defaultValue)363     private static boolean getResourceBoolean(Resources res, int resId, boolean defaultValue) {
364         try {
365             return res.getBoolean(resId);
366         } catch (Resources.NotFoundException e404) {
367             return defaultValue;
368         }
369     }
370 
getResourceStringArray(Resources res, int resId)371     private static String[] getResourceStringArray(Resources res, int resId) {
372         try {
373             final String[] strArray = res.getStringArray(resId);
374             return (strArray != null) ? strArray : EMPTY_STRING_ARRAY;
375         } catch (Resources.NotFoundException e404) {
376             return EMPTY_STRING_ARRAY;
377         }
378     }
379 
getResourceInteger(Resources res, int resId, int defaultValue)380     private static int getResourceInteger(Resources res, int resId, int defaultValue) {
381         try {
382             return res.getInteger(resId);
383         } catch (Resources.NotFoundException e404) {
384             return defaultValue;
385         }
386     }
387 
getEnableBpfOffload(final Resources res)388     private boolean getEnableBpfOffload(final Resources res) {
389         // Get BPF offload config
390         // Priority 1: Device config
391         // Priority 2: Resource config
392         // Priority 3: Default value
393         final boolean defaultValue = getResourceBoolean(
394                 res, R.bool.config_tether_enable_bpf_offload, true /** default value */);
395 
396         return getDeviceConfigBoolean(OVERRIDE_TETHER_ENABLE_BPF_OFFLOAD, defaultValue);
397     }
398 
getEnableLegacyDhcpServer(final Resources res)399     private boolean getEnableLegacyDhcpServer(final Resources res) {
400         return getResourceBoolean(
401                 res, R.bool.config_tether_enable_legacy_dhcp_server, false /** defaultValue */)
402                 || getDeviceConfigBoolean(
403                 TETHER_ENABLE_LEGACY_DHCP_SERVER, false /** defaultValue */);
404     }
405 
getDeviceConfigBoolean(final String name, final boolean defaultValue)406     private boolean getDeviceConfigBoolean(final String name, final boolean defaultValue) {
407         // Due to the limitation of static mock for testing, using #getDeviceConfigProperty instead
408         // of DeviceConfig#getBoolean. If using #getBoolean here, the test can't know that the
409         // returned boolean value comes from device config or default value (because of null
410         // property string). See the test case testBpfOffload{*} in TetheringConfigurationTest.java.
411         final String value = getDeviceConfigProperty(name);
412         return value != null ? Boolean.parseBoolean(value) : defaultValue;
413     }
414 
415     @VisibleForTesting
getDeviceConfigProperty(String name)416     protected String getDeviceConfigProperty(String name) {
417         return DeviceConfig.getProperty(NAMESPACE_CONNECTIVITY, name);
418     }
419 
getResources(Context ctx, int subId)420     private Resources getResources(Context ctx, int subId) {
421         if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
422             return getResourcesForSubIdWrapper(ctx, subId);
423         } else {
424             return ctx.getResources();
425         }
426     }
427 
428     @VisibleForTesting
getResourcesForSubIdWrapper(Context ctx, int subId)429     protected Resources getResourcesForSubIdWrapper(Context ctx, int subId) {
430         return SubscriptionManager.getResourcesForSubId(ctx, subId);
431     }
432 
copy(String[] strarray)433     private static String[] copy(String[] strarray) {
434         return Arrays.copyOf(strarray, strarray.length);
435     }
436 
prependIfNotPresent(ArrayList<Integer> list, int value)437     private static void prependIfNotPresent(ArrayList<Integer> list, int value) {
438         if (list.contains(value)) return;
439         list.add(0, value);
440     }
441 
appendIfNotPresent(ArrayList<Integer> list, int value)442     private static void appendIfNotPresent(ArrayList<Integer> list, int value) {
443         if (list.contains(value)) return;
444         list.add(value);
445     }
446 
containsOneOf(ArrayList<Integer> list, Integer... values)447     private static boolean containsOneOf(ArrayList<Integer> list, Integer... values) {
448         for (Integer value : values) {
449             if (list.contains(value)) return true;
450         }
451         return false;
452     }
453 
toIntArray(Collection<Integer> values)454     private static int[] toIntArray(Collection<Integer> values) {
455         final int[] result = new int[values.size()];
456         int index = 0;
457         for (Integer value : values) {
458             result[index++] = value;
459         }
460         return result;
461     }
462 
463     /**
464      * Convert this TetheringConfiguration to a TetheringConfigurationParcel.
465      */
toStableParcelable()466     public TetheringConfigurationParcel toStableParcelable() {
467         final TetheringConfigurationParcel parcel = new TetheringConfigurationParcel();
468         parcel.subId = activeDataSubId;
469         parcel.tetherableUsbRegexs = tetherableUsbRegexs;
470         parcel.tetherableWifiRegexs = tetherableWifiRegexs;
471         parcel.tetherableBluetoothRegexs = tetherableBluetoothRegexs;
472         parcel.isDunRequired = isDunRequired;
473         parcel.chooseUpstreamAutomatically = chooseUpstreamAutomatically;
474 
475         parcel.preferredUpstreamIfaceTypes = toIntArray(preferredUpstreamIfaceTypes);
476 
477         parcel.legacyDhcpRanges = legacyDhcpRanges;
478         parcel.defaultIPv4DNS = defaultIPv4DNS;
479         parcel.enableLegacyDhcpServer = enableLegacyDhcpServer;
480         parcel.provisioningApp = provisioningApp;
481         parcel.provisioningAppNoUi = provisioningAppNoUi;
482         parcel.provisioningCheckPeriod = provisioningCheckPeriod;
483         return parcel;
484     }
485 }
486