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