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.connectivity.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.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER;
25 
26 import static com.android.internal.R.array.config_mobile_hotspot_provision_app;
27 import static com.android.internal.R.array.config_tether_bluetooth_regexs;
28 import static com.android.internal.R.array.config_tether_dhcp_range;
29 import static com.android.internal.R.array.config_tether_upstream_types;
30 import static com.android.internal.R.array.config_tether_usb_regexs;
31 import static com.android.internal.R.array.config_tether_wifi_regexs;
32 import static com.android.internal.R.bool.config_tether_upstream_automatic;
33 import static com.android.internal.R.integer.config_mobile_hotspot_provision_check_period;
34 import static com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui;
35 
36 import android.content.ContentResolver;
37 import android.content.Context;
38 import android.content.res.Resources;
39 import android.net.ConnectivityManager;
40 import android.net.util.SharedLog;
41 import android.provider.Settings;
42 import android.telephony.SubscriptionManager;
43 import android.telephony.TelephonyManager;
44 import android.text.TextUtils;
45 
46 import com.android.internal.annotations.VisibleForTesting;
47 
48 import java.io.PrintWriter;
49 import java.util.ArrayList;
50 import java.util.Arrays;
51 import java.util.Collection;
52 import java.util.StringJoiner;
53 
54 
55 /**
56  * A utility class to encapsulate the various tethering configuration elements.
57  *
58  * This configuration data includes elements describing upstream properties
59  * (preferred and required types of upstream connectivity as well as default
60  * DNS servers to use if none are available) and downstream properties (such
61  * as regular expressions use to match suitable downstream interfaces and the
62  * DHCPv4 ranges to use).
63  *
64  * @hide
65  */
66 public class TetheringConfiguration {
67     private static final String TAG = TetheringConfiguration.class.getSimpleName();
68 
69     private static final String[] EMPTY_STRING_ARRAY = new String[0];
70 
71     // Default ranges used for the legacy DHCP server.
72     // USB is  192.168.42.1 and 255.255.255.0
73     // Wifi is 192.168.43.1 and 255.255.255.0
74     // BT is limited to max default of 5 connections. 192.168.44.1 to 192.168.48.1
75     // with 255.255.255.0
76     // P2P is 192.168.49.1 and 255.255.255.0
77     private static final String[] LEGACY_DHCP_DEFAULT_RANGE = {
78         "192.168.42.2", "192.168.42.254", "192.168.43.2", "192.168.43.254",
79         "192.168.44.2", "192.168.44.254", "192.168.45.2", "192.168.45.254",
80         "192.168.46.2", "192.168.46.254", "192.168.47.2", "192.168.47.254",
81         "192.168.48.2", "192.168.48.254", "192.168.49.2", "192.168.49.254",
82     };
83 
84     private final String[] DEFAULT_IPV4_DNS = {"8.8.4.4", "8.8.8.8"};
85 
86     public final String[] tetherableUsbRegexs;
87     public final String[] tetherableWifiRegexs;
88     public final String[] tetherableBluetoothRegexs;
89     public final boolean isDunRequired;
90     public final boolean chooseUpstreamAutomatically;
91     public final Collection<Integer> preferredUpstreamIfaceTypes;
92     public final String[] legacyDhcpRanges;
93     public final String[] defaultIPv4DNS;
94     public final boolean enableLegacyDhcpServer;
95 
96     public final String[] provisioningApp;
97     public final String provisioningAppNoUi;
98     public final int provisioningCheckPeriod;
99 
100     public final int subId;
101 
TetheringConfiguration(Context ctx, SharedLog log, int id)102     public TetheringConfiguration(Context ctx, SharedLog log, int id) {
103         final SharedLog configLog = log.forSubComponent("config");
104 
105         subId = id;
106         Resources res = getResources(ctx, subId);
107 
108         tetherableUsbRegexs = getResourceStringArray(res, config_tether_usb_regexs);
109         // TODO: Evaluate deleting this altogether now that Wi-Fi always passes
110         // us an interface name. Careful consideration needs to be given to
111         // implications for Settings and for provisioning checks.
112         tetherableWifiRegexs = getResourceStringArray(res, config_tether_wifi_regexs);
113         tetherableBluetoothRegexs = getResourceStringArray(res, config_tether_bluetooth_regexs);
114 
115         isDunRequired = checkDunRequired(ctx);
116 
117         chooseUpstreamAutomatically = getResourceBoolean(res, config_tether_upstream_automatic);
118         preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(res, isDunRequired);
119 
120         legacyDhcpRanges = getLegacyDhcpRanges(res);
121         defaultIPv4DNS = copy(DEFAULT_IPV4_DNS);
122         enableLegacyDhcpServer = getEnableLegacyDhcpServer(ctx);
123 
124         provisioningApp = getResourceStringArray(res, config_mobile_hotspot_provision_app);
125         provisioningAppNoUi = getProvisioningAppNoUi(res);
126         provisioningCheckPeriod = getResourceInteger(res,
127                 config_mobile_hotspot_provision_check_period,
128                 0 /* No periodic re-check */);
129 
130         configLog.log(toString());
131     }
132 
isUsb(String iface)133     public boolean isUsb(String iface) {
134         return matchesDownstreamRegexs(iface, tetherableUsbRegexs);
135     }
136 
isWifi(String iface)137     public boolean isWifi(String iface) {
138         return matchesDownstreamRegexs(iface, tetherableWifiRegexs);
139     }
140 
isBluetooth(String iface)141     public boolean isBluetooth(String iface) {
142         return matchesDownstreamRegexs(iface, tetherableBluetoothRegexs);
143     }
144 
hasMobileHotspotProvisionApp()145     public boolean hasMobileHotspotProvisionApp() {
146         return !TextUtils.isEmpty(provisioningAppNoUi);
147     }
148 
dump(PrintWriter pw)149     public void dump(PrintWriter pw) {
150         pw.print("subId: ");
151         pw.println(subId);
152 
153         dumpStringArray(pw, "tetherableUsbRegexs", tetherableUsbRegexs);
154         dumpStringArray(pw, "tetherableWifiRegexs", tetherableWifiRegexs);
155         dumpStringArray(pw, "tetherableBluetoothRegexs", tetherableBluetoothRegexs);
156 
157         pw.print("isDunRequired: ");
158         pw.println(isDunRequired);
159 
160         pw.print("chooseUpstreamAutomatically: ");
161         pw.println(chooseUpstreamAutomatically);
162         dumpStringArray(pw, "preferredUpstreamIfaceTypes",
163                 preferredUpstreamNames(preferredUpstreamIfaceTypes));
164 
165         dumpStringArray(pw, "legacyDhcpRanges", legacyDhcpRanges);
166         dumpStringArray(pw, "defaultIPv4DNS", defaultIPv4DNS);
167 
168         dumpStringArray(pw, "provisioningApp", provisioningApp);
169         pw.print("provisioningAppNoUi: ");
170         pw.println(provisioningAppNoUi);
171 
172         pw.print("enableLegacyDhcpServer: ");
173         pw.println(enableLegacyDhcpServer);
174     }
175 
toString()176     public String toString() {
177         final StringJoiner sj = new StringJoiner(" ");
178         sj.add(String.format("subId:%d", subId));
179         sj.add(String.format("tetherableUsbRegexs:%s", makeString(tetherableUsbRegexs)));
180         sj.add(String.format("tetherableWifiRegexs:%s", makeString(tetherableWifiRegexs)));
181         sj.add(String.format("tetherableBluetoothRegexs:%s",
182                 makeString(tetherableBluetoothRegexs)));
183         sj.add(String.format("isDunRequired:%s", isDunRequired));
184         sj.add(String.format("chooseUpstreamAutomatically:%s", chooseUpstreamAutomatically));
185         sj.add(String.format("preferredUpstreamIfaceTypes:%s",
186                 makeString(preferredUpstreamNames(preferredUpstreamIfaceTypes))));
187         sj.add(String.format("provisioningApp:%s", makeString(provisioningApp)));
188         sj.add(String.format("provisioningAppNoUi:%s", provisioningAppNoUi));
189         sj.add(String.format("enableLegacyDhcpServer:%s", enableLegacyDhcpServer));
190         return String.format("TetheringConfiguration{%s}", sj.toString());
191     }
192 
dumpStringArray(PrintWriter pw, String label, String[] values)193     private static void dumpStringArray(PrintWriter pw, String label, String[] values) {
194         pw.print(label);
195         pw.print(": ");
196 
197         if (values != null) {
198             final StringJoiner sj = new StringJoiner(", ", "[", "]");
199             for (String value : values) { sj.add(value); }
200             pw.print(sj.toString());
201         } else {
202             pw.print("null");
203         }
204 
205         pw.println();
206     }
207 
makeString(String[] strings)208     private static String makeString(String[] strings) {
209         if (strings == null) return "null";
210         final StringJoiner sj = new StringJoiner(",", "[", "]");
211         for (String s : strings) sj.add(s);
212         return sj.toString();
213     }
214 
preferredUpstreamNames(Collection<Integer> upstreamTypes)215     private static String[] preferredUpstreamNames(Collection<Integer> upstreamTypes) {
216         String[] upstreamNames = null;
217 
218         if (upstreamTypes != null) {
219             upstreamNames = new String[upstreamTypes.size()];
220             int i = 0;
221             for (Integer netType : upstreamTypes) {
222                 upstreamNames[i] = ConnectivityManager.getNetworkTypeName(netType);
223                 i++;
224             }
225         }
226 
227         return upstreamNames;
228     }
229 
230     /** Check whether dun is required. */
checkDunRequired(Context ctx)231     public static boolean checkDunRequired(Context ctx) {
232         final TelephonyManager tm = (TelephonyManager) ctx.getSystemService(TELEPHONY_SERVICE);
233         return (tm != null) ? tm.getTetherApnRequired() : false;
234     }
235 
getUpstreamIfaceTypes(Resources res, boolean dunRequired)236     private static Collection<Integer> getUpstreamIfaceTypes(Resources res, boolean dunRequired) {
237         final int[] ifaceTypes = res.getIntArray(config_tether_upstream_types);
238         final ArrayList<Integer> upstreamIfaceTypes = new ArrayList<>(ifaceTypes.length);
239         for (int i : ifaceTypes) {
240             switch (i) {
241                 case TYPE_MOBILE:
242                 case TYPE_MOBILE_HIPRI:
243                     if (dunRequired) continue;
244                     break;
245                 case TYPE_MOBILE_DUN:
246                     if (!dunRequired) continue;
247                     break;
248             }
249             upstreamIfaceTypes.add(i);
250         }
251 
252         // Fix up upstream interface types for DUN or mobile. NOTE: independent
253         // of the value of |dunRequired|, cell data of one form or another is
254         // *always* an upstream, regardless of the upstream interface types
255         // specified by configuration resources.
256         if (dunRequired) {
257             appendIfNotPresent(upstreamIfaceTypes, TYPE_MOBILE_DUN);
258         } else {
259             // Do not modify if a cellular interface type is already present in the
260             // upstream interface types. Add TYPE_MOBILE and TYPE_MOBILE_HIPRI if no
261             // cellular interface types are found in the upstream interface types.
262             // This preserves backwards compatibility and prevents the DUN and default
263             // mobile types incorrectly appearing together, which could happen on
264             // previous releases in the common case where checkDunRequired returned
265             // DUN_UNSPECIFIED.
266             if (!containsOneOf(upstreamIfaceTypes, TYPE_MOBILE, TYPE_MOBILE_HIPRI)) {
267                 upstreamIfaceTypes.add(TYPE_MOBILE);
268                 upstreamIfaceTypes.add(TYPE_MOBILE_HIPRI);
269             }
270         }
271 
272         // Always make sure our good friend Ethernet is present.
273         // TODO: consider unilaterally forcing this at the front.
274         prependIfNotPresent(upstreamIfaceTypes, TYPE_ETHERNET);
275 
276         return upstreamIfaceTypes;
277     }
278 
matchesDownstreamRegexs(String iface, String[] regexs)279     private static boolean matchesDownstreamRegexs(String iface, String[] regexs) {
280         for (String regex : regexs) {
281             if (iface.matches(regex)) return true;
282         }
283         return false;
284     }
285 
getLegacyDhcpRanges(Resources res)286     private static String[] getLegacyDhcpRanges(Resources res) {
287         final String[] fromResource = getResourceStringArray(res, config_tether_dhcp_range);
288         if ((fromResource.length > 0) && (fromResource.length % 2 == 0)) {
289             return fromResource;
290         }
291         return copy(LEGACY_DHCP_DEFAULT_RANGE);
292     }
293 
getProvisioningAppNoUi(Resources res)294     private static String getProvisioningAppNoUi(Resources res) {
295         try {
296             return res.getString(config_mobile_hotspot_provision_app_no_ui);
297         } catch (Resources.NotFoundException e) {
298             return "";
299         }
300     }
301 
getResourceBoolean(Resources res, int resId)302     private static boolean getResourceBoolean(Resources res, int resId) {
303         try {
304             return res.getBoolean(resId);
305         } catch (Resources.NotFoundException e404) {
306             return false;
307         }
308     }
309 
getResourceStringArray(Resources res, int resId)310     private static String[] getResourceStringArray(Resources res, int resId) {
311         try {
312             final String[] strArray = res.getStringArray(resId);
313             return (strArray != null) ? strArray : EMPTY_STRING_ARRAY;
314         } catch (Resources.NotFoundException e404) {
315             return EMPTY_STRING_ARRAY;
316         }
317     }
318 
getResourceInteger(Resources res, int resId, int defaultValue)319     private static int getResourceInteger(Resources res, int resId, int defaultValue) {
320         try {
321             return res.getInteger(resId);
322         } catch (Resources.NotFoundException e404) {
323             return defaultValue;
324         }
325     }
326 
getEnableLegacyDhcpServer(Context ctx)327     private static boolean getEnableLegacyDhcpServer(Context ctx) {
328         final ContentResolver cr = ctx.getContentResolver();
329         final int intVal = Settings.Global.getInt(cr, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0);
330         return intVal != 0;
331     }
332 
getResources(Context ctx, int subId)333     private Resources getResources(Context ctx, int subId) {
334         if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
335             return getResourcesForSubIdWrapper(ctx, subId);
336         } else {
337             return ctx.getResources();
338         }
339     }
340 
341     @VisibleForTesting
getResourcesForSubIdWrapper(Context ctx, int subId)342     protected Resources getResourcesForSubIdWrapper(Context ctx, int subId) {
343         return SubscriptionManager.getResourcesForSubId(ctx, subId);
344     }
345 
copy(String[] strarray)346     private static String[] copy(String[] strarray) {
347         return Arrays.copyOf(strarray, strarray.length);
348     }
349 
prependIfNotPresent(ArrayList<Integer> list, int value)350     private static void prependIfNotPresent(ArrayList<Integer> list, int value) {
351         if (list.contains(value)) return;
352         list.add(0, value);
353     }
354 
appendIfNotPresent(ArrayList<Integer> list, int value)355     private static void appendIfNotPresent(ArrayList<Integer> list, int value) {
356         if (list.contains(value)) return;
357         list.add(value);
358     }
359 
containsOneOf(ArrayList<Integer> list, Integer... values)360     private static boolean containsOneOf(ArrayList<Integer> list, Integer... values) {
361         for (Integer value : values) {
362             if (list.contains(value)) return true;
363         }
364         return false;
365     }
366 }
367