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