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 
TetheringConfiguration(Context ctx, SharedLog log)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 
isUsb(String iface)121     public boolean isUsb(String iface) {
122         return matchesDownstreamRegexs(iface, tetherableUsbRegexs);
123     }
124 
isWifi(String iface)125     public boolean isWifi(String iface) {
126         return matchesDownstreamRegexs(iface, tetherableWifiRegexs);
127     }
128 
isBluetooth(String iface)129     public boolean isBluetooth(String iface) {
130         return matchesDownstreamRegexs(iface, tetherableBluetoothRegexs);
131     }
132 
hasMobileHotspotProvisionApp()133     public boolean hasMobileHotspotProvisionApp() {
134         return !TextUtils.isEmpty(provisioningAppNoUi);
135     }
136 
dump(PrintWriter pw)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 
toString()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 
dumpStringArray(PrintWriter pw, String label, String[] values)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 
makeString(String[] strings)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 
preferredUpstreamNames(Collection<Integer> upstreamTypes)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 
checkDunRequired(Context ctx)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 
dunCheckString(int dunCheck)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 
getUpstreamIfaceTypes(Context ctx, int dunCheck)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 
matchesDownstreamRegexs(String iface, String[] regexs)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 
getDhcpRanges(Context ctx)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 
getProvisioningAppNoUi(Context ctx)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 
getResourceStringArray(Context ctx, int resId)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 
copy(String[] strarray)298     private static String[] copy(String[] strarray) {
299         return Arrays.copyOf(strarray, strarray.length);
300     }
301 
prependIfNotPresent(ArrayList<Integer> list, int value)302     private static void prependIfNotPresent(ArrayList<Integer> list, int value) {
303         if (list.contains(value)) return;
304         list.add(0, value);
305     }
306 
appendIfNotPresent(ArrayList<Integer> list, int value)307     private static void appendIfNotPresent(ArrayList<Integer> list, int value) {
308         if (list.contains(value)) return;
309         list.add(value);
310     }
311 
containsOneOf(ArrayList<Integer> list, Integer... values)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