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