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