blob: 8a46ff18979f196971e0080f803a0a274d57d062 [file] [log] [blame]
Erik Kline79868f82017-01-21 14:33:56 +09001/*
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
17package com.android.server.connectivity.tethering;
18
Erik Kline54f2f372017-05-15 21:11:47 +090019import static android.content.Context.TELEPHONY_SERVICE;
Erik Kline1e543512017-03-09 11:44:11 +090020import static android.net.ConnectivityManager.TYPE_ETHERNET;
Erik Kline79868f82017-01-21 14:33:56 +090021import static android.net.ConnectivityManager.TYPE_MOBILE;
22import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
23import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
Remi NGUYEN VANe3bb5c52018-06-12 15:57:04 +090024import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER;
25
Erik Klinee0f34032018-02-28 15:01:35 +090026import static com.android.internal.R.array.config_mobile_hotspot_provision_app;
27import static com.android.internal.R.array.config_tether_bluetooth_regexs;
28import static com.android.internal.R.array.config_tether_dhcp_range;
Erik Klinee0f34032018-02-28 15:01:35 +090029import static com.android.internal.R.array.config_tether_upstream_types;
markchien293422f2019-01-08 23:52:21 +080030import static com.android.internal.R.array.config_tether_usb_regexs;
Erik Klinee0f34032018-02-28 15:01:35 +090031import static com.android.internal.R.array.config_tether_wifi_regexs;
Erik Kline72302902018-06-14 17:36:40 +090032import static com.android.internal.R.bool.config_tether_upstream_automatic;
Erik Klinee0f34032018-02-28 15:01:35 +090033import static com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui;
Erik Kline79868f82017-01-21 14:33:56 +090034
Remi NGUYEN VANe3bb5c52018-06-12 15:57:04 +090035import android.content.ContentResolver;
Erik Kline79868f82017-01-21 14:33:56 +090036import android.content.Context;
37import android.content.res.Resources;
Erik Kline9db1b542017-03-16 14:10:27 +090038import android.net.ConnectivityManager;
Erik Klinee93ed612018-04-10 07:01:16 +000039import android.net.util.SharedLog;
Remi NGUYEN VANe3bb5c52018-06-12 15:57:04 +090040import android.provider.Settings;
markchien293422f2019-01-08 23:52:21 +080041import android.telephony.SubscriptionManager;
Erik Klinee0f34032018-02-28 15:01:35 +090042import android.telephony.TelephonyManager;
43import android.text.TextUtils;
Erik Kline79868f82017-01-21 14:33:56 +090044
Chalard Jean918a68b2018-01-19 17:00:47 +090045import com.android.internal.annotations.VisibleForTesting;
46
Erik Kline9db1b542017-03-16 14:10:27 +090047import java.io.PrintWriter;
Erik Kline79868f82017-01-21 14:33:56 +090048import java.util.ArrayList;
49import java.util.Arrays;
50import java.util.Collection;
Erik Kline9db1b542017-03-16 14:10:27 +090051import java.util.StringJoiner;
Erik Kline79868f82017-01-21 14:33:56 +090052
53
54/**
55 * A utility class to encapsulate the various tethering configuration elements.
56 *
57 * This configuration data includes elements describing upstream properties
58 * (preferred and required types of upstream connectivity as well as default
59 * DNS servers to use if none are available) and downstream properties (such
60 * as regular expressions use to match suitable downstream interfaces and the
61 * DHCPv4 ranges to use).
62 *
63 * @hide
64 */
65public class TetheringConfiguration {
66 private static final String TAG = TetheringConfiguration.class.getSimpleName();
67
Erik Klinee0f34032018-02-28 15:01:35 +090068 private static final String[] EMPTY_STRING_ARRAY = new String[0];
69
Chalard Jean918a68b2018-01-19 17:00:47 +090070 @VisibleForTesting
Erik Kline54f2f372017-05-15 21:11:47 +090071 public static final int DUN_NOT_REQUIRED = 0;
72 public static final int DUN_REQUIRED = 1;
73 public static final int DUN_UNSPECIFIED = 2;
Erik Kline79868f82017-01-21 14:33:56 +090074
Remi NGUYEN VANe3bb5c52018-06-12 15:57:04 +090075 // Default ranges used for the legacy DHCP server.
Erik Kline79868f82017-01-21 14:33:56 +090076 // USB is 192.168.42.1 and 255.255.255.0
77 // Wifi is 192.168.43.1 and 255.255.255.0
78 // BT is limited to max default of 5 connections. 192.168.44.1 to 192.168.48.1
79 // with 255.255.255.0
80 // P2P is 192.168.49.1 and 255.255.255.0
Remi NGUYEN VANe3bb5c52018-06-12 15:57:04 +090081 private static final String[] LEGACY_DHCP_DEFAULT_RANGE = {
Erik Kline79868f82017-01-21 14:33:56 +090082 "192.168.42.2", "192.168.42.254", "192.168.43.2", "192.168.43.254",
83 "192.168.44.2", "192.168.44.254", "192.168.45.2", "192.168.45.254",
84 "192.168.46.2", "192.168.46.254", "192.168.47.2", "192.168.47.254",
85 "192.168.48.2", "192.168.48.254", "192.168.49.2", "192.168.49.254",
86 };
87
88 private final String[] DEFAULT_IPV4_DNS = {"8.8.4.4", "8.8.8.8"};
89
90 public final String[] tetherableUsbRegexs;
91 public final String[] tetherableWifiRegexs;
92 public final String[] tetherableBluetoothRegexs;
Erik Kline6ee73da2017-07-08 20:36:37 +090093 public final int dunCheck;
Erik Kline79868f82017-01-21 14:33:56 +090094 public final boolean isDunRequired;
Erik Kline72302902018-06-14 17:36:40 +090095 public final boolean chooseUpstreamAutomatically;
Erik Kline79868f82017-01-21 14:33:56 +090096 public final Collection<Integer> preferredUpstreamIfaceTypes;
Remi NGUYEN VANe3bb5c52018-06-12 15:57:04 +090097 public final String[] legacyDhcpRanges;
Erik Kline79868f82017-01-21 14:33:56 +090098 public final String[] defaultIPv4DNS;
Remi NGUYEN VANe3bb5c52018-06-12 15:57:04 +090099 public final boolean enableLegacyDhcpServer;
Erik Kline79868f82017-01-21 14:33:56 +0900100
Erik Klinee0f34032018-02-28 15:01:35 +0900101 public final String[] provisioningApp;
102 public final String provisioningAppNoUi;
103
markchien293422f2019-01-08 23:52:21 +0800104 public final int subId;
105
106 public TetheringConfiguration(Context ctx, SharedLog log, int id) {
Erik Kline6bd74532017-05-19 10:10:41 +0900107 final SharedLog configLog = log.forSubComponent("config");
108
markchien293422f2019-01-08 23:52:21 +0800109 subId = id;
110 Resources res = getResources(ctx, subId);
111
112 tetherableUsbRegexs = getResourceStringArray(res, config_tether_usb_regexs);
Erik Kline9e225542017-06-08 17:48:48 +0900113 // TODO: Evaluate deleting this altogether now that Wi-Fi always passes
114 // us an interface name. Careful consideration needs to be given to
115 // implications for Settings and for provisioning checks.
markchien293422f2019-01-08 23:52:21 +0800116 tetherableWifiRegexs = getResourceStringArray(res, config_tether_wifi_regexs);
117 tetherableBluetoothRegexs = getResourceStringArray(res, config_tether_bluetooth_regexs);
Erik Kline79868f82017-01-21 14:33:56 +0900118
Erik Kline6ee73da2017-07-08 20:36:37 +0900119 dunCheck = checkDunRequired(ctx);
Erik Kline6bd74532017-05-19 10:10:41 +0900120 configLog.log("DUN check returned: " + dunCheckString(dunCheck));
121
markchien293422f2019-01-08 23:52:21 +0800122 chooseUpstreamAutomatically = getResourceBoolean(res, config_tether_upstream_automatic);
123 preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(res, dunCheck);
Erik Kline54f2f372017-05-15 21:11:47 +0900124 isDunRequired = preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_DUN);
Erik Kline79868f82017-01-21 14:33:56 +0900125
markchien293422f2019-01-08 23:52:21 +0800126 legacyDhcpRanges = getLegacyDhcpRanges(res);
Erik Kline79868f82017-01-21 14:33:56 +0900127 defaultIPv4DNS = copy(DEFAULT_IPV4_DNS);
Remi NGUYEN VANe3bb5c52018-06-12 15:57:04 +0900128 enableLegacyDhcpServer = getEnableLegacyDhcpServer(ctx);
Erik Kline6bd74532017-05-19 10:10:41 +0900129
markchien293422f2019-01-08 23:52:21 +0800130 provisioningApp = getResourceStringArray(res, config_mobile_hotspot_provision_app);
131 provisioningAppNoUi = getProvisioningAppNoUi(res);
Erik Klinee0f34032018-02-28 15:01:35 +0900132
Erik Kline6bd74532017-05-19 10:10:41 +0900133 configLog.log(toString());
Erik Kline79868f82017-01-21 14:33:56 +0900134 }
135
136 public boolean isUsb(String iface) {
137 return matchesDownstreamRegexs(iface, tetherableUsbRegexs);
138 }
139
140 public boolean isWifi(String iface) {
141 return matchesDownstreamRegexs(iface, tetherableWifiRegexs);
142 }
143
144 public boolean isBluetooth(String iface) {
145 return matchesDownstreamRegexs(iface, tetherableBluetoothRegexs);
146 }
147
Erik Klinee0f34032018-02-28 15:01:35 +0900148 public boolean hasMobileHotspotProvisionApp() {
149 return !TextUtils.isEmpty(provisioningAppNoUi);
150 }
151
Erik Kline9db1b542017-03-16 14:10:27 +0900152 public void dump(PrintWriter pw) {
markchien293422f2019-01-08 23:52:21 +0800153 pw.print("subId: ");
154 pw.println(subId);
155
Erik Kline9db1b542017-03-16 14:10:27 +0900156 dumpStringArray(pw, "tetherableUsbRegexs", tetherableUsbRegexs);
157 dumpStringArray(pw, "tetherableWifiRegexs", tetherableWifiRegexs);
158 dumpStringArray(pw, "tetherableBluetoothRegexs", tetherableBluetoothRegexs);
159
160 pw.print("isDunRequired: ");
161 pw.println(isDunRequired);
162
Erik Kline72302902018-06-14 17:36:40 +0900163 pw.print("chooseUpstreamAutomatically: ");
164 pw.println(chooseUpstreamAutomatically);
Erik Kline6bd74532017-05-19 10:10:41 +0900165 dumpStringArray(pw, "preferredUpstreamIfaceTypes",
166 preferredUpstreamNames(preferredUpstreamIfaceTypes));
Erik Kline9db1b542017-03-16 14:10:27 +0900167
Remi NGUYEN VANe3bb5c52018-06-12 15:57:04 +0900168 dumpStringArray(pw, "legacyDhcpRanges", legacyDhcpRanges);
Erik Kline9db1b542017-03-16 14:10:27 +0900169 dumpStringArray(pw, "defaultIPv4DNS", defaultIPv4DNS);
Erik Klinee0f34032018-02-28 15:01:35 +0900170
171 dumpStringArray(pw, "provisioningApp", provisioningApp);
172 pw.print("provisioningAppNoUi: ");
173 pw.println(provisioningAppNoUi);
Remi NGUYEN VAN9865cc12018-08-30 16:49:52 +0900174
175 pw.print("enableLegacyDhcpServer: ");
176 pw.println(enableLegacyDhcpServer);
Erik Kline9db1b542017-03-16 14:10:27 +0900177 }
178
Erik Kline6bd74532017-05-19 10:10:41 +0900179 public String toString() {
180 final StringJoiner sj = new StringJoiner(" ");
markchien293422f2019-01-08 23:52:21 +0800181 sj.add(String.format("subId:%d", subId));
Erik Kline6bd74532017-05-19 10:10:41 +0900182 sj.add(String.format("tetherableUsbRegexs:%s", makeString(tetherableUsbRegexs)));
183 sj.add(String.format("tetherableWifiRegexs:%s", makeString(tetherableWifiRegexs)));
184 sj.add(String.format("tetherableBluetoothRegexs:%s",
185 makeString(tetherableBluetoothRegexs)));
186 sj.add(String.format("isDunRequired:%s", isDunRequired));
Erik Kline72302902018-06-14 17:36:40 +0900187 sj.add(String.format("chooseUpstreamAutomatically:%s", chooseUpstreamAutomatically));
Erik Kline6bd74532017-05-19 10:10:41 +0900188 sj.add(String.format("preferredUpstreamIfaceTypes:%s",
189 makeString(preferredUpstreamNames(preferredUpstreamIfaceTypes))));
Erik Klinee0f34032018-02-28 15:01:35 +0900190 sj.add(String.format("provisioningApp:%s", makeString(provisioningApp)));
191 sj.add(String.format("provisioningAppNoUi:%s", provisioningAppNoUi));
Remi NGUYEN VAN9865cc12018-08-30 16:49:52 +0900192 sj.add(String.format("enableLegacyDhcpServer:%s", enableLegacyDhcpServer));
Erik Kline6bd74532017-05-19 10:10:41 +0900193 return String.format("TetheringConfiguration{%s}", sj.toString());
194 }
195
Erik Kline9db1b542017-03-16 14:10:27 +0900196 private static void dumpStringArray(PrintWriter pw, String label, String[] values) {
197 pw.print(label);
198 pw.print(": ");
199
200 if (values != null) {
201 final StringJoiner sj = new StringJoiner(", ", "[", "]");
202 for (String value : values) { sj.add(value); }
203 pw.print(sj.toString());
204 } else {
205 pw.print("null");
206 }
207
208 pw.println();
209 }
210
Erik Kline6bd74532017-05-19 10:10:41 +0900211 private static String makeString(String[] strings) {
Erik Klinee0f34032018-02-28 15:01:35 +0900212 if (strings == null) return "null";
Erik Kline6bd74532017-05-19 10:10:41 +0900213 final StringJoiner sj = new StringJoiner(",", "[", "]");
214 for (String s : strings) sj.add(s);
215 return sj.toString();
216 }
217
218 private static String[] preferredUpstreamNames(Collection<Integer> upstreamTypes) {
219 String[] upstreamNames = null;
220
221 if (upstreamTypes != null) {
222 upstreamNames = new String[upstreamTypes.size()];
223 int i = 0;
224 for (Integer netType : upstreamTypes) {
225 upstreamNames[i] = ConnectivityManager.getNetworkTypeName(netType);
226 i++;
227 }
228 }
229
230 return upstreamNames;
231 }
232
Erik Kline6ee73da2017-07-08 20:36:37 +0900233 public static int checkDunRequired(Context ctx) {
Erik Kline54f2f372017-05-15 21:11:47 +0900234 final TelephonyManager tm = (TelephonyManager) ctx.getSystemService(TELEPHONY_SERVICE);
235 return (tm != null) ? tm.getTetherApnRequired() : DUN_UNSPECIFIED;
Erik Kline79868f82017-01-21 14:33:56 +0900236 }
237
Erik Kline6bd74532017-05-19 10:10:41 +0900238 private static String dunCheckString(int dunCheck) {
239 switch (dunCheck) {
240 case DUN_NOT_REQUIRED: return "DUN_NOT_REQUIRED";
241 case DUN_REQUIRED: return "DUN_REQUIRED";
242 case DUN_UNSPECIFIED: return "DUN_UNSPECIFIED";
243 default:
244 return String.format("UNKNOWN (%s)", dunCheck);
245 }
246 }
247
markchien293422f2019-01-08 23:52:21 +0800248 private static Collection<Integer> getUpstreamIfaceTypes(Resources res, int dunCheck) {
249 final int[] ifaceTypes = res.getIntArray(config_tether_upstream_types);
Erik Kline79868f82017-01-21 14:33:56 +0900250 final ArrayList<Integer> upstreamIfaceTypes = new ArrayList<>(ifaceTypes.length);
251 for (int i : ifaceTypes) {
252 switch (i) {
253 case TYPE_MOBILE:
254 case TYPE_MOBILE_HIPRI:
Erik Kline54f2f372017-05-15 21:11:47 +0900255 if (dunCheck == DUN_REQUIRED) continue;
Erik Kline79868f82017-01-21 14:33:56 +0900256 break;
257 case TYPE_MOBILE_DUN:
Erik Kline54f2f372017-05-15 21:11:47 +0900258 if (dunCheck == DUN_NOT_REQUIRED) continue;
Erik Kline79868f82017-01-21 14:33:56 +0900259 break;
260 }
261 upstreamIfaceTypes.add(i);
262 }
263
264 // Fix up upstream interface types for DUN or mobile. NOTE: independent
Jayachandran C58059822017-05-17 23:53:59 -0700265 // of the value of |dunCheck|, cell data of one form or another is
Erik Kline79868f82017-01-21 14:33:56 +0900266 // *always* an upstream, regardless of the upstream interface types
267 // specified by configuration resources.
Erik Kline54f2f372017-05-15 21:11:47 +0900268 if (dunCheck == DUN_REQUIRED) {
Erik Kline1e543512017-03-09 11:44:11 +0900269 appendIfNotPresent(upstreamIfaceTypes, TYPE_MOBILE_DUN);
Jayachandran C58059822017-05-17 23:53:59 -0700270 } else if (dunCheck == DUN_NOT_REQUIRED) {
Erik Kline1e543512017-03-09 11:44:11 +0900271 appendIfNotPresent(upstreamIfaceTypes, TYPE_MOBILE);
272 appendIfNotPresent(upstreamIfaceTypes, TYPE_MOBILE_HIPRI);
Jayachandran C58059822017-05-17 23:53:59 -0700273 } else {
274 // Fix upstream interface types for case DUN_UNSPECIFIED.
275 // Do not modify if a cellular interface type is already present in the
276 // upstream interface types. Add TYPE_MOBILE and TYPE_MOBILE_HIPRI if no
277 // cellular interface types are found in the upstream interface types.
Erik Kline1e543512017-03-09 11:44:11 +0900278 if (!(containsOneOf(upstreamIfaceTypes,
279 TYPE_MOBILE_DUN, TYPE_MOBILE, TYPE_MOBILE_HIPRI))) {
Jayachandran C58059822017-05-17 23:53:59 -0700280 upstreamIfaceTypes.add(TYPE_MOBILE);
281 upstreamIfaceTypes.add(TYPE_MOBILE_HIPRI);
282 }
Erik Kline79868f82017-01-21 14:33:56 +0900283 }
284
Erik Kline1e543512017-03-09 11:44:11 +0900285 // Always make sure our good friend Ethernet is present.
286 // TODO: consider unilaterally forcing this at the front.
287 prependIfNotPresent(upstreamIfaceTypes, TYPE_ETHERNET);
288
Erik Kline79868f82017-01-21 14:33:56 +0900289 return upstreamIfaceTypes;
290 }
291
292 private static boolean matchesDownstreamRegexs(String iface, String[] regexs) {
293 for (String regex : regexs) {
294 if (iface.matches(regex)) return true;
295 }
296 return false;
297 }
298
markchien293422f2019-01-08 23:52:21 +0800299 private static String[] getLegacyDhcpRanges(Resources res) {
300 final String[] fromResource = getResourceStringArray(res, config_tether_dhcp_range);
Erik Kline79868f82017-01-21 14:33:56 +0900301 if ((fromResource.length > 0) && (fromResource.length % 2 == 0)) {
302 return fromResource;
303 }
Remi NGUYEN VANe3bb5c52018-06-12 15:57:04 +0900304 return copy(LEGACY_DHCP_DEFAULT_RANGE);
Erik Kline79868f82017-01-21 14:33:56 +0900305 }
306
markchien293422f2019-01-08 23:52:21 +0800307 private static String getProvisioningAppNoUi(Resources res) {
Erik Klinee0f34032018-02-28 15:01:35 +0900308 try {
markchien293422f2019-01-08 23:52:21 +0800309 return res.getString(config_mobile_hotspot_provision_app_no_ui);
Erik Klinee0f34032018-02-28 15:01:35 +0900310 } catch (Resources.NotFoundException e) {
311 return "";
312 }
313 }
314
markchien293422f2019-01-08 23:52:21 +0800315 private static boolean getResourceBoolean(Resources res, int resId) {
Erik Kline72302902018-06-14 17:36:40 +0900316 try {
markchien293422f2019-01-08 23:52:21 +0800317 return res.getBoolean(resId);
Erik Kline72302902018-06-14 17:36:40 +0900318 } catch (Resources.NotFoundException e404) {
319 return false;
320 }
321 }
322
markchien293422f2019-01-08 23:52:21 +0800323 private static String[] getResourceStringArray(Resources res, int resId) {
Erik Klinee0f34032018-02-28 15:01:35 +0900324 try {
markchien293422f2019-01-08 23:52:21 +0800325 final String[] strArray = res.getStringArray(resId);
Erik Klinee0f34032018-02-28 15:01:35 +0900326 return (strArray != null) ? strArray : EMPTY_STRING_ARRAY;
327 } catch (Resources.NotFoundException e404) {
328 return EMPTY_STRING_ARRAY;
329 }
330 }
331
Remi NGUYEN VANe3bb5c52018-06-12 15:57:04 +0900332 private static boolean getEnableLegacyDhcpServer(Context ctx) {
Remi NGUYEN VANe3bb5c52018-06-12 15:57:04 +0900333 final ContentResolver cr = ctx.getContentResolver();
Remi NGUYEN VANe0d8c0e2018-09-26 18:11:48 +0900334 final int intVal = Settings.Global.getInt(cr, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0);
Remi NGUYEN VANe3bb5c52018-06-12 15:57:04 +0900335 return intVal != 0;
336 }
337
markchien293422f2019-01-08 23:52:21 +0800338 private Resources getResources(Context ctx, int subId) {
339 if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
340 return getResourcesForSubIdWrapper(ctx, subId);
341 } else {
342 return ctx.getResources();
343 }
344 }
345
346 @VisibleForTesting
347 protected Resources getResourcesForSubIdWrapper(Context ctx, int subId) {
348 return SubscriptionManager.getResourcesForSubId(ctx, subId);
349 }
350
Erik Kline79868f82017-01-21 14:33:56 +0900351 private static String[] copy(String[] strarray) {
352 return Arrays.copyOf(strarray, strarray.length);
353 }
Erik Kline1e543512017-03-09 11:44:11 +0900354
355 private static void prependIfNotPresent(ArrayList<Integer> list, int value) {
356 if (list.contains(value)) return;
357 list.add(0, value);
358 }
359
360 private static void appendIfNotPresent(ArrayList<Integer> list, int value) {
361 if (list.contains(value)) return;
362 list.add(value);
363 }
364
365 private static boolean containsOneOf(ArrayList<Integer> list, Integer... values) {
366 for (Integer value : values) {
367 if (list.contains(value)) return true;
368 }
369 return false;
370 }
Erik Kline79868f82017-01-21 14:33:56 +0900371}