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