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