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