• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2018 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;
18 
19 import static android.net.ConnectivitySettingsManager.GLOBAL_HTTP_PROXY_EXCLUSION_LIST;
20 import static android.net.ConnectivitySettingsManager.GLOBAL_HTTP_PROXY_HOST;
21 import static android.net.ConnectivitySettingsManager.GLOBAL_HTTP_PROXY_PAC;
22 import static android.net.ConnectivitySettingsManager.GLOBAL_HTTP_PROXY_PORT;
23 import static android.provider.Settings.Global.HTTP_PROXY;
24 
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.content.ContentResolver;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.net.Network;
31 import android.net.PacProxyManager;
32 import android.net.Proxy;
33 import android.net.ProxyInfo;
34 import android.net.Uri;
35 import android.os.Binder;
36 import android.os.Handler;
37 import android.os.UserHandle;
38 import android.provider.Settings;
39 import android.text.TextUtils;
40 import android.util.Log;
41 import android.util.Pair;
42 
43 import com.android.internal.annotations.GuardedBy;
44 import com.android.net.module.util.ProxyUtils;
45 
46 import java.util.Collections;
47 import java.util.Objects;
48 
49 /**
50  * A class to handle proxy for ConnectivityService.
51  *
52  * @hide
53  */
54 public class ProxyTracker {
55     private static final String TAG = ProxyTracker.class.getSimpleName();
56     private static final boolean DBG = true;
57 
58     // EXTRA_PROXY_INFO is now @removed. In order to continue sending it, hardcode its value here.
59     // The Proxy.EXTRA_PROXY_INFO constant is not visible to this code because android.net.Proxy
60     // a hidden platform constant not visible to mainline modules.
61     private static final String EXTRA_PROXY_INFO = "android.intent.extra.PROXY_INFO";
62 
63     @NonNull
64     private final Context mContext;
65 
66     @NonNull
67     private final Object mProxyLock = new Object();
68     // The global proxy is the proxy that is set device-wide, overriding any network-specific
69     // proxy. Note however that proxies are hints ; the system does not enforce their use. Hence
70     // this value is only for querying.
71     @Nullable
72     @GuardedBy("mProxyLock")
73     private ProxyInfo mGlobalProxy = null;
74     // The default proxy is the proxy that applies to no particular network if the global proxy
75     // is not set. Individual networks have their own settings that override this. This member
76     // is set through setDefaultProxy, which is called when the default network changes proxies
77     // in its LinkProperties, or when ConnectivityService switches to a new default network, or
78     // when PacProxyService resolves the proxy.
79     @Nullable
80     @GuardedBy("mProxyLock")
81     private volatile ProxyInfo mDefaultProxy = null;
82     // Whether the default proxy is enabled.
83     @GuardedBy("mProxyLock")
84     private boolean mDefaultProxyEnabled = true;
85 
86     private final Handler mConnectivityServiceHandler;
87 
88     private final PacProxyManager mPacProxyManager;
89 
90     private class PacProxyInstalledListener implements PacProxyManager.PacProxyInstalledListener {
91         private final int mEvent;
92 
PacProxyInstalledListener(int event)93         PacProxyInstalledListener(int event) {
94             mEvent = event;
95         }
96 
onPacProxyInstalled(@ullable Network network, @NonNull ProxyInfo proxy)97         public void onPacProxyInstalled(@Nullable Network network, @NonNull ProxyInfo proxy) {
98             mConnectivityServiceHandler
99                     .sendMessage(mConnectivityServiceHandler
100                     .obtainMessage(mEvent, new Pair<>(network, proxy)));
101         }
102     }
103 
ProxyTracker(@onNull final Context context, @NonNull final Handler connectivityServiceInternalHandler, final int pacChangedEvent)104     public ProxyTracker(@NonNull final Context context,
105             @NonNull final Handler connectivityServiceInternalHandler, final int pacChangedEvent) {
106         mContext = context;
107         mConnectivityServiceHandler = connectivityServiceInternalHandler;
108         mPacProxyManager = context.getSystemService(PacProxyManager.class);
109 
110         PacProxyInstalledListener listener = new PacProxyInstalledListener(pacChangedEvent);
111         mPacProxyManager.addPacProxyInstalledListener(
112                 mConnectivityServiceHandler::post, listener);
113     }
114 
115     // Convert empty ProxyInfo's to null as null-checks are used to determine if proxies are present
116     // (e.g. if mGlobalProxy==null fall back to network-specific proxy, if network-specific
117     // proxy is null then there is no proxy in place).
118     @Nullable
canonicalizeProxyInfo(@ullable final ProxyInfo proxy)119     private static ProxyInfo canonicalizeProxyInfo(@Nullable final ProxyInfo proxy) {
120         if (proxy != null && TextUtils.isEmpty(proxy.getHost())
121                 && Uri.EMPTY.equals(proxy.getPacFileUrl())) {
122             return null;
123         }
124         return proxy;
125     }
126 
127     // ProxyInfo equality functions with a couple modifications over ProxyInfo.equals() to make it
128     // better for determining if a new proxy broadcast is necessary:
129     // 1. Canonicalize empty ProxyInfos to null so an empty proxy compares equal to null so as to
130     //    avoid unnecessary broadcasts.
131     // 2. Make sure all parts of the ProxyInfo's compare true, including the host when a PAC URL
132     //    is in place.  This is important so legacy PAC resolver (see com.android.proxyhandler)
133     //    changes aren't missed.  The legacy PAC resolver pretends to be a simple HTTP proxy but
134     //    actually uses the PAC to resolve; this results in ProxyInfo's with PAC URL, host and port
135     //    all set.
proxyInfoEqual(@ullable final ProxyInfo a, @Nullable final ProxyInfo b)136     public static boolean proxyInfoEqual(@Nullable final ProxyInfo a, @Nullable final ProxyInfo b) {
137         final ProxyInfo pa = canonicalizeProxyInfo(a);
138         final ProxyInfo pb = canonicalizeProxyInfo(b);
139         // ProxyInfo.equals() doesn't check hosts when PAC URLs are present, but we need to check
140         // hosts even when PAC URLs are present to account for the legacy PAC resolver.
141         return Objects.equals(pa, pb) && (pa == null || Objects.equals(pa.getHost(), pb.getHost()));
142     }
143 
144     /**
145      * Gets the default system-wide proxy.
146      *
147      * This will return the global proxy if set, otherwise the default proxy if in use. Note
148      * that this is not necessarily the proxy that any given process should use, as the right
149      * proxy for a process is the proxy for the network this process will use, which may be
150      * different from this value. This value is simply the default in case there is no proxy set
151      * in the network that will be used by a specific process.
152      * @return The default system-wide proxy or null if none.
153      */
154     @Nullable
getDefaultProxy()155     public ProxyInfo getDefaultProxy() {
156         // This information is already available as a world read/writable jvm property.
157         synchronized (mProxyLock) {
158             if (mGlobalProxy != null) return mGlobalProxy;
159             if (mDefaultProxyEnabled) return mDefaultProxy;
160             return null;
161         }
162     }
163 
164     /**
165      * Gets the global proxy.
166      *
167      * @return The global proxy or null if none.
168      */
169     @Nullable
getGlobalProxy()170     public ProxyInfo getGlobalProxy() {
171         // This information is already available as a world read/writable jvm property.
172         synchronized (mProxyLock) {
173             return mGlobalProxy;
174         }
175     }
176 
177     /**
178      * Read the global proxy settings and cache them in memory.
179      */
loadGlobalProxy()180     public void loadGlobalProxy() {
181         if (loadDeprecatedGlobalHttpProxy()) {
182             return;
183         }
184         ContentResolver res = mContext.getContentResolver();
185         String host = Settings.Global.getString(res, GLOBAL_HTTP_PROXY_HOST);
186         int port = Settings.Global.getInt(res, GLOBAL_HTTP_PROXY_PORT, 0);
187         String exclList = Settings.Global.getString(res, GLOBAL_HTTP_PROXY_EXCLUSION_LIST);
188         String pacFileUrl = Settings.Global.getString(res, GLOBAL_HTTP_PROXY_PAC);
189         if (!TextUtils.isEmpty(host) || !TextUtils.isEmpty(pacFileUrl)) {
190             ProxyInfo proxyProperties;
191             if (!TextUtils.isEmpty(pacFileUrl)) {
192                 proxyProperties = ProxyInfo.buildPacProxy(Uri.parse(pacFileUrl));
193             } else {
194                 proxyProperties = ProxyInfo.buildDirectProxy(host, port,
195                         ProxyUtils.exclusionStringAsList(exclList));
196             }
197             if (!proxyProperties.isValid()) {
198                 if (DBG) Log.d(TAG, "Invalid proxy properties, ignoring: " + proxyProperties);
199                 return;
200             }
201 
202             synchronized (mProxyLock) {
203                 mGlobalProxy = proxyProperties;
204             }
205 
206             if (!TextUtils.isEmpty(pacFileUrl)) {
207                 mConnectivityServiceHandler.post(
208                         () -> mPacProxyManager.setCurrentProxyScriptUrl(proxyProperties));
209             }
210         }
211     }
212 
213     /**
214      * Read the global proxy from the deprecated Settings.Global.HTTP_PROXY setting and apply it.
215      * Returns {@code true} when global proxy was set successfully from deprecated setting.
216      */
loadDeprecatedGlobalHttpProxy()217     public boolean loadDeprecatedGlobalHttpProxy() {
218         final String proxy = Settings.Global.getString(mContext.getContentResolver(), HTTP_PROXY);
219         if (!TextUtils.isEmpty(proxy)) {
220             String data[] = proxy.split(":");
221             if (data.length == 0) {
222                 return false;
223             }
224 
225             final String proxyHost = data[0];
226             int proxyPort = 8080;
227             if (data.length > 1) {
228                 try {
229                     proxyPort = Integer.parseInt(data[1]);
230                 } catch (NumberFormatException e) {
231                     return false;
232                 }
233             }
234             final ProxyInfo p = ProxyInfo.buildDirectProxy(proxyHost, proxyPort,
235                     Collections.emptyList());
236             setGlobalProxy(p);
237             return true;
238         }
239         return false;
240     }
241 
242     /**
243      * Sends the system broadcast informing apps about a new proxy configuration.
244      *
245      * Confusingly this method also sets the PAC file URL. TODO : separate this, it has nothing
246      * to do in a "sendProxyBroadcast" method.
247      */
sendProxyBroadcast()248     public void sendProxyBroadcast() {
249         final ProxyInfo defaultProxy = getDefaultProxy();
250         final ProxyInfo proxyInfo = null != defaultProxy ?
251                 defaultProxy : ProxyInfo.buildDirectProxy("", 0, Collections.emptyList());
252         mPacProxyManager.setCurrentProxyScriptUrl(proxyInfo);
253 
254         if (!shouldSendBroadcast(proxyInfo)) {
255             return;
256         }
257         if (DBG) Log.d(TAG, "sending Proxy Broadcast for " + proxyInfo);
258         Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
259         intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING |
260                 Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
261         intent.putExtra(EXTRA_PROXY_INFO, proxyInfo);
262         final long ident = Binder.clearCallingIdentity();
263         try {
264             mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
265         } finally {
266             Binder.restoreCallingIdentity(ident);
267         }
268     }
269 
shouldSendBroadcast(ProxyInfo proxy)270     private boolean shouldSendBroadcast(ProxyInfo proxy) {
271         return Uri.EMPTY.equals(proxy.getPacFileUrl()) || proxy.getPort() > 0;
272     }
273 
274     /**
275      * Sets the global proxy in memory. Also writes the values to the global settings of the device.
276      *
277      * @param proxyInfo the proxy spec, or null for no proxy.
278      */
setGlobalProxy(@ullable ProxyInfo proxyInfo)279     public void setGlobalProxy(@Nullable ProxyInfo proxyInfo) {
280         synchronized (mProxyLock) {
281             // ProxyInfo#equals is not commutative :( and is public API, so it can't be fixed.
282             if (proxyInfo == mGlobalProxy) return;
283             if (proxyInfo != null && proxyInfo.equals(mGlobalProxy)) return;
284             if (mGlobalProxy != null && mGlobalProxy.equals(proxyInfo)) return;
285 
286             final String host;
287             final int port;
288             final String exclList;
289             final String pacFileUrl;
290             if (proxyInfo != null && (!TextUtils.isEmpty(proxyInfo.getHost()) ||
291                     !Uri.EMPTY.equals(proxyInfo.getPacFileUrl()))) {
292                 if (!proxyInfo.isValid()) {
293                     if (DBG) Log.d(TAG, "Invalid proxy properties, ignoring: " + proxyInfo);
294                     return;
295                 }
296                 mGlobalProxy = new ProxyInfo(proxyInfo);
297                 host = mGlobalProxy.getHost();
298                 port = mGlobalProxy.getPort();
299                 exclList = ProxyUtils.exclusionListAsString(mGlobalProxy.getExclusionList());
300                 pacFileUrl = Uri.EMPTY.equals(proxyInfo.getPacFileUrl())
301                         ? "" : proxyInfo.getPacFileUrl().toString();
302             } else {
303                 host = "";
304                 port = 0;
305                 exclList = "";
306                 pacFileUrl = "";
307                 mGlobalProxy = null;
308             }
309             final ContentResolver res = mContext.getContentResolver();
310             final long token = Binder.clearCallingIdentity();
311             try {
312                 Settings.Global.putString(res, GLOBAL_HTTP_PROXY_HOST, host);
313                 Settings.Global.putInt(res, GLOBAL_HTTP_PROXY_PORT, port);
314                 Settings.Global.putString(res, GLOBAL_HTTP_PROXY_EXCLUSION_LIST, exclList);
315                 Settings.Global.putString(res, GLOBAL_HTTP_PROXY_PAC, pacFileUrl);
316             } finally {
317                 Binder.restoreCallingIdentity(token);
318             }
319 
320             sendProxyBroadcast();
321         }
322     }
323 
324     /**
325      * Sets the default proxy for the device.
326      *
327      * The default proxy is the proxy used for networks that do not have a specific proxy.
328      * @param proxyInfo the proxy spec, or null for no proxy.
329      */
setDefaultProxy(@ullable ProxyInfo proxyInfo)330     public void setDefaultProxy(@Nullable ProxyInfo proxyInfo) {
331         synchronized (mProxyLock) {
332             if (Objects.equals(mDefaultProxy, proxyInfo)) return;
333             if (proxyInfo != null &&  !proxyInfo.isValid()) {
334                 if (DBG) Log.d(TAG, "Invalid proxy properties, ignoring: " + proxyInfo);
335                 return;
336             }
337 
338             // This call could be coming from the PacProxyService, containing the port of the
339             // local proxy. If this new proxy matches the global proxy then copy this proxy to the
340             // global (to get the correct local port), and send a broadcast.
341             // TODO: Switch PacProxyService to have its own message to send back rather than
342             // reusing EVENT_HAS_CHANGED_PROXY and this call to handleApplyDefaultProxy.
343             if ((mGlobalProxy != null) && (proxyInfo != null)
344                     && (!Uri.EMPTY.equals(proxyInfo.getPacFileUrl()))
345                     && proxyInfo.getPacFileUrl().equals(mGlobalProxy.getPacFileUrl())) {
346                 mGlobalProxy = proxyInfo;
347                 sendProxyBroadcast();
348                 return;
349             }
350             mDefaultProxy = proxyInfo;
351 
352             if (mGlobalProxy != null) return;
353             if (mDefaultProxyEnabled) {
354                 sendProxyBroadcast();
355             }
356         }
357     }
358 }
359