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